// 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
{
///
/// 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.
///
public class Unit
{
#region Variables
///
/// Unit types
///
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,
};
///
/// Unit type, for enemy units this is 3-7 (Kamikaze - Mine).
/// Links to the unit settings list in UnitManager.
///
public UnitTypes unitType;
///
/// Life time of this unit in ms, just used for movement pattern.
///
public float lifeTimeMs;
///
/// Current hitpoints and max hitpoints, which are copied from
/// unit settings, but are also dependend on the unit level.
///
public float hitpoints, maxHitpoints;
///
/// Shoot time to determinate when we can shoot again.
///
public float shootTime, cooldownTime;
///
/// Damage this unit currently does. Copied from unit settings,
/// but increase as the level advances.
///
public float damage;
///
/// Explosion damage, used when we hit something. Used for projectiles,
/// but also for all units in case we collide with them.
///
public float explosionDamage;
///
/// 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.
///
public float speed;
///
/// 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.
///
public float maxSpeed;
///
/// Current position of this unit, will be updated each frame.
/// Absolute position in 3d space.
///
public Vector2 position;
///
/// Movement pattern for simple AI
///
public enum MovementPattern
{
StraightDown,
GetFasterAndMoveDown,
SinWave1,
SinWave2,
SinWave3,
CosWave1,
CosWave2,
CosWave3,
SweepLeft,
SweepRight,
} // enum MovementPattern
///
/// Number of possible movement patterns we can choose from.
///
public const int NumOfMovementPatterns = 10;
///
/// Movement pattern that is currently used by this unit (handles AI movement).
///
public MovementPattern movementPattern = MovementPattern.StraightDown;
#endregion
#region Constructor
///
/// Create unit of specific type at specific location.
/// All other parameters (hitpoints, etc.) are set from UnitManager.
///
/// Set type
/// Set position
/// Movement pattern
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
///
/// Render unit, returns false if we are done with it.
/// Has to be removed then. Else it updates just position and AI.
///
/// True if done, false otherwise
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);
///
/// Test unit AI movement
///
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