Ads

Tuesday, 5 August 2014

GAME DEVELOPMENT TUTORIAL: DAY 2-10: Painting Tilemap


Hello and welcome! This is the last lesson of Unit 2.

More fun is just around the corner in Unit 3. Let's finish strong!

In today's lesson, we will be building upon what we covered in Day 9.
If you have not looked at Day 9 yet, please do so before proceeding!

What we will be covering:
1. We will be creating a two-dimensional Array that holds a random set of values.
2. Using this two-dimensional array, we will print colored squares to the screen.

What is the significance of this lesson?
When we continue our 2D game development, we will be creating levels (maps) using text files. In each text file, we will represent objects in the game with keyboard characters, and our game will parse the file to create levels that we can interact with.

This lesson will be an application of what we learned in Day 9. After this, we should be able to accomplish the above task with ease in the coming lessons.

Note: We will be creating a NEW class today. You do not need to open any previous files.
Let's begin.

Lesson #2-28: Creating the Tilemap
1. Begin by creating a class named Renderer in any test Project. (Tip: You can create a new Project by right-clicking on the Package Explorer >> New >> Java Project.)

2. Extend Applet and import it.- add "extends Applet" at the end of the class declaration.
- Press Ctrl+Shift+O to organize imports automatically.

It should look like this:
import java.applet.Applet;

public class Renderer extends Applet{

}

3. Override the init() and paint() methods.
Since we have extended an Applet (inheritance), Java will look for the paint() and init() methods. As we will be defining these methods ourselves rather than using the one in the Applet method, so we use the "@Override" keyword.

The two methods will look like this (Make sure to import Graphics) (changes in Bold):
import java.applet.Applet;
import java.awt.Graphics;

public class Renderer extends Applet{

@Override
public void init() {

}

@Override
public void paint(Graphics g) {

}

}
4. Define the init() method.


In the init() method, we want to do the following:

- Set the size of the window.
- Set the color of the Background
- Create the Tilemap.

So we add the following lines of code:

setSize(800, 480);
setBackground(Color.BLACK);
createTilemap();

Eclipse will give you an error as follows:
Since we have never created a createTilemap() method, we must do so right now.

5. Click on the 1 quickfix: Create method 'createTilemap()'.

This should create a method as follows:


private void createTilemap() {


}


We must do two things in the Tilemap() method.
1. We need to create a two-dimensional array. We will call it tilemap and call its height rows and its width columns.
2. Using a random number generator, we will fill tilemap with integers between 0 and 4, inclusive.



Let's do these two things:

1. Creating a 2D Array
Before we begin, to make things easier, we will create three static variables at the top of the class.

- Directly below "public class Renderer extends Applet {", add the following:


   static int[][] tilemap;
   static int rows, columns;

This creates a static (class-wide) two-dimensional Array called tilemap and two integers called rows and columns.

Scroll back inside the createTilemap() method. We will now define the three variables by declaring the following:


   tilemap = new int[50][30];
   rows = tilemap.length;
   columns = tilemap[1].length;


- The first line creates a new 2D Array with 50 elements, which each contain 30 elements.
- tilemap.length is 50, as tilemap contains 50 elements.
- Each of these 50 elements contain 30 elements. Therefore, tilemap[1] refers to the element with index 1 in this 50 element list (the second element of fifty).

Note: Remember that indexes start at 0.
Note 2: We can refer to all 50 elements in tilemap using the indexes: 0 through 49.

2. Randomly Fill tilemap Array


- To fill the tilemap Array randomly, we first create a new Random object:
Random r = new Random();


- Then we use two for loops to fill each index with a random number between 0 and 4, inclusive:

               for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
tilemap[i][j] = r.nextInt(5);
}
}


Note: Recall that tilemap[i][j] refers to the spot with coordinates (i, j) inside the 2D array. 
Refer to this simplified illustration if you are confused!
Here's what the code will look like as of now:

FIGURE 2-36: Renderer Class, createTilemap()

import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Random;

public class Renderer extends Applet {

static int[][] tilemap;
static int rows, columns;

@Override
public void init() {
setSize(800, 480);
setBackground(Color.BLACK);
createTilemap();
}

private void createTilemap() {

tilemap = new int[50][30];


rows = tilemap.length;
columns = tilemap[49].length;


Random r = new Random();


for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
tilemap[i][j] = r.nextInt(5);
}
}
}

@Override
public void paint(Graphics g) {

}

}
With the tilemap created, we can now paint it to the screen!

Lesson #2-29: Painting the Tilemap
This is what we are trying to accomplish:

1. Create a square with side length 16 pixels - 50 across and 30 down (800 x 480).
2. Give this square a random color based on the integer value (0 to 4, inclusive) inside the tilemap.

- We begin Scroll to the paint(Graphics g) method. - Add the following code inside the method:


for (int i = 0; i < rows; i++) {
   for (int j = 0; j < columns; 
   
   }
}


This is the same nested for loop we saw in the createTilemap() method. We will use i and j to refer to locations in the 2D Array.

We want to translate this (i, j) location into a coordinate system of (16*i, 16*j), because we want to represent, for example, (50, 30), as (800, 480).

So within the inner for loop, we add the following in BOLD:

for (int i = 0; i < rows; i++) {
   for (int j = 0; j < columns;
      int mod_i = 16*i;
      int mod_j = 16*j;
   }
}

Note: The underscore "_" does not have any meaning. It is just part of the variable names, which stand for modified I and modified J.

Now directly below the two variable declarations (and still within the inner for loop), we need to create a switch that takes tilemap[i][j] as the key (recall that switches are used to compare the value of a key against cases) and compare with the integers 0 through 4, inclusive.

For each case, we will set the color of g, which needs to be set directly before you paint an object, and then paint a rectangle on the screen at the coordinates (mod_i, mod_j).

The full class will now look like this:

FIGURE 2-37: RENDERER CLASS, Completed

import java.applet.Applet;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.util.Random;

public class Renderer2 extends Applet {

static int[][] tilemap;
static int rows, columns;

@Override
public void init() {
setSize(800, 480);
setBackground(Color.BLACK);
createTilemap();

}

@Override
public void paint(Graphics g) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {

int mod_i = 16*i;
int mod_j = 16*j;

switch (tilemap[i][j]) {
case 0:
g.setColor(Color.RED);
g.fillRect(mod_i, mod_j, 16, 16);
break;
case 1:
g.setColor(Color.BLUE);
g.fillRect(mod_i, mod_j, 16, 16);
break;
case 2:
g.setColor(Color.YELLOW);
g.fillRect(mod_i, mod_j, 16, 16);
break;
case 3:
g.setColor(Color.WHITE);
g.fillRect(mod_i, mod_j, 16, 16);
break;
case 4:
g.setColor(Color.GREEN);
g.fillRect(mod_i, mod_j, 16, 16);
break;

}

}

}

}

private void createTilemap() {

tilemap = new int[50][30];

rows = tilemap.length;
columns = tilemap[49].length;


Random r = new Random();

for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
tilemap[i][j] = r.nextInt(5);
}
}
}

}
Now press Play! You should see a beautiful painting like this:

And with that, our mini-break from 2D game development (Days 9 and 10) is over, and so concludes Unit 2!

In the next lessons, we will be applying these techniques to create our levels, and eventually introduce collision detection.

I hope that this lesson made sense to everyone! I know that everyone is at different levels of Java experience and understanding, and I try to find a good balance as to not bore people and to not leave people behind. I might assume that you remember something from a previous lesson, but you might have forgotten it already. So let me know if you need anything better explained. I'd be glad to do that for you.

GAME DEVELOPMENT TUTORIAL: DAY 2-9: Two-Dimensional Arrays

Welcome to Day 9! Today's lesson will be short, but full of information. We are building towards loading pre-made Level designs and rendering tiles to the screen. To do so, we must first go over some basic fundamentals. Therefore, we will be creating a new project to practice and study these new topics!
Lesson #2-26: Arrays
One of the most powerful tools in a programmer's arsenal is the array. You can think of an array as a list of items. Let's say you had three favorite things:

1. Playing
2. Eating
3. Sleeping

These three things could easily be categorized into one list so that you can refer people to this list rather than three separate variables. That's what an array does, in principle.

1. To see this in action, create a new Project, and name it whatever you want.
2. Within the src folder, create a new Java class called: Array.
3. Add the main method:

  public static void main(String args[]) {

}

4. Within the main method, declare the following:

String[] favoriteThings = new String [3];


This creates an Array (list) of String objects called stringArray, and allocates memory to accommodate three (3) items.

Alternatively, this same line can be written as below, by shifting the brackets:
String favoriteThings[] = new String [3];

Astute observers will recognize this syntax. If you examine the parameter of the main method:
String args[], you will now see that the main method takes in an array of Strings as a parameter. These arrays are passed in at a lower level, and you will not encounter them in Eclipse.

Your code should be looking like this:

Figure 2-34: Array Class

 public class Array {

         public static void main(String args[]) {
String[] favoriteThings = new String [3];
}

}
Now let's talk about how to fill this Array.

We can refer to positions in the array as indices. The first item in the list will have an index of 0, the second will have 1, and so on.

To refer to a specific index of an Array, we can write:
array[index].

Going back to our code, we can list our three favorite things as follows:

...
public static void main(String args[]) {
String[] favoriteThings = new String [3];

favoriteThings[0] = "Playing";
favoriteThings[1] = "Eating";
favoriteThings[2] = "Sleeping";

}
...

What if we now want to print all these items?
It's tempting to use System.out.println(favoriteThings), but this will return what seems to be nonsense.

Instead, we must use a for loop, and iterate over an index to print each line.

This is how I would approach it:

for (int i = 0; i < favoriteThings.length; i++) {
System.out.print(i+1 + ". ");
System.out.println(favoriteThings[i]);
}


This code introduces nothing new. It is just application of previously-covered topics. Make sure you understand this before moving on!

If we place this for loop at the bottom of the main method, we will see the following output:
Understand how it works? Then you are ready for the next level.
Lesson #2-27: Two-Dimensional Arrays
The most practical application of a two-dimensional Array is when you need to store a list of values with (x, y) positions.

For our game's purposes, we will use a 2D array to hold a value at each position, and then paint the object/character/platform associated with said value at its (x, y) coordinate.


2D Arrays are very similar to single dimensional arrays, but we add a second dimension (like adding a width).

Two create a 2D array, we simply state:
int[][] intArray = new int[30][50]; //Creates a 2D array of integers with height 30 and width 50.

1. Create a new class called Tilemap.
2. Add the following code:

Figure 2-35: Tilemap Class

import java.util.Random;

public class Tilemap {


public static void main(String args[]) {
Tilemap tp = new Tilemap();

}

public Tilemap() {
int[][] tilemap = new int[30][50];

System.out.println("New Tilemap created.");
Random r = new Random();

int rows = tilemap.length;
int columns = tilemap[1].length;

printTiles(rows, columns, tilemap, r);

}

private void printTiles(int rows, int columns, int[][] tilemap, Random r) {

for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
tilemap[i][j] = r.nextInt(5);
System.out.print(" " + tilemap[i][j]);
}

System.out.println("");

}
}

}
Before you run this code, let's talk about what each segment of code does.

1. We import the Random class (to be used later)
2. In the main method, we create a new Tilemap object called tp. This calls the constructor below the main method.

3. In the constructor, we create a new two dimensional array called tilemap, with height 30, and width 50.
- We also create a Random object called r

Two-dimensional arrays can be thought of as Arrays of Arrays. Meaning that it is a list of lists. The larger list has 30 empty spots. Each of those 30 spots has 50 spots across.

Therefore, when we refer to tilemap.length, we get the length of the larger list, which is 30.
When we refer to tilemap[1].length, we get the length of the small list with index 1 in the large list, which is the second small list in the large list. This will have a value of 50.

4. We then call the printTiles() method, which takes in various parameters, uses two for loops (one for the columns and another for the rows).

This printTiles() method gives each space a value between 0 to 4 (at random). Then we print each row and column.

The result should look something like this:
In the next lesson, we will associate each integer from 0 to 4 with a colored rectangle and fill the screen. This will then segue into a Unit 3, beginning with a series of lessons on Level design/rendering and collision physics!

Stay Tuned!

GAME DEVELOPMENT TUTORIAL: DAY 2-8: Animations


Welcome to Day 8! It's been a while since the last update, and I hope this lesson will be both fun and informative. Today, we are going to be covering animations, which a lot of you have been asking for via email.

Theoretically, animations are simple. All you need to do is store a sequence of images (that we will refer to as frames) and the duration that each image will be displayed for. Next you just need to display the current frame in the sequence for the duration specified.

In practice, it requires a bit of work. So get ready to start thinking, and let's begin!
Lesson #2-24: Creating the Animation Class
As always, we will begin by creating a blueprint for all animations. This class will be different from those you have seen before (you will encounter a nested class!).

Note: This Animation class can be used over and over again in other projects (with certain modifications). That is why we label it a framework because it is basically reusable code that can help us in other games in the future.

Note 2: This Animation class is a simplified version of source code found in the book Developing Games in Java by David Brakeen. This book is quite technical and will probably be difficult for you right now, but after you've made a few games and know how everything works inside and out, I would suggest checking it out! The only downside is that the book is pretty old, and it focuses solely on full-screen Desktop games, which is not as open to indie developers as the mobile scene.

I. Creating a Package and animation class

We begin by creating a new package (this helps us keep our game classes and game framework classes separated).

1. Right click on the src folder of your project >> New >> Package.
2. Name it: kiloboltgame.framework.
3. Right click on the framework and create a new class named Animation.

Now let's talk about what we need to do in this class.


1. We will need variables that keep track of: frames, current frame, animation time, and total time.

2. First we must create a constructor, as always.

3. We need the following methods:
- One that will add a frame to a new animation.
- One that will update the current frame with the appropriate image.
- One that returns the current frame's image, so that we can paint it in the StartingClass.
- One that returns the current frame's index (index being the numerical location on a list of objects).

4. We need to create a nested inner-class called an AnimFrame that will be used to create objects that contain the current image and the duration that this image will be displayed. These AnimFrame objects will populate the ArrayList whose values we use to draw the object to the screen.

I will talk about each of these as we implement them, so let's just dive in (in the order listed).

II. Defining the Animation Class

1. Variables
Right now, you should just have an empty class.
Create the following variables:

public class Animation   {

   private ArrayList frames;
   private int currentFrame;
   private long animTime
; //long takes up more memory than int but can hold more accurate numbers.
   private long totalDuration;

}

- The ArrayList frames will contain AnimFrame objects (to be created later) that have two values: an image and a duration it is displayed.
- currentFrame refers to the integer value index (the numerical location in the list starting with 0) of the current frame in the ArrayList (the current image shown).
- The third variable animTime will keep track of how much time has elapsed since the current image was displayed, so that when animTime reaches a certain value, we can switch over to the next frame.
- The last variable, totalDuration, refers to the amount of time that each frame (image) will be displayed for.

Let me illustrate these variables with a simplified example:
Assume that we have an ArrayList called frames, and it contains the following:

AnimFrame(image1, 100);
AnimFrame(image2, 200);
AnimFrame(image3, 400);
AnimFrame(image2, 200);
AnimFrame(image1,100);

- This group of AnimFrame objects is what we call the frame ArrayList.
- currentFrame, then, would be the index of the current image displayed. So if image3 is the current frame, the currentFrame value would be 2 (because it goes 0 - image1, 1 - image2, - image3).
- animTime will then measure the amount of time for which image3 has been displayed. When animTime exceeds the totalDuration of 400, it will move onto the next AnimFrame: the fourth line in the example above.

Now that we got the variables taken care of, let's move on to the constructor.

2. Constructor

The constructor is simple. It will initialize the four variables that we have just created.
Place the following code in bold below the "private long totalDuration;" statement.
...
private long totalDuration;

public Animation() {
frames = new ArrayList();
totalDuration = 0;


synchronized (this) {
animTime = 0;
currentFrame = 0;
}
}


}

- The only thing you probably haven't seen before is synchronized. To understand this, we must first talk about Threads. You see, when we have multiple threads, they don't technically run simultaneously like I've mentioned in the previous lessons. They just switch back and forth (think of it as a switch. It can only be on one side at once).

In the above example, if animTime and currentFrame are synchronized, the two will always be called sequentially (together). If it is not synchronized, animTime = 0 might be called first, another thread will then taking over and run its statements, and then finally currentFrame = 0 will be called. This is a bit technical and you probably won't see the significance until later, so for now, just think of this as making things easier in the future.


3. Methods

We will talk about methods one at a time. As you add these methods, you will have a lot of errors until you finish implementing each one, so just ignore them until the class is complete.

I. addFrame method: This method will add take an AnimFrame object and "append" it to the ArrayList called frames.

It will look like this:

public synchronized void addFrame(Image image, long duration) {
totalDuration += duration;
frames.add(new AnimFrame(image, totalDuration));
}


Synchronized comes up again here. It serves the same purpose. This code would be equivalent if synchronized was taken out and placed inside the method like it is in the constructor.

II. update method: The update method will be called repeatedly, and will switch frames as necessary.


    public synchronized void update(long elapsedTime) {
if (frames.size() > 1) {
animTime += elapsedTime;
if (animTime >= totalDuration) {
animTime = animTime % totalDuration;
currentFrame = 0;

}


while (animTime > getFrame(currentFrame).endTime) {
currentFrame++;

}
}
}


As practice, you should logically try to understand what is going on here. Hint: elapsedTime is the amount of time that passed since the last update.

III. getImage method: This method will return the image that belongs to the currentFrame.

      public synchronized Image getImage() {
if (frames.size() == 0) {
return null;
} else {
return getFrame(currentFrame).image;
}
}


You might notice that this method does not have the void keyword, but instead, image. This basically means that this method will return a value to the statement that calls it.

For example, if I say:
System.out.println(getImage());

And the method had a keyword void, the method will return no value, and nothing will print.
But in this case, it will print the ID of the Image object that we return in the if and else statements.

IV. getFrame method: Finally, we create a method that returns the current AnimFrame of the animation sequence:
 
private AnimFrame getFrame(int i) {
   return (AnimFrame) frames.get(i);
}


4. The Nested AnimFrame Class:
We mentioned AnimFrame objects quite a bit, but we never created a class to create them. We could create a whole new class, but it is easier and more readable if we place this inside the Animation class (which will be the only class that makes objects using it). This is called nesting.

So as you would if you were creating a method, place this code inside your class:

private class AnimFrame {

   Image image;
   long endTime;

   public AnimFrame(Image image, long endTime) {
      this.image = image;

      this.endTime = endTime;
   }
}


Remember to import everything (Ctrl + Shift + O), and your class should be finished.

This is what it should look like now:

FIGURE 2-32: Animation Class

package kiloboltgame.framework;

import java.awt.Image;
import java.util.ArrayList;


public class Animation {

private ArrayList frames;
private int currentFrame;
private long animTime;
private long totalDuration;

public Animation() {
frames = new ArrayList();
totalDuration = 0;

synchronized (this) {
animTime = 0;
currentFrame = 0;
}
}

public synchronized void addFrame(Image image, long duration) {
totalDuration += duration;
frames.add(new AnimFrame(image, totalDuration));
}

public synchronized void update(long elapsedTime) {
if (frames.size() > 1) {
animTime += elapsedTime;
if (animTime >= totalDuration) {
animTime = animTime % totalDuration;
currentFrame = 0;

}

while (animTime > getFrame(currentFrame).endTime) {
currentFrame++;

}
}
}

public synchronized Image getImage() {
if (frames.size() == 0) {
return null;
} else {
return getFrame(currentFrame).image;
}
}

private AnimFrame getFrame(int i) {
return (AnimFrame) frames.get(i);
}

private class AnimFrame {

Image image;
long endTime;

public AnimFrame(Image image, long endTime) {
this.image = image;
this.endTime = endTime;
}
}
}
Phew. That was a lot of work. Now let's put this to use! (That will help you understand the Animation class better).
Lesson #2-25: Animating Our Characters
Download the following images (please excuse my programmer art) and place them in the data folder:
character2.png
Download File

heliboy3.png
Download File

character3.png
Download File

heliboy4.png
Download File

heliboy2.png
Download File

heliboy5.png
Download File

Then do: Project >> Clean >> OK!

We will now work inside the StartingClass.

To use the Animation class, since it is in a whole new package, we must import it like so:
import kiloboltgame.framework.Animation;


This is one of those moments when everything makes more sense. You've been importing without knowing what is really going on, but now that you have imported your own class for once, you can now understand how it really works! :D

1. Update your Image declarations (to reflect the downloaded images):

private Image image, currentSprite, character, character2, character3, characterDown,
characterJumped, background, heliboy, heliboy2, heliboy3, heliboy4, heliboy5;


2. Create the following Animation objects. Anim will be used to animate the main character and hanim to animate the heliboys.

private Animation anim, hanim;

3. Make the following changes in bold to the init() method (defining the new Image variables). You will also notice the anim.addFrame statements. These create new frames for the anim object with an image and a duration!

   @Override
public void init() {

setSize(800, 480);
setBackground(Color.BLACK);
setFocusable(true);
addKeyListener(this);
Frame frame = (Frame) this.getParent().getParent();
frame.setTitle("Q-Bot Alpha");
try {
base = getDocumentBase();
} catch (Exception e) {
// TODO: handle exception
}

// Image Setups
character = getImage(base, "data/character.png");
character2 = getImage(base, "data/character2.png");
character3 = getImage(base, "data/character3.png");

characterDown = getImage(base, "data/down.png");
characterJumped = getImage(base, "data/jumped.png");

heliboy = getImage(base, "data/heliboy.png");
heliboy2 = getImage(base, "data/heliboy2.png");
heliboy3 = getImage(base, "data/heliboy3.png");
heliboy4 = getImage(base, "data/heliboy4.png");
heliboy5 = getImage(base, "data/heliboy5.png");


background = getImage(base, "data/background.png");

anim = new Animation();
anim.addFrame(character, 1250);
anim.addFrame(character2, 50);
anim.addFrame(character3, 50);
anim.addFrame(character2, 50);

hanim = new Animation();
hanim.addFrame(heliboy, 100);
hanim.addFrame(heliboy2, 100);
hanim.addFrame(heliboy3, 100);
hanim.addFrame(heliboy4, 100);
hanim.addFrame(heliboy5, 100);
hanim.addFrame(heliboy4, 100);
hanim.addFrame(heliboy3, 100);
hanim.addFrame(heliboy2, 100);

currentSprite = anim.getImage();

}


5. Make the following changes in bold/red to the run() class. anim.getImage() retrieves the image associated with the current frame in the animation sequence. The currentSprite is updated so that it now has the value of this image:

        @Override
public void run() {
while (true) {
robot.update();
if (robot.isJumped()) {
currentSprite = characterJumped;
} else if (robot.isJumped() == false && robot.isDucked() == false) {
currentSprite = anim.getImage(); }

//Bullets
ArrayList projectiles = robot.getProjectiles();
for (int i = 0; i < projectiles.size(); i++) {
Projectile p = (Projectile) projectiles.get(i);
if (p.isVisible() == true) {
p.update();
} else {
projectiles.remove(i);
}
}


//Update Objects
hb.update();
hb2.update();
bg1.update();
bg2.update();

animate();
repaint();

try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

}

6. Create a new method called animate. The parameters (adjust as necessary) make it easy to change how quickly you animate. The higher the value, the faster each frame changes:

public void animate() {
   anim.update(10);
   hanim.update(50);
}


7. Within the paint() class, make the following changes in bold. hanim.getImage() retrieves the current frame's image for heliboy, and these bolded statements paint the heliboy with the current image:

    @Override

public void paint(Graphics g) {
g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this);
g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this);

ArrayList projectiles = robot.getProjectiles();
for (int i = 0; i < projectiles.size(); i++) {
Projectile p = (Projectile) projectiles.get(i);
g.setColor(Color.YELLOW);
g.fillRect(p.getX(), p.getY(), 10, 5);
}

g.drawImage(currentSprite, robot.getCenterX() - 61,
robot.getCenterY() - 63, this);
g.drawImage(hanim.getImage(), hb.getCenterX() - 48, hb.getCenterY() - 48, this);
g.drawImage(hanim.getImage(), hb2.getCenterX() - 48, hb2.getCenterY() - 48, this);
}

8. Finally, within the keyReleased() method, make the following changes in bold (when down button is released, it shows the current frame):

       @Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Stop moving up");
break;

case KeyEvent.VK_DOWN:
currentSprite = anim.getImage();
robot.setDucked(false);
break;

case KeyEvent.VK_LEFT:
robot.stopLeft();
break;

case KeyEvent.VK_RIGHT:
robot.stopRight();
break;

case KeyEvent.VK_SPACE:
break;

case KeyEvent.VK_CONTROL:
robot.setReadyToShoot(true);
break;

}

}
And that's it! Your StartingClass will now look like this:

Figure 2-33: StartingClass, end of Day 8

package kiloboltgame;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.net.URL;
import java.util.ArrayList;
import kiloboltgame.framework.Animation;

public class StartingClass extends Applet implements Runnable, KeyListener {

private Robot robot;
private Heliboy hb, hb2;
private Image image, currentSprite, character, character2, character3,
characterDown, characterJumped, background, heliboy, heliboy2,
heliboy3, heliboy4, heliboy5;
private Graphics second;
private URL base;
private static Background bg1, bg2;
private Animation anim, hanim;

@Override
public void init() {

setSize(800, 480);
setBackground(Color.BLACK);
setFocusable(true);
addKeyListener(this);
Frame frame = (Frame) this.getParent().getParent();
frame.setTitle("Q-Bot Alpha");
try {
base = getDocumentBase();
} catch (Exception e) {
// TODO: handle exception
}

// Image Setups
character = getImage(base, "data/character.png");
character2 = getImage(base, "data/character2.png");
character3 = getImage(base, "data/character3.png");

characterDown = getImage(base, "data/down.png");
characterJumped = getImage(base, "data/jumped.png");

heliboy = getImage(base, "data/heliboy.png");
heliboy2 = getImage(base, "data/heliboy2.png");
heliboy3 = getImage(base, "data/heliboy3.png");
heliboy4 = getImage(base, "data/heliboy4.png");
heliboy5 = getImage(base, "data/heliboy5.png");

background = getImage(base, "data/background.png");

anim = new Animation();
anim.addFrame(character, 1250);
anim.addFrame(character2, 50);
anim.addFrame(character3, 50);
anim.addFrame(character2, 50);

hanim = new Animation();
hanim.addFrame(heliboy, 100);
hanim.addFrame(heliboy2, 100);
hanim.addFrame(heliboy3, 100);
hanim.addFrame(heliboy4, 100);
hanim.addFrame(heliboy5, 100);
hanim.addFrame(heliboy4, 100);
hanim.addFrame(heliboy3, 100);
hanim.addFrame(heliboy2, 100);

currentSprite = anim.getImage();
}

@Override
public void start() {
bg1 = new Background(0, 0);
bg2 = new Background(2160, 0);
hb = new Heliboy(340, 360);
hb2 = new Heliboy(700, 360);
robot = new Robot();

Thread thread = new Thread(this);
thread.start();
}

@Override
public void stop() {
// TODO Auto-generated method stub
}

@Override
public void destroy() {
// TODO Auto-generated method stub
}

@Override
public void run() {
while (true) {
robot.update();
if (robot.isJumped()) {
currentSprite = characterJumped;
} else if (robot.isJumped() == false && robot.isDucked() == false) {
currentSprite = anim.getImage();
}

ArrayList projectiles = robot.getProjectiles();
for (int i = 0; i < projectiles.size(); i++) {
Projectile p = (Projectile) projectiles.get(i);
if (p.isVisible() == true) {
p.update();
} else {
projectiles.remove(i);
}
}

hb.update();
hb2.update();
bg1.update();
bg2.update();
animate();
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public void animate() {
anim.update(10);
hanim.update(50);
}

@Override
public void update(Graphics g) {
if (image == null) {
image = createImage(this.getWidth(), this.getHeight());
second = image.getGraphics();
}

second.setColor(getBackground());
second.fillRect(0, 0, getWidth(), getHeight());
second.setColor(getForeground());
paint(second);

g.drawImage(image, 0, 0, this);

}

@Override
public void paint(Graphics g) {
g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this);
g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this);

ArrayList projectiles = robot.getProjectiles();
for (int i = 0; i < projectiles.size(); i++) {
Projectile p = (Projectile) projectiles.get(i);
g.setColor(Color.YELLOW);
g.fillRect(p.getX(), p.getY(), 10, 5);
}

g.drawImage(currentSprite, robot.getCenterX() - 61,
robot.getCenterY() - 63, this);
g.drawImage(hanim.getImage(), hb.getCenterX() - 48, hb.getCenterY() - 48, this);
g.drawImage(hanim.getImage(), hb2.getCenterX() - 48, hb2.getCenterY() - 48, this);
}

@Override
public void keyPressed(KeyEvent e) {

switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Move up");
break;

case KeyEvent.VK_DOWN:
currentSprite = characterDown;
if (robot.isJumped() == false) {
robot.setDucked(true);
robot.setSpeedX(0);
}
break;

case KeyEvent.VK_LEFT:
robot.moveLeft();
robot.setMovingLeft(true);
break;

case KeyEvent.VK_RIGHT:
robot.moveRight();
robot.setMovingRight(true);
break;

case KeyEvent.VK_SPACE:
robot.jump();
break;

case KeyEvent.VK_CONTROL:
if (robot.isDucked() == false && robot.isJumped() == false) {
robot.shoot();
}
break;

}

}

@Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Stop moving up");
break;

case KeyEvent.VK_DOWN:
currentSprite = anim.getImage();
robot.setDucked(false);
break;

case KeyEvent.VK_LEFT:
robot.stopLeft();
break;

case KeyEvent.VK_RIGHT:
robot.stopRight();
break;

case KeyEvent.VK_SPACE:
break;

}

}

@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub

}

public static Background getBg1() {
return bg1;
}

public static Background getBg2() {
return bg2;
}

}
I covered a lot of code in this lesson. If you need more explanation on anything, let me know! I will update the guide.

Also please examine this code! By this time, you should have a good understanding of how each method in the StartingClass works. So start with the init() and start() methods and try to follow the code as it would run. It will help you a lot. :)

I intentionally omitted explanation on certain things (such as the order of animation frames) because I thought it would be best to let you learn by running the code. So run it, play with it and mess with it until you understand! If you have any questions after that, you know I'm here.

Thank you for reading, and thank you so much for your patience and kind words! :)
The wedding went really well!

I hope you have a good few days, and we will talk about more fun stuff in a few days.

GAME DEVELOPMENT TUTORIAL: DAY 2-7: Shooting Bullets


Welcome to Day 7. Thank you to those of you who submitted ideas on the story of this game. We will be implementing some of them in the coming lessons.

This lesson will be dedicated to arming our character, so that next week, when we go over collision detection, we will have more to work with.

This is how we will approach today's task:
1. We will first create a Projectiles class, which we will use as a blueprint for our bullet objects.
2. Within the Robot class, we will create a method that handles shooting.
3. Finally, in the StartingClass, we will paint the bullets to the screen, and allow the user to shoot with the Control button.


Lesson #2-20: Creating the Projectile Class
The Projectile class, as mentioned, will be a blueprint for creating bullets!

As with many of the previous classes, we will do the following:

1. Create the class.
2. Declare variables
3. Create an update() method and create helper Getters and Setters methods

I. Creating the Projectile Class

1. Right click on the kiloboltgame package >> New >> Class
2. Name it "Projectile".
3. Check the box labeled "Constructors from superclass:"
4. Press OK!

You should now see the following:

package kiloboltgame;

public class Projectile {

public Projectile() {
// TODO Auto-generated constructor stub
}

}

II. Declaring Variables

Let's now declare the variables that we will be using.

private int x, y, speedX;
private boolean visible;


These are class-wide variables, so they should be below the class declaration:
public class Projectile{
   private int x, y, speedX;
   private boolean visible;


Now, we need to make some changes to the constructor:

public Projectile(){
}

As of now, it takes in no parameters. We want it to take in two values, a starting X coordinate and a starting Y coordinate, which will represent the top left corner of each painted bullet.

1. So we add the two parameters:

public Projectile(int startX, int startY){
}

2. To set these startX and startY variables into the class-wide x and y variables, and to set the speedX of the bullet and to initialize the visible boolean, we declare the following four statements inside the constructor:

x = startX;
y = startY;
speedX = 7;
visible = true;


Your constructor should now look like this:

public Projectile(int startX, int startY){
x = startX;
y = startY;
speedX = 7;
visible = true;
}

III. Creating the Update() and Helper Methods

The update() method in this class, as in the other classes, will be called with each update of the game.
Naturally, then, we should change the location of the bullet here with respect to speed, and react when the bullet collides or goes off the screen (we won't handle collision just yet).

1. So first create an update method:

public void update(){


}

2. Add the following statements:

x += speedX;
if (x > 800) {
   visible = false;
}

- The x += speedX; statement will continually update the x coordinate by adding to it the speed in the x direction.

- The if statement checks if the bullet is off the screen, and makes it invisible. In the other classes, we will remove these bullets so they do not take up unnecessary memory.

3. Now create Getters and Setters:

- Right click on the code, go to Source >> Generate Getters and Setters
- Select All
- Press OK

You should get the following:

   public int getX() {
return x;
}


public int getY() {
return y;
}


public int getSpeedX() {
return speedX;
}


public boolean isVisible() {
return visible;
}


public void setX(int x) {
this.x = x;
}


public void setY(int y) {
this.y = y;
}


public void setSpeedX(int speedX) {
this.speedX = speedX;
}


public void setVisible(boolean visible) {
this.visible = visible;
}


That should be it for the Projectile class for now. Here's the final code:

FIGURE 2-29: Projectile Class

package kiloboltgame;

public class Projectile {

private int x, y, speedX;
private boolean visible;

public Projectile(int startX, int startY){
x = startX;
y = startY;
speedX = 7;
visible = true;
}

public void update(){
x += speedX;
if (x > 800){
visible = false;
}

}

public int getX() {
return x;
}

public int getY() {
return y;
}

public int getSpeedX() {
return speedX;
}

public boolean isVisible() {
return visible;
}

public void setX(int x) {
this.x = x;
}

public void setY(int y) {
this.y = y;
}

public void setSpeedX(int speedX) {
this.speedX = speedX;
}

public void setVisible(boolean visible) {
this.visible = visible;
}

}
Lesson #2-21: Creating an ArrayList in the Robot Class
It would be extremely time consuming and inefficient if we were to manually create bullets each time that the player pressed "shoot." So we will be implementing a type of List called an ArrayList, which will allow us to store our Projectile objects in a "container" of increasing size.

1. To do so, we first declare (below all the other variable declarations):

private ArrayList<Projectile> projectiles = new ArrayList<Projectile>();
(Make sure you import ArrayList, Ctrl+Shift+O).


Doing this creates an ArrayList (of Projectiles) called projectiles.

2. Now, below the jump() method, I will add the shoot() method:

  public void shoot() {
Projectile p = new Projectile(centerX + 50, centerY - 25);
projectiles.add(p);
}

This method simply creates a new Projectile, labels it p, and adds it to the projectiles ArrayList. We create this 50 pixels to the right and 25 pixels above the center of the robot, which is where the gun is.

3. Create a Getter method as follows:

  public ArrayList getProjectiles() {
return projectiles;
}

This will allow us to reference this newly created ArrayList from the other classes.

Your finished Robot class will look like this:

FIGURE 2-30: Robot Class (Changes in Bold)

package kiloboltgame;

import java.util.ArrayList;
public class Robot {

// Constants are Here
final int JUMPSPEED = -15;
final int MOVESPEED = 5;
final int GROUND = 382;

private int centerX = 100;
private int centerY = GROUND;
private boolean jumped = false;
private boolean movingLeft = false;
private boolean movingRight = false;
private boolean ducked = false;

private int speedX = 0;
private int speedY = 1;

private Background bg1 = StartingClass.getBg1();
private Background bg2 = StartingClass.getBg2();

private ArrayList<Projectile> projectiles = new ArrayList<Projectile>();

public void update() {

// Moves Character or Scrolls Background accordingly.

if (speedX < 0) {
centerX += speedX;
}
if (speedX == 0 || speedX < 0) {
bg1.setSpeedX(0);
bg2.setSpeedX(0);

}
if (centerX <= 200 && speedX > 0) {
centerX += speedX;
}
if (speedX > 0 && centerX > 200){
bg1.setSpeedX(-MOVESPEED);
bg2.setSpeedX(-MOVESPEED);
}

// Updates Y Position
centerY += speedY;
if (centerY + speedY >= GROUND) {
centerY = GROUND;
}

// Handles Jumping
if (jumped == true) {
speedY += 1;

if (centerY + speedY >= GROUND) {
centerY = GROUND;
speedY = 0;
jumped = false;
}
}
// Prevents going beyond X coordinate of 0
if (centerX + speedX <= 60) {
centerX = 61;
}
}
public void moveRight() {
if (ducked == false) {
speedX = MOVESPEED;
}
}

public void moveLeft() {
if (ducked == false) {
speedX = -MOVESPEED;
}
}

public void stopRight() {
setMovingRight(false);
stop();
}

public void stopLeft() {
setMovingLeft(false);
stop();
}

private void stop() {
if (isMovingRight() == false && isMovingLeft() == false) {
speedX = 0;
}


if (isMovingRight() == false && isMovingLeft() == true) {
moveLeft();
}


if (isMovingRight() == true && isMovingLeft() == false) {
moveRight();
}

}

public void jump() {
if (jumped == false) {
speedY = JUMPSPEED;
jumped = true;
}

}

public void shoot() {
Projectile p = new Projectile(centerX + 50, centerY - 25);
projectiles.add(p);
}



public int getCenterX() {
return centerX;
}

public int getCenterY() {
return centerY;
}


public boolean isJumped() {
return jumped;
}


public int getSpeedX() {
return speedX;
}


public int getSpeedY() {
return speedY;
}


public void setCenterX(int centerX) {
this.centerX = centerX;
}


public void setCenterY(int centerY) {
this.centerY = centerY;
}


public void setJumped(boolean jumped) {
this.jumped = jumped;
}


public void setSpeedX(int speedX) {
this.speedX = speedX;
}


public void setSpeedY(int speedY) {
this.speedY = speedY;
}


public boolean isDucked() {
return ducked;
}


public void setDucked(boolean ducked) {
this.ducked = ducked;
}


public boolean isMovingRight() {
return movingRight;
}


public void setMovingRight(boolean movingRight) {
this.movingRight = movingRight;
}


public boolean isMovingLeft() {
return movingLeft;
}


public void setMovingLeft(boolean movingLeft) {
this.movingLeft = movingLeft;
}


public ArrayList getProjectiles() {
return projectiles;
}


}
Lesson #2-22: Painting the Bullets in the StartingClass

I. Changing the Run() Method

Make the changes in bold to your run() method:

       @Override
public void run() {
while (true) {
robot.update();
if (robot.isJumped()) {
currentSprite = characterJumped;
} else if (robot.isJumped() == false && robot.isDucked() == false) {
currentSprite = character;
}

ArrayList projectiles = robot.getProjectiles();
for (int i = 0; i < projectiles.size(); i++) {
Projectile p = (Projectile) projectiles.get(i);
if (p.isVisible() == true) {
p.update();
} else {
projectiles.remove(i);
}
}

hb.update();
hb2.update();
bg1.update();
bg2.update();
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
(Make sure you import ArrayList: Ctrl+Shift+O)

Let's talk about what we did here:
We created a new ArrayList called projectiles and gave it the value of the projectiles ArrayList in robot.

We then created a for loop, which runs as long as the index i is lesser than the size of the projectiles ArrayList (which is the number of Projectile objects stored in it).

Then we check which Projectile object is in the i-th (4th, 5th, etc). place in the ArrayList (the Projectile object with an index of i).

To illustrate indexes, here's another example:
List = [1, 2, 3, 5, 7]
The indexes of each of these are: 0, 1, 2, 3, and 4. This means that the for loop will go through each object in the ArrayList of Projectiles and set it equal to p


After that, it checks if this p is on the screen (isVisible()). If true, it updates it. Else, it removes this p by removing the i-th index from the projectiles ArrayList.

To summarize all this, we create a for loop with an index called i which goes up by 1 each time. We set the i-th Projectile in the projectiles ArrayList, set it equal to p, and update it (if on screen) or remove it (if not on screen).

II. Changing the Paint() Method

Make the changes in bold in the paint() method:

       @Override
public void paint(Graphics g) {
g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this);
g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this);

ArrayList projectiles = robot.getProjectiles();
for (int i = 0; i < projectiles.size(); i++) {
Projectile p = (Projectile) projectiles.get(i);
g.setColor(Color.YELLOW);
g.fillRect(p.getX(), p.getY(), 10, 5);
}

g.drawImage(currentSprite, robot.getCenterX() - 61,
robot.getCenterY() - 63, this);
g.drawImage(heliboy, hb.getCenterX() - 48, hb.getCenterY() - 48, this);
g.drawImage(heliboy, hb2.getCenterX() - 48, hb2.getCenterY() - 48, this);
}
The same concepts apply here.

We created an ArrayList called projectiles (the one from the update() method is only usable by the update() method, so this is a unique ArrayList) and give it the value of the projectiles ArrayList in the Robot class.

We used a for loop and painted each Projectile object called p, which represents the i-th Projectile in the ArrayList at the current iteration of the for loop, and we draw a yellow rectangle with width 10 and height 5 at the X and Y coordinate of the p Projectile.
Lesson #2-23: Shooting on Keypress
We want to use the Ctrl button to shoot our bullets.
Navigate to the keyPressed() method in StartingClass, and make the following changes in bold:

  @Override
public void keyPressed(KeyEvent e) {

switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Move up");
break;

case KeyEvent.VK_DOWN:
currentSprite = characterDown;
if (robot.isJumped() == false) {
robot.setDucked(true);
robot.setSpeedX(0);
}
break;

case KeyEvent.VK_LEFT:
robot.moveLeft();
robot.setMovingLeft(true);
break;

case KeyEvent.VK_RIGHT:
robot.moveRight();
robot.setMovingRight(true);
break;

case KeyEvent.VK_SPACE:
robot.jump();
break;


case KeyEvent.VK_CONTROL:
if (robot.isDucked() == false && robot.isJumped() == false) {
robot.shoot();
}
break;

}

}

All we did was add another case to the switch, so that when CONTROL is pressed, the robot calls its shoot() method. We also check if the robot is ducking or jumping, and prevent shooting if it is doing either.

We can now run the game, and you should be able to shoot with the Control button!
The final StartingClass follows (changes in bold):

Figure 2-31: StartingClass, end of Day 7

package kiloboltgame;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.net.URL;
import java.util.ArrayList;

public class StartingClass extends Applet implements Runnable, KeyListener {

private Robot robot;
private Heliboy hb, hb2;
private Image image, currentSprite, character, characterDown,
characterJumped, background, heliboy;
private Graphics second;
private URL base;
private static Background bg1, bg2;

@Override
public void init() {

setSize(800, 480);
setBackground(Color.BLACK);
setFocusable(true);
addKeyListener(this);
Frame frame = (Frame) this.getParent().getParent();
frame.setTitle("Q-Bot Alpha");
try {
base = getDocumentBase();
} catch (Exception e) {
// TODO: handle exception
}

// Image Setups
character = getImage(base, "data/character.png");
characterDown = getImage(base, "data/down.png");
characterJumped = getImage(base, "data/jumped.png");
currentSprite = character;
heliboy = getImage(base, "data/heliboy.png");
background = getImage(base, "data/background.png");
}

@Override
public void start() {
bg1 = new Background(0, 0);
bg2 = new Background(2160, 0);
hb = new Heliboy(340, 360);
hb2 = new Heliboy(700, 360);
robot = new Robot();

Thread thread = new Thread(this);
thread.start();
}

@Override
public void stop() {
// TODO Auto-generated method stub
}

@Override
public void destroy() {
// TODO Auto-generated method stub
}

@Override
public void run() {
while (true) {
robot.update();
if (robot.isJumped()) {
currentSprite = characterJumped;
} else if (robot.isJumped() == false && robot.isDucked() == false) {
currentSprite = character;
}


ArrayList projectiles = robot.getProjectiles();
for (int i = 0; i < projectiles.size(); i++) {
Projectile p = (Projectile) projectiles.get(i);
if (p.isVisible() == true) {
p.update();
} else {
projectiles.remove(i);
}
}

hb.update();
hb2.update();
bg1.update();
bg2.update();
repaint();
try {
Thread.sleep(17);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

@Override
public void update(Graphics g) {
if (image == null) {
image = createImage(this.getWidth(), this.getHeight());
second = image.getGraphics();
}

second.setColor(getBackground());
second.fillRect(0, 0, getWidth(), getHeight());
second.setColor(getForeground());
paint(second);

g.drawImage(image, 0, 0, this);

}

@Override
public void paint(Graphics g) {
g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this);
g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this);

ArrayList projectiles = robot.getProjectiles();
for (int i = 0; i < projectiles.size(); i++) {
Projectile p = (Projectile) projectiles.get(i);
g.setColor(Color.YELLOW);
g.fillRect(p.getX(), p.getY(), 10, 5);
}

g.drawImage(currentSprite, robot.getCenterX() - 61,
robot.getCenterY() - 63, this);
g.drawImage(heliboy, hb.getCenterX() - 48, hb.getCenterY() - 48, this);
g.drawImage(heliboy, hb2.getCenterX() - 48, hb2.getCenterY() - 48, this);
}

@Override
public void keyPressed(KeyEvent e) {

switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Move up");
break;

case KeyEvent.VK_DOWN:
currentSprite = characterDown;
if (robot.isJumped() == false) {
robot.setDucked(true);
robot.setSpeedX(0);
}
break;

case KeyEvent.VK_LEFT:
robot.moveLeft();
robot.setMovingLeft(true);
break;

case KeyEvent.VK_RIGHT:
robot.moveRight();
robot.setMovingRight(true);
break;

case KeyEvent.VK_SPACE:
robot.jump();
break;

case KeyEvent.VK_CONTROL:
if (robot.isDucked() == false && robot.isJumped() == false) {
robot.shoot();
}
break;

}

}

@Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP:
System.out.println("Stop moving up");
break;

case KeyEvent.VK_DOWN:
currentSprite = character;
robot.setDucked(false);
break;

case KeyEvent.VK_LEFT:
robot.stopLeft();
break;

case KeyEvent.VK_RIGHT:
robot.stopRight();
break;

case KeyEvent.VK_SPACE:
break;

}

}

@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub

}

public static Background getBg1() {
return bg1;
}

public static Background getBg2() {
return bg2;
}

}
Thank you for reading this lesson! Things are starting to get a bit repetitive, so we will be mixing things up next time around.