0

I am trying to make my way through the book 'Beginning Java SE 6 Game Programming'. I have a bit of previous experience in Java, however I have never encountered a Runnable interface. The third chapter in the book shows a simple Asteroids game with almost all the needed code. The author does not provide a main() method, and everything I tried on my own ends up with either successfully compiling but doing nothing or with errors.

The code of the Asteroids.java file has almost 400 lines, and probably only the init(), start() and run() methods are the most important, however I will paste all of it.

package asteroids;

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;


public class Asteroids extends Applet implements Runnable, KeyListener {

    //the main thread becomes the game loop
    Thread gameloop;

    //use this as a double buffer
    BufferedImage backbuffer;

    //the main drawing object for the back buffer
    Graphics2D g2d;

    //toggle for drawing bounding boxes
    boolean showBounds = false;

    //create the asteroid array
    int ASTEROIDS = 20;
    Asteroid[] ast = new Asteroid[ASTEROIDS];

    //create the new bullet array
    int BULLETS = 10;
    Bullet[] bullet = new Bullet[BULLETS];
    int currentBullet = 0;

    //the player's ship
    Ship ship = new Ship();

    //create the identity transform(0,0)
    AffineTransform identity = new AffineTransform();

    //create a random number generator
    Random rand = new Random();

    //applet init event
    public void init(){
        //create the back buffer for smooth graphics
        backbuffer = new BufferedImage(640,480, BufferedImage.TYPE_INT_RGB);
        g2d = backbuffer.createGraphics();

        //det up the ship
        ship.setX(320);
        ship.setY(240);

        //set up the bullets
        for(int n = 0 ; n < BULLETS ; n++)
        {
            bullet[n] = new Bullet();
        }

        //create the asteroid
        for(int n = 0 ; n < ASTEROIDS ; n++)
        {
            ast[n] = new Asteroid();
            ast[n].setRotationVelocity(rand.nextInt(3)+1);
            ast[n].setX((double)rand.nextInt(600)+20);
            ast[n].setY((double)rand.nextInt(440)+20);
            ast[n].setMoveAngle(rand.nextInt(360));
            double ang = ast[n].getMoveAngle() - 90;
            ast[n].setVelX(calcAngleMoveX(ang));
            ast[n].setVelY(calcAngleMoveY(ang));
        }

        //start the user input listener
        addKeyListener(this);
        }

    //applet update event to redraw the screen
    public void update(Graphics g){
        //start off transform at identity
        g2d.setTransform(identity);

        //erase the background
        g2d.setPaint(Color.BLACK);
        g2d.fillRect(0, 0, getSize().width, getSize().height);

        //print some status information
        g2d.setColor(Color.WHITE);
        g2d.drawString("Ship: " + Math.round(ship.getX()) + ", " + Math.round(ship.getY()), 5, 10);
        g2d.drawString("Move angle: " + Math.round(ship.getMoveAngle())+90, 5, 25);
        g2d.drawString("Face angle: " + Math.round(ship.getFaceAngle()), 5, 40);

        //draw the game g raphics
        drawShip();
        drawBullets();
        drawAsteroids();

        //repaint the applet window
        paint(g);
    }

    //drawShip called by applet update event
    public void drawShip(){
        g2d.setTransform(identity);
        g2d.translate(ship.getX(), ship.getY());
        g2d.rotate(Math.toRadians(ship.getFaceAngle()));
        g2d.setColor(Color.ORANGE);
        g2d.fill(ship.getShape());
    }

    //drawBullets called by applet update event
    public void drawBullets(){
        //iterate through the array of bullets
        for(int n = 0 ; n < BULLETS ; n++)
        {
            //is this bullet currently in use?
            if(bullet[n].isAlive()){
                //draw the bullet
                g2d.setTransform(identity);
                g2d.translate(bullet[n].getX(), bullet[n].getY());
                g2d.setColor(Color.MAGENTA);
                g2d.draw(bullet[n].getShape());
            }
        }
    }

    //drawAsteroids called by applet update event
    public void drawAsteroids(){
        //iterate through the asteroids array
        for(int n = 0 ; n < ASTEROIDS ; n++)
        {
            //is this asterois currently in use?
            if(ast[n].isAlive()){
                //draw the Asteroid
                g2d.setTransform(identity);
                g2d.translate(ast[n].getX(), ast[n].getY());
                g2d.rotate(Math.toRadians(ast[n].getMoveAngle()));
                g2d.setColor(Color.DARK_GRAY);
                g2d.fill(ast[n].getShape());
            }            
        }
    }

    //applet window repaint event - - draw the back buffer
    public void paint(Graphics g){
        //draw the back buffer onto the applet window
        g.drawImage(backbuffer, 0, 0, this);
    }


    //thread start event - start the game loop running
    public void start(){
        //create the gameloop thread for real-time updates
        gameloop = new Thread(this);
        gameloop.start();
    }

    //thread run event (game loop)
    public void run(){
        //acquire the current thread
        Thread t = Thread.currentThread();

        //keep going as long as the thread is alive
        while(t == gameloop){
            try{
                //update game loop
                gameUpdate();

                //target framerate is 50 fps
                Thread.sleep(20);
            }
            catch(InterruptedException e){
                e.printStackTrace();
            }
            repaint();
            }
        }
    //thread stop event
    public void stop(){
        //kill the gameloop thread
        gameloop = null;
    }

    //move and animate objects in the game
    private void gameUpdate(){
        updateShip();
        updateBullets();
        updateAsteroids();
        checkCollisions();
    }

    //update the ship position based on velocity
    public void updateShip(){
        //update ship's X position
        ship.incX(ship.getVelX());

        //wrap around left/right
        if(ship.getX() < -10)
            ship.setX(getSize().width+10);
        else if(ship.getX() > getSize().width + 10)
            ship.setX(-10);

        //update ship's Y position
        ship.incY(ship.getVelY());

        //wrap around top/bottom
        if(ship.getY() < - 10)
            ship.setY(getSize().height + 10);
        else if(ship.getY() > getSize().height + 10)
            ship.setY(-10);
    }

    //update the bullets based on velocity
    public void updateBullets(){
        //move each of the bullets
        for(int n = 0 ; n < BULLETS ; n++)
        {
            //is the bullet alive?
            if(bullet[n].isAlive()){
                //update bullet's x position
                bullet[n].incX(bullet[n].getVelX());

                //bullet disappears at left/right edge
                if(bullet[n].getX() < 0 ||
                    bullet[n].getX() > getSize().width)
                {
                    bullet[n].setAlive(false);
                }

                //update bullet's y position
                bullet[n].incY(bullet[n].getVelY());

                //bullet disappears at top/bottom edge
                if(bullet[n].getY() < 0 ||
                    bullet[n].getY() > getSize().height)
                {
                    bullet[n].setAlive(false);
                }
            }
        }
    }

    //update asteroids based on velocity
    public void updateAsteroids(){
        //move and rotate the asteroids
        for(int n = 0 ; n < ASTEROIDS ; n++)
        {
            //is this asteroid being used?
            if(ast[n].isAlive()){
                //update this asteroid's X value
                ast[n].incX(ast[n].getVelX());

                //warp the asteroid at screen edges
                if(ast[n].getX() < -20)
                    ast[n].setX(getSize().width + 20);
                else if(ast[n].getX() > getSize().width + 20)
                    ast[n].setX(-20);

                //update the asteroid’s Y value
                ast[n].incY(ast[n].getVelY());

                //warp the asteroid at screen edges
                if (ast[n].getY() < -20)
                    ast[n].setY(getSize().height + 20);
                else if (ast[n].getY() > getSize().height + 20)
                    ast[n].setY(-20);

                //update the asteroid’s rotation
                ast[n].incMoveAngle(ast[n].getRotationVelocity());

                //keep the angle within 0-359 degrees
                if (ast[n].getMoveAngle() < 0)
                    ast[n].setMoveAngle(360 - ast[n].getRotationVelocity());
                else if (ast[n].getMoveAngle() > 360)
                    ast[n].setMoveAngle(ast[n].getRotationVelocity());

            }
        }
    }

    //Test asteroids for collisions with ship or bullets
    public void checkCollisions(){

        //iterate through the asteroids array
        for(int m = 0 ; m < ASTEROIDS ; m++)
        {
            //is this asteroid being used
            if(ast[m].isAlive())
            {
                //check for collisions with bullet
                for(int n = 0 ; n < BULLETS; n++)
                {
                    //is this bullet being used?
                    if(bullet[n].isAlive())
                    {
                        //perform collision test
                        if(ast[m].getBounds().contains(
                            bullet[n].getX(), bullet[n].getY()))
                                {
                                    bullet[n].setAlive(false);
                                    ast[m].setAlive(false);
                                    continue;
                                }
                    }
                }
            }

            //check for collision with ship
            if(ast[m].getBounds().intersects(ship.getBounds())){
                ast[m].setAlive(false);
                ship.setX(320);
                ship.setY(240);
                ship.setFaceAngle(0);
                ship.setVelX(0);
                ship.setVelY(0);
                continue;
            }
        }
    }

    //key listener events
    public void keyReleased(KeyEvent k){}
    public void keyTyped(KeyEvent k){}
    public void keyPressed(KeyEvent k){
        int keyCode = k.getKeyCode();
        switch(keyCode){
            case KeyEvent.VK_LEFT:

                //left arrow rotates ship left 5 degrees
                ship.incFaceAngle(-5);
                if(ship.getFaceAngle() < 0)ship.setFaceAngle(360-5);
                break;
            case KeyEvent.VK_RIGHT:

                //right arrow rotates ship right 5 degrees
                ship.incFaceAngle(5);
                if(ship.getFaceAngle() > 360)ship.setFaceAngle(5);
                break;
            case KeyEvent.VK_UP:

                //up arrow adds thrust to ship (1/10 normal speed)
                ship.setMoveAngle(ship.getFaceAngle() - 90);
                ship.incVelX(calcAngleMoveX(ship.getMoveAngle()) * 0.1);
                ship.incVelY(calcAngleMoveY(ship.getMoveAngle()) * 0.1);

                //Ctrl, Enter, or Space can be used to fire weapon
            case KeyEvent.VK_CONTROL:
            case KeyEvent.VK_ENTER:
            case KeyEvent.VK_SPACE:
                //fire a bullet
                currentBullet++;
                if(currentBullet > BULLETS -1)currentBullet = 0;
                bullet[currentBullet].setAlive(true);

                //point bullet in same direction ship is facing
                bullet[currentBullet].setX(ship.getX());
                bullet[currentBullet].setY(ship.getY());
                bullet[currentBullet].setMoveAngle(ship.getFaceAngle() - 90);

                //fire bullet at angle of the ship
                double angle = bullet[currentBullet].getMoveAngle();
                double svx = ship.getVelX();
                double svy = ship.getVelY();
                bullet[currentBullet].setVelX(svx + calcAngleMoveX(angle) * 2);
                bullet[currentBullet].setVelY(svy + calcAngleMoveY(angle) * 2);
                break;
        }
    }

        //calculate X movement value based on direction angle
        public double calcAngleMoveX(double angle){
            return (double)(Math.cos(angle * Math.PI / 180));
        }

        //calculate Y movement value based on direction able
        public double calcAngleMoveY(double angle){
            return (double)(Math.sin(angle * Math.PI / 180));
        }

}

I am aware that there is supposed to be a main() method right before the last bracket. Here are a few examples of what I've tried.

public static void main(String[] args) {

            Asteroids a = new Asteroids();
            a.init();
            a.run();
           }

BUILD SUCCESSFUL, nothing happens. I'm guessing that this should be a single command, instead of two separate ones? I'm also not sure if the .run() or .init() method should be called on an Asteroids object, or an instance of a Thread?

public static void main(String[] args) {

            Thread t = new Thread(new Asteroids());
            t.run();
           }

Again, BUILD SUCCESSFUL, nothing happens. Another guess is, since I already have a start() method, which creates a new Thread, I should just call it on a new Asteroids() object, but then I am getting a NullPointerException.

I have tried a couple of similar methods, but I think this post is long enough without showing them. The main questions are:

  • Having these methods, what should I call them on? A Thread object or an Asteroids object?
  • Shouldn't the only method called be run() (since the code examples I found regarding Threads and Runnable interfaces use only the run() method).
  • If not, since I have a couple of methods in the Asteroids class, should they be called in a specific order before the game actually runs? E.g. .start(), .init(), .run()?

I just have the feeling that there is a huge gap between my knowledge and the author's expectations, and, even if looking at the code I can make out what does most of the stuff stand for, it's hard to move on by my own. Any hints, specific Oracle docs which I could go through to get a grip on this? I am using NetBeans for compiling.

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • 2
    It's an applet. Starting a applet is a bit different to starting a normal java program. Have look at the [applet tutorial](http://docs.oracle.com/javase/tutorial/deployment/applet/). – fabian May 06 '14 at 16:34
  • 1
    @fabian You should make that an answer, not a comment. There's not really much else to be said. – Solomon Slow May 06 '14 at 16:42
  • 1) Why code an applet? If it is due to spec. by teacher, please refer them to [Why CS teachers should stop teaching Java applets](http://programmers.blogoverflow.com/2013/05/why-cs-teachers-should-stop-teaching-java-applets/). 2) Why AWT rather than Swing? See my answer on [Swing extras over AWT](http://stackoverflow.com/a/6255978/418556) for many good reasons to abandon using AWT components. – Andrew Thompson May 07 '14 at 07:11
  • Thanks for the comments. @fabian Thanks, I never knew applets didn't need a main class. Helped a lot! Andrew Thompson Well, the author of the book uses an Applet and AWT and I believe I'm not advanced enough to make any import changes on my own yet. Thank you for your suggestions, thou, I'll keep them in mind. – user2855670 May 08 '14 at 18:57

1 Answers1

0

Ok From what i read from your code, this is whats happening:

   Asteroids a = new Asteroids(); // Instantiate an object for the game
   a.init(); // Init the applet for the game, add key listeners.
   a.start(); // Start the thread(its handled internally) with the game loop, which handles UI updates and game mechanics

Your first attempt fails, because start() sets a member attribute, gameloop as a new Thread. so when you call run manually, gameloop is not set, so currentThread(t) its not rqual to gameloop(null).

Second attempt fails for same reason, and also because applet is not initialized.

Sinn
  • 274
  • 1
  • 7