//----------------------------------------------------------------------------- // Catapult.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Animation.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input.Touch; //using Microsoft.Devices; using System.Xml.Linq; namespace CatapultGame { [Flags] public enum CatapultState { Idle = 0x0, Aiming = 0x1, Firing = 0x2, ProjectileFlying = 0x4, ProjectileHit = 0x8, Hit = 0x10, Reset = 0x20, Stalling = 0x40 } public class Catapult : DrawableGameComponent { // Hold what the game to which the catapult belongs CatapultGame curGame = null; SpriteBatch spriteBatch; Random random; public bool AnimationRunning { get; set; } public string Name { get; set; } public bool IsActive { get; set; } // In some cases the game need to start second animation while first animation is still running; // this variable define at which frame the second animation should start Dictionary splitFrames; Texture2D idleTexture; Dictionary animations; SpriteEffects spriteEffects; // Projectile Projectile projectile; string idleTextureName; bool isAI; // Game constants const float gravity = 500f; // State of the catapult during its last update CatapultState lastUpdateState = CatapultState.Idle; // Used to stall animations int stallUpdateCycles; // Current state of the Catapult CatapultState currentState; public CatapultState CurrentState { get { return currentState; } set { currentState = value; } } float wind; public float Wind { set { wind = value; } } Player enemy; internal Player Enemy { set { enemy = value; } } Player self; internal Player Self { set { self = value; } } Vector2 catapultPosition; public Vector2 Position { get { return catapultPosition; } } /// /// Describes how powerful the current shot being fired is. The more powerful /// the shot, the further it goes. 0 is the weakest, 1 is the strongest. /// public float ShotStrength { get; set; } public float ShotVelocity { get; set; } /// /// Used to determine whether or not the game is over /// public bool GameOver { get; set; } const int winScore = 5; public Catapult(Game game) : base(game) { curGame = (CatapultGame)game; } public Catapult(Game game, SpriteBatch screenSpriteBatch, string IdleTexture, Vector2 CatapultPosition, SpriteEffects SpriteEffect, bool IsAI) : this(game) { idleTextureName = IdleTexture; catapultPosition = CatapultPosition; spriteEffects = SpriteEffect; spriteBatch = screenSpriteBatch; isAI = IsAI; splitFrames = new Dictionary(); animations = new Dictionary(); } /// /// Function initializes the catapult instance and loads the animations from XML definition sheet /// public override void Initialize() { // Define initial state of the catapult IsActive = true; AnimationRunning = false; currentState = CatapultState.Idle; stallUpdateCycles = 0; // Load multiple animations form XML definition XDocument doc = null; using (var stream = TitleContainer.OpenStream("Content/Textures/Catapults/AnimationsDef.xml")) { doc = XDocument.Load(stream); } XName name = XName.Get("Definition"); var definitions = doc.Document.Descendants(name); // Loop over all definitions in XML foreach (var animationDefinition in definitions) { bool? toLoad = null; bool val; if (bool.TryParse(animationDefinition.Attribute("IsAI").Value, out val)) toLoad = val; // Check if the animation definition need to be loaded for current catapult if (toLoad == isAI || null == toLoad) { // Get a name of the animation string animatonAlias = animationDefinition.Attribute("Alias").Value; Texture2D texture = curGame.Content.Load(animationDefinition.Attribute("SheetName").Value); // Get the frame size (width & height) Point frameSize = new Point(); frameSize.X = int.Parse(animationDefinition.Attribute("FrameWidth").Value); frameSize.Y = int.Parse(animationDefinition.Attribute("FrameHeight").Value); // Get the frames sheet dimensions Point sheetSize = new Point(); sheetSize.X = int.Parse(animationDefinition.Attribute("SheetColumns").Value); sheetSize.Y = int.Parse(animationDefinition.Attribute("SheetRows").Value); // If definition has a "SplitFrame" - means that other animation should start here - load it if (null != animationDefinition.Attribute("SplitFrame")) splitFrames.Add(animatonAlias, int.Parse(animationDefinition.Attribute("SplitFrame").Value)); // Defing animation speed TimeSpan frameInterval = TimeSpan.FromSeconds((float)1 / int.Parse(animationDefinition.Attribute("Speed").Value)); Animation animation = new Animation(texture, frameSize, sheetSize); // If definition has an offset defined - means that it should be rendered relatively // to some element/other animation - load it if (null != animationDefinition.Attribute("OffsetX") && null != animationDefinition.Attribute("OffsetY")) { animation.Offset = new Vector2(int.Parse(animationDefinition.Attribute("OffsetX").Value), int.Parse(animationDefinition.Attribute("OffsetY").Value)); } animations.Add(animatonAlias, animation); } } // Load the textures idleTexture = curGame.Content.Load(idleTextureName); // Initialize the projectile Vector2 projectileStartPosition; if (isAI) projectileStartPosition = new Vector2(630, 340); else projectileStartPosition = new Vector2(175, 340); projectile = new Projectile(curGame, spriteBatch, "Textures/Ammo/rock_ammo", projectileStartPosition, animations["Fire"].FrameSize.Y, isAI, gravity); projectile.Initialize(); // Initialize randomizer random = new Random(); base.Initialize(); } public override void Update(GameTime gameTime) { bool isGroundHit; bool startStall; CatapultState postUpdateStateChange = 0; if (gameTime == null) throw new ArgumentNullException("gameTime"); // The catapult is inactive, so there is nothing to update if (!IsActive) { base.Update(gameTime); return; } switch (currentState) { case CatapultState.Idle: // Nothing to do break; case CatapultState.Aiming: if (lastUpdateState != CatapultState.Aiming) { AudioManager.PlaySound("ropeStretch", true); AnimationRunning = true; if (isAI == true) { animations["Aim"].PlayFromFrameIndex(0); stallUpdateCycles = 20; startStall = false; } } // Progress Aiming "animation" if (isAI == false) { UpdateAimAccordingToShotStrength(); } else { animations["Aim"].Update(); startStall = AimReachedShotStrength(); currentState = (startStall) ? CatapultState.Stalling : CatapultState.Aiming; } break; case CatapultState.Stalling: if (stallUpdateCycles-- <= 0) { // We've finished stalling, fire the projectile Fire(ShotVelocity); postUpdateStateChange = CatapultState.Firing; } break; case CatapultState.Firing: // Progress Fire animation if (lastUpdateState != CatapultState.Firing) { AudioManager.StopSound("ropeStretch"); AudioManager.PlaySound("catapultFire"); StartFiringFromLastAimPosition(); } animations["Fire"].Update(); // If in the "split" point of the animation start // projectile fire sequence if (animations["Fire"].FrameIndex == splitFrames["Fire"]) { postUpdateStateChange = currentState | CatapultState.ProjectileFlying; projectile.ProjectilePosition = projectile.ProjectileStartPosition; } break; case CatapultState.Firing | CatapultState.ProjectileFlying: // Progress Fire animation animations["Fire"].Update(); // Update projectile velocity & position in flight projectile.UpdateProjectileFlightData(gameTime, wind, gravity, out isGroundHit); if (isGroundHit) { // Start hit sequence postUpdateStateChange = CatapultState.ProjectileHit; animations["fireMiss"].PlayFromFrameIndex(0); } break; case CatapultState.ProjectileFlying: // Update projectile velocity & position in flight projectile.UpdateProjectileFlightData(gameTime, wind, gravity, out isGroundHit); if (isGroundHit) { // Start hit sequence postUpdateStateChange = CatapultState.ProjectileHit; animations["fireMiss"].PlayFromFrameIndex(0); } break; case CatapultState.ProjectileHit: // Check hit on ground impact if (!CheckHit()) { if (lastUpdateState != CatapultState.ProjectileHit) { // VibrateController.Default.Start( // TimeSpan.FromMilliseconds(100)); // Play hit sound only on a missed hit, // a direct hit will trigger the explosion sound AudioManager.PlaySound("boulderHit"); } // Hit animation finished playing if (animations["fireMiss"].IsActive == false) { postUpdateStateChange = CatapultState.Reset; } animations["fireMiss"].Update(); } else { // Catapult hit - start longer vibration on any catapult hit // Remember that the call to "CheckHit" updates the catapult's // state to "Hit" // VibrateController.Default.Start( // TimeSpan.FromMilliseconds(500)); } break; case CatapultState.Hit: // Progress hit animation if ((animations["Destroyed"].IsActive == false) && (animations["hitSmoke"].IsActive == false)) { if (enemy.Score >= winScore) { GameOver = true; break; } postUpdateStateChange = CatapultState.Reset; } animations["Destroyed"].Update(); animations["hitSmoke"].Update(); break; case CatapultState.Reset: AnimationRunning = false; break; default: break; } lastUpdateState = currentState; if (postUpdateStateChange != 0) { currentState = postUpdateStateChange; } base.Update(gameTime); } /// /// Used to check if the current aim animation frame represents the shot /// strength set for the catapult. /// /// True if the current frame represents the shot strength, /// false otherwise. private bool AimReachedShotStrength() { return (animations["Aim"].FrameIndex == (Convert.ToInt32(animations["Aim"].FrameCount * ShotStrength) - 1)); } private void UpdateAimAccordingToShotStrength() { var aimAnimation = animations["Aim"]; int frameToDisplay = Convert.ToInt32(aimAnimation.FrameCount * ShotStrength); aimAnimation.FrameIndex = Math.Min(aimAnimation.FrameCount, frameToDisplay); } /// /// Calculates the frame from which to start the firing animation, /// and activates it. /// private void StartFiringFromLastAimPosition() { int startFrame = animations["Aim"].FrameCount - animations["Aim"].FrameIndex; animations["Fire"].PlayFromFrameIndex(startFrame); } public override void Draw(GameTime gameTime) { if (gameTime == null) throw new ArgumentNullException("gameTime"); // Using the last update state makes sure we do not draw // before updating animations properly switch (lastUpdateState) { case CatapultState.Idle: DrawIdleCatapult(); break; case CatapultState.Aiming: case CatapultState.Stalling: animations["Aim"].Draw(spriteBatch, catapultPosition, spriteEffects); break; case CatapultState.Firing: animations["Fire"].Draw(spriteBatch, catapultPosition, spriteEffects); break; case CatapultState.Firing | CatapultState.ProjectileFlying: case CatapultState.ProjectileFlying: animations["Fire"].Draw(spriteBatch, catapultPosition, spriteEffects); projectile.Draw(gameTime); break; case CatapultState.ProjectileHit: // Draw the catapult DrawIdleCatapult(); // Projectile Hit animation animations["fireMiss"].Draw(spriteBatch, projectile.ProjectileHitPosition, spriteEffects); break; case CatapultState.Hit: // Catapult hit animation animations["Destroyed"].Draw(spriteBatch, catapultPosition, spriteEffects); // Projectile smoke animation animations["hitSmoke"].Draw(spriteBatch, catapultPosition, spriteEffects); break; case CatapultState.Reset: DrawIdleCatapult(); break; default: break; } base.Draw(gameTime); } /// /// Start Hit sequence on catapult - could be executed on self or from enemy in case of hit /// public void Hit() { AnimationRunning = true; animations["Destroyed"].PlayFromFrameIndex(0); animations["hitSmoke"].PlayFromFrameIndex(0); currentState = CatapultState.Hit; } public void Fire(float velocity) { projectile.Fire(velocity, velocity); } /// /// Check if projectile hit some catapult. The possibilities are: /// Nothing hit, Hit enemy, Hit self /// /// private bool CheckHit() { bool bRes = false; // Build a sphere around a projectile Vector3 center = new Vector3(projectile.ProjectilePosition, 0); BoundingSphere sphere = new BoundingSphere(center, Math.Max(projectile.ProjectileTexture.Width / 2, projectile.ProjectileTexture.Height / 2)); // Check Self-Hit - create a bounding box around self Vector3 min = new Vector3(catapultPosition, 0); Vector3 max = new Vector3(catapultPosition + new Vector2(animations["Fire"].FrameSize.X, animations["Fire"].FrameSize.Y), 0); BoundingBox selfBox = new BoundingBox(min, max); // Check enemy - create a bounding box around the enemy min = new Vector3(enemy.Catapult.Position, 0); max = new Vector3(enemy.Catapult.Position + new Vector2(animations["Fire"].FrameSize.X, animations["Fire"].FrameSize.Y), 0); BoundingBox enemyBox = new BoundingBox(min, max); // Check self hit if (sphere.Intersects(selfBox) && currentState != CatapultState.Hit) { AudioManager.PlaySound("catapultExplosion"); // Launch hit animation sequence on self Hit(); enemy.Score++; bRes = true; } // Check if enemy was hit else if (sphere.Intersects(enemyBox) && enemy.Catapult.CurrentState != CatapultState.Hit && enemy.Catapult.CurrentState != CatapultState.Reset) { AudioManager.PlaySound("catapultExplosion"); // Launch enemy hit animaton enemy.Catapult.Hit(); self.Score++; bRes = true; currentState = CatapultState.Reset; } return bRes; } /// /// Draw catapult in Idle state /// private void DrawIdleCatapult() { spriteBatch.Draw(idleTexture, catapultPosition, null, Color.White, 0.0f, Vector2.Zero, 1.0f, spriteEffects, 0); } } }