A simple shooting game : Game « 3D « Java






A simple shooting game

/*
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);
  }
}



           
       








Related examples in the same category

1.Game: four by fourGame: four by four
2.Tick Tock PickingTick Tock Picking