#region File Description //----------------------------------------------------------------------------- // Bird.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; #endregion namespace Flocking { class Bird : Animal { #region Fields protected Random random; Vector2 aiNewDir; int aiNumSeen; #endregion #region Initialization /// /// Bird constructor /// /// /// movement direction /// spawn location /// screen size public Bird(Texture2D tex, Vector2 dir, Vector2 loc, int screenWidth, int screenHeight) : base(tex, screenWidth, screenHeight) { direction = dir; direction.Normalize(); location = loc; moveSpeed = 125.0f; fleeing = false; random = new Random((int)loc.X + (int)loc.Y); animaltype = AnimalType.Bird; BuildBehaviors(); } #endregion #region Update and Draw /// /// update bird position, wrapping around the screen edges /// /// public void Update(GameTime gameTime, ref AIParameters aiParams) { float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds; Vector2 randomDir = Vector2.Zero; randomDir.X = (float)random.NextDouble() - 0.5f; randomDir.Y = (float)random.NextDouble() - 0.5f; Vector2.Normalize(ref randomDir, out randomDir); if (aiNumSeen > 0) { aiNewDir = (direction * aiParams.MoveInOldDirectionInfluence) + (aiNewDir * (aiParams.MoveInFlockDirectionInfluence / (float)aiNumSeen)); } else { aiNewDir = direction * aiParams.MoveInOldDirectionInfluence; } aiNewDir += (randomDir * aiParams.MoveInRandomDirectionInfluence); Vector2.Normalize(ref aiNewDir, out aiNewDir); aiNewDir = ChangeDirection(direction, aiNewDir, aiParams.MaxTurnRadians * elapsedTime); direction = aiNewDir; if (direction.LengthSquared() > .01f) { Vector2 moveAmount = direction * moveSpeed * elapsedTime; location = location + moveAmount; //wrap bird to the other side of the screen if needed if (location.X < 0.0f) { location.X = boundryWidth + location.X; } else if (location.X > boundryWidth) { location.X = location.X - boundryWidth; } location.Y += direction.Y * moveSpeed * elapsedTime; if (location.Y < 0.0f) { location.Y = boundryHeight + location.Y; } else if (location.Y > boundryHeight) { location.Y = location.Y - boundryHeight; } } } /// /// Draw the bird, tinting it if it's currently fleeing /// /// /// public override void Draw(SpriteBatch spriteBatch, GameTime gameTime) { Color tintColor = color; float rotation = 0.0f; rotation = (float)Math.Atan2(direction.Y, direction.X); // if the entity is highlighted, we want to make it pulse with a red tint. if (fleeing) { // to do this, we'll first generate a value t, which we'll use to // determine how much tint to have. float t = (float)Math.Sin(10 * gameTime.TotalGameTime.TotalSeconds); // Sin varies from -1 to 1, and we want t to go from 0 to 1, so we'll // scale it now. t = .5f + .5f * t; // finally, we'll calculate our tint color by using Lerp to generate // a color in between Red and White. tintColor = new Color(Vector4.Lerp( Color.Red.ToVector4(), Color.White.ToVector4(), t)); } // Draw the animal, centered around its position, and using the //orientation and tint color. spriteBatch.Draw(texture, location, null, tintColor, rotation, textureCenter, 1.0f, SpriteEffects.None, 0.0f); } #endregion #region Methods /// /// Instantiates all the behaviors that this Bird knows about /// public void BuildBehaviors() { Behaviors catReactions = new Behaviors(); catReactions.Add(new FleeBehavior(this)); behaviors.Add(AnimalType.Cat, catReactions); Behaviors birdReactions = new Behaviors(); birdReactions.Add(new AlignBehavior(this)); birdReactions.Add(new CohesionBehavior(this)); birdReactions.Add(new SeparationBehavior(this)); behaviors.Add(AnimalType.Bird, birdReactions); } /// /// Setup the bird to figure out it's new movement direction /// /// flock AI parameters public void ResetThink() { Fleeing = false; aiNewDir = Vector2.Zero; aiNumSeen = 0; reactionDistance = 0f; reactionLocation = Vector2.Zero; } /// /// Since we're wrapping movement around the screen, two point at extreme /// sides of the screen are actually very close together, this function /// figures out if destLocation is closer the srcLocation if you wrap around /// the screen /// /// screen location of src /// screen location of dest /// relative location of dest to src private void ClosestLocation(ref Vector2 srcLocation, ref Vector2 destLocation, out Vector2 outLocation) { outLocation = new Vector2(); float x = destLocation.X; float y = destLocation.Y; float dX = Math.Abs(destLocation.X - srcLocation.X); float dY = Math.Abs(destLocation.Y - srcLocation.Y); // now see if the distance between birds is closer if going off one // side of the map and onto the other. if (Math.Abs(boundryWidth - destLocation.X + srcLocation.X) < dX) { dX = boundryWidth - destLocation.X + srcLocation.X; x = destLocation.X - boundryWidth; } if (Math.Abs(boundryWidth - srcLocation.X + destLocation.X) < dX) { dX = boundryWidth - srcLocation.X + destLocation.X; x = destLocation.X + boundryWidth; } if (Math.Abs(boundryHeight - destLocation.Y + srcLocation.Y) < dY) { dY = boundryHeight - destLocation.Y + srcLocation.Y; y = destLocation.Y - boundryHeight; } if (Math.Abs(boundryHeight - srcLocation.Y + destLocation.Y) < dY) { dY = boundryHeight - srcLocation.Y + destLocation.Y; y = destLocation.Y + boundryHeight; } outLocation.X = x; outLocation.Y = y; } /// /// React to an Animal based on it's type /// /// public void ReactTo(Animal animal, ref AIParameters AIparams) { if (animal != null) { //setting the the reactionLocation and reactionDistance here is //an optimization, many of the possible reactions use the distance //and location of theAnimal, so we might as well figure them out //only once ! Vector2 otherLocation = animal.Location; ClosestLocation(ref location, ref otherLocation, out reactionLocation); reactionDistance = Vector2.Distance(location, reactionLocation); //we only react if theAnimal is close enough that we can see it if (reactionDistance < AIparams.DetectionDistance) { Behaviors reactions = behaviors[animal.AnimalType]; foreach (Behavior reaction in reactions) { reaction.Update(animal, AIparams); if (reaction.Reacted) { aiNewDir += reaction.Reaction; aiNumSeen++; } } } } } /// /// This function clamps turn rates to no more than maxTurnRadians /// /// current movement direction /// desired movement direction /// max turn in radians /// private static Vector2 ChangeDirection( Vector2 oldDir, Vector2 newDir, float maxTurnRadians) { float oldAngle = (float)Math.Atan2(oldDir.Y, oldDir.X); float desiredAngle = (float)Math.Atan2(newDir.Y, newDir.X); float newAngle = MathHelper.Clamp(desiredAngle, WrapAngle( oldAngle - maxTurnRadians), WrapAngle(oldAngle + maxTurnRadians)); return new Vector2((float)Math.Cos(newAngle), (float)Math.Sin(newAngle)); } /// /// clamps the angle in radians between -Pi and Pi. /// /// /// private static float WrapAngle(float radians) { while (radians < -MathHelper.Pi) { radians += MathHelper.TwoPi; } while (radians > MathHelper.Pi) { radians -= MathHelper.TwoPi; } return radians; } #endregion } }