SimpleGame.java Source code

Java tutorial

Introduction

Here is the source code for SimpleGame.java

Source

/*
Essential Java 3D Fast
    
Ian Palmer
    
Publisher: Springer-Verlag
    
ISBN: 1-85233-394-4
    
*/

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Enumeration;

import javax.media.j3d.Alpha;
import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.Behavior;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.Bounds;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.Locale;
import javax.media.j3d.Material;
import javax.media.j3d.Node;
import javax.media.j3d.PhysicalBody;
import javax.media.j3d.PhysicalEnvironment;
import javax.media.j3d.PositionInterpolator;
import javax.media.j3d.Switch;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.media.j3d.ViewPlatform;
import javax.media.j3d.VirtualUniverse;
import javax.media.j3d.WakeupCriterion;
import javax.media.j3d.WakeupOnAWTEvent;
import javax.media.j3d.WakeupOnCollisionEntry;
import javax.media.j3d.WakeupOnElapsedTime;
import javax.media.j3d.WakeupOr;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix3d;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.loaders.Scene;
import com.sun.j3d.loaders.objectfile.ObjectFile;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Cylinder;
import com.sun.j3d.utils.geometry.Sphere;

/**
 * This application demonstrates a number of things in the implementation of a
 * simple shooting game. The object of the the game is to shoot a duck that
 * repeatedly moves across the screen from left to right. There are two duck
 * models, one for the 'live' duck and one for the 'dead' one. These are loaded
 * from 'duck.obj' and 'deadduck.obj' files. The 'gun' is built from primitives.
 * The duck and the ball that is used to shoot the duck use interpolators for
 * their animation. The gun uses key board input to aim and fire it, and
 * collision detection is used to 'kill' the duck.
 * 
 * @author I.J.Palmer
 * @version 1.0
 */
public class SimpleGame extends Frame implements ActionListener {
    protected Canvas3D myCanvas3D = new Canvas3D(null);

    protected Button exitButton = new Button("Exit");

    protected BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0);

    /** Switch that is used to swap the duck models */
    Switch duckSwitch;

    /** Alpha used to drive the duck animation */
    Alpha duckAlpha;

    /** Used to drive the ball animation */
    Alpha ballAlpha;

    /** Used to move the ball */
    PositionInterpolator moveBall;

    /** Used to rotate the gun */
    TransformGroup gunXfmGrp = new TransformGroup();

    /**
     * This builds the view branch of the scene graph.
     * 
     * @return BranchGroup with viewing objects attached.
     */
    protected BranchGroup buildViewBranch(Canvas3D c) {
        BranchGroup viewBranch = new BranchGroup();
        Transform3D viewXfm = new Transform3D();
        Matrix3d viewTilt = new Matrix3d();
        viewTilt.rotX(Math.PI / -6);
        viewXfm.set(viewTilt, new Vector3d(0.0, 10.0, 10.0), 1.0);
        TransformGroup viewXfmGroup = new TransformGroup(viewXfm);
        ViewPlatform myViewPlatform = new ViewPlatform();
        PhysicalBody myBody = new PhysicalBody();
        PhysicalEnvironment myEnvironment = new PhysicalEnvironment();
        viewXfmGroup.addChild(myViewPlatform);
        viewBranch.addChild(viewXfmGroup);
        View myView = new View();
        myView.addCanvas3D(c);
        myView.attachViewPlatform(myViewPlatform);
        myView.setPhysicalBody(myBody);
        myView.setPhysicalEnvironment(myEnvironment);
        return viewBranch;
    }

    /**
     * This adds some lights to the content branch of the scene graph.
     * 
     * @param b
     *            The BranchGroup to add the lights to.
     */
    protected void addLights(BranchGroup b) {
        Color3f ambLightColour = new Color3f(0.5f, 0.5f, 0.5f);
        AmbientLight ambLight = new AmbientLight(ambLightColour);
        ambLight.setInfluencingBounds(bounds);
        Color3f dirLightColour = new Color3f(1.0f, 1.0f, 1.0f);
        Vector3f dirLightDir = new Vector3f(-1.0f, -1.0f, -1.0f);
        DirectionalLight dirLight = new DirectionalLight(dirLightColour, dirLightDir);
        dirLight.setInfluencingBounds(bounds);
        b.addChild(ambLight);
        b.addChild(dirLight);
    }

    /**
     * This builds the gun geometry. It uses box and cylinder primitives and
     * sets up a transform group so that we can rotate the gun.
     */
    protected BranchGroup buildGun() {
        BranchGroup theGun = new BranchGroup();
        Appearance gunApp = new Appearance();
        Color3f ambientColour = new Color3f(0.5f, 0.5f, 0.5f);
        Color3f emissiveColour = new Color3f(0.0f, 0.0f, 0.0f);
        Color3f specularColour = new Color3f(1.0f, 1.0f, 1.0f);
        Color3f diffuseColour = new Color3f(0.5f, 0.5f, 0.5f);
        float shininess = 20.0f;
        gunApp.setMaterial(new Material(ambientColour, emissiveColour, diffuseColour, specularColour, shininess));
        TransformGroup init = new TransformGroup();
        TransformGroup barrel = new TransformGroup();
        Transform3D gunXfm = new Transform3D();
        Transform3D barrelXfm = new Transform3D();
        barrelXfm.set(new Vector3d(0.0, -2.0, 0.0));
        barrel.setTransform(barrelXfm);
        Matrix3d gunXfmMat = new Matrix3d();
        gunXfmMat.rotX(Math.PI / 2);
        gunXfm.set(gunXfmMat, new Vector3d(0.0, 0.0, 0.0), 1.0);
        init.setTransform(gunXfm);
        gunXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        gunXfmGrp.addChild(new Box(1.0f, 1.0f, 0.5f, gunApp));
        barrel.addChild(new Cylinder(0.3f, 4.0f, gunApp));
        gunXfmGrp.addChild(barrel);
        theGun.addChild(init);
        init.addChild(gunXfmGrp);
        return theGun;
    }

    /**
     * Creates the duck. This loads the two duck geometries from the files
     * 'duck.obj' and 'deadduck.obj' and loads these into a switch. The access
     * rights to the switch are then set so we can write to this switch to swap
     * between the two duck models. It also creates a transform group and an
     * interpolator to move the duck.
     * 
     * @return BranchGroup with content attached.
     */
    protected BranchGroup buildDuck() {
        BranchGroup theDuck = new BranchGroup();
        duckSwitch = new Switch(0);
        duckSwitch.setCapability(Switch.ALLOW_SWITCH_WRITE);

        ObjectFile f1 = new ObjectFile();
        ObjectFile f2 = new ObjectFile();
        Scene s1 = null;
        Scene s2 = null;
        try {
            s1 = f1.load("duck.obj");
            s2 = f2.load("deadduck.obj");
        } catch (Exception e) {
            System.exit(1);
        }

        TransformGroup duckRotXfmGrp = new TransformGroup();
        Transform3D duckRotXfm = new Transform3D();
        Matrix3d duckRotMat = new Matrix3d();
        duckRotMat.rotY(Math.PI / 2);
        duckRotXfm.set(duckRotMat, new Vector3d(0.0, 0.0, -30.0), 1.0);
        duckRotXfmGrp.setTransform(duckRotXfm);
        duckRotXfmGrp.addChild(duckSwitch);

        duckSwitch.addChild(s1.getSceneGroup());
        duckSwitch.addChild(s2.getSceneGroup());

        TransformGroup duckMovXfmGrp = new TransformGroup();
        duckMovXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        duckMovXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        duckMovXfmGrp.addChild(duckRotXfmGrp);

        duckAlpha = new Alpha(-1, 0, 0, 3000, 0, 0);
        Transform3D axis = new Transform3D();
        PositionInterpolator moveDuck = new PositionInterpolator(duckAlpha, duckMovXfmGrp, axis, -30.0f, 30.0f);
        moveDuck.setSchedulingBounds(bounds);
        theDuck.addChild(moveDuck);
        theDuck.addChild(duckMovXfmGrp);
        return theDuck;
    }

    /**
     * This builds the ball that acts as the bullet for our gun. The ball is
     * created from a sphere primitive, and a transform group and interpolator
     * are added so that we can 'fire' the bullet.
     * 
     * @return BranchGroup that is the root of the ball branch.
     */
    protected BranchGroup buildBall() {
        BranchGroup theBall = new BranchGroup();

        Appearance ballApp = new Appearance();
        Color3f ambientColour = new Color3f(1.0f, 0.0f, 0.0f);
        Color3f emissiveColour = new Color3f(0.0f, 0.0f, 0.0f);
        Color3f specularColour = new Color3f(1.0f, 1.0f, 1.0f);
        Color3f diffuseColour = new Color3f(1.0f, 0.0f, 0.0f);
        float shininess = 20.0f;
        ballApp.setMaterial(new Material(ambientColour, emissiveColour, diffuseColour, specularColour, shininess));

        Sphere ball = new Sphere(0.2f, ballApp);

        TransformGroup ballMovXfmGrp = new TransformGroup();
        ballMovXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        ballMovXfmGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        ballMovXfmGrp.addChild(ball);
        theBall.addChild(ballMovXfmGrp);

        ballAlpha = new Alpha(1, 0, 0, 500, 0, 0);
        Transform3D axis = new Transform3D();
        axis.rotY(Math.PI / 2);
        moveBall = new PositionInterpolator(ballAlpha, ballMovXfmGrp, axis, 0.0f, 50.0f);
        moveBall.setSchedulingBounds(bounds);
        theBall.addChild(moveBall);

        return theBall;

    }

    /**
     * This puts all the content togther. It used the three 'build' functions to
     * create the duck, the gun and the ball. It also creates the two behaviours
     * from the DuckBehaviour and GunBehaviour classes. It then puts all this
     * together.
     * 
     * @return BranchGroup that is the root of the content.
     */
    protected BranchGroup buildContentBranch() {
        BranchGroup contentBranch = new BranchGroup();
        Node theDuck = buildDuck();
        contentBranch.addChild(theDuck);
        Node theBall = buildBall();
        contentBranch.addChild(theBall);
        DuckBehaviour hitTheDuck = new DuckBehaviour(theDuck, duckSwitch, duckAlpha, bounds);
        GunBehaviour shootTheGun = new GunBehaviour(ballAlpha, moveBall, gunXfmGrp, bounds);
        contentBranch.addChild(hitTheDuck);
        contentBranch.addChild(shootTheGun);
        contentBranch.addChild(buildGun());
        addLights(contentBranch);
        return contentBranch;
    }

    /** Exit the application */
    public void actionPerformed(ActionEvent e) {
        dispose();
        System.exit(0);
    }

    public SimpleGame() {
        VirtualUniverse myUniverse = new VirtualUniverse();
        Locale myLocale = new Locale(myUniverse);
        myLocale.addBranchGraph(buildViewBranch(myCanvas3D));
        myLocale.addBranchGraph(buildContentBranch());
        setTitle("Duck Shoot!");
        setSize(400, 400);
        setLayout(new BorderLayout());
        add("Center", myCanvas3D);
        exitButton.addActionListener(this);
        add("South", exitButton);
        setVisible(true);

    }

    public static void main(String[] args) {
        SimpleGame sg = new SimpleGame();
    }

}

/**
 * This is used in the SimpleGame application. It defines the behaviour for the
 * duck, which is the target in the shooting game. If something collides with
 * the duck, it swaps a switch value to 'kill' the duck The duck is revived when
 * it's alpha value passes through zero.
 * 
 * @author I.J.Palmer
 * @version 1.0
 */

class DuckBehaviour extends Behavior {
    /** The shape that is being watched for collisions. */
    protected Node collidingShape;

    /** The separate criteria that trigger this behaviour */
    protected WakeupCriterion[] theCriteria;

    /** The result of the 'OR' of the separate criteria */
    protected WakeupOr oredCriteria;

    /** The switch that is used to swap the duck shapes */
    protected Switch theSwitch;

    /** The alpha generator that drives the animation */
    protected Alpha theTargetAlpha;

    /** Defines whether the duck is dead or alive */
    protected boolean dead = false;

    /**
     * This sets up the data for the behaviour.
     * 
     * @param theShape
     *            Node that is to be watched for collisions.
     * @param sw
     *            Switch that is used to swap shapes.
     * @param a1
     *            Alpha that drives the duck's animation.
     * @param theBounds
     *            Bounds that define the active region for this behaviour.
     */
    public DuckBehaviour(Node theShape, Switch sw, Alpha a1, Bounds theBounds) {
        collidingShape = theShape;
        theSwitch = sw;
        theTargetAlpha = a1;
        setSchedulingBounds(theBounds);
    }

    /**
     * This sets up the criteria for triggering the behaviour. It creates an
     * collision crtiterion and a time elapsed criterion, OR's these together
     * and then sets the OR'ed criterion as the wake up condition.
     */
    public void initialize() {
        theCriteria = new WakeupCriterion[2];
        theCriteria[0] = new WakeupOnCollisionEntry(collidingShape);
        theCriteria[1] = new WakeupOnElapsedTime(1);
        oredCriteria = new WakeupOr(theCriteria);
        wakeupOn(oredCriteria);
    }

    /**
     * This is where the work is done. If there is a collision, then if the duck
     * is alive we switch to the dead duck. If the duck was already dead then we
     * take no action. The other case we need to check for is when the alpha
     * value is zero, when we need to set the duck back to the live one for its
     * next traversal of the screen. Finally, the wake up condition is set to be
     * the OR'ed criterion again.
     */
    public void processStimulus(Enumeration criteria) {
        while (criteria.hasMoreElements()) {
            WakeupCriterion theCriterion = (WakeupCriterion) criteria.nextElement();
            if (theCriterion instanceof WakeupOnCollisionEntry) {
                //There'sa collision so if the duck is alive swap
                //it to the dead one
                if (dead == false) {
                    theSwitch.setWhichChild(1);
                    dead = true;
                }
            } else if (theCriterion instanceof WakeupOnElapsedTime) {
                //If there isn't a collision, then check the alpha
                //value and if it's zero, revive the duck
                if (theTargetAlpha.value() < 0.1) {
                    theSwitch.setWhichChild(0);
                    dead = false;
                }
            }

        }
        wakeupOn(oredCriteria);
    }
}

/**
 * This is used in the SimpleGame application. It defines a behaviour that
 * allows a 'gun' to be rotated when left and right cursor keys are pressed and
 * then a ball is 'fired' when the space bar is pressed. The 'firing' is
 * achieved by setting the start time of an interpolator to the current time.
 * 
 * @author I.J.Palmer
 * @version 1.0
 */

class GunBehaviour extends Behavior {
    /** The separate criteria that trigger this behaviour */
    protected WakeupCriterion theCriterion;

    /** The alpha that is used to 'fire' the ball */
    protected Alpha theGunAlpha;

    /** Used to animate the ball */
    protected PositionInterpolator theInterpolator;

    /** Used to calculate the current direction of the gun */
    protected int aim = 0;

    /** This is used to rotate the gun */
    protected TransformGroup aimXfmGrp;

    /** Used to aim the ball */
    protected Matrix3d aimShotMat = new Matrix3d();

    /** Used to aim the gun */
    protected Matrix3d aimGunMat = new Matrix3d();

    /** Used to define the ball's direction */
    protected Transform3D aimShotXfm = new Transform3D();

    /** Used to define the gun's direction */
    protected Transform3D aimGunXfm = new Transform3D();

    /**
     * Set up the data for the behaviour.
     * 
     * @param a1
     *            Alpha that drives the ball's animation.
     * @param pi
     *            PositionInterpolator used for the ball.
     * @param gunRotGrp
     *            TransformGroup that is used to rotate the gun.
     * @param theBounds
     *            Bounds that define the active region for this behaviour.
     */
    public GunBehaviour(Alpha a1, PositionInterpolator pi, TransformGroup gunRotGrp, Bounds theBounds) {
        theGunAlpha = a1;
        theInterpolator = pi;
        setSchedulingBounds(theBounds);
        aimXfmGrp = gunRotGrp;
    }

    /**
     * This sets up the criteria for triggering the behaviour. We simple want to
     * wait for a key to be pressed.
     */
    public void initialize() {
        theCriterion = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);
        wakeupOn(theCriterion);
    }

    /**
     * This is where the work is done. This identifies which key has been
     * pressed and acts accordingly: left key cursor rotate left, right cursor
     * key rotate right, spacebar fire.
     * 
     * @criteria Enumeration that represents the trigger conditions.
     */
    public void processStimulus(Enumeration criteria) {
        while (criteria.hasMoreElements()) {
            WakeupCriterion theCriterion = (WakeupCriterion) criteria.nextElement();
            if (theCriterion instanceof WakeupOnAWTEvent) {
                AWTEvent[] triggers = ((WakeupOnAWTEvent) theCriterion).getAWTEvent();
                //Check if it's a keyboard event
                if (triggers[0] instanceof KeyEvent) {
                    int keyPressed = ((KeyEvent) triggers[0]).getKeyCode();
                    if (keyPressed == KeyEvent.VK_LEFT) {
                        //It's a left key so move the turret
                        //and the aim of the gun left unless
                        //we're at our maximum angle
                        if (aim < 8)
                            aim += 1;
                        System.out.println("Left " + aim);
                        aimShotMat.rotY(((aim / 32.0) + 0.5) * Math.PI);
                        aimGunMat.rotZ(((aim / -32.0)) * Math.PI);
                        aimShotXfm.setRotation(aimShotMat);
                        aimGunXfm.setRotation(aimGunMat);
                        aimXfmGrp.setTransform(aimGunXfm);
                        theInterpolator.setAxisOfTranslation(aimShotXfm);
                    } else if (keyPressed == KeyEvent.VK_RIGHT) {
                        //It's the right key so do the same but rotate right
                        if (aim > -8)
                            aim -= 1;
                        System.out.println("Right " + aim);
                        aimShotMat.rotY(((aim / 32.0) + 0.5) * Math.PI);
                        aimGunMat.rotZ(((aim / -32.0)) * Math.PI);
                        aimGunXfm.setRotation(aimGunMat);
                        aimShotXfm.setRotation(aimShotMat);
                        aimXfmGrp.setTransform(aimGunXfm);
                        theInterpolator.setAxisOfTranslation(aimShotXfm);
                    } else if (keyPressed == KeyEvent.VK_SPACE) {
                        //It's the spacebar so reset the start time
                        //of the ball's animation
                        theGunAlpha.setStartTime(System.currentTimeMillis());
                    }
                }
            }
        }
        wakeupOn(theCriterion);
    }
}