Sunday, September 20, 2009

Programming java applet games part 4 - The Game Menu


The next step for our game will be the implementation on the Game Menu. This is the place where a game gives to players the available options ie Start Game,Option, Exit etc. In our game we will have only two options a) Start Game, B) Show Keys. We do not have an Exit option becaouse we write an applet game and we don't need this.
To implement the menu we need to change the run method to run more than one different "game loops", one for eatch game state.
public void run() {
        try {
            boolean exit = false;
            int menuOption = 0;
            while ( true ) {
                menuOption = getMenuOption();
                if ( menuOption == 0 ) {
                    gamePlayLoop();
                } else {
                    printKeys();
                }
            }
        } catch ( InterruptedException ex ) {
            Logger.getLogger( AsterpodsApplet.class.getName() ).log( Level.SEVERE, null, ex );
        }
    }
Let me explain the above code. The while statement changed to run forever because that we do not have an exit option. Then we define tree different game loops seperated in different methods.
a) int gerMenuOptions()
Contains the code to draw the game options and handle the user choices. The returned value is the user choice.
b)void gamePlayLoop()
the main game loop, it will contain all the action and it will be implemented later
c)void printKeys()
This method just prints key info

Now lets dig into this methods by starting with getMenuOptions
//the loop that will run while the player is on game menu
    private int getMenuOption() throws InterruptedException {
        long lastTime;
        int retValue = 0;//this will contain the players choice
        while ( true ) {
            lastTime = System.currentTimeMillis();
            drawMenu( retValue );
            if ( this.up_pressed == true ) {
                retValue = 0;
            } else if( this.down_pressed == true ) {
                retValue = 1;
            } else if ( this.space_pressed ) { // if it is the space that pressed then the user made his choice
                consumeTyped();
                break;
            }
            consumeTyped();
            repaint(); //rapaints the applet
            lastTime = System.currentTimeMillis() - lastTime;
            Thread.sleep( GAME_LOOP_MAX_TIME - lastTime );
        }
        return retValue;
    }
getMenuOption uses the method
void drawMenu( int option );
to draw the menu Options on the screen and check the user input to return game choices and change the state of the game. The user choice will be returned with the press of the space key while the up and down arrows change the choice.This is implemented by this
 if ( this.up_pressed == true ) {
                retValue = 0;
 } else if( this.down_pressed == true ) {
                retValue = 1;
 } else if ( this.space_pressed ) { // if it is the space that pressed then the user made his choice
                consumeTyped();
                break;
 }
  consumeTyped();
For this method except the up,down etc switches that i used in the previous tutorial to track user input i use extra switches up_presse, down_pressed etc. The difference between these switches is that the *_pressed versions will be used  to track that the keys pressed once and we do not care if they are still pressed.
The method
void consumeTyped()
is used to clear the *_pressed switches.

I will go out of the getMenuOptions and i will go to printKeys without explain the drawLogo becouse i want to let this method for the end of this tutorial.
private void printKeys() throws InterruptedException {
        long lastTime;
        while ( true ) {
            lastTime = System.currentTimeMillis();
            drawKeys();
            if ( this.space_pressed ) { // if it is the space that pressed then the user made his choice
                consumeTyped();
                break;
            }
            repaint(); //rapaints the applet
            lastTime = System.currentTimeMillis() - lastTime;
            Thread.sleep( GAME_LOOP_MAX_TIME - lastTime );
        }
    }
printKeys Method is identical to getMenuOptions with the difference that tracks only of the space key and calls the drawKeys to print the proper messages on the screen.

The interessting part of this part of the series is methods
void drawLogo(), void drawKeys() and void drawMenu( int retValue )
 private void drawLogo() {
        backBuffer.setColor( Color.black );
        backBuffer.fillRect( 0, 0, WIDTH, HEIGHT );
        color += nextInc;
        if ( color > 255 ) {
            color = 255;
            nextInc *= -1;
        } else if ( color < 100 ) {
            color = 100;
            nextInc *= -1;
        }
        //color = 250;
        Color c = new Color( color, color, color );
        backBuffer.setColor( c );

        //prints logo on the center
        String logo = "Asteroids - the rebirth";
        int strLength = backBuffer.getFontMetrics().stringWidth( logo );

        //the >> 1 is equals to /2
        backBuffer.drawString( logo, (WIDTH - strLength) >> 1, 140 );
    }

    //prints the key controls for the game
    private void drawKeys() {
        drawLogo();
        backBuffer.setColor( Color.WHITE );

        String[] lines = new String[ 6 ];
        lines[ 0] = "Press the Up arrow to accelerate";
        lines[ 1] = "Press the Down arrow to decelerate";
        lines[ 2] = "Press the Left arrow to turn left";
        lines[ 3] = "Press the Right arrow to turn right";
        lines[ 4] = "Press the Space to fire";
        lines[ 5] = "Press the Escape to exit";

        for ( int i = 0; i < lines.length; i++ ) {
            int strLength = backBuffer.getFontMetrics().stringWidth( lines[i] );
            backBuffer.drawString( lines[i], (WIDTH - strLength) >> 1, 170 + i * 15 );
        }

        String exitMessage = "Press space to go back";
        int strLength = backBuffer.getFontMetrics().stringWidth( exitMessage );
        backBuffer.drawString( exitMessage, (WIDTH - strLength) >> 1, 170 + lines.length * 15 + 30 );
    }

    private void drawMenu( int retValue ) {
        //System.out.println( "ret " + retValue );
        drawLogo();
        backBuffer.setColor( Color.WHITE );

        String start = "Start Game";
        int charHeight = backBuffer.getFontMetrics().getHeight();
        int strLength1 = backBuffer.getFontMetrics().stringWidth( start );
        backBuffer.drawString( start, (WIDTH - strLength1) >> 1, 175);

        String keys = "Show keys";
        int strLength2 = backBuffer.getFontMetrics().stringWidth( keys );
        backBuffer.drawString( keys, (WIDTH - strLength2) >> 1, 195 );

        String space = "Press space to choose";
        int strLength3 = backBuffer.getFontMetrics().stringWidth( space );
        backBuffer.drawString( space, (WIDTH - strLength3) >> 1, 235 );

        if( retValue == 0 ){
            backBuffer.drawRoundRect( ( (WIDTH - strLength1) >> 1 ) - 5, 160, strLength1 + 10, charHeight + 6, 10, 10 );
        } else {
            backBuffer.drawRoundRect( ( (WIDTH - strLength2) >> 1 ) - 5, 180, strLength2 + 10, charHeight + 6, 10, 10 );
        }
    }
 as you can see in everyone of these methods i use an object backBuffer to draw strings on the screen.
So what type is this object?
The answer is simple it is an instance of  the class Graphics that as i mention in the privious tutorials is used to draw strings, shapes and images on the screen. Ok the decleration and initialization of the backBuffer are
private Graphics backBuffer; 
Image offscreenImage;

private void createSecondBuffer() {
        offscreenImage = this.createImage( WIDTH, HEIGHT );
        backBuffer = offscreenImage.getGraphics(); }
When you want to print something on the screen you never print directly on the Graphics object that is passed as parameter on the paint method. If you do it you will get the flickerring effect.
 What is this?
i will let wikipedia to explain

Flicker is visible fading between cycles displayed on video displays, especially the refresh interval on cathode ray tube (CRT) based computer screens. Flicker occurs on CRTs when they are driven at a low refresh rate, allowing the screen's phosphors to lose their excitation (afterglow) between sweeps of the electron gun.
If you want to see live what flicker is just write the above code without the use of backBuffer but rather than that print dirrectrly to applet's Graphics object. Flickering occurs when the screen refreshes the same time that the Graphics object gets updates. So to transpass this we make every update in a single step inside the print method
public void paint( Graphics g ) {
        g.drawImage( offscreenImage, 0, 0, this );
}
This method is known as Double buffering and you must allways use it if you want to make a playable game.

I will close this article with the fade out/in effect. If you compile the code that i just wrote you will see the logo of the game to fade in and out. This effect is implemented by this
backBuffer.setColor( Color.black );
backBuffer.fillRect( 0, 0, WIDTH, HEIGHT );
color += nextInc;
if ( color > 255 ) {
        color = 255;
        nextInc *= -1;
} else if ( color < 100 ) {
        color = 100;
        nextInc *= -1;
}
//color = 250;
Color c = new Color( color, color, color );
backBuffer.setColor( c );

//prints logo on the center
String logo = "Asteroids - the rebirth";
int strLength = backBuffer.getFontMetrics().stringWidth( logo );
Ιτ uses the attribute color to calculate the color that will be used to draw the logo at the next frame.  When the color become greater than 255 (is the upper value) it starts to decrease will it increase again whet it becomes less than 100. Finally the getFontMetrics().stringWidth( String param ) returns the width of a given string with the specific font on the screen.

Below is the complete code of the class AsterpodsApplet as it just updated
package games.applet.Asteroid;

import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author alexm
 */
public class AsterpodsApplet extends Applet implements Runnable, KeyListener {

    private static final int WIDTH = 640;
    private static final int HEIGHT = 480;
    private static final int GAME_LOOP_MAX_TIME = 50;
    private Thread mainLoop;
    private boolean gameOver = false;
    //keyboard switches
    private boolean up;
    private boolean down;
    private boolean left;
    private boolean right;
    private boolean space;
    private boolean esc;

    private boolean up_pressed;
    private boolean down_pressed;
    private boolean left_pressed;
    private boolean right_pressed;
    private boolean space_pressed;
    private boolean esc_pressed;
    //Back buffer components
    private Graphics backBuffer;
    Image offscreenImage;

    @Override
    public void init() {
        mainLoop = new Thread( this );
        up = false;
        down = false;
        left = false;
        right = false;
        space = false;
        esc = false;

        //create the back buffer
        createSecondBuffer();
        //enabling the key listening
        addKeyListener( this );
    }

    //creates a second buffer to eliminate the flickering
    private void createSecondBuffer() {
        offscreenImage = this.createImage( WIDTH, HEIGHT );
        backBuffer = offscreenImage.getGraphics();
    }

    @Override
    public void start() {
        mainLoop.start();
    }

    @Override
    public void stop() {
        gameOver = true;
    }

    @Override
    public void update( Graphics g ) {
        paint( g );
    }

    @Override
    public void paint( Graphics g ) {
        g.drawImage( offscreenImage, 0, 0, this );
    }

    public void run() {
        try {
            boolean exit = false;
            int menuOption = 0;
            while ( true ) {
                menuOption = getMenuOption();
                if ( menuOption == 0 ) {
                    gamePlayLoop();
                } else {
                    printKeys();
                }
            }
        } catch ( InterruptedException ex ) {
            Logger.getLogger( AsterpodsApplet.class.getName() ).log( Level.SEVERE, null, ex );
        }
    }

    //the loop that will run while the player is on game menu
    private int getMenuOption() throws InterruptedException {
        long lastTime;
        int retValue = 0;//this will contain the players choice
        while ( true ) {
            lastTime = System.currentTimeMillis();
            drawMenu( retValue );
            if ( this.up_pressed == true ) {
                retValue = 0;
            } else if( this.down_pressed == true ) {
                retValue = 1;
            } else if ( this.space_pressed ) { // if it is the space that pressed then the user made his choice
                consumeTyped();
                break;
            }
            consumeTyped();
            repaint(); //rapaints the applet
            lastTime = System.currentTimeMillis() - lastTime;
            Thread.sleep( GAME_LOOP_MAX_TIME - lastTime );
        }
        return retValue;
    }

    private void printKeys() throws InterruptedException {
        long lastTime;
        while ( true ) {
            lastTime = System.currentTimeMillis();
            drawKeys();
            if ( this.space_pressed ) { // if it is the space that pressed then the user made his choice
                consumeTyped();
                break;
            }
            repaint(); //rapaints the applet
            lastTime = System.currentTimeMillis() - lastTime;
            Thread.sleep( GAME_LOOP_MAX_TIME - lastTime );
        }
    }
    //this is used to make the fade effect
    private int color = 100;
    private int nextInc = 5;

    private void drawLogo() {
        backBuffer.setColor( Color.black );
        backBuffer.fillRect( 0, 0, WIDTH, HEIGHT );
        color += nextInc;
        if ( color > 255 ) {
            color = 255;
            nextInc *= -1;
        } else if ( color < 100 ) {
            color = 100;
            nextInc *= -1;
        }
        //color = 250;
        Color c = new Color( color, color, color );
        backBuffer.setColor( c );

        //prints logo on the center
        String logo = "Asteroids - the rebirth";
        int strLength = backBuffer.getFontMetrics().stringWidth( logo );

        //the >> 1 is equals to /2
        backBuffer.drawString( logo, (WIDTH - strLength) >> 1, 140 );
    }

    //prints the key controls for the game
    private void drawKeys() {
        drawLogo();
        backBuffer.setColor( Color.WHITE );

        String[] lines = new String[ 6 ];
        lines[ 0] = "Press the Up arrow to accelerate";
        lines[ 1] = "Press the Down arrow to decelerate";
        lines[ 2] = "Press the Left arrow to turn left";
        lines[ 3] = "Press the Right arrow to turn right";
        lines[ 4] = "Press the Space to fire";
        lines[ 5] = "Press the Escape to exit";

        for ( int i = 0; i < lines.length; i++ ) {
            int strLength = backBuffer.getFontMetrics().stringWidth( lines[i] );
            backBuffer.drawString( lines[i], (WIDTH - strLength) >> 1, 170 + i * 15 );
        }

        String exitMessage = "Press space to go back";
        int strLength = backBuffer.getFontMetrics().stringWidth( exitMessage );
        backBuffer.drawString( exitMessage, (WIDTH - strLength) >> 1, 170 + lines.length * 15 + 30 );
    }

    private void drawMenu( int retValue ) {
        //System.out.println( "ret " + retValue );
        drawLogo();
        backBuffer.setColor( Color.WHITE );

        String start = "Start Game";
        int charHeight = backBuffer.getFontMetrics().getHeight();
        int strLength1 = backBuffer.getFontMetrics().stringWidth( start );
        backBuffer.drawString( start, (WIDTH - strLength1) >> 1, 175);

        String keys = "Show keys";
        int strLength2 = backBuffer.getFontMetrics().stringWidth( keys );
        backBuffer.drawString( keys, (WIDTH - strLength2) >> 1, 195 );

        String space = "Press space to choose";
        int strLength3 = backBuffer.getFontMetrics().stringWidth( space );
        backBuffer.drawString( space, (WIDTH - strLength3) >> 1, 235 );

        if( retValue == 0 ){
            backBuffer.drawRoundRect( ( (WIDTH - strLength1) >> 1 ) - 5, 160, strLength1 + 10, charHeight + 6, 10, 10 );
        } else {
            backBuffer.drawRoundRect( ( (WIDTH - strLength2) >> 1 ) - 5, 180, strLength2 + 10, charHeight + 6, 10, 10 );
        }
    }
    //the loop that will run while the player fights the asteroids

    private void gamePlayLoop() throws InterruptedException {
        long lastTime;
        while ( gameOver == false ) {
            lastTime = System.currentTimeMillis();
            checkUserInput();
            runAI();
            updateUser();
            updateEnemies();
            repaint(); //rapaints the applet
            lastTime = System.currentTimeMillis() - lastTime;
            Thread.sleep( GAME_LOOP_MAX_TIME - lastTime );
        }
    }

    private void checkUserInput() {
        throw new UnsupportedOperationException( "Not yet implemented" );
    }

    private void runAI() {
        throw new UnsupportedOperationException( "Not yet implemented" );
    }

    private void updateUser() {
        throw new UnsupportedOperationException( "Not yet implemented" );
    }

    private void updateEnemies() {
        throw new UnsupportedOperationException( "Not yet implemented" );
    }

    private void consumeTyped() {
        up_pressed = false;
        down_pressed = false;
        left_pressed = false;
        right_pressed = false;
        space_pressed = false;
        esc_pressed = false;
    }

    public void keyTyped( KeyEvent e ) {       
    }

    public void keyPressed( KeyEvent e ) {
        System.out.println( "key pressed " + e.getKeyCode() );
        if ( e.getKeyCode() == KeyEvent.VK_UP ) {
            up = true;
            up_pressed = true;
        }
        if ( e.getKeyCode() == KeyEvent.VK_DOWN ) {
            down = true;
            down_pressed = true;
        }
        if ( e.getKeyCode() == KeyEvent.VK_LEFT ) {
            left = true;
            left_pressed = true;
        }
        if ( e.getKeyCode() == KeyEvent.VK_RIGHT ) {
            right = true;
            right_pressed = true;
        }
        if ( e.getKeyCode() == KeyEvent.VK_SPACE ) {
            space = true;
            space_pressed = true;
        }
        if ( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
            esc = true;
            esc_pressed = true;
        }
    }

    public void keyReleased( KeyEvent e ) {
        System.out.println( "key releashed " + e.getKeyCode() );
        if ( e.getKeyCode() == KeyEvent.VK_UP ) {
            up = false;
        }
        if ( e.getKeyCode() == KeyEvent.VK_DOWN ) {
            down = false;
        }
        if ( e.getKeyCode() == KeyEvent.VK_LEFT ) {
            left = false;
        }
        if ( e.getKeyCode() == KeyEvent.VK_RIGHT ) {
            right = false;
        }
        if ( e.getKeyCode() == KeyEvent.VK_SPACE ) {
            space = false;
        }
        if ( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
            esc = false;
        }
    }
}

Have a nice coding time

No comments:

Post a Comment