Mega Code Archive

 
Categories / Java / 3D Graphics
 

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