#region File Description //----------------------------------------------------------------------------- // FlockingSample.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; #endregion namespace Flocking { #region FlockingAIParameters public struct AIParameters { /// /// how far away the animals see each other /// public float DetectionDistance; /// /// seperate from animals inside this distance /// public float SeparationDistance; /// /// how much the animal tends to move in it's previous direction /// public float MoveInOldDirectionInfluence; /// /// how much the animal tends to move with animals in it's detection distance /// public float MoveInFlockDirectionInfluence; /// /// how much the animal tends to move randomly /// public float MoveInRandomDirectionInfluence; /// /// how quickly the animal can turn /// public float MaxTurnRadians; /// /// how much each nearby animal influences it's behavior /// public float PerMemberWeight; /// /// how much dangerous animals influence it's behavior /// public float PerDangerWeight; } #endregion /// /// This is the main type for your game /// public class FlockingSample : Microsoft.Xna.Framework.Game { #region Constants // X location to start drawing the HUD from const int hudLocX = 200; // Y location to start drawing the HUD from const int hudLocY = 30; // Min value for the distance sliders const float sliderMin = 0.0f; // Max value for the distance sliders const float sliderMax = 100f; // Width of the slider button const int sliderButtonWidth = 10; // Default value for the AI parameters const float detectionDefault = 70.0f; const float separationDefault = 50.0f; const float moveInOldDirInfluenceDefault = 1.0f; const float moveInFlockDirInfluenceDefault = 1.0f; const float moveInRandomDirInfluenceDefault = 0.05f; const float maxTurnRadiansDefault = 6.0f; const float perMemberWeightDefault = 1.0f; const float perDangerWeightDefault = 50.0f; #endregion #region Fields GraphicsDeviceManager graphics; SpriteBatch spriteBatch; InputState inputState; SpriteFont hudFont; // Do we need to update AI parameers this Update bool aiParameterUpdate = false; bool moveCat = false; #if WINDOWS || XBOX Texture2D bButton; Texture2D xButton; Texture2D yButton; #endif Texture2D onePixelWhite; Texture2D birdTexture; Texture2D catTexture; Cat cat; Flock flock; AIParameters flockParams; // Definte the dimensions of the controls Rectangle barDetectionDistance = new Rectangle(205, 45, 85, 40); Rectangle barSeparationDistance = new Rectangle(205, 125, 85, 40); Rectangle buttonResetDistance = new Rectangle(105, 205, 140, 40); Rectangle buttonResetFlock = new Rectangle(105, 285, 140, 40); Rectangle buttonToggleCat = new Rectangle(105, 365, 140, 40); int selectionNum; #endregion #region Initialization public FlockingSample() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; #if WINDOWS_PHONE || IOS // Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); graphics.IsFullScreen = true; #endif inputState = new InputState(); flock = null; cat = null; flockParams = new AIParameters(); ResetAIParams(); } /// /// Allows the game to perform any initialization it needs to before starting /// to run. This is where it can query for any required services and load any /// non-graphic related content. Calling base.Initialize will enumerate /// through any components and initialize them as well. /// protected override void Initialize() { // Enable the gestures we care about. you must set EnabledGestures before // you can use any of the other gesture APIs. TouchPanel.EnabledGestures = GestureType.Tap | GestureType.FreeDrag; base.Initialize(); } /// /// LoadContent will be called once per game and is the place to load /// all of your content. /// protected override void LoadContent() { spriteBatch = new SpriteBatch(GraphicsDevice); catTexture = Content.Load("cat"); birdTexture = Content.Load("mouse"); #if WINDOWS || XBOX bButton = Content.Load("xboxControllerButtonB"); xButton = Content.Load("xboxControllerButtonX"); yButton = Content.Load("xboxControllerButtonY"); #endif hudFont = Content.Load("HUDFont"); onePixelWhite = new Texture2D( GraphicsDevice, 1, 1, false, SurfaceFormat.Color); // TODO onePixelWhite.SetData(new Color[] { Color.White }); } #endregion #region Handle Input /// /// Handles input for quitting the game. /// void HandleInput() { inputState.Update(); // Check for exit. if (inputState.Exit) { Exit(); } float dragDelta = 0f; // Check to see whether the user wants to modify their currently selected // weight. if (inputState.Up) { selectionNum--; if (selectionNum < 0) selectionNum = 1; } if (inputState.Down) { selectionNum = (selectionNum + 1) % 2; } // Update move for the cat if (cat != null) { cat.HandleInput(inputState); } // Turn the cat on or off if (inputState.ToggleCatButton) { ToggleCat(); } // Resets flock parameters back to default if (inputState.ResetDistances) { ResetAIParams(); aiParameterUpdate = true; } // Resets the location and orientation of the members of the flock if (inputState.ResetFlock) { flock.ResetFlock(); aiParameterUpdate = true; } dragDelta = inputState.SliderMove; // Apply to the changeAmount to the currentlySelectedWeight switch (selectionNum) { case 0: flockParams.DetectionDistance += dragDelta; break; case 1: flockParams.SeparationDistance += dragDelta; break; default: break; } if (dragDelta != 0f) aiParameterUpdate = true; // By default we can move the cat but if a touch registers against a control do not move the cat moveCat = true; TouchCollection rawTouch = TouchPanel.GetState(); // Use raw touch for the sliders if (rawTouch.Count > 0) { // Only grab the first one TouchLocation touchLocation = rawTouch[0]; // Create a collidable rectangle to determine if we touched the controls Rectangle touchRectangle = new Rectangle((int)touchLocation.Position.X, (int)touchLocation.Position.Y, 20, 20); // Have the sliders rely on the raw touch to function properly SliderInputHelper(touchRectangle); } // Next we handle all of the gestures. since we may have multiple gestures available, // we use a loop to read in all of the gestures. this is important to make sure the // TouchPanel's queue doesn't get backed up with old data while (TouchPanel.IsGestureAvailable) { // Read the next gesture from the queue GestureSample gesture = TouchPanel.ReadGesture(); // Create a collidable rectangle to determine if we touched the controls Rectangle touch = new Rectangle((int)gesture.Position.X, (int)gesture.Position.Y, 20, 20); // We can use the type of gesture to determine our behavior switch (gesture.GestureType) { case GestureType.Tap: if (buttonResetDistance.Intersects(touch)) { // Resets flock parameters back to default ResetAIParams(); aiParameterUpdate = true; moveCat = false; } else if (buttonResetFlock.Intersects(touch)) { // Resets the location and orientation of the members of the flock flock.ResetFlock(); aiParameterUpdate = true; moveCat = false; } else if (buttonToggleCat.Intersects(touch)) { ToggleCat(); moveCat = false; } break; } // Check if we can move the cat if (cat != null && moveCat) { // If we did not touch any controls then move the cat cat.Location = gesture.Position; } } // Clamp the slider values flockParams.DetectionDistance = MathHelper.Clamp(flockParams.DetectionDistance, sliderMin, sliderMax); flockParams.SeparationDistance = MathHelper.Clamp(flockParams.SeparationDistance, sliderMin, sliderMax); if (aiParameterUpdate) { flock.FlockParams = flockParams; } } /// /// Helper function that handles Slider interaction logic /// /// Rectangle representing a touch private void SliderInputHelper( Rectangle touchRectangle) { if (barDetectionDistance.Intersects(touchRectangle)) { selectionNum = 0; aiParameterUpdate = true; moveCat = false; flockParams.DetectionDistance = touchRectangle.X - barDetectionDistance.X; } else if (barSeparationDistance.Intersects(touchRectangle)) { selectionNum = 1; aiParameterUpdate = true; moveCat = false; flockParams.SeparationDistance = touchRectangle.X - barDetectionDistance.X; } } #endregion #region Update and Draw /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. /// /// Provides a snapshot of timing values. protected override void Update(GameTime gameTime) { HandleInput(); if (cat != null) { cat.Update(gameTime); } if (flock != null) { flock.Update(gameTime, cat); } else { SpawnFlock(); } base.Update(gameTime); } /// /// This is called when the game should draw itself. /// /// Provides a snapshot of timing values. protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); if (flock != null) { flock.Draw(spriteBatch, gameTime); } if (cat != null) { cat.Draw(spriteBatch, gameTime); } // Draw all the HUD elements DrawBar(barDetectionDistance, flockParams.DetectionDistance / 100f, "Detection Distance:", gameTime, selectionNum == 0); DrawBar(barSeparationDistance, flockParams.SeparationDistance / 100f, "Separation Distance:", gameTime, selectionNum == 1); #if WINDOWS_PHONE || IOS || PSM DrawButton(buttonResetDistance, "Reset Distance"); DrawButton(buttonResetFlock, "Reset Flock"); DrawButton(buttonToggleCat, "Add/Remove Cat"); #else spriteBatch.Draw(bButton, new Vector2(hudLocX + 110.0f, hudLocY), Color.White); spriteBatch.Draw(xButton, new Vector2(hudLocX + 110.0f, hudLocY + 20.0f), Color.White); spriteBatch.Draw(yButton, new Vector2(hudLocX + 110.0f, hudLocY + 40.0f), Color.White); spriteBatch.DrawString(hudFont, "Reset Distances", new Vector2(hudLocX + 135.0f, hudLocY), Color.White); spriteBatch.DrawString(hudFont, "Reset flock", new Vector2(hudLocX + 135.0f, hudLocY+20.0f), Color.White); spriteBatch.DrawString(hudFont, "Spawn/remove cat", new Vector2(hudLocX + 135.0f, hudLocY+40.0f), Color.White); #endif spriteBatch.End(); base.Draw(gameTime); } /// /// Helper function used by Draw. It is used to draw the buttons /// /// /// private void DrawButton(Rectangle button, string label) { spriteBatch.Draw(onePixelWhite, button, Color.Orange); spriteBatch.DrawString(hudFont, label, new Vector2(button.Left + 10, button.Top + 10), Color.Black); } /// /// Helper function used by Draw. It is used to draw the slider bars /// private void DrawBar(Rectangle bar, float barWidthNormalized, string label, GameTime gameTime, bool highlighted) { Color tintColor = Color.White; // If the bar is highlighted, we want to make it pulse with a red tint. if (highlighted) { // 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)); } // Calculate how wide the bar should be, and then draw it. bar.Height /= 2; spriteBatch.Draw(onePixelWhite, bar, Color.White); // Draw the slider spriteBatch.Draw(onePixelWhite, new Rectangle(bar.X + (int)(bar.Width * barWidthNormalized), bar.Y - bar.Height / 2, sliderButtonWidth, bar.Height * 2), Color.Orange); // Finally, draw the label to the left of the bar. Vector2 labelSize = hudFont.MeasureString(label); Vector2 labelPosition = new Vector2(bar.X - 5 - labelSize.X, bar.Y); spriteBatch.DrawString(hudFont, label, labelPosition, tintColor); } #endregion #region Methods /// /// Create the bird flock /// /// protected void SpawnFlock() { if (flock == null) { flock = new Flock(birdTexture, GraphicsDevice.Viewport.TitleSafeArea.Width, GraphicsDevice.Viewport.TitleSafeArea.Height, flockParams); } } /// /// Reset flock AI parameters /// private void ResetAIParams() { flockParams.DetectionDistance = detectionDefault; flockParams.SeparationDistance = separationDefault; flockParams.MoveInOldDirectionInfluence = moveInOldDirInfluenceDefault; flockParams.MoveInFlockDirectionInfluence = moveInFlockDirInfluenceDefault; flockParams.MoveInRandomDirectionInfluence = moveInRandomDirInfluenceDefault; flockParams.MaxTurnRadians = maxTurnRadiansDefault; flockParams.PerMemberWeight = perMemberWeightDefault; flockParams.PerDangerWeight = perDangerWeightDefault; } /// /// Create or remove the cat /// protected void ToggleCat() { if (cat == null) { cat = new Cat(catTexture, GraphicsDevice.Viewport.TitleSafeArea.Width, GraphicsDevice.Viewport.TitleSafeArea.Height); } else { cat = null; } } #endregion } }