Brand new to all of this coding :( Any help at all is very much appreciated

Nov 2 2009 9:55 AM
Hiiii everyone... I am brand new to the whole idea of XNA programming and I'm trying to teach myself how to go about it...I am using the XNA code 'Chase and Evade' and am trying to implement a state for the mouse which stops it from moving when collision is detected with the cat - a captured state. I tried it earlier and my attempt was rubbish...the mouse state keeps saying Evading when in effect caught. Yikes :( #region File Description //----------------------------------------------------------------------------- // Game.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using System; #endregion namespace ChaseAndEvade { /// /// Sample showing how to implement simple chase, evade, and wander AI behaviors. /// The behaviors are based on the TurnToFace function, which was explained in /// AI Sample 1: Aiming. /// public class ChaseAndEvadeGame : Microsoft.Xna.Framework. Game { /// /// TankAiState is used to keep track of what the tank is currently doing. /// enum TankAiState { // chasing the cat Chasing, // the tank has gotten close enough that the cat that it can stop chasing it Caught, // the tank can't "see" the cat, and is wandering around. Wander } /// /// MouseAiState is used to keep track of what the mouse is currently doing. /// enum MouseAiState { // evading the cat Evading, // the mouse can't see the "cat", and it's wandering around. Wander, //the mouse has been captured by the cat Captured } #region Constants // The following values control the different characteristics of the characters // in this sample, including their speed, turning rates. distances are specified // in pixels, angles are specified in radians. // how fast can the cat move? const float MaxCatSpeed = 3.5f; // how fast can the tank move? const float MaxTankSpeed = 2.0f; // how fast can he turn? const float TankTurnSpeed = 0.10f; // this value controls the distance at which the tank will start to chase the // cat. const float TankChaseDistance = 450.0f; // TankCaughtDistance controls the distance at which the tank will stop because // he has "caught" the cat. const float TankCaughtDistance = 60.0f; // this constant is used to avoid hysteresis, which is common in ai programming. // see the doc for more details. const float TankHysteresis = 15.0f; // how fast can the mouse move? const float MaxMouseSpeed = 4.5f; // and how fast can it turn? const float MouseTurnSpeed = 0.30f; // this value controls the distance at which the mouse will enter into captured state const float MouseCapturedDistance = 0f; // MouseEvadeDistance controls the distance at which the mouse will flee from // cat. If the mouse is further than "MouseEvadeDistance" pixels away, he will // consider himself safe. const float MouseEvadeDistance = 100.0f; // this constant is similar to TankHysteresis. The value is larger than the // tank's hysteresis value because the mouse is faster than the tank: with a // higher velocity, small fluctuations are much more visible. const float MouseHysteresis = 20.0f; #endregion #region Fields GraphicsDeviceManager graphics; SpriteBatch spriteBatch; SpriteFont spriteFont; Texture2D tankTexture; Vector2 tankTextureCenter; Vector2 tankPosition; TankAiState tankState = TankAiState .Wander; float tankOrientation; Vector2 tankWanderDirection; Texture2D catTexture; Vector2 catTextureCenter; Vector2 catPosition; Texture2D mouseTexture; Vector2 mouseTextureCenter; Vector2 mousePosition; MouseAiState mouseState = MouseAiState .Wander; float mouseOrientation; Vector2 mouseWanderDirection; Random random = new Random (); #endregion // Our texture manipulation object TextureArrray textureMemory; Texture2D newTexture; #region Initialization /// /// standard everyday constructor, nothing too fancy here. /// public ChaseAndEvadeGame() { graphics = new GraphicsDeviceManager ( this ); Content.RootDirectory = "Content" ; graphics.PreferredBackBufferWidth = 800; graphics.PreferredBackBufferHeight = 600; } /// /// Overridden from the base Game.Initialize. Once the GraphicsDevice is setup, /// we'll use the viewport to initialize some values. /// protected override void Initialize() { base .Initialize(); // once base.Initialize has finished, the GraphicsDevice will have been // created, and we'll know how big the Viewport is. We want the tank, cat // and mouse to be spread out across the screen, so we'll use the viewport // to figure out where they should be. Viewport vp = graphics.GraphicsDevice.Viewport; tankPosition = new Vector2 (vp.Width / 4, vp.Height / 2); catPosition = new Vector2 (vp.Width / 2, vp.Height / 2); mousePosition = new Vector2 (3 * vp.Width / 4, vp.Height / 2); } /// /// Load your graphics content. /// protected override void LoadContent() { // create a SpriteBatch, and load the textures and font that we'll need // during the game. spriteBatch = new SpriteBatch (graphics.GraphicsDevice); spriteFont = Content.Load< SpriteFont >( "Arial" ); tankTexture = Content.Load< Texture2D >( "tank" ); catTexture = Content.Load< Texture2D >( "cat" ); mouseTexture = Content.Load< Texture2D >( "mouse" ); // once all the content is loaded, we can calculate the centers of each // of the textures that we loaded. Just like in the previous sample in // this series, the aiming sample, we want spriteBatch to draw the // textures centered on their position vectors. SpriteBatch.Draw will // center the sprite on the vector that we pass in as the "origin" // parameter, so we'll just calculate that to be the middle of // the texture. tankTextureCenter = new Vector2 (tankTexture.Width / 2, tankTexture.Height / 2); catTextureCenter = new Vector2 (catTexture.Width / 2, catTexture.Height / 2); mouseTextureCenter = new Vector2 (mouseTexture.Width / 2, mouseTexture.Height / 2); // create a blank texture for drawing on newTexture = new Texture2D (GraphicsDevice, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height, 1, TextureUsage .None, SurfaceFormat .Color); // create our texture manipulation object for getting and setting pixel colors textureMemory = new TextureArrray (GraphicsDevice, newTexture); } #endregion #region Update and Draw /// /// Allows the game to run logic. /// protected override void Update( GameTime gameTime) { // handle input will read the controller input, and update the cat // to move according to the user's whim. HandleInput(); // UpdateTank will run the AI code that controls the tank's movement... UpdateTank(); // ... and UpdateMouse does the same thing for the mouse. UpdateMouse(); // Once we've finished that, we'll use the ClampToViewport helper function // to clamp everyone's position so that they stay on the screen. tankPosition = ClampToViewport(tankPosition); catPosition = ClampToViewport(catPosition); mousePosition = ClampToViewport(mousePosition); base .Update(gameTime); } /// /// This function takes a Vector2 as input, and returns that vector "clamped" /// to the current graphics viewport. We use this function to make sure that /// no one can go off of the screen. /// /// an input vector /// the input vector, clamped between the minimum and maximum of the /// viewport. private Vector2 ClampToViewport( Vector2 vector) { Viewport vp = graphics.GraphicsDevice.Viewport; vector.X = MathHelper .Clamp(vector.X, vp.X, vp.X + vp.Width); vector.Y = MathHelper .Clamp(vector.Y, vp.Y, vp.Y + vp.Height); return vector; } /// /// This function contains the code that controls the mouse. It decides what the /// mouse should do based on the position of the cat: if the cat is too close, /// it will attempt to flee. Otherwise, it will idly wander around the screen. /// /// private void UpdateMouse() { // first, calculate how far away the mouse is from the cat, and use that // information to decide how to behave. If they are too close, the mouse // will switch to "active" mode - fleeing. if they are far apart, the mouse // will switch to "idle" mode, where it roams around the screen. // we use a hysteresis constant in the decision making process, as described // in the accompanying doc file. float distanceFromCat = Vector2 .Distance(mousePosition, catPosition); // the cat is a safe distance away, so the mouse should idle: if (distanceFromCat > MouseEvadeDistance + MouseHysteresis) { mouseState = MouseAiState .Wander; } // the cat is too close; the mouse should run: else if (distanceFromCat < MouseEvadeDistance - MouseHysteresis) { mouseState = MouseAiState .Evading; } // if neither of those if blocks hit, we are in the "hysteresis" range, // and the mouse will continue doing whatever it is doing now. // the cat is upon the mouse, so the mouse should stop: else if (distanceFromCat > MouseEvadeDistance) { mouseState = MouseAiState .Captured; } // the mouse will move at a different speed depending on what state it // is in. when idle it won't move at full speed, but when actively evading // it will move as fast as it can. this variable is used to track which // speed the mouse should be moving. float currentMouseSpeed; //If the mouse is captured it will stop moving if (mouseState == MouseAiState .Captured) { currentMouseSpeed = 0f; } // the second step of the Update is to change the mouse's orientation based // on its current state. else if (mouseState == MouseAiState .Evading) { // If the mouse is "active," it is trying to evade the cat. The evasion // behavior is accomplished by using the TurnToFace function to turn // towards a point on a straight line facing away from the cat. In other // words, if the cat is point A, and the mouse is point B, the "seek // point" is C. // C // B // A Vector2 seekPosition = 2 * mousePosition - catPosition; // Use the TurnToFace function, which we introduced in the AI Series 1: // Aiming sample, to turn the mouse towards the seekPosition. Now when // the mouse moves forward, it'll be trying to move in a straight line // away from the cat. mouseOrientation = TurnToFace(mousePosition, seekPosition, mouseOrientation, MouseTurnSpeed); // set currentMouseSpeed to MaxMouseSpeed - the mouse should run as fast // as it can. currentMouseSpeed = MaxMouseSpeed; } else { // if the mouse isn't trying to evade the cat, it should just meander // around the screen. we'll use the Wander function, which the mouse and // tank share, to accomplish this. mouseWanderDirection and // mouseOrientation are passed by ref so that the wander function can // modify them. for more information on ref parameters, see // http://msdn2.microsoft.com/en-us/library/14akc2c7(VS.80).aspx Wander(mousePosition, ref mouseWanderDirection, ref mouseOrientation, MouseTurnSpeed); // if the mouse is wandering, it should only move at 25% of its maximum // speed. currentMouseSpeed = .25f * MaxMouseSpeed; } // The final step is to move the mouse forward based on its current // orientation. First, we construct a "heading" vector from the orientation // angle. To do this, we'll use Cosine and Sine to tell us the x and y // components of the heading vector. See the accompanying doc for more // information. Vector2 heading = new Vector2 ( ( float ) Math .Cos(mouseOrientation), ( float ) Math .Sin(mouseOrientation)); // by multiplying the heading and speed, we can get a velocity vector. the // velocity vector is then added to the mouse's current position, moving him // forward. mousePosition += heading * currentMouseSpeed; Rectangle mouseBounds; mouseBounds.X = ( int )mousePosition.X - mouseTexture.Width / 2 ; if (mouseBounds.X < 0) mouseBounds.X = 0; mouseBounds.Y = ( int )mousePosition.Y - mouseTexture.Height / 2 ; if (mouseBounds.Y < 0) mouseBounds.Y = 0; mouseBounds.Width = mouseTexture.Width; mouseBounds.Height = mouseTexture.Height; if ( (textureMemory.isColorInRectangle(mouseBounds, Color .Black)) == true ) mousePosition -= heading * currentMouseSpeed; //textureMemory.DrawRectangle(mouseBounds, Color.White); } /// /// UpdateTank runs the AI code that will update the tank's orientation and /// position. It is very similar to UpdateMouse, but is slightly more /// complicated: where mouse only has two states, idle and active, the Tank has /// three. /// private void UpdateTank() { // However, the tank's behavior is more complicated than the mouse's, and so // the decision making process is a little different. // First we have to use the current state to decide what the thresholds are // for changing state, as described in the doc. float tankChaseThreshold = TankChaseDistance; float tankCaughtThreshold = TankCaughtDistance; // if the tank is idle, he prefers to stay idle. we do this by making the // chase distance smaller, so the tank will be less likely to begin chasing // the cat. if (tankState == TankAiState .Wander) { tankChaseThreshold -= TankHysteresis / 2; } // similarly, if the tank is active, he prefers to stay active. we // accomplish this by increasing the range of values that will cause the // tank to go into the active state. else if (tankState == TankAiState .Chasing) { tankChaseThreshold += TankHysteresis / 2; tankCaughtThreshold -= TankHysteresis / 2; } // the same logic is applied to the finished state. else if (tankState == TankAiState .Caught) { tankCaughtThreshold += TankHysteresis / 2; } // Second, now that we know what the thresholds are, we compare the tank's // distance from the cat against the thresholds to decide what the tank's // current state is. float distanceFromCat = Vector2 .Distance(tankPosition, catPosition); if (distanceFromCat > tankChaseThreshold) { // just like the mouse, if the tank is far away from the cat, it should // idle. tankState = TankAiState .Wander; } else if (distanceFromCat > tankCaughtThreshold) { tankState = TankAiState .Chasing; } else { tankState = TankAiState .Caught; } // Third, once we know what state we're in, act on that state. float currentTankSpeed; if (tankState == TankAiState .Chasing) { // the tank wants to chase the cat, so it will just use the TurnToFace // function to turn towards the cat's position. Then, when the tank // moves forward, he will chase the cat. tankOrientation = TurnToFace(tankPosition, catPosition, tankOrientation, TankTurnSpeed); currentTankSpeed = MaxTankSpeed; } else if (tankState == TankAiState .Wander) { // wander works just like the mouse's. Wander(tankPosition, ref tankWanderDirection, ref tankOrientation, TankTurnSpeed); currentTankSpeed = .25f * MaxTankSpeed; } else { // this part is different from the mouse. if the tank catches the cat, // it should stop. otherwise it will run right by, then spin around and // try to catch it all over again. The end result is that it will kind // of "run laps" around the cat, which looks funny, but is not what // we're after. currentTankSpeed = 0.0f; } // this calculation is also just like the mouse's: we construct a heading // vector based on the tank's orientation, and then make the tank move along // that heading. Vector2 heading = new Vector2 ( ( float ) Math .Cos(tankOrientation), ( float ) Math .Sin(tankOrientation)); tankPosition += heading * currentTankSpeed; Rectangle tankBounds; tankBounds.X = ( int )(tankPosition.X - ( float )tankTexture.Width / 2.0f); if (tankBounds.X < 0) tankBounds.X = 0; tankBounds.Y = ( int )(tankPosition.Y - ( float )tankTexture.Height / 2.0f); if (tankBounds.Y < 0) tankBounds.Y = 0; tankBounds.Width = tankTexture.Width; tankBounds.Height = tankTexture.Height; if ((textureMemory.isColorInRectangle(tankBounds, Color .Black)) == true ) tankPosition -= heading * currentTankSpeed; // draw bounding box to test visually // textureMemory.DrawRectangle(tankBounds, Color.White); } /// /// Wander contains functionality that is shared between both the mouse and the /// tank, and does just what its name implies: makes them wander around the /// screen. The specifics of the function are described in more detail in the /// accompanying doc. /// /// the position of the character that is wandering /// /// the direction that the character is currently /// wandering. this parameter is passed by reference because it is an input and /// output parameter: Wander accepts it as input, and will update it as well. /// /// the character's orientation. this parameter is /// also passed by reference and is an input/output parameter. /// the character's maximum turning speed. private void Wander( Vector2 position, ref Vector2 wanderDirection, ref float orientation, float turnSpeed) { // The wander effect is accomplished by having the character aim in a random // direction. Every frame, this random direction is slightly modified. // Finally, to keep the characters on the center of the screen, we have them // turn to face the screen center. The further they are from the screen // center, the more they will aim back towards it. // the first step of the wander behavior is to use the random number // generator to offset the current wanderDirection by some random amount. // .25 is a bit of a magic number, but it controls how erratic the wander // behavior is. Larger numbers will make the characters "wobble" more, // smaller numbers will make them more stable. we want just enough // wobbliness to be interesting without looking odd. wanderDirection.X += MathHelper .Lerp(-.15f, .15f, ( float )random.NextDouble()); wanderDirection.Y += MathHelper .Lerp(-.15f, .15f, ( float )random.NextDouble()); // we'll renormalize the wander direction, ... if (wanderDirection != Vector2 .Zero) { wanderDirection.Normalize(); } // ... and then turn to face in the wander direction. We don't turn at the // maximum turning speed, but at 15% of it. Again, this is a bit of a magic // number: it works well for this sample, but feel free to tweak it. orientation = TurnToFace(position, position + wanderDirection, orientation, .15f * turnSpeed); // next, we'll turn the characters back towards the center of the screen, to // prevent them from getting stuck on the edges of the screen. Vector2 screenCenter = Vector2 .Zero; screenCenter.X = graphics.GraphicsDevice.Viewport.Width / 2; screenCenter.Y = graphics.GraphicsDevice.Viewport.Height / 2; // Here we are creating a curve that we can apply to the turnSpeed. This // curve will make it so that if we are close to the center of the screen, // we won't turn very much. However, the further we are from the screen // center, the more we turn. At most, we will turn at 30% of our maximum // turn speed. This too is a "magic number" which works well for the sample. // Feel free to play around with this one as well: smaller values will make // the characters explore further away from the center, but they may get // stuck on the walls. Larger numbers will hold the characters to center of // the screen. If the number is too large, the characters may end up // "orbiting" the center. float distanceFromScreenCenter = Vector2 .Distance(screenCenter, position); float MaxDistanceFromScreenCenter = Math .Min(screenCenter.Y, screenCenter.X); float normalizedDistance = distanceFromScreenCenter / MaxDistanceFromScreenCenter; float turnToCenterSpeed = .3f * normalizedDistance * normalizedDistance * turnSpeed; // once we've calculated how much we want to turn towards the center, we can // use the TurnToFace function to actually do the work. orientation = TurnToFace(position, screenCenter, orientation, turnToCenterSpeed ); } /// /// Calculates the angle that an object should face, given its position, its /// target's position, its current angle, and its maximum turning speed. /// private static float TurnToFace( Vector2 position, Vector2 faceThis, float currentAngle, float turnSpeed) { // consider this diagram: // B // /| // / | // / | y // / o | // A-------- // x // // where A is the position of the object, B is the position of the target, // and "o" is the angle that the object should be facing in order to // point at the target. we need to know what o is. using trig, we know that // tan(theta) = opposite / adjacent // tan(o) = y / x // if we take the arctan of both sides of this equation... // arctan( tan(o) ) = arctan( y / x ) // o = arctan( y / x ) // so, we can use x and y to find o, our "desiredAngle." // x and y are just the differences in position between the two objects. float x = faceThis.X - position.X; float y = faceThis.Y - position.Y; // we'll use the Atan2 function. Atan will calculates the arc tangent of // y / x for us, and has the added benefit that it will use the signs of x // and y to determine what cartesian quadrant to put the result in. // http://msdn2.microsoft.com/en-us/library/system.math.atan2.aspx float desiredAngle = ( float ) Math .Atan2(y, x); // so now we know where we WANT to be facing, and where we ARE facing... // if we weren't constrained by turnSpeed, this would be easy: we'd just // return desiredAngle. // instead, we have to calculate how much we WANT to turn, and then make // sure that's not more than turnSpeed. // first, figure out how much we want to turn, using WrapAngle to get our // result from -Pi to Pi ( -180 degrees to 180 degrees ) float difference = WrapAngle(desiredAngle - currentAngle); // clamp that between -turnSpeed and turnSpeed. difference = MathHelper .Clamp(difference, -turnSpeed, turnSpeed); // so, the closest we can get to our target is currentAngle + difference. // return that, using WrapAngle again. return WrapAngle(currentAngle + difference); } /// /// Returns the angle expressed in radians between -Pi and Pi. /// the angle to wrap, in radians. /// the input value expressed in radians from -Pi to Pi. /// private static float WrapAngle( float radians) { while (radians < - MathHelper .Pi) { radians += MathHelper .TwoPi; } while (radians > MathHelper .Pi) { radians -= MathHelper .TwoPi; } return radians; } /// /// This is called when the game should draw itself. Nothing too fancy in here, /// we'll just call Begin on the SpriteBatch, and then draw the tank, cat, and /// mouse, and some overlay text. Once we're finished drawing, we'll call /// SpriteBatch.End. /// protected override void Draw( GameTime gameTime) { GraphicsDevice device = graphics.GraphicsDevice; device.Clear( Color .SeaGreen); spriteBatch.Begin(); // Draw 4 black rectangles on to the texture array memory and then draw this to video memory Rectangle [,] rectArray = new Rectangle [2, 2]; for ( int i =0; i<2; i++) for ( int j = 0; j < 2; j++) { rectArray[i, j].X = 100 + 500 * i; rectArray[i, j].Y = 100 + 300 * j; rectArray[i, j].Width = 100; rectArray[i, j].Height = 100; textureMemory.DrawRectangle(rectArray[i,j], Color .Black); } spriteBatch.Draw(textureMemory.mTexture, Vector2 .Zero, Color .White); // a cheap check to see if it works // if (textureMemory.isColorInRectangle(rect, Color.Black)) Exit(); // draw the tank, cat and mouse... spriteBatch.Draw(tankTexture, tankPosition, null , Color .White, tankOrientation, tankTextureCenter, 1.0f, SpriteEffects .None, 0.0f); spriteBatch.Draw(catTexture, catPosition, null , Color .White, 0.0f, catTextureCenter, 1.0f, SpriteEffects .None, 0.0f); spriteBatch.Draw(mouseTexture, mousePosition, null , Color .White, mouseOrientation, mouseTextureCenter, 1.0f, SpriteEffects .None, 0.0f); // and then draw some text showing the tank's and mouse's current state. // to make the text stand out more, we'll draw the text twice, once black // and once white, to create a drop shadow effect. Vector2 shadowOffset = Vector2 .One; Vector2 screenCenter = Vector2 .Zero; screenCenter.X = graphics.GraphicsDevice.Viewport.Width / 2; screenCenter.Y = graphics.GraphicsDevice.Viewport.Height / 2; spriteBatch.DrawString(spriteFont, "Tank State: " + tankState.ToString(), new Vector2 (10, 10) + shadowOffset, Color .Black); spriteBatch.DrawString(spriteFont, "Tank State: " + tankState.ToString(), new Vector2 (10, 10), Color .White); spriteBatch.DrawString(spriteFont, "Mouse State: " + mouseState.ToString(), new Vector2 (10, 35) + shadowOffset, Color .Black); spriteBatch.DrawString(spriteFont, "Mouse State: " + mouseState.ToString(), new Vector2 (10, 35), Color .White); spriteBatch.End(); base .Draw(gameTime); } #endregion #region Handle Input /// /// Handles input for quitting the game. /// void HandleInput() { KeyboardState currentKeyboardState = Keyboard .GetState(); GamePadState currentGamePadState = GamePad .GetState( PlayerIndex .One); // Check for exit. if (currentKeyboardState.IsKeyDown( Keys .Escape) || currentGamePadState.Buttons.Back == ButtonState .Pressed) { Exit(); } // check to see if the user wants to move the cat. we'll create a vector // called catMovement, which will store the sum of all the user's inputs. Vector2 catMovement = currentGamePadState.ThumbSticks.Left; // flip y: on the thumbsticks, down is -1, but on the screen, down is bigger // numbers. catMovement.Y *= -1; if (currentKeyboardState.IsKeyDown( Keys .Left) || currentGamePadState.DPad.Left == ButtonState .Pressed) { catMovement.X -= 1.0f; } if (currentKeyboardState.IsKeyDown( Keys .Right) || currentGamePadState.DPad.Right == ButtonState .Pressed) { catMovement.X += 1.0f; } if (currentKeyboardState.IsKeyDown( Keys .Up) || currentGamePadState.DPad.Up == ButtonState .Pressed) { catMovement.Y -= 1.0f; } if (currentKeyboardState.IsKeyDown( Keys .Down) || currentGamePadState.DPad.Down == ButtonState .Pressed) { catMovement.Y += 1.0f; } // normalize the user's input, so the cat can never be going faster than // CatSpeed. if (catMovement != Vector2 .Zero) { catMovement.Normalize(); } catPosition += catMovement * MaxCatSpeed; } #endregion } #region Entry Point /// /// The main entry point for the application. /// static class Program { static void Main() { using ( ChaseAndEvadeGame game = new ChaseAndEvadeGame ()) { game.Run(); } } } #endregion

Answers (1)