This page is optimized for mobile devices, if you would prefer the desktop version just click here

1.14 Game 2302-0140: our first 3d game program  (Page 24/27)

Listing 31 . Source code for the game program named GM01test08.

/*GM01test08.java Copyright 2008, R.G.BaldwinRevised 02/28/08. This is an interactive 3D game program This gamesimulates a school of prey fish being attacked and eaten by a predator. The objective for the player is to causethe predator to catch and eat all of the prey fish in a minimum amount of time.The strategy is for the player to initiate a series of attacks by the predator, properly timed so as to minimizethe elapsed time required for the predator to catch and eat all of the prey fish. The final elapsed time dependsboth on player strategy and random chance. In other words, as is the case in many games, this game is part strategyand part chance. The prey fish, (shown in red), swim in a large swirlingcluster. The predator, (shown in blue), swims around the cluster, darting into the cluster on command to catch andeat as many prey fish as possible during each attack. When the predator attacks, the prey fish tend to scatter,breaking up their tight formation and making it more difficult for the predator to catch them. However, if thepredator is allowed to swim around them again for a short period following an attack, they will form back into acluster. The attacks should be timed so that the prey fish are in a reasonably tight cluster at the beginning of eachattack so that multiple prey fish will be caught and eaten during the attack. However, allowing too much timebetween attacks is detrimental to minimizing the total elapsed time. Thus, the player must make a tradeoffbetween elapsed time between attacks and the tightness of the cluster during the attacks. Another disadvantage ofwaiting too long between attacks is explained below. In addition to some other controls, the GUI provides anAttack button and an elapsed-time/kill-count indicator. At any point in time, this indicator displays the elapsedtime in milliseconds when the most recent prey fish was caught along with the total number of prey fish that havebeen caught and eaten. The two values are separated by a "/" character.When all of the prey fish have been caught and eaten by the predator, the elapsed time indicator shows the timein milliseconds required to catch and eat all of the prey fish. It also shows the number of prey fish that werecaught and eaten, which should match one of the player input values.Initially, when the player clicks the Start button, the predator is not in attack mode. The predator swims aroundthe school of prey fish encouraging them to bunch up together in a smaller and tighter cluster. This behaviorcontinues until the player clicks the Attack button, at which time the predator enters the attack mode and makes an attack on the cluster.If the player clicks the Attack button too early, or doesn't wait long enough between attacks, the prey fishwill be in a loose cluster, and the attack will yield very few fish.If the player waits too long to click the Attack button, or waits too long between attacks, the predator will havespiraled in so close to the prey fish that they will break formation and begin to scatter, making it difficult forthe predator to catch a large number of fish during the attack. This is the other disadvantage of waiting toolong that I mentioned above. The prey fish have a fairly effective defense mechanismand can do a reasonably good job of escaping the predator. The final three or four prey fish are usually the mostdifficult to catch. When the predator is successful in catching a prey fish,that fish is removed from the prey-fish population, causing the prey-fish population to shrink over time.Each time the predator is successful in catching a prey fish, the program emits an audible beep to providefeedback to the player.Even when the predator is not in the attack mode, its presence has a significant effect on the behavior of theschool of prey fish. As the predator swims around the school of prey fish, they tend to bunch up into a smallerand tighter cluster, but when the predator swims too close, the prey fish panic and tend to scatter, breakingup the tight cluster. In addition to the elapsed time indicator and the Attackbutton, the GUI contains an input text field for the number of prey fish that will be in the population plus aStart button and a Stop button. The GUI also contains check boxes that allow the player to display points only,direction vectors only, or both for the prey fish. (Only the direction vector is displayed for the predator.)The player specifies the number of randomly-placed prey fish that will be in the population and clicks the Startbutton to start the animation. At this point, the prey fish swim in a large swirling cluster and the predatorswims around them encouraging them to form a tighter cluster Prey fish motion is random but each fishgenerally tends to spiral toward the center of the cluster.When the user clicks the Attack button, the behavior is as described earlier.The animation continues until the user clicks the Stop button. The user can click the Stop button, change any ofthe parameters, and then click Start again to re-start the game with zero elapsed time and different parameters.The animation is most impressive when the direction vectors are displayed for the prey fish because thevectors provide a lot of information about how the prey fish are reacting to the predator.In addition to the school of prey fish and the predator, the graphical output also shows a large circle drawnwith broken lines. This circle represents the intersection of a sphere and the x-y plane.The purpose of the sphere, which is centered on the origin, is to provide a soft boundary for keeping theprey fish and the predator in the 3D playing field. The prey fish and the predator all have a tendency to remaininside the sphere, but they may occasionally stray outside the sphere. If they do, the program code will encouragethem to return to the 3D playing field inside the sphere. This game is fully 3D. The prey fish and the predator arefree to swim in any direction in 3D space. The tails on the prey fish and the predator appear longest when theyare swimming parallel to the x-y plane. As they change their angle relative to the x-y plane, the tails appearto become shorter and shorter until in some cases, they appear not to have a tail at all. This is best observedby clicking the Stop button just as the fish scatter during an attack and freezing the state of the fish inthe 3D world. If you examine the image at that point, you are likely to see some fish with shorter tails than otherfish. Tested using JDK 1.6 under WinXP.*********************************************************/ import java.awt.*;import javax.swing.*; import java.awt.geom.*;import java.awt.event.*; import java.util.*;class GM01test08{ public static void main(String[]args){ GUI guiObj = new GUI();}//end main }//end controlling class GM01test08//======================================================// class GUI extends JFrame implements ActionListener{int hSize = 400;//horizontal size of JFrame. int vSize = 450;//vertical size of JFrameImage osi;//an off-screen image int osiWidth;//off-screen image widthint osiHeight;//off-screen image height MyCanvas myCanvas;//a subclass of CanvasGraphics2D g2D;//off-screen graphics context. int numberPoints = 0;//can be modified by the user.JTextField numberPointsField; //user input field. Random random = new Random();//random number generator//The following collection is used to store the prey // objects.ArrayList<GM01.Point3D>preyObjects;//The following collection is used to store the vectors // for display.ArrayList<GM01.Vector3D>displayVectors;//The following are used to support user input. Checkbox drawPointsBox;//User input fieldCheckbox drawVectorsBox;//User input field. JButton attackButton;boolean drawPoints = true; boolean drawVectors = true;//The following JTextField is used to display the// elapsed time and the number of prey fish that // have been caught and eaten.JTextField timer; long baseTime;//Used to compute the elapsed time.//Used to compute the number of fish caught and eaten. int killCount = 0;//Animation takes place while the following is true. boolean animate = false;//Attacks take place while the following is true.boolean attack = false;//Working variables used by the animation code. GM01.Vector3D predatorVec;GM01.Point3D preyCenter; GM01.Point3D predator;//----------------------------------------------------//GUI(){//constructor//Set JFrame size, title, and close operation. setSize(hSize,vSize);setTitle("Copyright 2008,R.G.Baldwin"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//Instantiate the user input components.numberPointsField = new JTextField("100",5); JButton startButton = new JButton("Start");attackButton = new JButton("Attack"); JButton stopButton = new JButton("Stop");timer = new JTextField("0",5); drawPointsBox = new Checkbox("Draw Points",false);drawVectorsBox = new Checkbox("Draw Vectors",true); //Instantiate a JPanel that will house the user input// components and set its layout manager. JPanel controlPanel = new JPanel();controlPanel.setLayout(new GridLayout(0,2)); //Add the user input component and appropriate labels// to the control panel. controlPanel.add(new JLabel(" Number Prey Fish"));controlPanel.add(numberPointsField); controlPanel.add(drawPointsBox);controlPanel.add(drawVectorsBox); controlPanel.add(startButton);controlPanel.add(attackButton); controlPanel.add(stopButton);controlPanel.add(timer); //Add the control panel to the SOUTH position in the// JFrame. this.getContentPane().add(BorderLayout.SOUTH,controlPanel); //Instantiate a new drawing canvas and add it to the// CENTER of the JFrame above the control panel. myCanvas = new MyCanvas();this.getContentPane().add( BorderLayout.CENTER,myCanvas);//This object must be visible before you can get an // off-screen image. It must also be visible before// you can compute the size of the canvas. setVisible(true);//Make the size of the off-screen image match the// size of the canvas. osiWidth = myCanvas.getWidth();osiHeight = myCanvas.getHeight(); //Create an off-screen image and get a graphics// context on it. osi = createImage(osiWidth,osiHeight);g2D = (Graphics2D)(osi.getGraphics());//Translate the origin to the center. GM01.translate(g2D,0.5*osiWidth,-0.5*osiHeight);//Register this object as an action listener on all // three buttons.startButton.addActionListener(this); attackButton.addActionListener(this);stopButton.addActionListener(this);//Make the drawing color RED at startup. g2D.setColor(Color.RED);}//end constructor //----------------------------------------------------////This method is called to respond to a click on any// of the three buttons. public void actionPerformed(ActionEvent e){if(e.getActionCommand().equals("Start")&&!animate){ //Service the Start button.//Get several user input values.numberPoints = Integer.parseInt( numberPointsField.getText());if(drawPointsBox.getState()){ drawPoints = true;}else{ drawPoints = false;}//end elseif(drawVectorsBox.getState()){ drawVectors = true;}else{ drawVectors = false;}//end else //Initialize some working variables used in the// animation process. animate = true;baseTime = new Date().getTime(); killCount = 0;//Enable the Attack button.attackButton.setEnabled(true);//Initialize the text in the timer field. timer.setText("0 / 0");//Cause the run method belonging to the animation// thread object to be executed. new Animate().start();}//end ifif(e.getActionCommand().equals("Attack")&&animate){ //Service the Attack button.//This will cause the predator to attack the prey// objects. attack = true;//Point the predator toward the center of the prey// fish formation. Reduce the scale factor to less // than 1.0 to slow down the attack and make it// easier for the prey fish to escape the predator. predatorVec =predator.getDisplacementVector(preyCenter). scale(1.0);//Disable the Attack button. It will be enabled// again when the attack is finished. attackButton.setEnabled(false);}//end ifif(e.getActionCommand().equals("Stop")&&animate){ //Service the Stop button.//This will cause the run method to terminate and// stop the animation. It will also clear the // attack flag so that the next time the animation// is started, the predator won't be in the // attack mode.animate = false; attack = false;}//end if }//end actionPerformed//====================================================////This is an inner class of the GUI class. class MyCanvas extends Canvas{//Override the update method to eliminate the default // clearing of the Canvas in order to reduce or// eliminate the flashing that that is often caused by // such default clearing.//In this case, it isn't necessary to clear the canvas // because the off-screen image is cleared each time// it is updated. This method will be called when the // JFrame and the Canvas appear on the screen or when// the repaint method is called on the Canvas object. public void update(Graphics g){paint(g);//Call the overridden paint method. }//end overridden update()//Override the paint() method. The purpose of the// paint method is to display the off-screen image on // the screen. This method is called by the update// method above. public void paint(Graphics g){g.drawImage(osi,0,0,this); }//end overridden paint()}//end inner class MyCanvas //====================================================////This is an animation thread. The purpose of this// animation is to simulate something like a school of // prey fish and a predator. The animation behavior// is described in the opening comments for the program // as a whole.class Animate extends Thread{ //Declare a general purpose variable of type Point3D.// It will be used for a variety of purposes. GM01.Point3D tempPrey;//Declare two general purpose variables of type// Vector3D. They will be used for a variety of // purposes.GM01.Vector3D tempVectorA; GM01.Vector3D tempVectorB;//--------------------------------------------------//public void run(){ //This method is executed when start is called on// this Thread object. //Create a new empty container for the prey objects.//Note the use of "Generics" syntax. preyObjects = new ArrayList<GM01.Point3D>();//Create a new empty container for the vectors. The // only reason the vectors are saved is so that they// can be displayed later displayVectors = new ArrayList<GM01.Vector3D>();//Create a set of prey objects at random locations // and store references to the prey objects in the// preyObjects container. for(int cnt = 0;cnt<numberPoints;cnt++){ preyObjects.add(new GM01.Point3D(new GM01.ColMatrix3D( 100*(random.nextDouble()-0.5),100*(random.nextDouble()-0.5), 100*(random.nextDouble()-0.5))));//Populate the displayVectors collection with// dummy vectors. displayVectors.add(tempVectorA);}//end for loop //This object that will be the predator.// Position it initially near the top of the screen. predator = new GM01.Point3D(new GM01.ColMatrix3D(-100,100,-100)); //Create a reference point to mark the origin. Among// other things, it will be used as the center of a // sphere, which in turn will be used to attempt to// keep the prey and predator objects from leaving // the playing field.GM01.Point3D origin = new GM01.Point3D(new GM01.ColMatrix3D(0,0,0));//Declare some variables that will be used to// compute and save the average position of all of // the prey objects.double xSum = 0; double ySum = 0;double zSum = 0; //This is the animation loop. The value of animate// is set to true by the Start button and is set to // false by the Stop button. Setting it to false// will cause the run method to terminate and stop // the animation.while(animate){ //Compute and save the average position of all the// prey objects at the beginning of the loop. Save // the average position in the Point3D object// referred to by preyCenter. xSum = 0;ySum = 0; zSum = 0;for(int cnt = 0;cnt<preyObjects.size();cnt++){ tempPrey = preyObjects.get(cnt);xSum += tempPrey.getData(0); ySum += tempPrey.getData(1);zSum += tempPrey.getData(2); }//end for loop//Construct a reference point at the average// position of all the prey objects. preyCenter = new GM01.Point3D(new GM01.ColMatrix3D(xSum/preyObjects.size(), ySum/preyObjects.size(),zSum/preyObjects.size())); //Move all of the prey objects in a way that// causes them to spiral toward the preyCenter. for(int cnt = 0;cnt<preyObjects.size();cnt++){ //Stop spiraling toward the center when the// population of prey objects contains a // single object because there is no center at// that point. if(preyObjects.size()>1){ //Get the next prey objecttempPrey = preyObjects.get(cnt); //Find the displacement vector from this prey// object to the preyCenter tempVectorA = tempPrey.getDisplacementVector(preyCenter);//Create a vector that is rotated relative to // tempVectorA. Note how each component in // this new vector is set equal to a different// component in tempVectorA. Scale it. tempVectorB = new GM01.Vector3D(new GM01.ColMatrix3D( tempVectorA.getData(1),tempVectorA.getData(2), tempVectorA.getData(0))).scale(1.2);//Add the two vectors and move the prey object// according to the sum of the vectors. Scale // one of the vectors during the addition to// adjust the rate at which the object spirals // toward the center.//Moving the prey object in the direction of // the sum of these two vectors produces a// motion that causes the prey object to // spiral toward the preyCenter instead of// simply moving in a straight line toward the // preyCenter.tempVectorA = tempVectorA.scale(0.9).add(tempVectorB);//Apply an overall scale factor to the sum // vector before using it to move the prey// object. tempPrey = tempPrey.addVectorToPoint(tempVectorA.scale(0.1)); //Save a clone of the prey object in its new// position. Save a clone instead of the // actual object as a safety measure to avoid// the possibility of corrupting the object // later when the reference variable is used// for some other purpose. preyObjects.set(cnt,tempPrey.clone());//Save a normalized version of the direction// vector for drawing later. Set the length // of the normalized vector to 15 units.displayVectors.set( cnt,tempVectorA.normalize().scale(15.0));}else{ //When the population consists of a single// prey object, save a dummy direction vector // for drawing later. This is necessary to// prevent a null pointer exception when the // user specifies only one prey object and// then clicks the Start button. displayVectors.set(cnt,new GM01.Vector3D( new GM01.ColMatrix3D(10,10,0)));}//end else}//end loop to spiral prey objects toward center.//Try to keep the prey objects from colliding with // one another by moving each prey object away// from its close neighbors. GM01.Point3D refPrey = null;GM01.Point3D testPrey= null; for(int row = 0;row<preyObjects.size();row++){ refPrey = preyObjects.get(row);//Compare the position of the reference prey // object with the positions of each of the// other prey objects. for(int col = 0;col<preyObjects.size();col++){ //Get another prey object for proximity test.testPrey = preyObjects.get(col); //Don't test a prey object against itself.if(col != row){ //Get the vector from the refPrey object to// the testPrey object. tempVectorA = refPrey.getDisplacementVector(testPrey);//If refPrey is too close to testPrey, move // it away from the testPrey object.if(tempVectorA.getLength()<10){ //Move refPrey away from testPrey by a// small amount in the opposite direction. refPrey = refPrey.addVectorToPoint(tempVectorA.scale(0.2).negate()); }//end if on proximity test}//end if col != row }//end loop on col//The refPrey object may or may not have been // moved. Save it anyway.preyObjects.set(row,refPrey.clone()); }//end loop on row//Make each prey object react to the predator with // a defensive mechanism when the predator// approaches the prey object. for(int cnt = 0;cnt<preyObjects.size();cnt++){ tempPrey = preyObjects.get(cnt);//Get a displacement vector from the prey object// to the predator. tempVectorA = tempPrey.getDisplacementVector(predator); //If the prey object is within a certain// threshold distance from the predator, move // the prey object a random distance in the// opposite direction. Then test again to see if // the prey object is still within another// smaller threshold distance. If so, the prey // object was not successful in escaping the// predator and has been caught and eaten by the // predator. Remove the prey object from the// population and sound a beep to notify the // user of the successful attack by the// predator. if(tempVectorA.getLength()<50){ //The predator is within striking range of the// prey object. Make the prey object try to // escape by moving a random distance in the// opposite direction. The value returned by // the random number generator ranges from 0// to 1.0. tempVectorA = tempVectorA.negate().scale(random.nextDouble()); tempPrey =tempPrey.addVectorToPoint(tempVectorA);//Check to see if the escape was successful // by testing against a smaller threshold// distance. //Get a new displacement vector from the prey// object to the predator. tempVectorA =tempPrey.getDisplacementVector(predator); //Decrease or increase the following// threshold distance to cause the prey // object to be more or less successful in the// escape attempt. if(tempVectorA.getLength()<25){ //Escape was not successful. The predator is// still within striking distance of the // prey object. Remove the prey object and// its direction vector from the population // and sound a beep. Also increase the// killCount by 1 for purposes of display // only.tempPrey = preyObjects.remove(cnt); displayVectors.remove(cnt);Toolkit.getDefaultToolkit().beep(); killCount++;//Display the elapsed time in milliseconds// since the Start button was clicked along // with the number of fish that have been// caught and eaten. The value that is // displayed is the elapsed time when the// most recent prey fish was caught by the // predator and is not the total elapsed// time since the Start button was clicked. // When all prey fish have been caught, the// final time that is displayed is the time // required for the predator to catch all of// the fish in the population. timer.setText("" + (new Date().getTime() - baseTime)+ " / " + killCount);}else{ //The escape attempt was successful. Restore// a clone of the prey object in its new // location along with its direction vector// to the population. preyObjects.set(cnt,tempPrey.clone());//Save a normalized version of the direction // vector for drawing later. It will// overwrite the previously saved vector and// if you watch closely it will show the // prey object running away from the// predator.. displayVectors.set(cnt,tempVectorA.normalize().scale(15.0)); }//end else}//end if distance<50 }//end for loop on population//Deal with prey objects that stray outside the // spherical boundary. Give them a nudge back// toward the origin. for(int cnt = 0;cnt<preyObjects.size();cnt++){ tempPrey = preyObjects.get(cnt);//Test to determine if the prey object is// outside of a sphere centered on the origin // with a radius equal to the maximum dimension// on the vertical axis. if(tempPrey.getDisplacementVector(origin).getLength()>0.5*osiHeight){ //Enable the following statement to get an// audible beep each time a prey object goes // out of bounds.//Toolkit.getDefaultToolkit().beep(); //Give the prey object a nudge back toward the// origin and save a clone of the prey object // in its new location.tempVectorA = tempPrey.getDisplacementVector(origin);tempPrey = tempPrey.addVectorToPoint( tempVectorA.scale(0.1));preyObjects.set(cnt,tempPrey.clone()); }//end if prey object is out of bounds}//end for loop to process each prey object //Erase the screeng2D.setColor(Color.WHITE); GM01.fillRect(g2D,-osiWidth/2,osiHeight/2,osiWidth,osiHeight); //Draw a broken-line circle that represents the// intersection of the spherical boundary with the // x-y plane. Although the prey objects and the// predator can stray outside this sphere, they // prefer to be inside the sphere.GM01.Point3D tempPointA = new GM01.Point3D( new GM01.ColMatrix3D(osiHeight/2,0,0));GM01.Point3D tempPointB; //The circle is defined by 39 points around the// circumference. g2D.setColor(Color.BLACK);for(int cnt = 0;cnt<39;cnt++){ tempPointB =new GM01.Point3D(new GM01.ColMatrix3D( osiHeight/2*Math.cos((cnt*360/39)*Math.PI/180),osiHeight/2*Math.sin((cnt*360/39)*Math.PI/180), 0));//Connect every third pair of points with a lineif(cnt%3 == 0){ new GM01.Line3D(tempPointA,tempPointB).draw(g2D); }//end if//Save the old point. tempPointA = tempPointB;}//end for loop//Draw the final line required to close the circle new GM01.Line3D(tempPointA,new GM01.Point3D(new GM01.ColMatrix3D( osiHeight/2,0,0))).draw(g2D);//Restore the drawing color. g2D.setColor(Color.RED);//Draw the objects in the preyObjects container // and the vectors in the displayVectors// container. for(int cnt = 0;cnt<preyObjects.size();cnt++){ tempPrey = preyObjects.get(cnt);if(drawPoints){ tempPrey.draw(g2D);//draw circle around point}//end ifif(drawVectors){ //Draw the vector with its tail at the point.displayVectors.get(cnt).draw(g2D,tempPrey); }//end if}//end for loop//When the predator is not in attack mode, cause// it to slowly circle the cluster of prey // objects.if(!attack){ //Get a displacement vector pointing from the// predator to the preyCenter. predatorVec =predator.getDisplacementVector(preyCenter);//Create a vector that is rotated relative to // predatorVec. Note how each component in// this new vector is set equal to a different // component in predatorVectempVectorB = new GM01.Vector3D( new GM01.ColMatrix3D(predatorVec.getData(1), predatorVec.getData(2),predatorVec.getData(0))).scale(0.15);//Scale predatorVec and add the two vectors. // Then move the predator according to the sum// of the vectors. //Moving the prey object in the direction of// the sum of these two vectors produces a // motion that causes the predator to// spiral toward the preyCenter instead of // simply moving in a straight line toward the// preyCenter. The scale factors control the // relative motion between the two directions,// and are fairly sensitive. predatorVec =predatorVec.scale(0.095).add(tempVectorB);predator = predator.addVectorToPoint( predatorVec.scale(1.0));}else{//attack is true //Predator is in attack mode now. Stay in attack// mode using the same displacement vector until // the predator traverses the entire playing// field. This displacement vector originally // pointed toward the preyCenter and was created// in the actionPerformed method in response to // a click on the Attack button. In other words,// even though the prey fish scatter, the // predator is constrained to move in a straight// line across the playing field once an attack // is begun. An interesting update might be to// allow the predator to adjust its direction // as the prey fish scatter and the location of// preyCenter changes during the traversal // across the playing field.predator = predator.addVectorToPoint( predatorVec.scale(0.25));//Check to see if the predator is outside the // spherical boundary that defines the playing// field. if(predator.getDisplacementVector(origin).getLength()>0.5*osiHeight){ //Shift out of attack mode and start circling// the prey fish again. attack = false;attackButton.setEnabled(true); }//end if}//end else //Set the color to BLUE and draw the predator and// its displacement vector. g2D.setColor(Color.BLUE);//Enable the following statement to draw a circle // around the point that represents the predator.//predator.draw(g2D);//Draw the predator's vector. predatorVec.normalize().scale(15.0).draw(g2D,predator); g2D.setColor(Color.RED);//restore red color//Copy the off-screen image to the canvas and then// do it all again. myCanvas.repaint();//Insert a time delay. Change the sleep time to// speed up or slow down the animation. try{Thread.currentThread().sleep(166); }catch(Exception e){e.printStackTrace(); }//end catch}//end animation loop }//end run method}//end inner class named Animate //====================================================//}//end class GUI
<< Chapter < Page Page > Chapter >>

Read also:

OpenStax, Game 2302 - mathematical applications for game development. OpenStax CNX. Jan 09, 2016 Download for free at https://legacy.cnx.org/content/col11450/1.33
Google Play and the Google Play logo are trademarks of Google Inc.
Jobilize.com uses cookies to ensure that you get the best experience. By continuing to use Jobilize.com web-site, you agree to the Terms of Use and Privacy Policy.