topical media & game development
game-xna-intro-XnaShooter-Game-Unit.cs / cs
// Project: XnaShooter, File: Unit.cs
// Namespace: XnaShooter.Game, Class: Unit
// Path: C:\code\XnaShooter\Game, Author: Abi
// Code lines: 167, Size of file: 4,73 KB
// Creation date: 27.12.2006 06:10
// Last modified: 27.12.2006 15:14
// Generated with Commenter by abi.exDream.com
#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
using XnaShooter.Shaders;
using XnaShooter.Graphics;
using XnaShooter.Helpers;
using XnaShooter.GameScreens;
using XnaShooter.Game;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
#endregion
namespace XnaShooter.Game
{
<summary>
Unit values, used for both the GameAsteroidManager to keep all
enemy units in the current level and in UnitManager for all active
units we are currently rendering.
</summary>
public class Unit
{
#region Variables
<summary>
Unit types
</summary>
public enum UnitTypes
{
Corvette,
SmallTransporter,
Asteroid,
Firebird,
RocketFrigate,
} // enum UnitTypes
static readonly float[] DefaultUnitHitpoints = new float[]
{
// Corvette,
500,
// SmallTransporter,
400,
// Asteroid,
10000,
// Firebird,
850,
// RocketFrigate,
2222,
};
static readonly float[] DefaultCooldownTime = new float[]
{
// Corvette,
250,
// SmallTransporter,
0,
// Asteroid,
0,
// Firebird,
1250,
// RocketFrigate,
1750,
};
//*unused
static readonly float[] DefaultUnitDamage = new float[]
{
// Corvette,
10,
// SmallTransporter,
0,
// Asteroid,
0,
// Firebird,
75,
// RocketFrigate,
125,
};
static readonly float[] DefaultExplosionDamage = new float[]
{
// Corvette,
250,
// SmallTransporter,
150,
// Asteroid,
500,
// Firebird,
350,
// RocketFrigate,
750,
};
//*/
static readonly float[] DefaultMaxSpeed = new float[]
{
// Corvette,
13,//20,//50,
// SmallTransporter,
16,//25,//60,
// Asteroid,
0,//3,
// Firebird,
1.5f,//9,//30,
// RocketFrigate,
-10,//18,
};
<summary>
Unit type, for enemy units this is 3-7 (Kamikaze - Mine).
Links to the unit settings list in UnitManager.
</summary>
public UnitTypes unitType;
<summary>
Life time of this unit in ms, just used for movement pattern.
</summary>
public float lifeTimeMs;
<summary>
Current hitpoints and max hitpoints, which are copied from
unit settings, but are also dependend on the unit level.
</summary>
public float hitpoints, maxHitpoints;
<summary>
Shoot time to determinate when we can shoot again.
</summary>
public float shootTime, cooldownTime;
<summary>
Damage this unit currently does. Copied from unit settings,
but increase as the level advances.
</summary>
public float damage;
<summary>
Explosion damage, used when we hit something. Used for projectiles,
but also for all units in case we collide with them.
</summary>
public float explosionDamage;
<summary>
Current speed of this ship. Limited to maxSpeed.
For our own ship this value isn't really used because
we use the shipMovement vector in SpaceCamera, but for enemy ships
this value is very important to speed up/slow down in the unit ai.
</summary>
public float speed;
<summary>
Max speed for this unit (calculated from unit settings, increased
a little for each level). Any movement is limited by this value.
Units don't have to fly as fast as this value, this is just the limit.
</summary>
public float maxSpeed;
<summary>
Current position of this unit, will be updated each frame.
Absolute position in 3d space.
</summary>
public Vector2 position;
<summary>
Movement pattern for simple AI
</summary>
public enum MovementPattern
{
StraightDown,
GetFasterAndMoveDown,
SinWave1,
SinWave2,
SinWave3,
CosWave1,
CosWave2,
CosWave3,
SweepLeft,
SweepRight,
} // enum MovementPattern
<summary>
Number of possible movement patterns we can choose from.
</summary>
public const int NumOfMovementPatterns = 10;
<summary>
Movement pattern that is currently used by this unit (handles AI movement).
</summary>
public MovementPattern movementPattern = MovementPattern.StraightDown;
#endregion
#region Constructor
<summary>
Create unit of specific type at specific location.
All other parameters (hitpoints, etc.) are set from UnitManager.
</summary>
<param name="setType">Set type
</param>
<param name="setPosition">Set position
</param>
<param name="setMovementPattern">
Movement pattern
</param>
public Unit(UnitTypes setType, Vector2 setPosition,
MovementPattern setMovementPattern)
{
unitType =
setType;
//(Unit.UnitTypes)RandomHelper.GetRandomInt(5);
position = setPosition;
movementPattern = setMovementPattern;
// Don't allow swing left/right for asteroid
if (unitType == UnitTypes.Asteroid)
movementPattern = MovementPattern.StraightDown;
// Recalculate our unit values and reset hitpoints to 100%.
maxHitpoints = hitpoints = DefaultUnitHitpoints[(int)unitType];
cooldownTime = DefaultCooldownTime[(int)unitType];
damage = DefaultUnitDamage[(int)unitType];
explosionDamage = DefaultExplosionDamage[(int)unitType];
maxSpeed = DefaultMaxSpeed[(int)unitType];
shootTime = 0;
lifeTimeMs = 0;
} // Unit(setType, setPosition, setLevel)
#endregion
#region Render
<summary>
Render unit, returns false if we are done with it.
Has to be removed then. Else it updates just position and AI.
</summary>
<returns>True if done, false otherwise
</returns>
public bool Render(Mission mission)
{
#region Update movement with AI
lifeTimeMs += BaseGame.ElapsedTimeThisFrameInMs;
float moveSpeed = BaseGame.MoveFactorPerSecond;
if (Player.GameOver)
moveSpeed = 0;
switch (movementPattern)
{
case MovementPattern.StraightDown:
position += new Vector2(0, -1) * maxSpeed * moveSpeed;
break;
case MovementPattern.GetFasterAndMoveDown:
// Out of visible area? Then keep speed slow and wait.
if (position.Y - Mission.LookAtPosition.Y > 30)
lifeTimeMs = 300;
if (lifeTimeMs < 3000)
speed = lifeTimeMs / 3000;
position += new Vector2(0, -1) * speed * 1.5f * maxSpeed * moveSpeed;
break;
case MovementPattern.SinWave1:
position += new Vector2(
(float)Math.Sin(lifeTimeMs / 759.0f),
-1) * maxSpeed * moveSpeed;
break;
case MovementPattern.SinWave2:
position += new Vector2(
-(float)Math.Sin(lifeTimeMs / 759.0f),
-1) * maxSpeed * moveSpeed;
break;
case MovementPattern.SinWave3:
position += new Vector2(
(float)Math.Sin(lifeTimeMs / 359.0f),
-1) * maxSpeed * moveSpeed;
break;
case MovementPattern.CosWave1:
position += new Vector2(
(float)Math.Cos(lifeTimeMs / 759.0f),
-1) * maxSpeed * moveSpeed;
break;
case MovementPattern.CosWave2:
position += new Vector2(
-(float)Math.Cos(lifeTimeMs / 759.0f),
-1) * maxSpeed * moveSpeed;
break;
case MovementPattern.CosWave3:
position += new Vector2(
(float)Math.Cos(lifeTimeMs / 1759.0f),
-1) * maxSpeed * moveSpeed;
break;
case MovementPattern.SweepLeft:
position += new Vector2(0, -1) * maxSpeed * moveSpeed;
if (lifeTimeMs > 1750)
position += new Vector2(-0.7f, 0) * maxSpeed * moveSpeed;
break;
case MovementPattern.SweepRight:
position += new Vector2(0, -1) * maxSpeed * moveSpeed;
if (lifeTimeMs > 1750)
position += new Vector2(+0.7f, 0) * maxSpeed * moveSpeed;
break;
} // switch(movementPattern)
// Keep in bounds
if (position.X < -Player.MaxXPosition)
position.X = -Player.MaxXPosition;
if (position.X > Player.MaxXPosition)
position.X = Player.MaxXPosition;
#endregion
#region Skip if out of visible range
float distance = Mission.LookAtPosition.Y - position.Y;
const float MaxUnitDistance = 60;
// Remove unit if it is out of visible range!
if (distance > MaxUnitDistance)
return true;
bool visible = distance > -35;
#endregion
#region Render
Vector3 shipPos = new Vector3(position, Mission.AllShipsZHeight);
Mission.ShipModelTypes shipModelType =
unitType == UnitTypes.Corvette ? Mission.ShipModelTypes.Corvette :
unitType == UnitTypes.SmallTransporter ? Mission.ShipModelTypes.SmallTransporter :
unitType == UnitTypes.Firebird ? Mission.ShipModelTypes.Firebird :
unitType == UnitTypes.RocketFrigate ? Mission.ShipModelTypes.RocketFrigate :
Mission.ShipModelTypes.Asteroid;
float shipSize = Mission.ShipModelSize[(int)shipModelType];
//Note: rotation could be implemented here, but game is fine without
float shipRotation = 0;
Matrix rotationMatrix = Matrix.CreateRotationZ(shipRotation);
if (unitType == UnitTypes.Asteroid)
rotationMatrix *=
Matrix.CreateRotationX(position.X / 10 + Player.gameTimeMs / 1539.0f) *
Matrix.CreateRotationY(position.Y / 20 + Player.gameTimeMs / 1839.0f);
mission.AddModelToRender(
mission.shipModels[(int)shipModelType],
Matrix.CreateScale(shipSize) *
rotationMatrix *
Matrix.CreateTranslation(shipPos));
// Add rocket smoke
if (unitType != UnitTypes.Asteroid)
EffectManager.AddRocketOrShipFlareAndSmoke(
shipPos + shipSize *
new Vector3(0, 0.6789f + ((int)unitType > 3 ? 0.25f : 0.0f), 0),
shipSize / 4.0f, 12 * maxSpeed);
#endregion
#region Shooting?
if (Player.GameOver)
return false;
// Peng peng?
bool fireProjectile = false;
if (cooldownTime > 0 &&
visible)
{
// Ready to shoot again?
if (shootTime <= 0)
{
fireProjectile = true;
shootTime += cooldownTime;
} // if (shootTime)
} // if (GameForm.Mouse.LeftButtonPressed)
// Weapon needs cooldown time?
if (shootTime > 0)
shootTime -= BaseGame.ElapsedTimeThisFrameInMs;
if (fireProjectile)
{
Vector3 shootPos = Vector3.Zero;
Vector3 enemyUnitPos = Player.shipPos;
switch (unitType)
{
case UnitTypes.Corvette:
if ((int)(lifeTimeMs / 700) % 3 == 0)
{
// Just shoot straight ahead.
bool hitUnit = Math.Abs(Player.position.X - shipPos.X) < 4.5f &&
Player.shipPos.Y < shipPos.Y;
shootPos = shipPos +
new Vector3(-2.35f, -3.5f, +0.2f);
EffectManager.AddMgEffect(shootPos,
hitUnit ? enemyUnitPos : shootPos + new Vector3(0, -40, 0),
10, 12, hitUnit, false);//shootNum % 2 == 0);
shootPos = shipPos +
new Vector3(+2.35f, -3.5f, +0.2f);
EffectManager.AddMgEffect(shootPos,
hitUnit ? enemyUnitPos : shootPos + new Vector3(0, -40, 0),
10, 13, hitUnit, false);
EffectManager.PlaySoundEffect(
EffectManager.EffectSoundType.MgShootEnemy);
if (hitUnit)
{
Player.health -= damage / 1000.0f;
} // if
} // if
break;
case UnitTypes.Firebird:
shootPos = shipPos + new Vector3(0, -1.5f, 3);
Mission.AddWeaponProjectile(
Projectile.WeaponTypes.Fireball, shootPos, false);
EffectManager.AddFireFlash(shootPos);
EffectManager.PlaySoundEffect(
EffectManager.EffectSoundType.EnemyShoot);
break;
case UnitTypes.RocketFrigate:
shootPos = shipPos + new Vector3(
RandomHelper.GetRandomInt(2) == 0 ? -2.64f : +2.64f, 0, 0);
Mission.AddWeaponProjectile(
Projectile.WeaponTypes.Rocket, shootPos, false);
EffectManager.AddFireFlash(shootPos);
EffectManager.PlaySoundEffect(
EffectManager.EffectSoundType.RocketShoot);
break;
} // switch
} // if
#endregion
#region Explode if out of hitpoints
// Destroy ship and damage player if colliding!
// Near enough to our ship?
Vector2 distVec =
new Vector2(Player.shipPos.X, Player.shipPos.Y) -
new Vector2(position.X, position.Y);
if (distVec.Length() < 4.75f)//5.5f)
{
// Explode and do damage!
EffectManager.AddFlameExplosion(Player.shipPos);
Player.health -= explosionDamage / 1000.0f;
hitpoints = 0;
} // else
if (maxHitpoints > 0 &&
hitpoints <= 0)
{
// Explode and kill unit!
float size = 10 + (int)unitType * 3;
if (unitType == UnitTypes.Asteroid)
size = 12;
EffectManager.AddExplosion(
new Vector3(position, Mission.AllShipsZHeight),
size * 0.425f);
//size * 0.55f);
return true;
} // if (hitpoints.X)
#endregion
// Don't remove unit from units list
return false;
} // Render()
#endregion
#region Unit testing
if DEBUG
delegate void ResetUnitDelegate(MovementPattern setPattern);
<summary>
Test unit AI movement
</summary>
public static void TestUnitAI()
{
Unit testUnit = null;
Mission dummyMission = null;
TestGame.Start("TestUnitAI",
delegate
{
dummyMission = new Mission();
testUnit = new Unit(UnitTypes.Corvette, Vector2.Zero,
MovementPattern.StraightDown);
// Call dummyMission.RenderLandscape once to initialize everything
dummyMission.RenderLevelBackground(0);
// Remove the all enemy units (the start enemies)+ all neutral objects
dummyMission.numOfModelsToRender = 2;
},
delegate
{
BaseGame.Device.Clear(
ClearOptions.DepthBuffer | ClearOptions.Target,
Color.Black, 1.0f, 0);
TextureFont.WriteText(2, 30,
"Press 1-0 to test all available movement patterns");
TextureFont.WriteText(2, 60,
"Press C, S, F, R, A to switch unit type");
TextureFont.WriteText(2, 90,
"Space restarts the current unit");
TextureFont.WriteText(2, 150,
"Unit: "+testUnit.unitType);
TextureFont.WriteText(2, 180,
"Movement AI: " + testUnit.movementPattern);
TextureFont.WriteText(2, 210,
"Position: " + testUnit.position);
TextureFont.WriteText(2, 240,
"Speed: " + testUnit.speed);
TextureFont.WriteText(2, 270,
"LifeTime: " + (int)(testUnit.lifeTimeMs / 10) / 100.0f);
ResetUnitDelegate ResetUnit = delegate(MovementPattern setPattern)
{
testUnit.movementPattern = setPattern;
testUnit.position = new Vector2(
RandomHelper.GetRandomFloat(-20, +20),
Mission.SegmentLength/2);
testUnit.hitpoints = testUnit.maxHitpoints;
testUnit.speed = 0;
testUnit.lifeTimeMs = 0;
};
if (Input.KeyboardKeyJustPressed(Keys.D1))
ResetUnit(MovementPattern.StraightDown);
if (Input.KeyboardKeyJustPressed(Keys.D2))
ResetUnit(MovementPattern.GetFasterAndMoveDown);
if (Input.KeyboardKeyJustPressed(Keys.D3))
ResetUnit(MovementPattern.SinWave1);
if (Input.KeyboardKeyJustPressed(Keys.D4))
ResetUnit(MovementPattern.SinWave2);
if (Input.KeyboardKeyJustPressed(Keys.D5))
ResetUnit(MovementPattern.SinWave3);
if (Input.KeyboardKeyJustPressed(Keys.D6))
ResetUnit(MovementPattern.CosWave1);
if (Input.KeyboardKeyJustPressed(Keys.D7))
ResetUnit(MovementPattern.CosWave2);
if (Input.KeyboardKeyJustPressed(Keys.D8))
ResetUnit(MovementPattern.CosWave3);
if (Input.KeyboardKeyJustPressed(Keys.D9))
ResetUnit(MovementPattern.SweepLeft);
if (Input.KeyboardKeyJustPressed(Keys.D0))
ResetUnit(MovementPattern.SweepRight);
if (Input.KeyboardKeyJustPressed(Keys.Space))
ResetUnit(testUnit.movementPattern);
if (Input.KeyboardKeyJustPressed(Keys.C))
testUnit.unitType = UnitTypes.Corvette;
if (Input.KeyboardKeyJustPressed(Keys.S))
testUnit.unitType = UnitTypes.SmallTransporter;
if (Input.KeyboardKeyJustPressed(Keys.F))
testUnit.unitType = UnitTypes.Firebird;
if (Input.KeyboardKeyJustPressed(Keys.R))
testUnit.unitType = UnitTypes.RocketFrigate;
if (Input.KeyboardKeyJustPressed(Keys.A))
testUnit.unitType = UnitTypes.Asteroid;
// Update and render unit
if (testUnit.Render(dummyMission))
// Restart unit if it was removed because it was too far down
ResetUnit(testUnit.movementPattern);
// Render all models the normal way
for (int num = 0; num < dummyMission.numOfModelsToRender; num++)
dummyMission.modelsToRender[num].model.Render(
dummyMission.modelsToRender[num].matrix);
BaseGame.MeshRenderManager.Render();
// Restore number of units as before.
// Our test unit will be added next frame again.
dummyMission.numOfModelsToRender = 2;
// Show all effects (unit smoke, etc.)
BaseGame.effectManager.HandleAllEffects();
});
} // TestUnitAI()
endif
#endregion
} // class Unit
} // namespace XnaShooter.Game
(C) Æliens
20/2/2008
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.