/// <summary> /// Defines the interaction between this power-up and a target actor /// when they touch. /// </summary> /// <param name="target">The actor that is touching this object.</param> /// <returns>True if the objects meaningfully interacted.</returns> public override bool Touch(Actor target) { // if we hit a ship, give it the weapon Ship ship = target as Ship; if (ship != null) { ship.SetWeapon(new RocketWeapon(ship)); } return base.Touch(target); }
/// <summary> /// Damages all actors in a radius around the mine. /// </summary> /// <param name="touchedActor">The actor that was originally hit.</param> public override void Explode(Actor touchedActor) { // play the explosion cue world.AudioManager.PlayCue("explosionLarge"); // add a double-particle system effect world.ParticleSystems.Add(new ParticleSystem(this.position, Vector2.Zero, 64, 32f, 64f, 3f, 0.05f, explosionColors)); world.ParticleSystems.Add(new ParticleSystem(this.position, Vector2.Zero, 16, 128f, 256f, 4f, 0.1f, explosionColors)); base.Explode(touchedActor); }
/// <summary> /// Defines the interaction between this projectile and a target actor /// when they touch. /// </summary> /// <param name="target">The actor that is touching this object.</param> /// <returns>True if the objects meaningfully interacted.</returns> public override bool Touch(Actor target) { // add a particle effect if we touched anything if (base.Touch(target)) { // make the particle effect slightly more significant if it was a ship if (target is Ship) { world.ParticleSystems.Add(new ParticleSystem(this.position, Vector2.Zero, 16, 32f, 64f, 1f, 0.1f, explosionColors)); } else { world.ParticleSystems.Add(new ParticleSystem(this.position, Vector2.Zero, 4, 32f, 64f, 1f, 0.05f, explosionColors)); } return true; } return false; }
/// <summary> /// Damages all actors in a radius around the rocket. /// </summary> /// <param name="touchedActor">The actor that was originally hit.</param> public override void Explode(Actor touchedActor) { // stop the rocket-flying cue if (rocketCue != null) { rocketCue.Stop(AudioStopOptions.Immediate); rocketCue.Dispose(); rocketCue = null; } // play the explosion cue world.AudioManager.PlayCue("explosionMedium"); // add a double-particle system effect world.ParticleSystems.Add(new ParticleSystem(this.position, Vector2.Zero, 64, 32f, 64f, 3f, 0.05f, explosionColors)); world.ParticleSystems.Add(new ParticleSystem(this.position, Vector2.Zero, 16, 128f, 256f, 4f, 0.1f, explosionColors)); base.Explode(touchedActor); }
/// <summary> /// Defines the interaction between this actor and a target actor /// when they touch. /// </summary> /// <param name="target">The actor that is touching this object.</param> /// <returns>True if the objects meaningfully interacted.</returns> public virtual bool Touch(Actor target) { return true; }
/// <summary> /// Kills this actor, in response to the given actor. /// </summary> /// <param name="source">The actor responsible for the kill.</param> public virtual void Die(Actor source) { if (dead == false) { // arrrggghhhh dead = true; // remove this actor from the world world.Actors.Garbage.Add(this); } }
/// <summary> /// Damage this actor 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 actor responsible for the damage.</param> /// <param name="damageAmount">The amount of damage.</param> /// <returns>If true, this object was damaged.</returns> public virtual bool Damage(Actor source, float damageAmount) { // reduce life by the given amound life -= damageAmount; // if life had gone below 0, then we're dead // -- 0 health actors are destroyed by any damage if (life < 0f) { Die(source); } return true; }
/// <summary> /// Defines the interaction between this projectile and a target actor /// when they touch. /// </summary> /// <param name="target">The actor that is touching this object.</param> /// <returns>True if the objects meaningfully interacted.</returns> public override bool Touch(Actor target) { // check the target, if we have one if (target != null) { // don't bother hitting any power-ups if (target is PowerUp) { return false; } // don't hit the owner if the damageOwner flag isn't set if ((target == owner) && (this.damageOwner == false)) { return false; } // don't hit other projectiles from the same ship Projectile projectile = target as Projectile; if ((projectile != null) && (projectile.Owner == this.Owner)) { return false; } // damage the target target.Damage(this, this.damageAmount); } // either we hit something or the target is null - in either case, die Die(target); return base.Touch(target); }
/// <summary> /// Find a valid point for the actor to spawn. /// </summary> /// <param name="actor">The actor to find a location for.</param> /// <remarks>This query is not bounded, which would be needed in a more complex /// game with a likelihood of no valid spawn locations.</remarks> /// <returns>A valid location for the user to spawn.</returns> public Vector2 FindSpawnPoint(Actor actor) { if (actor == null) { throw new ArgumentNullException("actor"); } Vector2 spawnPoint; float radius = actor.Radius; // fudge the radius slightly so we're not right on top of another actor if (actor is Ship) { radius *= 2f; } else { radius *= 1.1f; } radius = (float)Math.Ceiling(radius); Vector2 spawnMinimum = new Vector2( safeDimensions.X + radius, safeDimensions.Y + radius); Vector2 spawnDimensions = new Vector2( (float)Math.Floor(safeDimensions.Width - 2f * radius), (float)Math.Floor(safeDimensions.Height - 2f * radius)); Vector2 spawnMaximum = spawnMinimum + spawnDimensions; Collision.CircleLineCollisionResult result = new Collision.CircleLineCollisionResult(); bool valid = true; while (true) { valid = true; // generate a new spawn point spawnPoint = new Vector2( spawnMinimum.X + spawnDimensions.X * (float)random.NextDouble(), spawnMinimum.Y + spawnDimensions.Y * (float)random.NextDouble()); if ((spawnPoint.X < spawnMinimum.X) || (spawnPoint.Y < spawnMinimum.Y) || (spawnPoint.X > spawnMaximum.X) || (spawnPoint.Y > spawnMaximum.Y)) { continue; } // if we don't collide, then one is good enough if (actor.Collidable == false) { break; } // check against the walls if (valid == true) { for (int wall = 0; wall < walls.Length / 2; wall++) { if (Collision.CircleLineCollide(spawnPoint, radius, walls[wall * 2], walls[wall * 2 + 1], ref result)) { valid = false; break; } } } // check against all other actors if (valid == true) { foreach (Actor checkActor in actors) { if ((actor == checkActor) || checkActor.Dead) { continue; } if (Collision.CircleCircleIntersect(checkActor.Position, checkActor.Radius, spawnPoint, radius)) { valid = false; break; } } } // if we have gotten this far, then the spawn point is good if (valid == true) { break; } } return spawnPoint; }
/// <summary> /// Kills this ship, in response to the given actor. /// </summary> /// <param name="source">The actor responsible for the kill.</param> public override void Die(Actor source) { if (dead == false) { // hit the gamepad vibration motors FireGamepadMotors(0.75f, 0.25f); // play several explosion cues world.AudioManager.PlayCue("playerDeath"); // add several particle systems for effect world.ParticleSystems.Add(new ParticleSystem(this.position, Vector2.Zero, 128, 64f, 256f, 3f, 0.05f, explosionColors)); world.ParticleSystems.Add(new ParticleSystem(this.position, Vector2.Zero, 64, 256f, 1024f, 3f, 0.05f, explosionColors)); // reset the respawning timer respawnTimer = respawnTimerOnDeath; // change 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--; } // ships should not be added to the garbage list, so just set dead dead = true; } }
/// <summary> /// Damage this asteroid 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 actor 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(Actor source, float damageAmount) { // nothing hurst asteroids, nothing! return false; }
/// <summary> /// Defines the interaction between the asteroid and a target actor /// when they touch. /// </summary> /// <param name="target">The actor that is touching this object.</param> /// <returns>True if the objects meaningfully interacted.</returns> public override bool Touch(Actor target) { // if the asteroid has touched a player, then damage it Ship player = target as Ship; if (player != null) { // calculate damage as a function of how much the two actor's // velocities were going towards one another Vector2 playerAsteroidVector = Vector2.Normalize(this.position - player.Position); float rammingSpeed = Vector2.Dot(playerAsteroidVector, player.Velocity) - Vector2.Dot(playerAsteroidVector, this.velocity); player.Damage(this, this.mass * rammingSpeed * damageScalar); } // if the asteroid didn't hit a projectile, play the asteroid-touch cue if ((target is Projectile) == false) { this.world.AudioManager.PlayCue("asteroidTouch"); } return base.Touch(target); }
/// <summary> /// Damage this power-up by the amount provided. /// </summary> /// <remarks> /// Power-ups cannot be damaged. /// </remarks> /// <param name="source">The actor 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(Actor source, float damageAmount) { return false; }
/// <summary> /// Defines the interaction between this power-up and a target actor /// when they touch. /// </summary> /// <param name="target">The actor that is touching this object.</param> /// <returns>True if the objects meaningfully interacted.</returns> public override bool Touch(Actor target) { // if it touched a ship, then create a particle system and play a sound Ship ship = target as Ship; if (ship != null) { // tickle the ship's vibration motors ship.FireGamepadMotors(0f, 0.25f); // add a particle system for effect world.ParticleSystems.Add(new ParticleSystem(this.Position, Vector2.Zero, 24, 32f, 64f, 2f, 0.1f, particleColors)); // play the "power-up picked up" cue world.AudioManager.PlayCue("powerUpTouch"); // kill the power-up Die(target); return false; } return base.Touch(target); }
/// <summary> /// Move the given actor by the given movement, colliding and adjusting /// as necessary. /// </summary> /// <param name="actor">The actor who is moving.</param> /// <param name="movement">The desired movement vector for this update.</param> /// <returns>The movement vector after considering all collisions.</returns> private Vector2 MoveAndCollide(Actor actor, Vector2 movement) { if (actor == null) { throw new ArgumentNullException("actor"); } // make sure we care about where this actor goes if (actor.Dead || (actor.Collidable == false)) { return movement; } // make sure the movement is significant if (movement.LengthSquared() <= 0f) { return movement; } // generate the list of collisions Collide(actor, movement); // determine if we had any collisions if (collisionResults.Count > 0) { collisionResults.Sort(CollisionResult.Compare); foreach (CollisionResult collision in collisionResults) { // let the two actors touch each other, and see what happens if (actor.Touch(collision.Actor) && collision.Actor.Touch(actor)) { actor.CollidedThisFrame = collision.Actor.CollidedThisFrame = true; // they should react to the other, even if they just died AdjustVelocities(actor, collision.Actor); return Vector2.Zero; } } } return movement; }
/// <summary> /// Adjust the velocities of the two actors as if they have collided, /// distributing their velocities according to their masses. /// </summary> /// <param name="actor1">The first actor.</param> /// <param name="actor2">The second actor.</param> private static void AdjustVelocities(Actor actor1, Actor actor2) { // don't adjust velocities if at least one has negative mass if ((actor1.Mass <= 0f) || (actor2.Mass <= 0f)) { return; } // determine the vectors normal and tangent to the collision Vector2 collisionNormal = Vector2.Normalize( actor2.Position - actor1.Position); Vector2 collisionTangent = new Vector2( -collisionNormal.Y, collisionNormal.X); // determine the velocity components along the normal and tangent vectors float velocityNormal1 = Vector2.Dot(actor1.Velocity, collisionNormal); float velocityTangent1 = Vector2.Dot(actor1.Velocity, collisionTangent); float velocityNormal2 = Vector2.Dot(actor2.Velocity, collisionNormal); float velocityTangent2 = Vector2.Dot(actor2.Velocity, collisionTangent); // determine the new velocities along the normal float velocityNormal1New = ((velocityNormal1 * (actor1.Mass - actor2.Mass)) + (2f * actor2.Mass * velocityNormal2)) / (actor1.Mass + actor2.Mass); float velocityNormal2New = ((velocityNormal2 * (actor2.Mass - actor1.Mass)) + (2f * actor1.Mass * velocityNormal1)) / (actor1.Mass + actor2.Mass); // determine the new total velocities actor1.Velocity = (velocityNormal1New * collisionNormal) + (velocityTangent1 * collisionTangent); actor2.Velocity = (velocityNormal2New * collisionNormal) + (velocityTangent2 * collisionTangent); }
/// <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 actor 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(Actor source, float damageAmount) { // if the safe timer hasn't yet gone off, then the ship can't be hurt if (safeTimer > 0f) { return false; } // tickle the gamepad vibration motors FireGamepadMotors(0f, 0.25f); // 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; } } // if the ship is out of life, it dies if (life < 0f) { Die(source); } return true; }
/// <summary> /// Kills this projectile, in response to the given actor. /// </summary> /// <param name="source">The actor responsible for the kill.</param> public override void Die(Actor source) { if (dead == false) { base.Die(source); if (dead && explodes) { Explode(source); } } }
/// <summary> /// Damages all actors in a radius around the projectile. /// </summary> /// <param name="touchedActor">The actor that was originally hit.</param> public virtual void Explode(Actor touchedActor) { // if there is no radius, then don't bother if (damageRadius <= 0f) { return; } // check each actor for damage foreach (Actor actor in world.Actors) { // don't bother if it's already dead if (actor.Dead == true) { continue; } // don't hurt the actor that the projectile hit, it's already hurt if (actor == touchedActor) { continue; } // don't hit the owner if the damageOwner flag is off if ((actor == owner) && (damageOwner == false)) { continue; } // measure the distance to the actor and see if it's in range float distance = (actor.Position - this.Position).Length(); if (distance <= damageRadius) { // adjust the amount of damage based on the distance // -- note that damageRadius <= 0 is accounted for earlier float adjustedDamage = damageAmount * (damageRadius - distance) / damageRadius; // if we're still damaging the actor, then go ahead and apply it if (adjustedDamage > 0f) { actor.Damage(this, adjustedDamage); } } } }
/// <summary> /// Determine all collisions that will happen as the given actor moves. /// </summary> /// <param name="actor">The actor that is moving.</param> /// <param name="movement">The actor's movement vector this update.</param> /// <remarks>The results are stored in the cached list.</remarks> public void Collide(Actor actor, Vector2 movement) { collisionResults.Clear(); if (actor == null) { throw new ArgumentNullException("actor"); } if (actor.Dead || (actor.Collidable == false)) { return; } // determine the movement direction and scalar float movementLength = movement.Length(); if (movementLength <= 0f) { return; } // check each actor foreach (Actor checkActor in actors) { if ((actor == checkActor) || checkActor.Dead || !checkActor.Collidable) { continue; } // calculate the target vector Vector2 checkVector = checkActor.Position - actor.Position; float distanceBetween = checkVector.Length() - (checkActor.Radius + actor.Radius); // check if they could possibly touch no matter the direction if (movementLength < distanceBetween) { continue; } // determine how much of the movement is bringing the two together float movementTowards = Vector2.Dot(movement, checkVector); // check to see if the movement is away from each other if (movementTowards < 0f) { continue; } if (movementTowards < distanceBetween) { continue; } CollisionResult result = new CollisionResult(); result.Distance = distanceBetween; result.Normal = Vector2.Normalize(checkVector); result.Actor = checkActor; collisionResults.Add(result); } }