Summary: Learn how to write your first interactive 3D game using the game-math library. Also learn how to write a Java program that simulates flocking behavior such as that exhibited by birds and fish and how to incorporate that behavior in a game.
This module is one in a collection of modules designed for teaching GAME2302 Mathematical Applications for Game Development at Austin Community College in Austin, TX.
What you have learned
In the previous module, you learned how to update the game-math library to support 3D math, how to program the equations for projecting a 3D world onto a 2D plane, how to add vectors in 3D, about scaling, translation, and rotation of a point in both 2D and 3D, about the rotation equations and how to implement them in both 2D and 3D, and much more.
What you will learn
In this module, you will learn how to write your first interactive 3D game using the game-math library named GM01 . You will also learn how to write a Java program that simulates flocking behavior such as that exhibited by birds and fish and how to incorporate that behavior into a game. Finally, you will examine three other programs that illustrate various aspects of both 2D and 3D animation using the game-math library.
I recommend that you open another copy of this module in a separate browser window and use the following links to easily find and view the Images and Listings while you are reading about them.
It's time to kick back and have a little fun with what you have learned. In this module, I will present four programs, (one of which is an interactive 3D game) , that use the current capabilities of the game-math library to produce some fairly serious animation. The programs are named:
I will explain the game program in detail and will provide a brief description of the other three programs.
GM01test08
The first three programs are based on an artificial life concept frequently referred to as boids , which is "a computer model of coordinated animal motion such as bird flocks and fish schools."
I got the idea for this game while watching a segment of a documentary series named Blue Earth on one of the cable channels. I even surprised myself as to how well I was able to simulate some of the scenes in that TV program using programming concepts similar to those used by Reynolds in boids.
A flocking game program
Although the flocking algorithms won't be the same as those used in the original boids program written by Reynolds, the general concepts will be similar.
The first program named GM01test08 is an interactive 3D game program based on the flocking behavior of fish. This game simulates a school of prey fish being attacked and eaten by a predator. The objective for the player is to cause the predator to catch and eat all of the prey fish in a minimum amount of time.
A preview
I will get into a lot more detail about the game aspects of the program later. For a preview, Image 1 shows the instant in time when a school of 500 prey fish (shown in red) have just been attacked by the blue predator shown near the center right.
| Image 1: A school of red prey fish being attacked by a blue predator. |
|---|
![]() |
The predator in Image 1 has penetrated the school of fish by swimming from left to right, eating fish all along the way, and has emerged from the right side of the school. The prey fish, which were originally in a small tight cluster, have reacted to the presence of the predator by scattering in an attempt to avoid being eaten.
Fully three-dimensional
This game program is fully three-dimensional. Any or all of the fish are capable of swimming in any direction at any time in the 3D world. This is evidenced by the prey fish near the center that appear to have very short or non-existent tails. The fish that appear to have no tails actually have tails that are the same length as the tails on all of the other fish. These fish appear to have no tails because they are swimming either directly toward or directly away from the viewer. I will have more to say about this later.
GM01test04 and GM01test03
These two programs provide essentially the same behavior (as one another) , except that one is a 2D program and the other is a 3D program. They are not game programs. Rather, they are demonstration programs that show another aspect of flocking behavior. In these two programs, a large number of predators pursue a single prey object.
A screen shot of the animated output from the 2D version is shown in Image 2 and a screen shot of the output from the 3D version is shown in Image 3 .
| Image 2: Screen shot of the graphic output from GM01test04. |
|---|
![]() |
.
| Image 3: Screen shot of the graphic output from GM01test03. |
|---|
![]() |
I included these two programs in this module primarily to provide a good comparison between 2D and 3D, both from a programming viewpoint and a behavior viewpoint.
Eight predator objects and one prey object
In both versions shown in Image 2 and Image 3 , you see eight predator objects (colored black) pursuing a red prey object. The actual length of every predator object is the same as the length of every other predator object and the length of the prey object is the same as the length of the predator objects.
Some significant differences
At first glance, the two versions may look the same. However, they are significantly different. In Image 2 , the prey and the predators are constrained to move in a single plane. Hence, the apparent length of each predator is the same as the length of every other predator and is also the same as the length of the prey regardless of the direction in which the object is moving.
However, in Image 3 , the predators and the prey are free to move in any direction in a 3D world. As a result, at any point in time, the objects may be moving parallel to or at an angle to the x-y plane. When the 3D world is projected onto the viewing plane, those objects that are moving at an angle to the viewing plane will be foreshortened. In the extreme case, (as shown earlier in Image 1 ) , when an object is moving directly toward or directly away from the viewer, its apparent length will go to zero. (At least one predator is almost in that state in Image 3 .)
In Image 3 the apparent lengths of the predator and prey objects are all different indicating that they are all moving in slightly different directions in the 3D world.
The flocking algorithms in these two programs are much simpler than the algorithm used in Image 1 . I encourage you to experiment with the flocking algorithms for these two programs to see what you can produce that is new and different from my version of the two programs.
StringArt04
The fourth program named StringArt04 is completely different from the other three programs. In a previous module, I presented and explained a program that rotates a disk around an arbitrary anchor point in 3D space with the ability to rotate by different angles around each of the three axes. It can be very difficult to visualize exactly what is happening with such complex rotations. This program animates the rotation process and runs it in slow motion so that you can see exactly what is happening as the disk moves and rotates from its initial position and orientation to its final position and orientation.
For example. set the values as follows, click the Animate button, and watch the rotations take place.
This should give you a good idea as to the process. Then change the parameters to other values, click the button, and observe the results.
It is also useful to set only one of the rotation angles to a non-zero value and watch the rotation around a line parallel to that axis take place.
A stop-action screen shot
Image 4 shows a screen shot of the disk in motion somewhere between the beginning and the end of its very complex trajectory for one particular set of rotation and anchor point parameters.
| Image 4: Screen shot of the graphic output from StringArt04. |
|---|
![]() |
Exercises
I will also provide exercises for you to complete on your own at the end of the module. The exercises will concentrate on the material that you have learned in this and previous modules.
The game-math library named GM01 has not been modified since I presented and explained it in an earlier module titled GAME 2302-0135: Venturing into a 3D World . A copy of the source code for the library is provided in Listing 30 for your convenience. Since I have explained the code in the library in earlier modules, I won't repeat those explanations here.
A link to a zip file containing documentation for the library was provided in the earlier module.
This is an interactive 3D game program. In fact, this is the first game program that I have presented in this collection on game math. This game simulates a school of prey fish being attacked and eaten by a predator. The objective for the player is to cause the predator to eat all of the prey fish in a minimum amount of time. Although the story line of the game is rather simple, the math involved is not simple at all.
Player initiates attacks
The player in this game initiates a series of attacks by the predator by clicking the Attack button shown in Image 5 . The winning strategy for the player is to time the attacks in such a way as to minimize the elapsed time required for the predator to eat all of the prey fish. The final elapsed time depends on both strategy and chance. In other words, as is the case in many games, this game is part strategy and part chance.
A swirling cluster of prey fish
The prey fish, (shown in red) , tend to swim in a large swirling cluster as shown in Image 5 when they aren't being harassed by the predator.
| Image 5: Prey fish in a large swirling cluster. |
|---|
![]() |
Predator behavior
The predator, (shown in blue in Image 5 ) , swims around the cluster, darting into and through the cluster when the user clicks the Attack button. During an attack, the predator attempts to eat as many prey fish as possible. When the predator attacks, the prey fish tend to scatter (as shown in Image 1 ) , breaking up their tight formation and making it more difficult for the predator to catch them. If the predator is allowed to swim around them again for a short period following an attack, they will form back into a cluster.
Proper timing is important
The attacks should be timed so that the prey fish are in a reasonably tight cluster at the beginning of each attack so that multiple prey fish will be eaten during the attack. However, allowing too much time between attacks is detrimental because it increases the total elapsed time. Thus, the player must make a tradeoff between elapsed time between attacks and the tightness of the cluster at the beginning of each attack. Another disadvantage of waiting too long between attacks will be explained later.
The graphical user interface
In addition to some other controls, the GUI provides an Attack button and an elapsed-time/kill-count indicator, shown in the bottom right in Image 5 . At any point in time, this indicator displays the elapsed time in milliseconds when the most recent prey fish was eaten along with the total number of prey fish that have been eaten. The two values are separated by a "/" character.
When all of the prey fish have been eaten by the predator, the elapsed time indicator shows the time in milliseconds required for the predator to catch and eat all of the prey fish. It also shows the number of prey fish that were eaten, which should match the value that the player entered into the upper-right text field before starting the game. (This field contains 500 in Image 1 and 200 in Image 5 ) .
Not initially in attack mode
When the player clicks the Start button and the game begins, the predator is not in attack mode. Rather, the predator swims around the school of prey fish encouraging them to bunch up into a smaller and tighter cluster as shown in Image 5 . The purpose of this behavior is to increase the number of fish that will be eaten when an attack is made.
The attack will be more or less successful
This circling behavior on the part of the predator continues until the player clicks the Attack button, at which time the predator enters the attack mode and makes an attack on the cluster as shown in Image 1 . If the player clicks the Attack button too early, or doesn't wait long enough between attacks, the prey fish will be in a loose cluster and the predator will eat very few fish during the attack.
The predator always has an impact
Even when the predator is not in attack mode, its presence has a significant effect on the behavior of the school of prey fish. As the predator swims around the prey fish, they tend to bunch up into a smaller and tighter cluster, but when the predator swims too close, the prey fish tend to panic and scatter, breaking up the tight cluster as shown in Image 6 .
Therefore, if the player waits too long to click the Attack button or waits too long between attacks, the predator will have spiraled in so close to the prey fish that they will break formation and begin to scatter, making it difficult for the predator to eat a large number of fish during the next attack. This is the other disadvantage of waiting too long to attack that I mentioned earlier .
Formation starting to break up due to closeness of the predator
Image 6 shows the formation starting to break up because the predator has strayed too close to the cluster of prey fish. (The fish in the upper right of the formation have pulled out of the cluster and have started to flee the predator.)
| Image 6: Formation starting to break up due to closeness of the predator. |
|---|
![]() |
An effective defense mechanism
The prey fish have a fairly effective defense mechanism and can do a reasonably good job of escaping the predator. The predator can increase the odds of catching the prey fish by attacking very fast. (You can modify the code that controls the speed of the attack.) If the predator goes into the formation slowly, the prey fish will simply run away as is starting to be the case in Image 6 .
The final three or four prey fish are often the most difficult to catch because they have more room to run without colliding with another fish.
Captured fish are removed from the population
When the predator is successful in eating a prey fish, that fish is removed from the prey-fish population causing the prey-fish population to decrease over time. Also, each time the predator eats a prey fish, the program emits an audible beep to provide feedback to the player.
The other GUI components
In addition to the elapsed time indicator and the Attack button, the GUI contains an input text field by which the player can specify the number of prey fish that will be in the population when the game begins. The GUI also contains a Start button and a Stop button. Finally, the GUI 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.)
Playing the game
In practice, the player specifies the number of randomly-placed prey fish that will be in the population and clicks the Start button to start the game. At this point, the prey fish swim in a large swirling cluster and the predator swims around them encouraging them to form a tighter cluster as shown in Image 5 .
Prey fish motion is random but each prey fish generally tends to spiral toward the center of the cluster. When the user clicks the Attack button, the predator turns and swims rapidly into and through the center of the cluster of prey fish. If the predator manages to get to within a specified distance from a prey fish, that prey fish will be deemed to have been eaten, and will be removed from the population. However, each prey fish will sense that the predator is coming and will try to escape. Whether or not an individual prey fish manages to escape the predator when an encounter between the two occurs is based on a random number generator.
The Start and Stop buttons
The animation continues until all of the fish have been eaten or the user clicks the Stop button. The user can click the Stop button at any time, change any of the parameters, and then click the Start button again to re-start the game with zero elapsed time and different parameters.
Displaying the vectors
The animation is most impressive when the direction vectors are displayed for the prey fish because the vectors provide a lot of visual information about how the prey fish are reacting to the predator.
A spherical playing-field boundary
In addition to the school of prey fish and the predator, the graphical output also shows a large circle drawn with broken lines, as shown in Image 5 . 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 the prey fish and the predator inside the 3D playing field. The prey fish and the predator all have a tendency to remain inside the sphere, but they may occasionally stray outside the sphere. If they do, the program code will encourage them to return to the 3D playing field inside the sphere.
A 3D display
As mentioned earlier, this game is fully three dimensional. The prey fish and the predator are free to swim in any direction in 3D space. The tails on the prey fish and the predator appear longest when they are swimming parallel to the x-y plane. As they change their angle relative to the x-y plane, the tails appear to become shorter or longer and in some cases, they appear not to have a tail at all. The fish that appear to have no tails are either swimming directly toward the viewer or swimming directly away from the viewer.
This can best be observed by clicking the Stop button just as the fish scatter during an attack and freezing the state of the fish in the 3D world as shown in Image 1 . If you examine the image at that point, you are likely to see some fish with shorter tails than other fish. This is evident by some of the prey fish near the center of Image 1 that appear to have no tails at all.
Enough talk, let's see some code
A complete listing of this program is provided in Listing 31 near the end of the module.
Portions of this program are very similar to programs that I have explained in earlier modules in this series. I won't repeat those explanations here. Rather, I will concentrate mostly on the code that is new and different in this module.
Abbreviated constructor for the GUI class
Listing 1 shows an abbreviated constructor for the GUI class. (Much of the code in the constructor has been explained before, and was deleted from Listing 1 for brevity.)
| Listing 1: Abbreviated constructor for the GUI class in GM01test08. |
|---|
|
If you examine Listing 31 , you will see that the GUI class implements the ActionListener interface. Therefore, an object of the GUI class can be registered as an action listener on a button. Listing 1 registers the object of the GUI class as a listener on all three of the buttons shown in Image 6 .
Beginning of the actionPerformed method
The actionPerformed method that begins in Listing 2 is called whenever the player clicks any of the three buttons shown in Image 6 .
| Listing 2: Beginning of the actionPerformed method in GM01test08. |
|---|
|
Servicing the Start button
The code in Listing 2 is executed when the user clicks the Start button. All of this code is also very similar to code that I have explained in previous modules, so further explanation beyond the embedded comments should not be necessary. However, I will emphasize two statements in Listing 2 .
The first statement that I will emphasize (about two-thirds of the way down) enables the Attack button. The second statement (at the end) instantiates a new object of the Thread class named Animate and causes the run method of the animation thread to be executed. I will explain the animation thread later.
Code that services the Attack button
The code in Listing 3 is executed when the player clicks the Attack button after it is enabled by the code in Listing 2 .
| Listing 3: Code that services the Attack button. |
|---|
|
Listing 3 begins by setting the value of the variable named attack to true. As you will see later, this causes the predator to attack the prey fish in the animation loop.
Control the speed and direction of the predator attack
Then Listing 3 calls the getDisplacementVector method and the scale method of the game-math library to set the value of the variable named predatorVec to a direction that will be used to point the predator toward the center of the prey-fish cluster. In Listing 3 , the scale factor is 1.0, so the application of the scale factor does nothing. However, you can modify this value to control the speed of the predator during the upcoming attack. If you increase the scale factor, the predator will move faster. If you decrease the scale factor, the predator will move more slowly.
Temporarily disable the Attack button
Finally, Listing 3 temporarily disables the Attack button so that it will have no effect if the player clicks it again during the attack. You could also remove this statement, slow down the attack, and experiment with multiple clicks on the Attack button during the course of an attack to see what happens.
Code that services the Stop button
The code in Listing 4 is executed when the player clicks the Stop button.
| Listing 4: Code that services the Stop button. |
|---|
|
No explanation beyond the embedded comment should be required for this code.
Beginning of the Animate class and the run method
When the Start button is clicked, the code in Listing 2 instantiates a new object of the Thread class named Animate and causes the run method belonging to that object to be executed. Listing 5 shows the beginning of the Animate class and its run method.
| Listing 5: Beginning of the Animate class and the run method. |
|---|
|
Importance of the game-math library named GM01
The code in Listing 5 is straightforward and shouldn't require an explanation beyond the embedded comments. However, I will emphasize the heavy use of the game-math library classes named GM01.Point3D and GM01.Vector3D in the code in Listing 5 .
This entire game program is heavily dependent on the use of the ColMatrix3D , Point3D , Vector3D , and Line3D classes along with some of the static methods in the game-math library named GM01 . If you were to start from scratch and write this game program without the availability of the game-math library, the program would be much longer and would be much more complex.
Create the population of prey fish
Listing 6 creates the population of prey fish and positions them at random locations in 3D space. Listing 6 also stores references to the prey fish objects in the preyObjects container, which was created in Listing 5 .
| Listing 6: Create the population of prey fish. |
|---|
|
In addition, Listing 6 populates a container named displayVectors that was also created in Listing 5 to store a direction vector belonging to each prey fish. This container is populated with dummy vectors in Listing 6 , simply to set the size of the container to be the same as the size of the container containing references to the prey fish objects. (There is probably a more efficient way to set the size of that container.)
Note that each prey fish object is actually represented by an object of the game-math library class named GM01.Point3D .
Create and position the predator object
Listing 7 creates an object of the GM01.Point3D class that will represent the predator in the game.
| Listing 7: Create and position the predator object. |
|---|
|
The initial position given to the predator in Listing 7 causes it to appear in the game near the top center of the screen when the user clicks the Start button. You could also make the initial position of the predator random if you think that would improve the game.
A few more housekeeping details
Listing 8 takes care of a few more housekeeping details before the animation actually begins.
| Listing 8: A few more housekeeping details. |
|---|
|
Let the show begin
Listing 9 shows the beginning of the animation loop, which will continue to execute for as long as the value of the variable named animate is true.
| Listing 9: Beginning of the animation loop. |
|---|
|
The value of animate is set to true by clicking the Start button and is set to false by clicking the Stop button. Setting the value to false causes the run method to terminate, thereby stopping the animation.
Attack toward the geometric center
When the predator attacks, it will move to and through the geometric center of the cluster of prey fish, eating prey fish all along the way. The geometric center of the cluster of prey fish is computed and saved by the code in Listing 9 as the average location of all the prey fish in the population.
Note that a vector that points to the geometric center may or may not be a good indicator of the best direction for eating large numbers of prey fish during an attack. In Image 1 , for example, a predator attacking from the 11:00 o'clock position and heading toward the center would encounter quite a few prey fish. However, a predator attacking from the 8:00 o'clock position and heading toward the center would encounter far fewer prey fish.
You will also learn that although the predator will always attack in a direction pointing toward this geometric center, the positions of the prey fish can change after the center is computed and before the attack begins, causing the position of the actual geometric center to change, before the predator has an opportunity to attack. That can decrease the probability of a successful attack by the predator.
These issues form part of the strategy of the game that must be mastered by the player in order to earn a good score.
Cause all of the prey fish to spiral toward the center
Listing 10 shows the beginning of a fairly complicated algorithm that causes all of the prey fish to have a tendency to spiral toward the center of the cluster of prey fish.
| Listing 10: Cause all of the prey fish to spiral toward the center. |
|---|
|
The code in the body of the for loop that begins in Listing 10 is executed once during each animation cycle for each of the prey fish in the population of prey fish.
To begin with, the if statement prevents the tendency to spiral toward the geometric center from being applied when there is only one prey fish remaining in the population. I will leave it as an exercise for the student to think about this and to decide whether or not this is a good decision.
The code in Listing 10 gets a reference to the next prey fish object in the population and then gets and saves a reference to a displacement vector pointing from the prey fish to the geometric center.
Create a rotated vector.
Listing 11 creates, scales, and saves a new GM01.Vector3D object having the same component values as the displacement vector from Listing 11 , but assigning those values to different axes.
| Listing 11: Create a rotated vector. |
|---|
|
Once again, I will leave it as an exercise for the student to think about the length and direction of such a vector in relation to the original displacement vector. It might help to begin by drawing some vectors in 2D that swap components and then extending the same thinking to 3D. One thing is for certain, unless all of the components have the same value, the new vector created in Listing 11 has a different length and direction than the displacement vector created in Listing 10 , and that would be true even if the vector from Listing 11 had not been scaled.
Move the prey fish object based on the sum of the vectors
Listing 12 begins by adding the two vectors that were created in Listing 10 and Listing 11 , scaling the vector from Listing 10 before performing the addition. Note that the scale factors applied to the rotated vector in Listing 11 is different from the scale factor applied to the original displacement vector in Listing 12 . The ratio of these two scale factors influences the tendency of the prey fish object to move toward the center relative to its tendency to move around the center.
| Listing 12: Move the prey fish object based on the sum of the vectors. |
|---|
|
Then Listing 12 calls the GM01.Point3D.addVectorToPoint method to relocate the prey fish object to a new position based on the scaled sum of the two vectors. The bottom line is that this will cause the prey fish object to spiral toward the geometric center that was computed in Listing 9 as the animation progresses.
Another scale factor is applied to the sum vector before using it to relocate the prey fish object. This scale factor controls the overall speed of the prey fish. Increasing the scale factor causes the prey fish to spiral faster. (Increasing the scale factor also causes some interesting patterns to appear and if you increase it too much, the prey fish will all leave the playing field.)
Save a clone of the relocated prey fish object
Listing 13 creates a clone of the relocated prey fish object and uses it to replace the original prey fish object in the container.
| Listing 13: Save a clone of the relocated prey fish object. |
|---|
|
Creating and saving a reference to a clone instead of saving a reference to the relocated prey fish object may be overkill. However, I simply wanted to guard against the possibility of ending up with a corrupted object later due to the repeated use of the reference variable. I will leave it up to the student to think about this and to decide if this was a good or a bad decision.
Save a normalized direction vector
Listing 14 calls the GM01.Vector3D.normalize method to create a vector having a length of 15 units and the same direction as the vector that was used to relocate the prey fish object.
| Listing 14: Save a normalized direction vector. |
|---|
|
This vector is saved and used for drawing later. Drawing this vector in conjunction with the point that represents the prey fish object produces the tails shown on the prey fish objects in Image 1 .
Code for when the population contains only one prey fish
Listing 15 shows the else clause that matches up with the if statement in Listing 10 . This code is executed when the population has been reduced to only one prey fish object.
| Listing 15: Code for when the population contains only one prey fish. |
|---|
|
Listing 15 saves a dummy direction vector for drawing later. This is necessary to prevent a null pointer exception when the user specifies only one prey fish object and then clicks the Start button.
Not the end of the story
Listing 15 also signals the end of the for loop that causes the prey fish to spiral toward the center. The code in this for loop is sometimes referred to as a cohesion algorithm in flocking terminology. It causes the prey fish to stay together as a group.
This is not the end of the story, however. If the code in this cohesion algorithm were the only code controlling the behavior of the prey fish in this animation, they would simply continue spiraling toward the center, eventually producing a cluster that looks something like that shown in Image 7 where 100 prey fish are trying to occupy the same location in 3D space.
| Image 7: 100 prey fish trying to occupy the same location in 3D space. |
|---|
![]() |
Interesting but also boring
While it may be interesting to watch the animation progress to this point, the animation becomes very boring when all of the prey fish cluster at the center. What we need is an additional algorithm that will cause each prey fish to attempt to maintain a respectable distance between itself and the other prey fish.
Prey fish maintaining a reasonable separation
For example, Image 8 shows the result of temporarily making each prey fish immune to the presence of the predator, telling each prey fish to spiral toward the center, and also telling each prey fish to maintain a separation of ten units (pixels) between itself and all of the other prey fish.
| Image 8: 100 prey fish maintaining a reasonable separation in 3D space. |
|---|
![]() |
A 3D world
Remember, Image 8 is a 3D world projected onto on a 2D screen display. Even if every prey fish is separated from every other prey fish by at least ten pixels (which is probably not the case as I will explain later) , the projection of the 3D world onto the 2D display can make it appear that two or more prey fish occupy the same location.
Separate the prey fish
Listing 16 shows the beginning of a pair of nested for loops that attempt to keep the prey fish from colliding with one another by moving each prey fish object away from its close neighbors if necessary. In flocking terminology, this is often referred to as a separation algorithm.
| Listing 16: Separate the prey fish. |
|---|
|
This algorithm gets a reference to each prey fish object (primary object) and compares its position with the positions of all the other prey fish objects (secondary objects) . If the primary object is too close to a secondary object, the primary object is moved away from the secondary object.
Not a perfect algorithm
This is not a perfect algorithm however. A primary object can be moved away from all of its neighbors early in the execution of the algorithm, but a neighbor could be moved closer to the primary object later in the execution of the algorithm. While not perfect, the algorithm does a pretty respectable job of keeping the prey fish separated as evidenced by comparing the positions of the prey fish in Image 7 with the positions of the prey fish in Image 8 . This separation algorithm was disabled in Image 7 and was enabled in Image 8 .
Gets two objects that will be compared
Listing 16 gets a reference to a primary prey fish object in the outer loop that iterates on the counter named row and gets a reference to one of the secondary prey fish objects to which it will be compared in the inner loop that iterates on the counter named col .
The remainder of the inner loop
Listing 17 shows the remainder of the inner loop.
| Listing 17: The remainder of the inner loop. |
|---|
|
It wouldn't make any sense to compare the position of a prey fish with itself, so the if statement in Listing 17 prevents that from happening.
Code is relatively straightforward
Since you already know how to use most of the methods in the game-math library, you should have no difficulty understanding the code in Listing 17 . This code:
As mentioned earlier, however, moving the primary fish object away from one prey fish could cause it to be moved closer to a different prey fish object, so the separation algorithm is far from perfect.
Listing 17 signals the end of the inner loop that began in Listing 16 .
Save the primary prey fish object and do another iteration
The primary prey fish object may or may not have been moved. Regardless, a clone of that object is created and saved in the container that contains the prey-fish population in Listing 18 .
| Listing 18: Save the primary prey fish object and do another iteration. |
|---|
|
After the object is saved in Listing 18 , control is transferred back to the top of the outer for loop in Listing 16 and the next prey fish object in the population is compared with all of the other prey-fish objects in the population, making corrections to the position of the prey fish as necessary.
Prey fish objects react to the predator
As I mentioned earlier, each prey fish object has a reasonably good defense mechanism to avoid being eaten by the predator. However, in the final analysis, whether or not a prey fish will escape when it encounters the predator face to face is a random process. To some extent, success or failure to escape depends on how quickly the prey fish senses the presence of the predator.
Listing 19 is the beginning of a for loop in which the proximity of each prey fish to the predator is tested and evasive action on the part of the prey fish is taken if the distance between the two is less than 50 units.
| Listing 19: Prey fish objects react to the predator. |
|---|
|
The code in Listing 19 gets the next prey fish in the population and gets a displacement vector describing the separation of the prey fish from the predator.
Prey fish object takes evasive action
If the prey fish is at least 50 units away from the predator, the prey fish simply goes on swimming without concern for the predator. However, if the prey fish is less than 50 units away from the predator, the prey fish takes evasive action in an attempt to escape the predator. The evasive action is accomplished in Listing 20 , and this is where some of the random behavior comes into play.
| Listing 20: Prey fish object takes evasive action. |
|---|
|
The prey fish moves by a random distance
In Listing 20 , the negative of the displacement vector separating the prey fish from the predator is scaled by a random value ranging from 0 to 1. Then the prey fish is moved by the distance and direction specified by the resulting vector. If the random value is 0, the prey fish won't be moved at all. If the random value is 1, the distance between the prey fish and the predator will be doubled. For values between 0 and 1, the prey fish will be moved different distances away from the predator.
Did the prey fish escape?
Once the prey fish has taken the evasive action and has (possibly) moved away from the predator, the separation between the prey fish and the predator is tested again in Listing 21 .
| Listing 21: Test the separation again. |
|---|
|
If the new separation is at least 25 units, the escape attempt is deemed successful and the prey fish will continue to live. (You can decrease or increase the threshold distance used in the test in Listing 21 to cause the prey fish object to be more or less successful in the escape attempt. If you make the threshold distance small enough, the prey fish will almost always escape.)
When the escape attempt is not successful...
When the escape attempt is not successful, the remaining code in Listing 21 is executed. The prey fish is deemed to have been eaten by the predator. Therefore, the prey fish and its direction vector are removed from the containers that contain them. This causes the prey fish to be removed from the population.
In addition, the program sounds a beep to notify the user of the successful attack by the predator and the kill count that is displayed in the bottom right of Image 1 is incremented by one.
Display elapsed time and number of fish eaten
Finally, the code in Listing 21 displays the elapsed time in milliseconds since the Start button was clicked along with the number of fish that have been eaten. Note that the value that is displayed is the elapsed time when the most recent prey fish was eaten by the predator and is not the total elapsed time since the Start button was clicked. (The elapsed time value won't change again until another prey fish is eaten.)
When all of the prey fish have been eaten, the final time that is displayed is the time that was required for the predator to eat all of the fish in the population and the final kill count that is displayed is the same of the original number of prey fish in the population. (Also note that the player must click the Stop button before starting a new game. Fixing this inconvenience will be a good exercise for the student.)
When the escape attempt is successful...
When the escape attempt is successful, a clone of the prey fish in its new location is stored in the preyObjects container and a normalized version of the fish's direction vector is stored in the displayVectors container as shown in Listing 22 .
| Listing 22: Restore the prey fish to the population. |
|---|
|
Listing 22 also signals the end of the for loop that began in Listing 19 .
Keep the prey fish in the playing field
I have one more section of code to explain that deals with the behavior of the prey fish during each iteration of the animation loop. The code in Listing 23 causes prey fish that stray outside the playing field to return to the playing field.
| Listing 23: Keep the prey fish in the playing field. |
|---|
|
The playing field is the interior of a sphere
The playing field is defined to be the interior of a sphere that is centered on the origin with a radius that is one-half the height of the off-screen image. The for loop in Listing 23 iterates once for each prey fish remaining in the population. The if statement in Listing 23 tests to determine if a prey fish is outside of the sphere.
If the prey fish is outside the sphere, the code in Listing 23 gives that prey fish a nudge back toward the origin and saves a clone of the prey fish in its new location.
Erase the off-screen image and draw the large circle
Listing 24 erases the off-screen image and draws the large circle that defines the playing field shown in Image 1 .
| Listing 24: Erase the off-screen image and draw the large circle. |
|---|
|
Although somewhat tedious, the code in Listing 24 is straightforward and shouldn't require an explanation beyond the embedded comments.
Draw the prey fish on the off-screen image
Depending on the states of the two check boxes in Image 1 , Listing 25 draws the GM01.Point3D objects that represent the prey fish or GM01.Vector3D objects that represent the direction vectors for the prey fish, or both, on the off-screen image. (Note that only the vectors were drawn on the off-screen image in Image 1 .)
| Listing 25: Draw the prey fish on the off-screen image. |
|---|
|
There is nothing new in Listing 25 , so I won't bore you with a detailed explanation of the code.
It's time to deal with the predator
So far, we have been dealing exclusively with code that controls the behavior of the prey fish. The time has come to deal with the code that controls the behavior of the predator.
Cause the predator to slowly circle the cluster of prey fish
When the player clicks the Attack button, a boolean variable named attack is set to true , causing the predator to enter attack mode during the next iteration of the animation loop. However, when the value of this variable is false , the predator is not in attack mode and its behavior is to swim slowly around the cluster of prey fish encouraging them to bunch up into a smaller and tighter cluster.
The code that accomplishes this circling behavior is shown in Listing 26 .
| Listing 26: Cause the predator to slowly circle the cluster of prey fish. |
|---|
|
The code in Listing 26 is very similar to code that I explained earlier in conjunction with the behavior of the prey fish. Therefore, no explanation beyond the embedded comments should be required.
Execute the attack
When the user clicks the Attack button, the value of the variable named attack is set to true , causing the code in Listing 27 to be executed during subsequent iterations of the animation loop.
| Listing 27: Execute the attack. |
|---|
|
The predator is in attack mode
The predator is in attack mode at the beginning of Listing 27 , and will remain in attack mode using the same displacement vector to control its speed and direction until it leaves the spherical playing field. When it leaves the playing field, the value of the attack variable will be set to false , causing the predator to revert to non-attack mode.
As the predator encounters prey fish along its trip toward the edge of the playing field, the code in Listing 21 (explained earlier) will determine whether those prey fish escape or get eaten by the predator.
Control of speed and direction
The displacement vector that controls the speed and direction of the predator ( predatorVec in Listing 27 ) is created in the actionPerformed method in Listing 3 in response to a click on the Attack button.
This vector is constructed to point to the most recently computed geometric center of the cluster of prey fish. Note however, that the vector may no longer point to the exact center of the cluster because the exact center of the cluster may have changed since it was last computed. In other words, the position of the geometric center of the prey-fish cluster changes as the predator attacks and causes the prey fish to scatter. As programmed, the predator is unable to respond to such changes and continues to move in the same direction at the same speed until it leaves the playing field.
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 has begun.
A program upgrade to cause the predator to accommodate such changes in the geometric center would be an interesting exercise for the student.
Draw the predator on the off-screen image
Listing 28 sets the drawing color to BLUE and draws the predator's direction vector on the off-screen image.
| Listing 28: Draw the predator on the off-screen image. |
|---|
|
Copy off-screen image, insert time delay, etc
Listing 29 copies the off-screen image to the canvas and then causes the animation thread to sleep for 166 milliseconds.
| Listing 29: Copy off-screen image, insert time delay, etc. |
|---|
|
Listing 29 also signals the end of the animation loop, the end of the run method, and the end of the inner class named Animate .
That is all of the code that I will explain for this program. You can view the remainder of the code in Listing 31 near the end of the module.
Not a graphics program
Even though this program produces quite a lot of 3D graphics, and those graphics are important in the playing of the game, this is not a graphics program. Rather, it is a 3D data-processing program that produces graphics as a side effect.
That is not to say that the graphics are unimportant. To the contrary, the graphics provide visual feedback to the player, which allows the player to implement a strategy for success. Without graphics, the game would be very boring. However, the programming effort to produce the graphics represents an almost trivial part of the total programming effort for this game.
An almost trivial part of the programming effort
Exclusive of the code required to draw the large circle shown in Image 1 , all of the graphics shown in Image 1 , Image 5 , Image 6 , Image 7 , and Image 8 are produced by only two calls to game-math library methods named draw . In other words, all of the required graphics are produced by only two statements in the program code. (One additional statement is required if you want to display the small circles that represent the locations of the prey fish.)
If those two statements are removed, the program will still compile and run, and the game can still be played. However, without visual feedback, the game isn't much fun. The lack of visual feedback eliminates the strategy aspect of the game, causing it to be solely a game of chance. Be that as it may, with or without visual feedback, the player can still click the Start button and then repetitively click the Attack button until the display in the bottom-right of Image 1 shows that all of the prey fish have been eaten.
Why am I telling you this?
I'm telling you this to emphasize that the essential skills required to program this game (and probably most games for that matter) consist of skills in mathematics, programming logic, and several other technical areas. The ability to produce on-screen graphics is necessary for an enjoyable game, but, (given a good game-math library that supports graphics) , producing on-screen graphics is almost a trivial part of the programming effort. In this collection of modules, you need to be mainly concentrating on learning mathematics and programming logic and treating the production of on-screen graphics almost as an afterthought.
This animation program is designed to exercise many of the 2D features of the GM01 game-math library. The animation is generally based on the idea of a flocking behavior similar to that exhibited by birds and fish. A set of GM01.Point2D objects is created with random locations to act as predators. An additional GM01.Point2D object is also created to play the part of a prey object.
The prey object is drawn in red while the predators are drawn in black as shown in Image 2 . An algorithm is executed that attempts to cause the predators to chase the prey object without colliding with one another.
Even though the algorithm causes the predators to chase the prey object, it also tries to keep the predators from colliding with the prey object.
The user input GUI
A GUI is provided that contains an input text field for the number of predators plus a Start button and a Stop button. The GUI also contains check boxes that allow the user to elect to display points only, direction vectors only, or both. (Both the point and the direction vector is always displayed for the prey object.)
The user specifies the number of randomly-placed predators and clicks the Start button, at which time the animation begins and the predators start chasing the prey object. Prey-object motion is random.
The animation continues until the user clicks the Stop button. The user can click the Stop button, change any of the input parameters, and then click the Start button again to re-start the animation with different parameters such as the number of predator objects.
Swimming in formation
An unexpected result is that the algorithm seems to cause the predators to come together and swim in formation while chasing the prey object. The most common formation is hexagonal as shown in Image 9 , which shows 12 predators swimming in a hexagonal formation.
(Note that some of the twelve predators are hidden by other predators.)
| Image 9: Twelve predators swimming in a hexagon formation in GM01test04. |
|---|
![]() |
Other formations appear as well
Some triangles, diamonds, and incomplete hexagons also appear. For example, Image 10 shows six predators swimming in a diamond formation.
| Image 10: Six predators swimming in a diamond formation. |
|---|
![]() |
No explanation for this behavior
I haven't given the matter a lot of thought, but at this point, I have no explanation for this behavior. Note that the tendency to swim in formation is more visually obvious when only the points are displayed. When the vectors are displayed, it is more difficult to pick out the formation.
Dogged determination
On the other hand, the animation is most impressive when the direction vectors are displayed, with or without points, because the vectors illustrate the dogged determination and undying focus that the predators maintain while chasing the prey object.
Won't explain the code
Once you understand the code in the program named GM01test08 that I explained earlier in this module, you should have no difficulty understanding the code in this program. Therefore, I won't explain the code in this program. I included this program in this module mainly to illustrate the differences between 2D and 3D from both a visual and programming viewpoint.
A complete listing of this program is provided in Listing 32 near the end of the module.
This is a 3D update of the 2D program named GM01test04 discussed above.
A comparison of programming requirements
Basically all that was required to perform the update was to specify 3D classes from the game-math library in place of the 2D classes used in the 2D version of the program. In some cases, this in turn required that argument lists for constructors and methods be expanded from two dimensions to three dimensions. Just about everything else took care of itself.
A comparison of these two programs illustrates the value of the game-math library named GM01 and the ease with which you can switch back and forth between 2D and 3D programming when using the library.
A comparison of visual behavior
The visual behavior of this 3D version, as shown in Image 3 , is more realistic than the 2D version. This is particularly true when the prey object gets in the middle of the predators and the display is showing vectors. In the 2D version, a predator is constrained to swing around only in the plane of the screen. However, in this 3D version, a predator is not subject to that constraint and is free to swing around in the most appropriate way as the prey object passes by.
This constraint causes the motion in the 2D version to be less fluid than the motion in the 3D version. This can best be demonstrated with only one predator because that makes it easy to see the behavior of an individual predator as the animation is running.
No swimming in formation
One very interesting thing that I have noticed is that unlike the 2D version, the predators in this 3D version don't seem to have a tendency to form up and swim in formation while chasing the prey object. This may be because they have more options in terms of avoiding collisions while giving chase. However, that is pure speculation on my part since I don't know why the predators tend to swim in formation in the 2D version anyway. (It is also possible that the predators form into a 3D formation, which isn't visually obvious in the 2D projection.)
Won't explain the code
As is the case with the earlier program named GM01test04 , once you understand the code in the program named GM01test08 , you should have no difficulty understanding the code in this program. Therefore, I won't explain the code in this program. I included this program and the earlier 2D version in this module mainly to illustrate the differences between 2D and 3D from both a visual viewpoint and programming viewpoint.
A complete listing of this program is provided in Listing 33 near the end of the module.
This program animates the behavior of the earlier program named StringArt03 that I explained in an earlier module. See the comments at the beginning of that program for a description of both programs.
The only significant difference in the behavior of the two programs is that this program slows the rotation process down and animates it so that the user can see it happening in slow motion. Of course, quite a few changes were required to convert the program from a static program to an animated program.
However, if you understand the code in the earlier program named StringArt03 and you understand the code in the program named GM01test08 that I explained earlier in this module, you should have no difficulty understanding the code in the program named StringArt04 . Therefore, I won't explain the code in this program. A screen shot of the program in action is shown in Image 4 . A complete listing of the program is provided in Listing 34 .
Visual output
When viewing the output, remember that the program executes the rotations around the axes sequentially in the following order:
The homework assignment for this module was to study the Kjell tutorial through Chapter6 - Scaling and Unit Vectors .
The homework assignment for the next module is to study the Kjell tutorial through Chapter 10, Angle between 3D Vectors .
In addition to studying the Kjell material, you should read at least the next two modules in this collection and bring your questions about that material to the next classroom session.
Finally, you should have begun studying the physics material at the beginning of the semester and you should continue studying one physics module per week thereafter. You should also feel free to bring your questions about that material to the classroom for discussion.
I encourage you to copy the code from Listing 30 through Listing 34 . Compile the code and execute it in conjunction with the game-math library named GM01 provided in Listing 30 . Experiment with the code, making changes, and observing the results of your changes. Make certain that you can explain why your changes behave as they do.
In this module, you learned how to write your first interactive 3D game using the game-math library named GM01 . You also learned how to write a Java program that simulates flocking behavior such as that exhibited by birds and fish and you learned how to incorporate that behavior into the game. Finally, you examined three other programs that illustrate various aspects of both 2D and 3D animation using the game-math library.
In the next module, you will learn the fundamentals of the vector dot product in both 2D and 3D. You will learn how to update the game-math library to support various aspects of the vector dot product, and you will learn how to write 2D and 3D programs that use the vector dot product methods in the game-math library.
This section contains a variety of miscellaneous information.
Financial : Although the Connexions site makes it possible for you to download a PDF file for this module at no charge, and also makes it possible for you to purchase a pre-printed version of the PDF file, you should be aware that some of the HTML elements in this module may not translate well into PDF.
I also want you to know that, I receive no financial compensation from the Connexions website even if you purchase the PDF version of the module.
In the past, unknown individuals have copied my modules from cnx.org, converted them to Kindle books, and placed them for sale on Amazon.com showing me as the author. I neither receive compensation for those sales nor do I know who does receive compensation. If you purchase such a book, please be aware that it is a copy of a module that is freely available on cnx.org and that it was made and published without my prior knowledge.
Affiliation : I am a professor of Computer Information Technology at Austin Community College in Austin, TX.
Complete listings of the programs discussed in this module are shown in Listing 30 through Listing 34 below.
| Listing 30: Source code for the game-math library named GM01. |
|---|
|
.
| Listing 31: Source code for the game program named GM01test08. |
|---|
|
.
| Listing 32: Source code for the program named GM01test04. |
|---|
|
.
| Listing 33: Source code for the program named GM01test03. |
|---|
|
.
| Listing 34: Source code for the program named StringArt04. |
|---|
|
Using Java and the game-math library named GM01 , or using a different programming environment of your choice, write a program that creates the wireframe drawing of half of a sphere protruding upward from the x-z plane as shown in Image 11 . The north and south poles of the sphere lie on the y-axis. The x-z plane intersects the sphere at the equator and only the northern hemisphere is visible.
Cause your name to appear in the screen output in some manner.
| Image 11: Screen output from Exercise 1. |
|---|
![]() |
Using Java and the game-math library named GM01 , or using a different programming environment of your choice, write a program that demonstrates your ability to animate two or more objects and have them moving around in a 3D world.
Cause your name to appear in the screen output in some manner.
Beginning with a 3D world similar to the one that you created in Exercise 2 , demonstrate your ability to cause your program to recognize when two of your animated objects collide and to do something that can be noticed by a human observer when the collision occurs.
Cause your name to appear in the screen output in some manner.
Beginning with a program similar to the one that you wrote in Exercise 1 , create a wireframe drawing of a complete sphere as shown in Image 12 . The north and south poles of the sphere lie on the y-axis. As before, the x-z plane intersects the sphere at the equator, but in this case, the entire sphere is visible.
| Image 12: Screen output from Exercise 4. |
|---|
![]() |
Beginning with a program similar to the one that you wrote in Exercise 1 , create a wireframe drawing of one-eighth of a sphere as shown in Image 13 . The north and south poles of the sphere lie on the y-axis. As before, the x-z plane intersects the sphere at the equator. In this case, only that portion of the sphere described by positive coordinate values is visible.
Cause your name to appear in the screen output in some manner.
| Image 13: Screen output from Exercise 5. |
|---|
![]() |
-end-