Zombie Math Hunter (Project)

Make sure that you are using the latest env3d_template.zip (or use the "Update Env3D Library" option if you are using NetBeans).  You will also need to select "Extract additional models".

 

Introduction

The Zombie Math Hunter is a game created using the Env3D Scene Creator.  In this project, you will be given the source code for it and be required to make modifications to various parts of the software.

 

To get the source code, simply use the "Example" menu item in BlueJ to download it into your current project.  If you are using NetBeans, download the zip file that contains all the files.  Extract it and put it inside the your NetBeans project folder.

 

Points of Interests

When you study the source code carefully, you will noticed that the code is very much based on the "Working with animation" lesson.  However, there are several enhancements that you should understand before attempting the exercies.

 

Collision detection with non-spherical shapes

Our distance method can easily give us the distance between the center of 2 objects.  When using this method for collision detection, the advantage is that the code is fast and easy to write.  However, unless all your objects are spheres, the method is not very accurate.

 

We could devise a scheme to improve on the accuracy of our collision detection mechanism by using multiple spheres for each on-screen object.  Check out the source code to LongHouse.  We start by adding an array of EnvNode for collision detection purposes:

    
   private EnvNode[] collisionNode;

Our move method looks like the following:

    public void move() 
    {
        // Create nodes for collision purposes -- these are not visible.        
        if (collisionNode == null) {
            collisionNode = new EnvNode[3];
            double scale = 3;
            for (int i = 0; i < 3; i++) {
                collisionNode[i] = new EnvNode();
                collisionNode[i].setX(this.getX()-scale*Math.cos(Math.toRadians(this.getRotateY())));
                collisionNode[i].setY(1);
                collisionNode[i].setZ(this.getZ()+scale*Math.sin(Math.toRadians(this.getRotateY())));
                collisionNode[i].setScale(4);
                scale -= 3;
                // for debugging purpose, you can make the nodes visible
                // env.addObject(collisionNode[i]);
            }
        }
        
        // Check every actor for collision
        for (Actor z : getEnv().getObjects(Actor.class)) {   
            for (EnvNode cNode : collisionNode) {
                if (z.distance(cNode) < cNode.getScale()+z.getScale()) {
                    if (z instanceof Zombie) {
                        z.moveForward(-0.1);
                        z.setRotateY(z.getRotateY()+180);
                    } else if (z instanceof Hunter) {
                        ((Hunter) z).revert();
                    }
                }
            }
        }
        
    }    

The first part of the code creates the collision node array if it has not been created already. Noticed that the collision node is made up of 3 separate EnvNode (which are spheres). We placed these objects to approximately cover the length of the entire LongHouse.

The second part of the code performs collision detection every time the move method is called (which is 30 times a second). You'll noticed the use of a nested for loop -- for each object in our world, we perform collision detection with all 3 of our collision nodes.

Weapons with a range

So far, we are able to create a bullet in our world by adding a "bullet" object into the environment and animating it.  With each move() method call, we perform collision detection between the "bullet" and the rest of the world object.

The major drawback of this method is that the bullet can only travel at a certain speed.  Beyond that speed, the bullet will start "missing" some targets (because it moves too fast between frames).  One common solution to this problem is to create a mathematical "ray" in 3D space and performs collision detection with the ray instead of the bullet.  This requires a good working knowledge of 3D mathematics and is beyond the scope of this exercise.

We could, however, create a range weapon by performing multiple collision detections with an invisible bullet objects every time a bullet is fired -- thus solving the "missing traget" problem without resorting to advanced mathematics.  Look at the Hunter code, we start by having an array of EnvNode to act as the invisible bullet:

    private EnvNode[] gunRange;

Every time our gun is fired, we move all the objects in my gunRange array to match the line-of-sight of my character, we then perform collision detection between each Zombie and each object in my gunRange, all inside the same frame.

        // Eevery time we fire our gun, we check all of the bullets in my gunRange array.  The more
        // bullets I have in my array, the farther is my gun's reach
        for (int i = 0; i < gunRange.length; i++) {             
            // Look at every zombie            
            EnvNode bullet = gunRange[i];            
            for (Zombie zombie : getEnv().getObjects(Zombie.class)) {
                // Calculate the location of each bullet
                double weaponX = this.getX()+i*Math.sin(Math.toRadians(getRotateY()));
                double weaponZ = this.getZ()+i*Math.cos(Math.toRadians(getRotateY()));
                bullet.setX(weaponX); bullet.setY(getY()); bullet.setZ(weaponZ); bullet.setScale(bulletScale);
                // performs collision detection between each Zombie and each bullet
                if (zombie.distance(bullet) < bullet.getScale() && !zombie.isDead()) {
                    zombie.turnToFace(this);
                    zombie.setState(Zombie.BLOWNED);
                    kills++;
                    if (kills % 3 == 0) {
                        shield = new Shield(getEnv());       
                        getEnv().addObject(shield);
                    }                    
                    return;
                }
            }           
        }

The integration of a mini-math game

One of the unique aspect of our game is the integration of simple arithematic:

  • Every time the player attempts to fire a gun, the player must successfully answer a simple math question. 
  • Every time the player is under attack, the player must successfully answer a simple math question to avoid damage and performs a counter-attack.

This math game is supplied by the MiniMath class.

In BlueJ, you can test the MiniMath class on its own by first creating a miniMath object and then call the "test" method.

We integrate the mini math game with the firing mechanism by first including a miniMath object inside my Hunter.  When our player wants to fire the gun, we pass control to the miniMath game first.  As follows:

        // Determine the size of the bullet by asking simple math
        // keep calling play until the play has finished.
        miniMath.play();
        double bulletScale = 0.5;
        // The math game has finished
        if (miniMath.isCorrect()) {
            // the range of our weapon
            bulletScale = bulletScale + (miniMath.getTimeRemain()/150.0);
        }    
        miniMath.reset();

 

In the above code, the miniMath object takes control when we call the miniMath.play() method.  When miniMath.play() returns, we check to see if the player has answered correctly and determine the bullet's size.  We reset the game so that it is ready to be used the next time. 

Similar code is used in the Zombie class for counter-attack.

 

Exercises

  1. Currently, the Mountain class is still using simple sphere-based collision detection.  Modify it to use the same technique as the LongHouse.
  2. Create one additional enemy type with different AI than the Zombie.
  3. Modify the miniMath class so it asks multiplication questions also.  Make is such that when the gun is fired, the miniMath object randomly chooses between addition and multiplication questions.