#region File Description //----------------------------------------------------------------------------- // BeeKeeper.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 System.Collections.Generic; #endregion namespace HoneycombRush { /// /// Represents the beekeeper, the player's avatar. /// public class BeeKeeper : TexturedDrawableGameComponent { #region Enums /// /// Represents the direction in which the beekeeper is walking. /// enum WalkingDirection { Down = 0, Up = 8, Left = 16, Right = 24, LeftDown = 32, RightDown = 40, LeftUp = 48, RightUp = 56 } #endregion #region Fields/Properties // Animation name constants const string LegAnimationKey = "LegAnimation"; const string BodyAnimationKey = "BodyAnimation"; const string SmokeAnimationKey = "SmokeAnimation"; const string ShootingAnimationKey = "ShootingAnimation"; const string BeekeeperCollectingHoneyAnimationKey = "BeekeeperCollectingHoney"; const string BeekeeperDepositingHoneyAnimationKey = "BeekeeperDepositingHoney"; Vector2 bodySize = new Vector2(85, 132); Vector2 velocity; Vector2 smokeAdjustment; SpriteEffects lastEffect; SpriteEffects currentEffect; // Beekeeper state variables bool needToShootSmoke; bool isStung; bool isFlashing; bool isDrawnLastStungInterval; bool isDepositingHoney; TimeSpan stungTime; TimeSpan stungDuration; TimeSpan flashingDuration; TimeSpan depositHoneyUpdatingInterval = TimeSpan.FromMilliseconds(200); TimeSpan depositHoneyUpdatingTimer = TimeSpan.Zero; TimeSpan shootSmokePuffTimer = TimeSpan.Zero; readonly TimeSpan shootSmokePuffTimerInitialValue = TimeSpan.FromMilliseconds(325); Texture2D smokeAnimationTexture; Texture2D smokePuffTexture; const int MaxSmokePuffs = 20; /// /// Contains all smoke puffs which are currently active /// public Queue FiredSmokePuffs { get; private set; } /// /// Serves as a pool of available smoke puff objects. /// Stack availableSmokePuffs; int stungDrawingInterval = 5; int stungDrawingCounter = 0; int honeyDepositFrameCount; int depositHoneyTimerCounter = -1; int collectingHoneyFrameCounter; AsyncCallback depositHoneyCallback; WalkingDirection newDirection = WalkingDirection.Up; WalkingDirection direction = WalkingDirection.Up; int lastFrameCounter; public bool IsStung { get { return isStung; } } public bool IsFlashing { get { return isFlashing; } } /// /// Mark the beekeeper as shooting or not shooting smoke. /// public bool IsShootingSmoke { set { if (!isStung) { needToShootSmoke = value; if (value) { AudioManager.PlaySound("SmokeGun_Loop"); } else { shootSmokePuffTimer = TimeSpan.Zero; } } } } public override Rectangle Bounds { get { int height = (int)bodySize.Y / 10 * 8; int width = (int)bodySize.X / 10 * 5; int offsetY = ((int)bodySize.Y - height) / 2; int offsetX = ((int)bodySize.X - width) / 2; return new Rectangle((int)position.X + offsetX, (int)position.Y + offsetY, width, height); } } public override Rectangle CentralCollisionArea { get { Rectangle bounds = Bounds; int height = (int)bounds.Height / 10 * 5; int width = (int)bounds.Width / 10 * 8; int offsetY = ((int)bounds.Height - height) / 2; int offsetX = ((int)bounds.Width - width) / 2; return new Rectangle((int)bounds.X + offsetX, (int)bounds.Y + offsetY, width, height); } } public bool IsDepositingHoney { get { return isDepositingHoney; } } public bool IsCollectingHoney { get; set; } public Vector2 Position { get { return position; } } public Rectangle ThumbStickArea { get; set; } public bool IsInMotion { get; set; } #endregion #region Initialization /// /// Creates a new beekeeper instance. /// /// The game object. /// The gameplay screen. public BeeKeeper(Game game, GameplayScreen gamePlayScreen) : base(game, gamePlayScreen) { } /// /// Initialize the beekepper. /// public override void Initialize() { // Initialize the animation AnimationDefinitions[LegAnimationKey].PlayFromFrameIndex(0); AnimationDefinitions[BodyAnimationKey].PlayFromFrameIndex(0); AnimationDefinitions[SmokeAnimationKey].PlayFromFrameIndex(0); AnimationDefinitions[ShootingAnimationKey].PlayFromFrameIndex(0); AnimationDefinitions[BeekeeperCollectingHoneyAnimationKey].PlayFromFrameIndex(0); AnimationDefinitions[BeekeeperDepositingHoneyAnimationKey].PlayFromFrameIndex(0); Point bodyAnimationFrameSize = AnimationDefinitions[LegAnimationKey].frameSize; bodySize = new Vector2(bodyAnimationFrameSize.X, bodyAnimationFrameSize.Y) * scaledSpriteBatch.ScaleVector; isStung = false; stungDuration = TimeSpan.FromSeconds(1); flashingDuration = TimeSpan.FromSeconds(2); availableSmokePuffs = new Stack(MaxSmokePuffs); FiredSmokePuffs = new Queue(MaxSmokePuffs); base.Initialize(); } /// /// Loads content that will be used later on by the beekeeper. /// protected override void LoadContent() { smokeAnimationTexture = Game.Content.Load("Textures/SmokeAnimationStrip"); smokePuffTexture = Game.Content.Load("Textures/SmokePuff"); position = new Vector2(Game.GraphicsDevice.Viewport.Width / 2 - (int)bodySize.X / 2, Game.GraphicsDevice.Viewport.Height / 2 - (int)bodySize.Y / 2); // Create smoke puffs for the smoke puff pool for (int i = 0; i < MaxSmokePuffs; i++) { availableSmokePuffs.Push(new SmokePuff(Game, gamePlayScreen, smokePuffTexture)); } base.LoadContent(); } #endregion #region Update /// /// Updates the beekeeper's status. /// /// Game time information public override void Update(GameTime gameTime) { if (!(gamePlayScreen.IsActive && gamePlayScreen.IsStarted)) { base.Update(gameTime); return; } if (IsCollectingHoney) { // We want this animation to use a sub animation // So must calculate when to call the sub animation if (collectingHoneyFrameCounter > 3) { AnimationDefinitions[BeekeeperCollectingHoneyAnimationKey].Update(gameTime, true, true); } else { AnimationDefinitions[BeekeeperCollectingHoneyAnimationKey].Update(gameTime, true, false); } collectingHoneyFrameCounter++; } else { collectingHoneyFrameCounter = 0; } if (isDepositingHoney) { if (depositHoneyUpdatingTimer == TimeSpan.Zero) { depositHoneyUpdatingTimer = gameTime.TotalGameTime; } AnimationDefinitions[BeekeeperDepositingHoneyAnimationKey].Update(gameTime, true); } // The oldest smoke puff might have expired and should therefore be recycled if ((FiredSmokePuffs.Count > 0) && (FiredSmokePuffs.Peek().IsGone)) { availableSmokePuffs.Push(FiredSmokePuffs.Dequeue()); } // If the beeKeeper is stung by a bee we want to create a flashing // effect. if (isStung || isFlashing) { stungDrawingCounter++; if (stungDrawingCounter > stungDrawingInterval) { stungDrawingCounter = 0; isDrawnLastStungInterval = !isDrawnLastStungInterval; } // if time is up, end the flashing effect if (stungTime + stungDuration < gameTime.TotalGameTime) { isStung = false; if (stungTime + stungDuration + flashingDuration < gameTime.TotalGameTime) { isFlashing = false; stungDrawingCounter = -1; } AnimationDefinitions[LegAnimationKey].Update(gameTime, IsInMotion); } } else { AnimationDefinitions[LegAnimationKey].Update(gameTime, IsInMotion); } if (needToShootSmoke) { AnimationDefinitions[SmokeAnimationKey].Update(gameTime, needToShootSmoke); shootSmokePuffTimer -= gameTime.ElapsedGameTime; if (shootSmokePuffTimer <= TimeSpan.Zero) { ShootSmoke(); shootSmokePuffTimer = shootSmokePuffTimerInitialValue; } } base.Update(gameTime); } #endregion #region Render /// /// Renders the beekeeper. /// /// public override void Draw(GameTime gameTime) { if (!(gamePlayScreen.IsActive && gamePlayScreen.IsStarted)) { base.Draw(gameTime); return; } // Make sure not to draw the beekeeper while flashing if (isStung || isFlashing) { if (stungDrawingCounter != stungDrawingInterval) { if (isDrawnLastStungInterval) { return; } } } scaledSpriteBatch.Begin(); // if stung we want to show another animation if (isStung) { scaledSpriteBatch.Draw(Game.Content.Load("Textures/hit"), position, Color.White); scaledSpriteBatch.End(); return; } // If collecting honey, draw the appropriate animation if (IsCollectingHoney) { AnimationDefinitions[BeekeeperCollectingHoneyAnimationKey].Draw(scaledSpriteBatch, position, SpriteEffects.None); scaledSpriteBatch.End(); return; } if (isDepositingHoney) { if (velocity != Vector2.Zero) { isDepositingHoney = false; AudioManager.StopSound("DepositingIntoVat_Loop"); } // We want the deposit duration to sync with the deposit // animation // So we manage the timing ourselves if (depositHoneyUpdatingTimer != TimeSpan.Zero && depositHoneyUpdatingTimer + depositHoneyUpdatingInterval < gameTime.TotalGameTime) { depositHoneyTimerCounter++; depositHoneyUpdatingTimer = TimeSpan.Zero; } AnimationDefinitions[BeekeeperDepositingHoneyAnimationKey].Draw(scaledSpriteBatch, position, SpriteEffects.None); if (depositHoneyTimerCounter == honeyDepositFrameCount - 1) { isDepositingHoney = false; depositHoneyCallback.Invoke(null); AnimationDefinitions[BeekeeperDepositingHoneyAnimationKey].PlayFromFrameIndex(0); } scaledSpriteBatch.End(); return; } bool hadDirectionChanged = false; // See if the direction changed if (newDirection != direction) { hadDirectionChanged = true; direction = newDirection; } if (hadDirectionChanged) { // Update the animation lastFrameCounter = 0; AnimationDefinitions[LegAnimationKey].PlayFromFrameIndex(lastFrameCounter + (int)direction); AnimationDefinitions[ShootingAnimationKey].PlayFromFrameIndex(lastFrameCounter + (int)direction); AnimationDefinitions[BodyAnimationKey].PlayFromFrameIndex(lastFrameCounter + (int)direction); } else { // Because our animation is 8 cells, but the row is 16 cells, // we need to reset the counter after 8 rounds if (lastFrameCounter == 8) { lastFrameCounter = 0; AnimationDefinitions[LegAnimationKey].PlayFromFrameIndex(lastFrameCounter + (int)direction); AnimationDefinitions[ShootingAnimationKey].PlayFromFrameIndex( lastFrameCounter + (int)direction); AnimationDefinitions[BodyAnimationKey].PlayFromFrameIndex(lastFrameCounter + (int)direction); } else { lastFrameCounter++; } } AnimationDefinitions[LegAnimationKey].Draw(scaledSpriteBatch, position, 1f, SpriteEffects.None); if (needToShootSmoke) { // Draw the body AnimationDefinitions[ShootingAnimationKey].Draw(scaledSpriteBatch, position, 1f, SpriteEffects.None); // If true we need to draw smoke if (smokeAdjustment != Vector2.Zero) { AnimationDefinitions[SmokeAnimationKey].Draw(scaledSpriteBatch, position + smokeAdjustment, 1f, currentEffect); } } else { AnimationDefinitions[BodyAnimationKey].Draw(scaledSpriteBatch, position, 1f, SpriteEffects.None); } scaledSpriteBatch.End(); base.Draw(gameTime); } #endregion #region Public Methods /// /// Checks if a given rectanlge intersects with one of the smoke puffs fired by the beekeeper. /// /// The rectangle to check for collisions with smoke puffs. /// One of the smoke puffs with which the supplied regtangle collides, or null if it collides with /// none. public SmokePuff CheckSmokeCollision(Rectangle checkRectangle) { foreach (SmokePuff smokePuff in FiredSmokePuffs) { if (checkRectangle.HasCollision(smokePuff.CentralCollisionArea)) { return smokePuff; } } return null; } /// /// Maek the beekeeper as being stung by a bee. /// /// The time at which the beekeeper was stung. public void Stung(TimeSpan occurTime) { if (!isStung && !isFlashing) { isStung = true; isFlashing = true; stungTime = occurTime; needToShootSmoke = false; } } /// /// Updates the beekeeper's position. /// /// A vector which contains the desired adjustment to /// the beekeeper's position. public void SetMovement(Vector2 movement) { if (!IsStung) { velocity = movement; position += velocity; } } /// /// Makes sure the beekeeper's direction matches his movement direction. /// /// A vector indicating the beekeeper's movement /// direction. public void SetDirection(Vector2 movementDirection) { DetermineDirection(movementDirection, ref newDirection, ref smokeAdjustment); currentEffect = GetSpriteEffect(movementDirection); } /// /// Starts the process of transferring honey to the honey vat. /// /// The amount of frames in the honey /// depositing animation. /// Callback to invoke once the process is /// complete. public void StartTransferHoney(int honeyDepositFrameCount, AsyncCallback callback) { depositHoneyCallback = callback; this.honeyDepositFrameCount = honeyDepositFrameCount; isDepositingHoney = true; depositHoneyTimerCounter = 0; AudioManager.PlaySound("DepositingIntoVat_Loop"); } /// /// Marks the honey transfer process as complete. /// public void EndTransferHoney() { isDepositingHoney = false; } #endregion #region Private Methods /// /// Shoots a puff of smoke. If too many puffs of smoke have already been fired, the oldest one vanishes and /// is replaced with a new one. /// private void ShootSmoke() { SmokePuff availableSmokePuff; if (availableSmokePuffs.Count > 0) { // Take a smoke puff from the pool availableSmokePuff = availableSmokePuffs.Pop(); } else { // Take the oldest smoke puff and use it availableSmokePuff = FiredSmokePuffs.Dequeue(); } Vector2 beeKeeperCenter = Bounds.Center.GetVector(); Vector2 smokeInitialPosition = beeKeeperCenter; availableSmokePuff.Fire(smokeInitialPosition, GetSmokeVelocityVector()); FiredSmokePuffs.Enqueue(availableSmokePuff); } /// /// Used to return a vector which will serve as shot smoke velocity. /// /// A vector which serves as the initial velocity of smoke puffs being shot. private Vector2 GetSmokeVelocityVector() { Vector2 initialVector; switch (direction) { case WalkingDirection.Down: initialVector = new Vector2(0, 1); break; case WalkingDirection.Up: initialVector = new Vector2(0, -1); break; case WalkingDirection.Left: initialVector = new Vector2(-1, 0); break; case WalkingDirection.Right: initialVector = new Vector2(1, 0); break; case WalkingDirection.LeftDown: initialVector = new Vector2(-1, 1); break; case WalkingDirection.RightDown: initialVector = new Vector2(1, 1); break; case WalkingDirection.LeftUp: initialVector = new Vector2(-1, -1); break; case WalkingDirection.RightUp: initialVector = new Vector2(1, -1); break; default: throw new InvalidOperationException("Determining the vector for an invalid walking direction"); } return initialVector * 2f + velocity * 1f; } /// /// Returns an effect appropriate to the supplied vector which either does /// nothing or flips the beekeeper horizontally. /// /// A vector depicting the beekeeper's /// movement. /// A sprite effect that should be applied to the beekeeper. private SpriteEffects GetSpriteEffect(Vector2 movementDirection) { // Checks if there is any movement input if (movementDirection != Vector2.Zero) { // If beekeeper is facing left if (movementDirection.X < 0) { lastEffect = SpriteEffects.FlipHorizontally; } else if (movementDirection.X > 0) { lastEffect = SpriteEffects.None; } } return lastEffect; } /// /// Returns movement information according to the current virtual thumbstick input. /// /// Vector indicating the current beekeeper movement. /// Enum describing the inpot direction. /// Adjustment to smoke position according to input direction. private void DetermineDirection(Vector2 movement, ref WalkingDirection tempDirection, ref Vector2 smokeAdjustment) { if (movement == Vector2.Zero) { return; } if (Math.Abs(movement.X) > Math.Abs(movement.Y)) { DetermineDirectionDominantX(movement, ref tempDirection, ref smokeAdjustment); } else { DetermineDirectionDominantY(movement, ref tempDirection, ref smokeAdjustment); } } /// /// Returns movement information according to the current virtual thumbstick input, given that advancement /// along the X axis is greater than along the Y axis. /// /// Vector indicating the current beekeeper movement. /// Enum describing the input direction. /// Adjustment to smoke position according to input direction. private void DetermineDirectionDominantX(Vector2 movement, ref WalkingDirection tempDirection, ref Vector2 smokeAdjustment) { if (movement.X > 0) { if (movement.Y > 0.25f) { tempDirection = WalkingDirection.RightDown; smokeAdjustment = new Vector2(UIConstants.SprayRightOffset, UIConstants.SprayDownOffset); } else if (movement.Y < -0.25f) { tempDirection = WalkingDirection.RightUp; smokeAdjustment = new Vector2(UIConstants.SprayRightOffset, UIConstants.SprayUpOffset); } else { tempDirection = WalkingDirection.Right; smokeAdjustment = new Vector2(UIConstants.SprayRightOffset, UIConstants.SprayMiddleOffset); } } else { if (movement.Y > 0.25f) { tempDirection = WalkingDirection.LeftDown; smokeAdjustment = new Vector2(-UIConstants.SprayLeftOffset, UIConstants.SprayDownOffset); } else if (movement.Y < -0.25f) { tempDirection = WalkingDirection.LeftUp; smokeAdjustment = new Vector2(-UIConstants.SprayLeftOffset, UIConstants.SprayUpOffset); } else { tempDirection = WalkingDirection.Left; smokeAdjustment = new Vector2(-UIConstants.SprayLeftOffset, UIConstants.SprayMiddleOffset); } } } /// /// Returns movement information according to the current virtual thumbstick input, given that advancement /// along the Y axis is greater than along the X axis. /// /// Vector indicating the current beekeeper movement. /// Enum describing the input direction. /// Adjustment to smoke position according to input direction. private void DetermineDirectionDominantY(Vector2 movement, ref WalkingDirection tempDirection, ref Vector2 smokeAdjustment) { if (movement.Y > 0) { if (movement.X > 0.25f) { tempDirection = WalkingDirection.RightDown; smokeAdjustment = new Vector2(UIConstants.SprayRightOffset, UIConstants.SprayDownOffset); } else if (movement.X < -0.25f) { tempDirection = WalkingDirection.LeftDown; smokeAdjustment = new Vector2(-UIConstants.SprayLeftOffset, UIConstants.SprayDownOffset); } else { tempDirection = WalkingDirection.Down; smokeAdjustment = Vector2.Zero; } } else { if (movement.X > 0.25f) { tempDirection = WalkingDirection.RightUp; smokeAdjustment = new Vector2(UIConstants.SprayRightOffset, UIConstants.SprayUpOffset); } else if (movement.X < -0.25f) { tempDirection = WalkingDirection.LeftUp; smokeAdjustment = new Vector2(-UIConstants.SprayLeftOffset, UIConstants.SprayUpOffset); } else { tempDirection = WalkingDirection.Up; smokeAdjustment = Vector2.Zero; } } } #endregion } }