Picture
Welcome to Day 3 of Unit 3. We will be continuing right where we left on in Day 2.

In this lesson, we will clean up some of the code in the classes, and we will be conceptually discussing collision detection, on which we will spend the next few lessons starting on Day 4.

It will be a very short lesson, as the meat of the discussion will be the collision detection basics that follow.

I'm glad you are keeping up with the series, and I hope this lesson will be interesting!

The first thing we will do will be some housekeeping. If you would rather skip all this, skip the cleaning steps and you will find the edited Robot, StartingClass, and Tiles classes below.

Cleaning up our Robot Class

We will need to make several changes to our code to ensure that we will be able to smoothly implement collision detection in the future.

Most of these changes will deal with our Robot class. So let's open that up.

When this class was created, we assumed that the ground would always be at a certain level. We will now be changing that.

1. Remove the following lines of code highlighted in RED
2. Make the changes as shown in GREEN

Picture
Now this will break our game a little bit and our character will no longer be able to stand at "ground level" but we will be fixing that in the next lesson.

Next, we want to do something about that shooting mechanism. Rather than let the player hold the button to shoot, we want him to repress the button each time.

1. For that, we simply create a variable in the class declaration (add the following in Bold):

private boolean movingLeft = false;
private boolean movingRight = false;
private boolean ducked = false;
private boolean readyToFire = true;

2. We want this new boolean to be accessible from other classes, so we right click >> source >> Generate Getters and Setters. Make sure that "readyToFire" is checked, and press OK.

You can manually do this by adding:

  public boolean isReadyToFire() {
return readyToFire;
}

public void setReadyToFire(boolean readyToFire) {
this.readyToFire = readyToFire;
}

Wherever you feel appropriate.

3. Now scroll down to the shoot() method, and surround the two statements around an if statement like so:
 
  public void shoot() {
if (readyToFire) {
Projectile p = new Projectile(centerX + 50, centerY - 25);
projectiles.add(p);
}
}


This will ensure that only when the boolean "readyToFire" is true, the player will be able to shoot.


4. Now, open up StartingClass. Navigate down to the keyPressed() method. For case KeyEvent.VK_CONTROL, you want to set it so that each time that robot.shoot() is called, readyToFire will be set to false.

It will look like this:

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

5. Lastly, scroll further down to the keyReleased() method. Add the following case (and statement).

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


With all of this, you should be able to run the game again, and this time holding down the Control button will not shoot endlessly.

CLEANING UP OUR StartingCLASS

We will work a bit more in the StartingClass to make it ready for the future.

1. Scroll up to where we first declare private Robot robot;
We will be making robot a static variable that can be accessed by other classes (for collision detection purposes). So add the static keyword after private.


2. We want to create a Getter method for the robot. So scroll down to the bottom (below getBg2) and add the following:

  public static Robot getRobot(){
return robot;
}


3. Lastly, we want to make a small change to the start() method.

Locate the following line: robot = new Robot();

Move it directly below this one: bg2 = new Background(2160, 0);

Now we can call getRobot() as soon as the tiles are initialized.


Cleaning Up our Tiles Class

Our last changes will be in the Tiles class. 

1. Locate the update() method and change it to look like this one:

    public void update() {
        speedX = bg.getSpeedX() * 5;
        tileX += speedX;
    }
 
As we will not be handling layers of background with tiles, we simply remove unneeded if statements.

2. Secondly, above where we create the bg object, add this line:
   private Robot robot = StartingClass.getRobot();

The Resulting Classes

You can use these to check for any mistakes.

Robot Class

package kiloboltgame;

import java.util.ArrayList;

public class Robot {

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

    private int centerX = 100;
    private int centerY = 377;
    private boolean jumped = false;
    private boolean movingLeft = false;
    private boolean movingRight = false;
    private boolean ducked = false;
    private boolean readyToFire = true;

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

    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 / 5);
            bg2.setSpeedX(-MOVESPEED / 5);
        }

        // Updates Y Position
        centerY += speedY;

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

        }

        // Prevents going beyond X coordinate of 0
        if (centerX + speedX <= 60) {
            centerX = 61;
        }
    }

    public boolean isReadyToFire() {
        return readyToFire;
    }

    public void setReadyToFire(boolean readyToFire) {
        this.readyToFire = readyToFire;
    }

    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() {
        if (readyToFire) {
            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;
    }

}
 

StartingClass

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.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import kiloboltgame.framework.Animation;

public class StartingClass extends Applet implements Runnable, KeyListener {

    private static Robot robot;
    private Heliboy hb, hb2;
    private Image image, currentSprite, character, character2, character3,
            characterDown, characterJumped, background, heliboy, heliboy2,
            heliboy3, heliboy4, heliboy5;

    public static Image tilegrassTop, tilegrassBot, tilegrassLeft, tilegrassRight, tiledirt;

    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");
        tilegrassTop = getImage(base, "data/tilegrasstop.png");
        tilegrassBot = getImage(base, "data/tilegrassbot.png");
        tilegrassLeft = getImage(base, "data/tilegrassleft.png");
        tilegrassRight = getImage(base, "data/tilegrassright.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);
        robot = new Robot();
        // Initialize Tiles
        try {
            loadMap("data/map1.txt");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        hb = new Heliboy(340, 360);
        hb2 = new Heliboy(700, 360);
       

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

    private void loadMap(String filename) throws IOException {
        ArrayList lines = new ArrayList();
        int width = 0;
        int height = 0;

        BufferedReader reader = new BufferedReader(new FileReader(filename));
        while (true) {
            String line = reader.readLine();
            // no more lines to read
            if (line == null) {
                reader.close();
                break;
            }

            if (!line.startsWith("!")) {
                lines.add(line);
                width = Math.max(width, line.length());

            }
        }
        height = lines.size();

        for (int j = 0; j < 12; j++) {
            String line = (String) lines.get(j);
            for (int i = 0; i < width; i++) {

                if (i < line.length()) {
                    char ch = line.charAt(i);
                    Tile t = new Tile(i, j, Character.getNumericValue(ch));
                    tilearray.add(t);
                }

            }
        }

    }

    @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;
    }
   
    public static Robot getRobot(){
        return robot;
    }

}

Tile Class

package kiloboltgame;

import java.awt.Image;

public class Tile {

    private int tileX, tileY, speedX, type;
    public Image tileImage;

    private Robot robot = StartingClass.getRobot();
    private Background bg = StartingClass.getBg1();

    public Tile(int x, int y, int typeInt) {
        tileX = x * 40;
        tileY = y * 40;

        type = typeInt;

        if (type == 5) {
            tileImage = StartingClass.tiledirt;
        } else if (type == 8) {
            tileImage = StartingClass.tilegrassTop;
        } else if (type == 4) {
            tileImage = StartingClass.tilegrassLeft;

        } else if (type == 6) {
            tileImage = StartingClass.tilegrassRight;

        } else if (type == 2) {
            tileImage = StartingClass.tilegrassBot;
        }

    }

    public void update() {
        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;
    }

}
 
Click to set custom HTML
Now let's talk about collision detection. As this information is more general, I will be discussing it in a new page (so it is easier to find). 

Click on the button below!
 


Comments

Doug
12/09/2012 11:14am

One mistake on this page...

"1. For that, we simply create a variable in the class declaration (add the following in Bold):

private boolean movingLeft = false;
private boolean movingRight = false;
private boolean ducked = false;
private boolean readyToFire = true;"

You say changes in bold but don't bold the statement :) would expect people to be able to work out which one as it's only the new one.

Also are we meant to fall through the ground when we jump?


Thank you again for these tutorials they are really helping me get a grasp of programming.

Reply
James C
12/09/2012 1:56pm

Thanks! Fixed.

And yes, we didn't set any collision detection, so you are supposed to fall.

Reply
SRISHTI GOEL
12/16/2012 7:00am

can u tell me one thing that this tutorial is halway. u didnt use xml whch is used in android.n finally how will i use emulator?. should do the java coding n not borther bout ol dis?plz help

Reply
James
12/16/2012 7:05am

Srishti,

All of that is coming when we port this game to Android (Unit 4). Don't worry. It's only days away!

Reply
Felix
01/21/2013 12:00pm

I have noticed that in the resume of the StartingClass at the end of lesson, Inside the method KeyReleased has to be the "KeyEvent.VK_CONTROL" in order to control when the shoot button has been released.

Reply
JP
03/02/2013 8:27am

About the shooting while pressing Ctrl, since we achieved the shooting previous lessons before, when I used to press Ctrl, it only used to fire once. So the shooting mechanism didn't shoot endlessly while pressing down Ctrl. It only used to shoot once. I had to press Ctrl everytime for it to shoot. So bascially the shooting changes that you did in this lesson gives the same result, unless I had something wrong from before which I shouldn't since I always double check my code.

Reply
JP
03/02/2013 9:16am

I also noticed that you are missing the changes you have done to keyReleased and keyPressed with the boolean readyToFire on the StartingClass.java picture

Reply
Will
03/09/2013 2:04am

Previously holding down Ctrl would shoot a stream of bullets. With the new changes, it is no longer possible to to hold down the Ctrl like before.

Murtuza link
07/14/2013 6:11am

JP for me when I used to hold down the Ctrl Key it used to shoot endlessly..
I was happy to see it was corrected as the game would end up boring if it was a continuous shooting :P
For you, maybe there must be some other problem not sure.. but for others that problem was there.. :-) Hope this helps!

Reply
Devilo
03/08/2013 4:17am

hi im so grateful for the tutorials ,, and im sorry this has nothing to do with the tutorial but what program should i use to design charactars ,,, backgrounds and stuff in 2d :) just like the one u drew lol it was nice

Reply
James C.
03/12/2013 12:07pm

Look into Photoshop and Gimp.

Reply
Reece
03/15/2013 5:25pm

Really good stuff!

Thank you!!

Reply
newbie
04/24/2013 11:48am

nice tutorial, but i have a problem i follow all the steps but i got error in loadMap("data/map1.txt") can't load the file,

Reply
Sorin
05/04/2013 5:33am

Hey, I really need help with something. I made a game after you tutorials, because I want to understand every concept you explained. So far, I managed to do the map, the collision with the player. Now I want to make some points system, Like in Mario, if you collect stars you get points. Any idea how I can do that? I searched for a solution for almost 2 days, but without result. I tried to make a star tile, and to change with a transparent tile on player collision(to make the illusion that the player get the star ), and it worked. But what I couldn't do is the point to be added to the score. I tried taking the score variable from SartingClass, and adding 10 points everytime the player intersect with the starTile, but it don't work, because it continues to add 10 points to the score forever. Is there anyone who can help me, please?

Reply
Tom
07/08/2013 1:58pm

Hey, These steps are great but my project is saying that it cannot find the file map1.txt that I put in the data folder. I typed up my own classes and had this problem then I downloaded yours and it still cannot find the map file.

Reply
Murtuza link
07/14/2013 6:17am

Hi James,
I am doing all that is said in your tutorials and finally when I compare my code file with your final file I always find my code different then yours.. Even in this page, there were some additional functions/methods in my code which were not in your final file. Is there something wrong I am doing or did you forgot to tell us to delete :P
One suggestion is that I hope you are trying out your own tutorial step by step just to make sure things are all in place :)

By the way did I mention that you are AWESOME? :D

Reply



Leave a Reply

    Author

    James Cho is the lead developer at Kilobolt Studios. He is a college student at Duke University and loves soccer, music, and sharing knowledge.