123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878 |
- #region File Description
- //-----------------------------------------------------------------------------
- // Ship.cs
- //
- // Microsoft XNA Community Game Platform
- // Copyright (C) Microsoft Corporation. All rights reserved.
- //-----------------------------------------------------------------------------
- #endregion
- #region Using Statements
- using System;
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Graphics;
- using Microsoft.Xna.Framework.Input;
- using Microsoft.Xna.Framework.Net;
- using Microsoft.Xna.Framework.Content;
- #endregion
- namespace NetRumble
- {
- /// <summary>
- /// The ship, which is the primary playing-piece in the game.
- /// </summary>
- public class Ship : GameplayObject
- {
- #region Constants
- /// <summary>
- /// The full speed possible for the ship.
- /// </summary>
- const float fullSpeed = 320f;
- /// <summary>
- /// The amount of drag applied to velocity per second,
- /// as a percentage of velocity.
- /// </summary>
- const float dragPerSecond = 0.9f;
- /// <summary>
- /// The amount that the right-stick must be pressed to fire, squared so that
- /// we can use LengthSquared instead of Length, which has a square-root in it.
- /// </summary>
- const float fireThresholdSquared = 0.25f;
- /// <summary>
- /// The number of radians that the ship can turn in a second at full left-stick.
- /// </summary>
- const float rotationRadiansPerSecond = 6f;
- /// <summary>
- /// The maximum length of the velocity vector on a ship.
- /// </summary>
- const float velocityMaximum = 320f;
- /// <summary>
- /// The maximum strength of the shield.
- /// </summary>
- const float shieldMaximum = 100f;
- /// <summary>
- /// The maximum opacity for the shield, when it's fully recharged.
- /// </summary>
- const float shieldAlphaMaximum = 150f;
- /// <summary>
- /// How much the shield recharges per second.
- /// </summary>
- const float shieldRechargePerSecond = 50f;
- /// <summary>
- /// The duration of the shield-recharge timer when the ship is hit.
- /// </summary>
- const float shieldRechargeTimerMaximum = 2.5f;
- /// <summary>
- /// The base scale for the shield, compared to the size of the ship.
- /// </summary>
- const float shieldScaleBase = 1.2f;
- /// <summary>
- /// The amplitude of the shield pulse
- /// </summary>
- const float shieldPulseAmplitude = 0.15f;
- /// <summary>
- /// The rate of the shield pulse.
- /// </summary>
- const float shieldPulseRate = 0.2f;
- /// <summary>
- /// The maximum value of the "safe" timer.
- /// </summary>
- const float safeTimerMaximum = 4f;
- /// <summary>
- /// The maximum amount of life that a ship can have.
- /// </summary>
- const float lifeMaximum = 25f;
- /// <summary>
- /// The value of the spawn timer set when the ship dies.
- /// </summary>
- const float respawnTimerOnDeath = 5f;
- /// <summary>
- /// The number of variations in textures for ships.
- /// </summary>
- const int variations = 4;
- #endregion
- #region Static Graphics Data
- /// <summary>
- /// The primary ship textures.
- /// </summary>
- private static Texture2D[] primaryTextures = new Texture2D[variations];
- /// <summary>
- /// The overlay ship textures, which get tinted.
- /// </summary>
- private static Texture2D[] overlayTextures = new Texture2D[variations];
- /// <summary>
- /// The ship shield texture.
- /// </summary>
- private static Texture2D shieldTexture;
- /// <summary>
- /// The colors used for each ship.
- /// </summary>
- public static readonly Color[] ShipColors =
- {
- Color.Lime, Color.CornflowerBlue, Color.Fuchsia,
- Color.Red, Color.LightSeaGreen, Color.LightGray,
- Color.Gold, Color.ForestGreen, Color.Beige,
- Color.LightPink, Color.Lavender, Color.OrangeRed,
- Color.Plum, Color.Tan, Color.YellowGreen,
- Color.Azure, Color.Aqua, Color.Salmon
- };
- /// <summary>
- /// The particle-effect manager which recieves the effects from ships.
- /// </summary>
- public static ParticleEffectManager ParticleEffectManager;
- #endregion
- #region Gameplay Data
- /// <summary>
- /// The score for this ship.
- /// </summary>
- private int score = 0;
- public int Score
- {
- get { return score; }
- set { score = value; }
- }
- /// <summary>
- /// The amount of damage that the ship can take before exploding.
- /// </summary>
- private float life;
- public float Life
- {
- get { return life; }
- set { life = value; }
- }
- /// <summary>
- /// The ship's primary weapon.
- /// </summary>
- private Weapon weapon;
- public Weapon Weapon
- {
- get { return weapon; }
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException("value");
- }
- weapon = value;
- }
- }
- /// <summary>
- /// The ship's mine-laying weapon.
- /// </summary>
- private MineWeapon mineWeapon;
- /// <summary>
- /// All of the projectiles fired by this ship.
- /// </summary>
- private BatchRemovalCollection<Projectile> projectiles =
- new BatchRemovalCollection<Projectile>();
- public BatchRemovalCollection<Projectile> Projectiles
- {
- get { return projectiles; }
- }
- /// <summary>
- /// The strength of the shield.
- /// </summary>
- private float shield;
- public float Shield
- {
- get { return shield; }
- set { shield = value; }
- }
- /// <summary>
- /// Timer for how long until the shield starts to recharge.
- /// </summary>
- private float shieldRechargeTimer;
- /// <summary>
- /// Timer for how long the player is safe after spawning.
- /// </summary>
- private float safeTimer;
- public bool Safe
- {
- get { return (safeTimer > 0f); }
- set
- {
- if (value)
- {
- safeTimer = safeTimerMaximum;
- }
- else
- {
- safeTimer = 0f;
- }
- }
- }
- /// <summary>
- /// The tint of the ship.
- /// </summary>
- private Color color = Color.White;
- public Color Color
- {
- get { return color; }
- set { color = value; }
- }
-
- /// <summary>
- /// The amount of time left before respawning the ship.
- /// </summary>
- private float respawnTimer;
- public float RespawnTimer
- {
- get { return respawnTimer; }
- set { respawnTimer = value; }
- }
- /// <summary>
- /// The last object to damage the ship.
- /// </summary>
- private GameplayObject lastDamagedBy = null;
- public GameplayObject LastDamagedBy
- {
- get { return lastDamagedBy; }
- }
- #endregion
- #region Input Data
- /// <summary>
- /// The current player input for the ship.
- /// </summary>
- private ShipInput shipInput;
- public ShipInput ShipInput
- {
- get { return shipInput; }
- set { shipInput = value; }
- }
- #endregion
- #region Graphics Data
- /// <summary>
- /// The variation of this ship.
- /// </summary>
- private int variation = 0;
- public int Variation
- {
- get { return variation; }
- set
- {
- if ((value < 0) || (value >= variations))
- {
- throw new ArgumentOutOfRangeException("value");
- }
- variation = value;
- }
- }
- /// <summary>
- /// The time accumulator for the shield pulse.
- /// </summary>
- private float shieldPulseTime = 0f;
- #endregion
- #region Initialization Methods
- /// <summary>
- /// Construct a new ship.
- /// </summary>
- public Ship()
- : base()
- {
- // set the collision data
- this.radius = 24f;
- this.mass = 32f;
- }
- /// <summary>
- /// Initialize a ship to its default gameplay states.
- /// </summary>
- public override void Initialize()
- {
- if (!active)
- {
- // set the initial gameplay data values
- shipInput = ShipInput.Empty;
- rotation = 0f;
- velocity = Vector2.Zero;
- life = lifeMaximum;
- shield = shieldMaximum;
- shieldRechargeTimer = 0f;
- safeTimer = safeTimerMaximum;
- weapon = new LaserWeapon(this);
- mineWeapon = new MineWeapon(this);
- // play the player-spawn sound
- AudioManager.PlaySoundEffect("player_spawn");
- // add the ship-spawn particle effect
- if (ParticleEffectManager != null)
- {
- ParticleEffectManager.SpawnEffect(ParticleEffectType.ShipSpawn,
- this);
- }
- // clear out the projectiles list
- projectiles.Clear();
- }
- base.Initialize();
- }
- #endregion
- #region Updating Methods
- /// <summary>
- /// Update the ship.
- /// </summary>
- /// <param name="elapsedTime">The amount of elapsed time, in seconds.</param>
- public override void Update(float elapsedTime)
- {
- // calculate the current forward vector
- Vector2 forward = new Vector2((float)Math.Sin(Rotation),
- -(float)Math.Cos(Rotation));
- Vector2 right = new Vector2(-forward.Y, forward.X);
- // calculate the new forward vector with the left stick
- shipInput.LeftStick.Y *= -1f;
- if (shipInput.LeftStick.LengthSquared() > 0f)
- {
- // change the direction
- Vector2 wantedForward = Vector2.Normalize(shipInput.LeftStick);
- float angleDiff = (float)Math.Acos(
- Vector2.Dot(wantedForward, forward));
- float facing = (Vector2.Dot(wantedForward, right) > 0f) ?
- 1f : -1f;
- if (angleDiff > 0.001f)
- {
- Rotation += Math.Min(angleDiff, facing * elapsedTime *
- rotationRadiansPerSecond);
- }
- // add velocity
- Velocity += shipInput.LeftStick * (elapsedTime * fullSpeed);
- if (Velocity.Length() > velocityMaximum)
- {
- Velocity = Vector2.Normalize(Velocity) *
- velocityMaximum;
- }
- }
- shipInput.LeftStick = Vector2.Zero;
- // apply drag to the velocity
- Velocity -= Velocity * (elapsedTime * dragPerSecond);
- if (Velocity.LengthSquared() <= 0f)
- {
- Velocity = Vector2.Zero;
- }
- // check for firing with the right stick
- shipInput.RightStick.Y *= -1f;
- if (shipInput.RightStick.LengthSquared() > fireThresholdSquared)
- {
- weapon.Fire(Vector2.Normalize(shipInput.RightStick));
- }
- shipInput.RightStick = Vector2.Zero;
- // check for laying mines
- if (shipInput.MineFired)
- {
- // fire behind the ship
- mineWeapon.Fire(-forward);
- }
- shipInput.MineFired = false;
-
- // recharge the shields
- if (shieldRechargeTimer > 0f)
- {
- shieldRechargeTimer = Math.Max(shieldRechargeTimer - elapsedTime,
- 0f);
- }
- if (shieldRechargeTimer <= 0f)
- {
- if (shield < shieldMaximum)
- {
- shield = Math.Min(shieldMaximum,
- shield + shieldRechargePerSecond * elapsedTime);
- }
- }
- // update the radius based on the shield
- radius = (shield > 0f) ? 24f : 20f;
- // update the weapons
- if (weapon != null)
- {
- weapon.Update(elapsedTime);
- }
- if (mineWeapon != null)
- {
- mineWeapon.Update(elapsedTime);
- }
- // decrement the safe timer
- if (safeTimer > 0f)
- {
- safeTimer = Math.Max(safeTimer - elapsedTime, 0f);
- }
- // update the projectiles
- foreach (Projectile projectile in projectiles)
- {
- if (projectile.Active)
- {
- projectile.Update(elapsedTime);
- }
- else
- {
- projectiles.QueuePendingRemoval(projectile);
- }
- }
- projectiles.ApplyPendingRemovals();
- base.Update(elapsedTime);
- }
- #endregion
- #region Drawing Methods
- /// <summary>
- /// Draw the ship.
- /// </summary>
- /// <param name="elapsedTime">The amount of elapsed time, in seconds.</param>
- /// <param name="spriteBatch">The SpriteBatch object used to draw.</param>
- public void Draw(float elapsedTime, SpriteBatch spriteBatch)
- {
- // draw the uniform section of the ship
- base.Draw(elapsedTime, spriteBatch, primaryTextures[variation],
- null, Color.White);
- // draw the tinted section of the ship
- base.Draw(elapsedTime, spriteBatch, overlayTextures[variation],
- null, color);
- if (shield > 0)
- {
- // calculate the current shield radius
- float oldRadius = radius;
- shieldPulseTime += elapsedTime;
- radius *= shieldScaleBase + shieldPulseAmplitude *
- (float)Math.Sin(shieldPulseTime / shieldPulseRate);
- // draw the shield
- base.Draw(elapsedTime, spriteBatch, shieldTexture, null,
- new Color(color.R, color.G, color.B,
- (byte)Math.Floor(shieldAlphaMaximum * shield / shieldMaximum)));
- // return to the old radius
- radius = oldRadius;
- }
- // draw the projectiles
- foreach (Projectile projectile in projectiles)
- {
- projectile.Draw(elapsedTime, spriteBatch);
- }
- }
- #endregion
- #region Interaction Methods
- /// <summary>
- /// Damage this ship by the amount provided.
- /// </summary>
- /// <remarks>
- /// This function is provided in lieu of a Life mutation property to allow
- /// classes of objects to restrict which kinds of objects may damage them,
- /// and under what circumstances they may be damaged.
- /// </remarks>
- /// <param name="source">The GameplayObject responsible for the damage.</param>
- /// <param name="damageAmount">The amount of damage.</param>
- /// <returns>If true, this object was damaged.</returns>
- public override bool Damage(GameplayObject source, float damageAmount)
- {
- // if the safe timer hasn't yet gone off, then the ship can't be hurt
- if ((safeTimer > 0f) || (damageAmount <= 0f))
- {
- return false;
- }
- // once you're hit, the shield-recharge timer starts over
- shieldRechargeTimer = 2.5f;
- // damage the shield first, then life
- if (shield <= 0f)
- {
- life -= damageAmount;
- }
- else
- {
- shield -= damageAmount;
- if (shield < 0f)
- {
- // shield has the overflow value as a negative value, just add it
- life += shield;
- shield = 0f;
- }
- }
- Projectile sourceAsProjectile = source as Projectile;
- if (sourceAsProjectile != null)
- {
- lastDamagedBy = sourceAsProjectile.Owner;
- }
- else
- {
- lastDamagedBy = source;
- }
- return true;
- }
- /// <summary>
- /// Kills this ship, in response to the given GameplayObject.
- /// </summary>
- /// <param name="source">The GameplayObject responsible for the kill.</param>
- /// <param name="cleanupOnly">
- /// If true, the object dies without any further effects.
- /// </param>
- public override void Die(GameplayObject source, bool cleanupOnly)
- {
- if (active)
- {
- if (!cleanupOnly)
- {
- // update the score
- Ship ship = source as Ship;
- if (ship == null)
- {
- Projectile projectile = source as Projectile;
- if (projectile != null)
- {
- ship = projectile.Owner;
- }
- }
- if (ship != null)
- {
- if (ship == this)
- {
- // reduce the score, since i blew myself up
- ship.Score--;
- }
- else
- {
- // add score to the ship who shot this object
- ship.Score++;
- }
- }
- else
- {
- // if it wasn't a ship, then this object loses score
- this.Score--;
- }
- // play the player-death sound
- AudioManager.PlaySoundEffect("explosion_shockwave");
- AudioManager.PlaySoundEffect("explosion_large");
- // display the ship explosion
- if (ParticleEffectManager != null)
- {
- ParticleEffectManager.SpawnEffect(
- ParticleEffectType.ShipExplosion, Position);
- }
- }
- // clear out the projectiles list
- foreach (Projectile projectile in projectiles)
- {
- projectile.Die(null, true);
- }
- projectiles.Clear();
- // set the respawn timer
- respawnTimer = respawnTimerOnDeath;
- }
- base.Die(source, cleanupOnly);
- }
- #endregion
- #region Static Graphics Methods
- /// <summary>
- /// Load all of the static graphics content for this class.
- /// </summary>
- /// <param name="contentManager">The content manager to load with.</param>
- public static void LoadContent(ContentManager contentManager)
- {
- // safety-check the parameters
- if (contentManager == null)
- {
- throw new ArgumentNullException("contentManager");
- }
- // load each ship's texture
- for (int i = 0; i < variations; i++)
- {
- primaryTextures[i] = contentManager.Load<Texture2D>(
- "Textures/ship" + i.ToString());
- overlayTextures[i] = contentManager.Load<Texture2D>(
- "Textures/ship" + i.ToString() + "Overlay");
- }
- // load the shield texture
- shieldTexture = contentManager.Load<Texture2D>("Textures/shipShields");
- }
- /// <summary>
- /// Unload all of the static graphics content for this class.
- /// </summary>
- public static void UnloadContent()
- {
- for (int i = 0; i < variations; i++)
- {
- primaryTextures[i] = overlayTextures[i] = null;
- }
- shieldTexture = null;
- }
- /// <summary>
- /// Determines if a color index is unique.
- /// </summary>
- /// <param name="networkGamer">The gamer with the color index.</param>
- /// <param name="networkSession">The session the player belongs to.</param>
- /// <returns>If true, the gamer has a unique color ID.</returns>
- public static bool HasUniqueColorIndex(NetworkGamer networkGamer,
- NetworkSession networkSession)
- {
- // safety-check the parameters
- if (networkGamer == null)
- {
- throw new ArgumentNullException("networkGamer");
- }
- if (networkSession == null)
- {
- throw new ArgumentNullException("networkSession");
- }
- PlayerData playerData = networkGamer.Tag as PlayerData;
- if (playerData == null)
- {
- throw new ArgumentNullException("networkGamer.Tag as PlayerData");
- }
- // search for a match
- foreach (NetworkGamer gamer in networkSession.AllGamers)
- {
- if (gamer == networkGamer)
- {
- continue;
- }
- PlayerData gamerData = gamer.Tag as PlayerData;
- if ((gamerData != null) &&
- (gamerData.ShipColor == playerData.ShipColor))
- {
- return false;
- }
- }
- return true;
- }
- /// <summary>
- /// Find the next unique color index among the players.
- /// </summary>
- /// <param name="currentColorIndex">The current color index.</param>
- /// <param name="networkSession">The network session.</param>
- /// <returns>The next unique color index.</returns>
- public static byte GetNextUniqueColorIndex(byte currentColorIndex,
- NetworkSession networkSession)
- {
- // safety-check the parameters
- if ((currentColorIndex < 0) || (currentColorIndex >= ShipColors.Length))
- {
- throw new ArgumentOutOfRangeException("currentColorIndex");
- }
- if (networkSession == null)
- {
- throw new ArgumentNullException("networkSession");
- }
- if (networkSession.AllGamers.Count > ShipColors.Length)
- {
- throw new InvalidOperationException(
- "There are more gamers than there are colors.");
- }
- // if there are as many gamers as there are colors, then we can't change
- if (networkSession.AllGamers.Count == ShipColors.Length)
- {
- return currentColorIndex;
- }
- bool colorFound;
- byte newColorIndex = currentColorIndex;
- do
- {
- newColorIndex++;
- if (newColorIndex >= ShipColors.Length)
- {
- newColorIndex = 0;
- }
- colorFound = false;
- foreach (NetworkGamer networkGamer in networkSession.AllGamers)
- {
- PlayerData playerData = networkGamer.Tag as PlayerData;
- if ((playerData != null) && (playerData.ShipColor == newColorIndex))
- {
- colorFound = true;
- break;
- }
- }
- }
- while (colorFound && (newColorIndex != currentColorIndex));
- return newColorIndex;
- }
- /// <summary>
- /// Find the previous unique color index among the players.
- /// </summary>
- /// <param name="currentColorIndex">The current color index.</param>
- /// <param name="networkSession">The network session..</param>
- /// <returns>The previous unique color index.</returns>
- public static byte GetPreviousUniqueColorIndex(byte currentColorIndex,
- NetworkSession networkSession)
- {
- // safety-check the parameters
- if ((currentColorIndex < 0) || (currentColorIndex >= ShipColors.Length))
- {
- throw new ArgumentOutOfRangeException("currentColorIndex");
- }
- if (networkSession == null)
- {
- throw new ArgumentNullException("networkSession");
- }
- if (networkSession.AllGamers.Count > ShipColors.Length)
- {
- throw new InvalidOperationException(
- "There are more gamers than there are colors.");
- }
- // if there are as many gamers as there are colors, then we can't change
- if (networkSession.AllGamers.Count == ShipColors.Length)
- {
- return currentColorIndex;
- }
- bool colorFound;
- byte newColorIndex = currentColorIndex;
- do
- {
- if (newColorIndex == 0)
- {
- newColorIndex = (byte)(ShipColors.Length - 1);
- }
- else
- {
- newColorIndex--;
- }
- colorFound = false;
- foreach (NetworkGamer networkGamer in networkSession.AllGamers)
- {
- PlayerData playerData = networkGamer.Tag as PlayerData;
- if ((playerData != null) && (playerData.ShipColor == newColorIndex))
- {
- colorFound = true;
- break;
- }
- }
- }
- while (colorFound && (newColorIndex != currentColorIndex));
- return newColorIndex;
- }
- /// <summary>
- /// The number of variations in ships.
- /// </summary>
- public static int Variations
- {
- get { return variations; }
- }
- #endregion
- }
- }
|