643-Project3-Bombardo-Davis

Spring 2010
This project builds upon the groundwork developed in Project 2. After looking at many samples and reading more about first person shoots in XNA, we decided that there should be several modifications to transition it into a full game.

A lot of the ideas and changes come from the book “Beginning XNA 3.0 Game Programming: From Novice to Professional” By Alexandre Santos Lobao Bruno Pereira Evangelista José Antonio Leal de Farias Riemer Grootjans.

As you can see from the image below, we are making use of the first-person camer perspective, which means the player is essentially seeing through the eyes (or in our case, the rifle scope) of the character he is controlling. This was discussed in more detail in the __Game Design__ book.



The first thing need was better organization. As the game sat after project two it was fine, however, as more objects were going to be added it was best to introduce more structure and organization. The first step was to move classes into folders to better demonstrate what the classes represented. We introduced two folders, GameAttributes and GameLogics.

The GameAttributes folder will contain the files and folders for such things as the effects, lights, models, textures, and terrains.

The GameLogics folder will contain the classes such as player1, enemy, weapon, terrain. Notice the terrain is both folders. The terrain classes in the GameAttributes will handle the drawing, the texture, lighting – the look and feel of the terrain, while the terrain in the GameLogics folder will handle collisions with the players and enemy. This will make sure objects walk on the terrain and not through it.



The next change was the terrain itself. In project 2, the terrain allowed the player to "walk through" hills and other terrain elements. As we explored how to make the camera collide with the terrain, we decided it would be best to completely rewrite the terrain classes. In fact, in meant separating the terrain class into multiple classes.

The terrain will now be created and managed by:

1. GameAttributes.TerrainMaterial.cs – This class will manage the textures of the terrain. Though we currently are only making one terrain, this change will give us the ability to easily change the texture with future growth of the game.

2. GameAttributes.TerrainEffect.cs – This class will apply effects such as lighting, colors, and other XNA effects to the terrain.

3. GameLogics.TerrainUnit.cs – This class will manage the collision of terrain and other objects.

code format="csharp" using System; using System.Collections.Generic;using System.Text;
 * GameAttributes.TerrainMaterial.cs:**

using Microsoft.Xna.Framework.Graphics;

namespace ASSIGNMENT2_FIRSTPERSON.GameAttributes.Materials {   public class TerrainMaterial {       // Surface material LightMaterial lightMaterial;

// Diffuse Textures TextureMaterial diffuseTexture1; TextureMaterial diffuseTexture2; TextureMaterial diffuseTexture3; TextureMaterial diffuseTexture4; TextureMaterial alphaMapTexture;

// Normal map TextureMaterial normalMapTexture;

public LightMaterial LightMaterial {       get{return lightMaterial;} set{lightMaterial = value;} }

public TextureMaterial DiffuseTexture1 {       get{return diffuseTexture1;} set{diffuseTexture1 = value;} }

public TextureMaterial DiffuseTexture2 {       get{return diffuseTexture2;} set{diffuseTexture2 = value;} }

public TextureMaterial DiffuseTexture3 {       get{return diffuseTexture3;} set{diffuseTexture3 = value;} }

public TextureMaterial DiffuseTexture4 {       get{return diffuseTexture4;} set{diffuseTexture4 = value;} }

public TextureMaterial AlphaMapTexture {       get{return alphaMapTexture;} set{alphaMapTexture = value;} }

public TextureMaterial NormalMapTexture {       get{return normalMapTexture;} set{normalMapTexture = value;} }

}

code

code format="csharp" namespace ASSIGNMENT2_FIRSTPERSON.GameAttributes.Effects {   public class TerrainEffect {       … code The terrain uses a terrain effect file. Since it already had some effects built into the terrain the first thing we need to do is direct the class to the file: code format="csharp" public static string EFFECT_FILENAME = GameAssetsPath.EFFECTS_PATH + "Terrain";
 * GameAttributes.TerrainEffect.cs:**

Effect effect; code

Each object in XNA that will be displayed in the camera is going to have three matrices: The world matrix, the view matrix and the projection matrix:

code format="csharp" Matrix worldMatrix, Matrix viewMatrix Matrix projectionMatrix;

code

Next is the declaration of all the possible effects for the terrain:

code format="csharp" // Effect parameters - Matrices EffectParameter worldParam; EffectParameter viewInverseParam; EffectParameter worldViewProjectionParam;

// Effect parameters - Material EffectParameter diffuseColorParam; EffectParameter specularColorParam; EffectParameter specularPowerParam;

// Effect parameters - Lights EffectParameter ambientLightColorParam; EffectParameter light1PositionParam; EffectParameter light1ColorParam; EffectParameter light2PositionParam; EffectParameter light2ColorParam;

// Effect parameters - Textures EffectParameter diffuseTexture1Param; EffectParameter diffuseTexture2Param; EffectParameter diffuseTexture3Param; EffectParameter diffuseTexture4Param; EffectParameter normalMapTextureParam; EffectParameter alphaMapTextureParam;

// Effect parameters - Textures UVs EffectParameter textureUV1TileParam; EffectParameter textureUV2TileParam; EffectParameter textureUV3TileParam; EffectParameter textureUV4TileParam; EffectParameter textureUVNormalTileParam; code

Next, we need a way to pass our world mattrix to the class and to retrive the it. This will be followed by the View matrix and the projection matrix:

code format="csharp" public Matrix World {           get{return worldMatrix;} set{worldMatrix = value; worldParam.SetValue(value); worldViewProjectionParam.SetValue(worldMatrix * viewMatrix * projectionMatrix);} }

public Matrix View {           get{return viewMatrix;} set{viewMatrix = value; Matrix viewInverseMatrix = Matrix.Invert(viewMatrix);

viewInverseParam.SetValue(viewInverseMatrix); worldViewProjectionParam.SetValue(worldMatrix * viewMatrix * projectionMatrix);} }

public Matrix Projection {           get{return projectionMatrix;} set{projectionMatrix = value; worldViewProjectionParam.SetValue(worldMatrix * viewMatrix * projectionMatrix);} }

public Vector3 AmbientLightColor {           get{return ambientLightColorParam.GetValueVector3;} set{ambientLightColorParam.SetValue(value);} }

public Vector3 DiffuseColor {           get{return diffuseColorParam.GetValueVector3;} set{diffuseColorParam.SetValue(value);} }

public Vector3 SpecularColor {           get{return specularColorParam.GetValueVector3;} set{specularColorParam.SetValue(value);} }

public float SpecularPower {           get{return specularPowerParam.GetValueSingle;} set{specularPowerParam.SetValue(value);} }

public Vector3 Light1Position {           get{return light1PositionParam.GetValueVector3;} set{light1PositionParam.SetValue(value);} }

public Vector3 Light1Color {           get{return light1ColorParam.GetValueVector3;} set{light1ColorParam.SetValue(value);} }

public Vector3 Light2Position {           get{return light2PositionParam.GetValueVector3;} set{light2PositionParam.SetValue(value);} }

public Vector3 Light2Color {           get{return light2ColorParam.GetValueVector3;} set{light2ColorParam.SetValue(value);} }

public Texture DiffuseTexture1 {           get{return diffuseTexture1Param.GetValueTexture2D;} set{diffuseTexture1Param.SetValue(value); } }

public Texture DiffuseTexture2 {           get{return diffuseTexture2Param.GetValueTexture2D;} set{diffuseTexture2Param.SetValue(value);} }

public Texture DiffuseTexture3 {           get{return diffuseTexture3Param.GetValueTexture2D;} set{diffuseTexture3Param.SetValue(value); } }

public Texture DiffuseTexture4 {           get{return diffuseTexture4Param.GetValueTexture2D;} set{diffuseTexture4Param.SetValue(value);} }

public Texture NormalMapTexture {           get{return normalMapTextureParam.GetValueTexture2D;} set{normalMapTextureParam.SetValue(value);} }

public Texture AlphaMapTexture {           get{return alphaMapTextureParam.GetValueTexture2D;} set{alphaMapTextureParam.SetValue(value);} }

public Vector2 TextureUV1Tile { get{return textureUV1TileParam.GetValueVector2;} set{textureUV1TileParam.SetValue(value);} }

public Vector2 TextureUV2Tile {           get{return textureUV2TileParam.GetValueVector2;} set{textureUV2TileParam.SetValue(value);} }

public Vector2 TextureUV3Tile {           get{return textureUV3TileParam.GetValueVector2;} set{textureUV3TileParam.SetValue(value);} }

public Vector2 TextureUV4Tile {           get{return textureUV4TileParam.GetValueVector2;} set{textureUV4TileParam.SetValue(value);} }

public Vector2 TextureUVBumpTile {           get{return textureUVNormalTileParam.GetValueVector2;} set{textureUVNormalTileParam.SetValue(value);} }

code

The terrain effects will actually be set in the terrain.cs in:

code format="csharp" This actually starts the effects on the terrain:

code format="csharp" public void Begin { effect.Begin; }

code

We can also end the effects on the terrain:

code format="csharp" public void End { effect.End; } } }

code Terrain:


 * GameLogics.TerrainUnit.cs:**

In the using statements we have needed to add the GameAttributes, Attributes.Cameras, and Shapes. This will give us access to classes within each of the folders.

code format="csharp" using ASSIGNMENT2_FIRSTPERSON.GameAttributes; using ASSIGNMENT2_FIRSTPERSON.GameAttributes.Cameras; using ASSIGNMENT2_FIRSTPERSON.GameAttributes.Shapes;

namespace ASSIGNMENT2_FIRSTPERSON.GameLogic { public class TerrainUnit {

code

Since we will now be able to walk up hills and down hills, there should be gravity pulling us down slopes or making us slip down in places where the slopes are great. So we need to add in a gravity pull. This will also affect our jumps:

code format="csharp" public static float MIN_GRAVITY = -1.5f; public static float GRAVITY_ACCELERATION = 4.0f;

code

Following the examples in the "Beginning XNA 3.0 Game Programming: From Novice to Professional", we are going to move all of the 3D model drawing (animation) to a separate class called AnimatedModel. Thus, in each of our classes where we the 3D draw we will declare the AnimatedModel.

code format="csharp" AnimatedModel animatedModel; int currentAnimationId; code

The BondingBox and BondingSpere is going the be the area around the terrain that will be check for collisions to keep objects above the terrain.

code format="csharp" BoundingBox boundingBox; BoundingSphere boundingSphere; bool needUpdateCollision; code

BoundingBox



BoundingSphere



http://sharky.bluecog.co.nz/?p=119 – The Sphere image is a multiple sphere image – we will talk about it later.

Sharky (website above) provideds a great description of using XNA Bounding. Basically, XNA provides two classes specifically for collision detection: BoundingSphere & BoundingBox. Both are non-visual, and contain an Intersects method that tracks collisions associated with your BoundingBox/Sphere.

Once each of the objects on the screen has a BoundingSphere or BoundingBox, you can loop through them to test for collisions. We will cover this later.

To assist in calculting the gravitation pull on the player we need to track and store the slope angle they are are walking on.

code format="csharp" Vector3 linearVelocity; Vector3 angularVelocity; float gravityVelocity;

int speed bool isOnTerrain;

code

The next section is just the set and get for the variables discussed above:

code format="csharp" public float Speed { get{return speed;} set{speed = value;} }

public Vector3 LinearVelocity { get{return linearVelocity;} set{linearVelocity = value;} }

public Vector3 AngularVelocity { get{return angularVelocity;} set{angularVelocity = value;} }

public float GravityVelocity { get{return gravityVelocity;} set{gravityVelocity = value;} }

public Vector3 HeadingVector { get{return headingVec;} }

public Vector3 StrafeVector { get{return strafeVec;} }

public Vector3 UpVector { get{return upVec;} }

public AnimatedModel AnimatedModel { get{return animatedModel;} }

protected int CurrentAnimationId { get{return currentAnimationId;} }

code

The next two get/sets actually do a little more than just set or return the variable. First, the transformation object is a new class separated out to control the X,Y,Z axis. Instead of rewriting the rotate and scale in the class for each object, the Transformation class will translate the X,Y,Z axis as each object moves.

code format="csharp" public virtual Transformation Transformation { get{return animatedModel.Transformation;} set{animatedModel.Transformation = value; UpdateHeight(0); NormalizeBaseVectors; } }

code

Then next get/set is the BoundingBox. The BoundingBox, as mention above, is the invisible box surround an object to detect collisions. The BoundingBox is an XNA provided class. It is located under the Microsoft.Xna.Framework.

Whenever the BoundBox for terrain is called, we first check to see if it needs to be updated. It will need to be updated after each game update, which from project 2 we determined XNA calls the game update each frame. However, if there is a collision we need to wait for the collision to be reported and the UpdateCollision will update the boundingbox. If each collision is a point of our life if we called the UpdateCollision twice in a frame it may report to “hits”.

code format="csharp" public BoundingBox BoundingBox { get{if (needUpdateCollision) UpdateCollision; return boundingBox;} }

code

The BoundingSphere is the box, however, the sphere can be broken up to cover specific parts on the model. Thus, it would bring in the possible intersections of the model much tighter. Here are several images to visual the the differce. The first is a simple box, the second is a single sphere and the third is multiple spheres. As you will see is the multiple sphere has the pattern closes to the model.

Again, thanks to http://sharky.bluecog.co.nz for the images and the great tutorial on Bounding and collisions.

code format="csharp" public BoundingSphere BoundingSphere

{ get{if (needUpdateCollision) UpdateCollision; return boundingSphere;} }

code

This is a just a check to verify our player is still on the terrain. We may not be on the terrain if we are jumping or falling from a ledge. If we are not on the terrain, then our update of the terrain needs to be different and the gravity acceleration comes into play. This will be covered a little later, when we cover jumping.

code format="csharp" public bool IsOnTerrain { get{return isOnTerrain;} }

code format="csharp"

Below, is the intialization of the TerrainUnit class. It simple sets the variables.

code format="csharp" public TerrainUnit(Game game)
 * base(game)

{ gravityVelocity = 0.0f; isOnTerrain = false; isDead = false; adjustJumpChanges = false; needUpdateCollision = true; }

code

The load method is used to load the model. As mentioned above, we moved the actual drawing of the models to a new separate class called AnimatedModel. We moved it for several reason, but primarily because it was best to keep all of the models together for cleaner code and easier management as a greater number of models are added.

code format="csharp" protected void Load(string unitModelFileName) { animatedModel = new AnimatedModel(Game); animatedModel.Initialize; animatedModel.Load(unitModelFileName); code

Start the player in the normal walking position:

code format="csharp" UpdateHeight(0); isOnTerrain = true;

NormalizeBaseVectors; }

private void NormalizeBaseVectors { headingVec = Transformation.Matrix.Forward; strafeVec = Transformation.Matrix.Right; upVec = Transformation.Matrix.Up;

headingVec.Normalize; strafeVec.Normalize; upVec.Normalize; }

code

The update method is where everything comes together. Everything we have done pretty much leads to this method. The update method checks where our objects are on the terrain. If they are on the terrain, this method updates the model, udpate the matrix, applies the gravity, and updates the rotation. Although it is just a couple of lines of code, it calls most of everything (or at least links to) we have done thus far.

code format="csharp" public override void Update(GameTime time) { float elapsedTimeSeconds = (float)time.ElapsedGameTime.TotalSeconds; animatedModel.Update(time, Matrix.Identity);

// Update the height and collision volumes if (linearVelocity != Vector3.Zero || gravityVelocity != 0.0f) { Transformation.Translate += linearVelocity * elapsedTimeSeconds * speed; UpdateHeight(elapsedTimeSeconds); needUpdateCollision = true; }

// Update coordinate system when the unit rotates if (angularVelocity != Vector3.Zero) { Transformation.Rotate += angularVelocity * elapsedTimeSeconds * speed; NormalizeBaseVectors; }

base.Update(time);

} code

When the player jumps, we must update the player and the terrain:

code format="csharp" public void Jump(float jumpHeight) { if (isOnTerrain) { speed *= 1.5f; adjustJumpChanges = true;

gravityVelocity = (float)GRAVITY_ACCELERATION * jumpHeight * 0.1f; isOnTerrain = false; } } code

We need to get the terrain height to know if our player is on the terrain:

code format="csharp" private void UpdateHeight(float elapsedTimeSeconds) { // Get terrain height float terrainHeight = terrain.GetHeight(Transformation.Translate); Vector3 newPosition = Transformation.Translate;

// Unit is on terrain float HEIGHT_EPSILON = 2.0f; if (Transformation.Translate.Y <= (terrainHeight + HEIGHT_EPSILON) && gravityVelocity <= 0) { isOnTerrain = true; gravityVelocity = 0.0f; newPosition.Y = terrainHeight;

} // Unit is above the terrain else { isOnTerrain = false; // Gravity if (gravityVelocity > MIN_GRAVITY) gravityVelocity -= GRAVITY_ACCELERATION * elapsedTimeSeconds; newPosition.Y = Math.Max(terrainHeight, Transformation.Translate.Y + gravityVelocity); } Transformation.Translate = newPosition; } code

Since we moved the model drawing into animated class, we need to simply call the animated class. In the past, each object would need 10 or more lines of code to draw the models. Now, we have added one to each object class. The 10+ lines of code are only needed once, thanks to the use of Object Oriented Programming:

code format="csharp" public override void Draw(GameTime time) { animatedModel.Draw(time); } code

It may seem odd to receive damage in the TerrainUnit class, but both the player and enemy is going to use the terrain unit as a base class. It is the one class that both player and enemy will share. If we put it in the terrain class, we can use it in both the player and the enemy class. We did not have enough methods to justify creating a separate class, so it fit well here.

code format="csharp" public virtual void ReceiveDamage(int damageValue) { life = Math.Max(0, life - damageValue); if (life == 0) isDead = true; }

private void UpdateCollision {

boundingBox = animatedModel.BoundingBox; boundingBox.Min += Transformation.Translate; boundingBox.Max += Transformation.Translate;

// Update bounding sphere boundingSphere = animatedModel.BoundingSphere; boundingSphere.Center += Transformation.Translate;

needUpdateCollision = false; }

code

The BoxIntersects is where we are going to check for collisions, at least part of it. There is something new introduced here: the ray. A ray or ray tracing is basically following a vector to check what it intersects. The ray is the gun shot, the best way to think of it is a ray gun. In the shooting section we will cover more on XNA’s ray method.

Here, the ray is based in to check if the ray intersects and where the model. It returns the float value of the intersects.

code format="csharp" public float BoxIntersects(Ray ray) { Matrix inverseTransformation = Matrix.Invert(Transformation.Matrix); ray.Position = Vector3.Transform(ray.Position, inverseTransformation); ray.Direction = Vector3.TransformNormal(ray.Direction, inverseTransformation);

return animatedModel.BoundingBox.Intersects(ray); } } } code


 * Enemies**

Now we have the terrain complete, it is time to add the enemies. The model of the enemy is going to be a large ugly spider type creator, because that is the best looking enemy model that we were able to find at no cost.

code format="csharp" namespace Assignment2_FirstPerson.GameLogics { code

We are inheriting the base class TerrainUnit into the enemy class. A lot of the controls for speed, gravity, collsions are in the TerrainUnit, so this will give us direct access to the methods.

This particular model had several different animations built in. Not only did it walk and run, but it also had a front leg attack, a blood spot when it got hit, and a crumpled look for death. We need to keep track or what animation the enemy is using:

code format="csharp" public enum EnemyAnimations { Idle = 0, Run, Bite, TakeDamage, Die } code

The enemyState is the action of the enemy. We decided on four different states, because that is what most games we looked at had. Wander is to just keep the enemy randomly moving. ChasePlay is when the enemy for one of several reason (we will touch on later) is moving to the location of the player. AttackPlayer will be when the enemy is close enough to cause go into attack mode and damage the player. Finally, is dead - when the enemy’s life hits 0 or below.

code format="csharp" public enum EnemyState { Wander = 0, ChasePlayer, AttackPlayer, Dead } code

We have several static variables that control different aspects of the enemy. The first three control movement of the enemy and limitation of the movement.

code format="csharp" static float DISTANCE_EPSILON = 1.0f; static float LINEAR_VELOCITY_CONSTANT = 35.0f; static float ANGULAR_VELOCITY_CONSTANT = 100.0f; code

The first three variables control how often and how far the enemy moves when it is wander state. The next four variables sets the delay between damaging the player when in attackplayer state.

code format="csharp" static int WANDER_MAX_MOVES = 3; static int WANDER_DISTANCE = 80; static float WANDER_DELAY_SECONDS = 4.0f; static float ATTACK_DELAY_SECONDS = 1.5f;

EnemyState state; float nextActionTime; code

Wandering Controls:

code format="csharp" int wanderMovesCount; Vector3 wanderPosition; Vector3 wanderStartPosition; code Wandering Enemy:



Chasing Controls:

code format="csharp" float perceptionDistance; Vector3 chaseVector; code Chasing Enemy:

Attack Controls:

code format="csharp" bool isHit; Player player; float attackDistance; int attackDamage; code Attacking Enemy:

Dead Enemy:

UnitTypes is a class to set the variables for the each object. Originally, we had the all of the variables in each class. However, after looking at Beginning XNA 3.0 Game Programming: From Novice to Professional, we realized the benefit of separating some of the static variables into a class to make it easy to control. This includes such things as life, bullets, model names, max speeds, and attack distances. We can now control the game by looking at one class or make it hard or easier by modifying the variable for each unit type:

code format="csharp" UnitTypes.EnemyType enemyType; code

Similar to the terrain, the drawing of the enemy has moved into an animation class:

code format="csharp" Get/sets for our enemyState:

code format="csharp" public EnemyState State { get{return state;} set{state = value;} }

public Player Player { set{player = value;} } code

Drawing the enemy in the transformation class:

code format="csharp" public override Transformation Transformation { get{return AnimatedModel.Transformation; } set{base.Transformation = value; wanderPosition = value.Translate; wanderStartPosition = value.Translate;} } code

In the constructor of the enemy class, we set the type and pass in the game. Next, we set the initial values of the enemy:

code format="csharp" public Enemy(Game game, UnitTypes.EnemyType enemyType)
 * base(game)

{ this.enemyType = enemyType; isHit = false; wanderMovesCount = 0; } code

We now load the variables into the our enemy along with the models:

code format="csharp" protected override void LoadContent { Load(UnitTypes.EnemyModelFileName[(int)enemyType]);

// Unit configurations Life = UnitTypes.EnemyLife[(int)enemyType]; MaxLife = Life; Speed = UnitTypes.EnemySpeed[(int)enemyType]; perceptionDistance = UnitTypes.EnemyPerceptionDistance[(int)enemyType]; attackDamage = UnitTypes.EnemyAttackDamage[(int)enemyType]; attackDistance = UnitTypes.EnemyAttackDistance[(int)enemyType];

wanderPosition = Transformation.Translate; SetAnimation(EnemyAnimations.Idle, false, true, false);

base.LoadContent; } code

As stated above, the actual receiveDamage method is in the terrainUnit.cs, so that both the enemy and player could use it:

code format="csharp" public override void ReceiveDamage(int damageValue) { base.ReceiveDamage(damageValue); code

When we have a hit, the ray from the gun intersects the enemy and damage is received. Sicne it is a hit, we need to set the hit to true. The isHit variable will be used to to change the state of the enemy. Even if the enemy’s distance is outside of the range to place the enemy from a wonder state to a chase state, a true isHit may cause it to change to chase. Basically, if you shoot and hit an enemy the distance in which it goes into chase state is extended.

code format="csharp" isHit = true; code

Here, we ensure the enemy is still alive the change the animation if needed:

code format="csharp" if (Life > 0) { if (CurrentAnimation != EnemyAnimations.Bite) if (CurrentAnimation == EnemyAnimations.TakeDamage) SetAnimation(EnemyAnimations.TakeDamage, true, false, true); else SetAnimation(EnemyAnimations.TakeDamage, false, false, false); } else { state = EnemyState.Dead; SetAnimation(EnemyAnimations.Die, false, false, false); } }

public void SetAnimation(EnemyAnimations animation, bool reset, bool enableLoop, bool waitFinish) { SetAnimation((int)animation, reset, enableLoop, waitFinish); } code

The move method takes the direction the enemy is pointing and moves it:

code format="csharp" private void Move(Vector3 direction) { SetAnimation(EnemyAnimations.Run, false, true, (CurrentAnimation == EnemyAnimations.TakeDamage)); LinearVelocity = direction * LINEAR_VELOCITY_CONSTANT;

// Angle between heading and move direction float radianAngle = (float)Math.Acos(Vector3.Dot(HeadingVector, direction)); if (radianAngle >= 0.1f) { // Find short side to rodade CW or CCW float sideToRotate = Vector3.Dot(StrafeVector, direction);

Vector3 rotationVector = new Vector3(0, ANGULAR_VELOCITY_CONSTANT * radianAngle, 0); if (sideToRotate > 0) AngularVelocity = -rotationVector; else AngularVelocity = rotationVector; } } code

When in the wandering state, the Wander method is called. The wander method moves the enemy in a random direction between 3 moves up to an distance of 80 each move. Between each wander is a slight delay. Each of the items - 3 moves, distance 80, and time delay - are all set in the enemy UnitType. The Wander moving does not move towards anything but a random created distance.

code format="csharp" private void Wander(GameTime time) { Vector3 wanderVector = wanderPosition - Transformation.Translate; wanderVector.Y = 0; float wanderVectorLength = wanderVector.Length;

if (wanderVectorLength < DISTANCE_EPSILON) { SetAnimation(EnemyAnimations.Idle, false, true, false);

if (wanderMovesCount < WANDER_MAX_MOVES) { wanderPosition = Transformation.Translate + RandomHelper.GeneratePositionXZ(WANDER_DISTANCE); wanderMovesCount++; } else { wanderPosition = wanderStartPosition; wanderMovesCount = 0; }

nextActionTime = (float)time.TotalGameTime.TotalSeconds + WANDER_DELAY_SECONDS + WANDER_DELAY_SECONDS * (float)RandomHelper.RandomGenerator.NextDouble; if (time.TotalGameTime.TotalSeconds > nextActionTime) { wanderVector *= (1.0f / wanderVectorLength); Move(wanderVector); } } code

AttackPlay is called when the enemy is within the “attack zone” set in the unittype of the enemy. The attack zone is the maximum distance the enemy can be from the player before it can cause and damage to the player. Once inside the range the enemy is changed to to attackplayer state and moves in attackplay.

code format="csharp" private void AttackPlayer(GameTime time) { float elapsedTimeSeconds = (float)time.TotalGameTime.TotalSeconds; if (elapsedTimeSeconds > nextActionTime) { SetAnimation(EnemyAnimations.Bite, false, true, false);

player.ReceiveDamage(attackDamage); nextActionTime = elapsedTimeSeconds + ATTACK_DELAY_SECONDS; } } code

Each time the game frame is updated the update method is called, pulling everything together:

code format="csharp" public override void Update(GameTime time) { LinearVelocity = Vector3.Zero; AngularVelocity = Vector3.Zero; code

We need to get the chase direction to the player in case we are in chase state:

code format="csharp" chaseVector = player.Transformation.Translate - Transformation.Translate; float distanceToPlayer = chaseVector.Length;

chaseVector *= (1.0f / distanceToPlayer); code

Now we set the act depending on our state. A simple switch statement is used to determine the state then make call the method.

code format="csharp" switch (state) { case EnemyState.Wander: code

We are now going to change the enemies state to chase if the enemy is hit or within the perceptionDistance (distance set in unitType).

code format="csharp" if (isHit || distanceToPlayer < perceptionDistance)

state = EnemyState.ChasePlayer; else Wander(time); break;

case EnemyState.ChasePlayer: code

If the enemy is in the chase state, it checks to see if it is within the attackDistance. If so, it changes its state to attack.

code format="csharp" if (distanceToPlayer <= attackDistance) { // Change state state = EnemyState.AttackPlayer; nextActionTime = 0; } else ChasePlayer(time); break;

case EnemyState.AttackPlayer: code

Finally, if the enemy is in attack state we need to check if the player moved far enough away to change the enemy to chase.

code format="csharp" if (distanceToPlayer > attackDistance * 1.5f) state = EnemyState.ChasePlayer; else AttackPlayer(time); break;

default: break; }

base.Update(time); } code

Similar to receiveDamage, we move the draw method to terrainunit, so we only needed to write it once for the player, enemy and terrain.

code format="csharp" public override void Draw(GameTime time) { base.Draw(time); } } code


 * Shooting**

The first thing needed to record in shooting was the firePosition of direction to shot the ray. The basics of shooting is the user is going to hit the button to shoot and form the barrel of the gun (based on the direction the gun is pointed) a XNA method Ray is going to be “shot”. A ray is a non-ending invisiable line from a start position in a specific vector path. In other words think of a ray gun that never ends. The ray method gives an easy method to determine of a shot hit a target and introduces one problem. First wne the player creates the ray we can test for any collisions. If there is a collision a float is past back which is the distance from the ray start to the collision. If the distance is within the “hit range”, set in the unittype for weapon, then we have a hit. Before we declare it as a hit we must check to make sure the ray collision is within the camera view. If there is something in front of the gun the ray will go through it. A ray only has a collision is the object has a Bounding shape around it. So a ray may go thought objects.

First, we need to store the vector the gun is point to get the direction and start point of the ray:

code format="csharp" firePosition = weaponModel.BonesAbsolute[WEAPON_AIM_BONE].Translation; code

When the player hits the button, the ShootWeapon method is called:

code format="csharp" private void ShootWeapon { code

First, we create our ray from the firePosition.

code format="csharp" float? distance = Terrain.Intersects(ray); code

Next, we will test each enemy still in the enemylist to see if the ray collides with any of the enemy’s Bounding shapes. If we collide we will store the data if it is closer than the terrain distance. In the terrainUnit we will read the data pass it to the enemy class and cause a hit.

code format="csharp" foreach (Enemy enemy in EnemyList) { if (!enemy.IsDead) { numEnemiesAlive++;

float? enemyDistance = enemy.BoxIntersects(ray); if (enemyDistance != null) { if (distance == null || enemyDistance <= distance) { distance = enemyDistance; aimEnemy = enemy; } } } }

weaponTargetPosition = gameLevel.Player.Weapon.FirePosition + Player.Weapon.TargetDirection * 300; } code

After each shot, we test the amount of damage the bullet shot can cause. If the game is expanded to have multiple guns, each gun may have a different damage number. It can easily be stored in the UnitType. Also, the amount of bullets is a gun can hold is stored in the UnitType and we need to minus one from the gun each shot we take.

code format="csharp" bulletDamage = UnitTypes.BulletDamage[(int)weaponType]; bulletsCount = UnitTypes.BulletsCount[(int)weaponType]; maxBullets = bulletsCount; code

Now we have completed the terrain, the enemy, and shooting. We have a semi-operational game. In the future, we still would need to add in a score, life, and some text to provide feedback to the player. It would probably also be a good idea to also have a win or lose. Currently, if you kill all of the enemies, you simply continuing walking around the terrain. It would be good to end the game at some point.