We are about half way finished with our first Java game. You've almost made it. Let's give it a final push.
Today, we will begin a three part series on Level Creation. This series will entail reading a text file to create tile maps, which will then be used to create objects that the character can interact with on the screen.
We are implementing concepts that we have covered in the last 2 lessons of Unit 2. If you ever get confused, refer back to them. This lesson will be here when you come back.
Part 1: Common Ground
If you have misplaced your files, or just want to start fresh, download the source code below:
![]()
|
Instructions on Importing Eclipse Projects:
Click Here (Eclipse.org) |
If you downloaded the source code above (unit3begin.zip), you can skip the downloading and just read through. I already did this for you.
Using your own code? Follow along.
1. Download these two awesome tiles I created (please excuse my programmer art).
Place 'em in the data folder.
![]()
|
![]()
|
2. Also, we will be replacing my horrible background with this one:

background.png |
Part 2: Creating the Tile Class
This is the desired effect:
The full Tile Class is to the right.
Here's what I am doing (Ctrl+ to zoom): 1. Create a new class called Tile. 2. Add the following variables: Integers: tileX, tileY, speedX, type. Images: tileImage tileX represents the x coordinate (horizontal position) of the tile. tileY represents the y coordinate (vertical position) of the tile). speedX is equal to the speed of the tile. type indicates whether the tile is an ocean tile or dirt tile. 3. Create a Background object called bg, and point it to the bg1 object in StartingClass. This allows us to reference the bg1 object within our tile class. 4. Create a constructor. As I mentioned, each tile will have the value of 40 pixels. We will be creating Tiles using indexes, rather than pixels. This is why we multiple each index by 40 to get the pixel location. For example, the tile in the upper left corner will have index (0, 0), so we multiply 40 and still have (0, 0,) as tileX, tileY. However, for the adjacent tile (1, 0), the tileX and tileY will be (40, 0), just as we want it to be. 5. Create an update() method. As with those of other classes, this update method will run on every loop (of the game loop). Type 1 is the ocean tile. We want it to move slowly in the background; however, when the background is scrolling, we want the ocean to scroll faster to accommodate movement. Also, you will notice that this ocean scrolls slower than our character moves. This makes sense, because the character is moving right in front of the camera, while the ocean is in the mid-ground. This is parallax scrolling at work, albeit simplified. And the same reason is why the ocean will scroll 5x faster than the background layer (sky and clouds - we will be slowing it down). 6. The rest of the class is just getters and setters (generated by right-clicking >> Source >> Generate Getters and Setters. |
Tile Class
package kiloboltgame;
import java.awt.Image; public class Tile { private int tileX, tileY, speedX, type; public Image tileImage; private Background bg = StartingClass.getBg1(); public Tile(int x, int y, int typeInt) { tileX = x * 40; tileY = y * 40; type = typeInt; if (type == 1) { tileImage = StartingClass.tileocean; } else if (type == 2) { tileImage = StartingClass.tiledirt; } } public void update() { // TODO Auto-generated method stub if (type == 1) { if (bg.getSpeedX() == 0){ speedX = -1; }else{ speedX = -2; } } else { speedX = bg.getSpeedX()*5; } tileX += speedX; } public int getTileX() { return tileX; } public void setTileX(int tileX) { this.tileX = tileX; } public int getTileY() { return tileY; } public void setTileY(int tileY) { this.tileY = tileY; } public Image getTileImage() { return tileImage; } public void setTileImage(Image tileImage) { this.tileImage = tileImage; } } |
Part 3: Making a Few Changes
Locate the 4th if statement in the update() method inside the Robot class.
Make the following changes:
if (speedX > 0 && centerX > 200){
bg1.setSpeedX(-MOVESPEED/5);
bg2.setSpeedX(-MOVESPEED/5);
}
Now the background will move at 1/5 the character's speed.
// Behavioral Methods
public void update() {
centerX += speedX;
speedX = bg.getSpeedX()*5;
}
Part 4: Adding the Tiles to StartingClass
1. We begin at the class declaration. Add the following changes in bold.
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;
public static Image tiledirt, tileocean;
private Graphics second;
private URL base;
private static Background bg1, bg2;
private Animation anim, hanim;
private ArrayList<Tile> tilearray = new ArrayList<Tile>();
// 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");
tiledirt = getImage(base, "data/tiledirt.png");
tileocean = getImage(base, "data/tileocean.png");
@Override
public void start() {
bg1 = new Background(0, 0);
bg2 = new Background(2160, 0);
// Initialize Tiles
for (int i = 0; i < 200; i++) {
for (int j = 0; j < 12; j++) {
if (j == 11) {
Tile t = new Tile(i, j, 2);
tilearray.add(t);
} if (j == 10) {
Tile t = new Tile(i, j, 1);
tilearray.add(t);
}
}
}
hb = new Heliboy(340, 360);
hb2 = new Heliboy(700, 360);
robot = new Robot();
Thread thread = new Thread(this);
thread.start();
}
The numbers i < 200, j < 12 creates 2400 possible locations for tiles. Of those, we fill 400 (i = 200, j = 2).
Create these two methods. Add them below the paint() method for organization:
private void updateTiles() {
for (int i = 0; i < tilearray.size(); i++) {
Tile t = (Tile) tilearray.get(i);
t.update();
}
}
private void paintTiles(Graphics g) {
for (int i = 0; i < tilearray.size(); i++) {
Tile t = (Tile) tilearray.get(i);
g.drawImage(t.getTileImage(), t.getTileX(), t.getTileY(), this);
}
}
Look at the parameters inside the g.drawImage method, and make sense of them. We are drawing the image associated with the tile at the tile's X coordinate, and Y coordinate.
First. The updateTiles() method will be called inside the run() method. Place it above this statement like so:
updateTiles();
hb.update();
Second. The paintTiles() method will be called inside the paint() method. We want the tiles to appear on top of the background but behind the character and enemies, so we place it below the two g.drawImage(background) methods like so:
g.drawImage(background, bg1.getBgX(), bg1.getBgY(), this);
g.drawImage(background, bg2.getBgX(), bg2.getBgY(), this);
paintTiles(g);
Finished!
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;
public static Image tiledirt, tileocean;
private Graphics second;
private URL base;
private static Background bg1, bg2;
private Animation anim, hanim;
private ArrayList<Tile> tilearray = new ArrayList<Tile>();
@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");
tiledirt = getImage(base, "data/tiledirt.png");
tileocean = getImage(base, "data/tileocean.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);
// Initialize Tiles
for (int i = 0; i < 200; i++) {
for (int j = 0; j < 12; j++) {
if (j == 11) {
Tile t = new Tile(i, j, 2);
tilearray.add(t);
} if (j == 10) {
Tile t = new Tile(i, j, 1);
tilearray.add(t);
}
}
}
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);
}
}
updateTiles();
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);
paintTiles(g);
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);
}
private void updateTiles() {
for (int i = 0; i < tilearray.size(); i++) {
Tile t = (Tile) tilearray.get(i);
t.update();
}
}
private void paintTiles(Graphics g) {
for (int i = 0; i < tilearray.size(); i++) {
Tile t = (Tile) tilearray.get(i);
g.drawImage(t.getTileImage(), t.getTileX(), t.getTileY(), 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;
}
}
That's It. Thanks for reading!


unit_3_day_1.zip |