2
0

ChaseAndEvadeGame.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // Game.cs
  4. //
  5. // Microsoft XNA Community Game Platform
  6. // Copyright (C) Microsoft Corporation. All rights reserved.
  7. //-----------------------------------------------------------------------------
  8. #endregion
  9. #region Using Statements
  10. using System;
  11. #if ANDROID
  12. using Android.App;
  13. #endif
  14. using Microsoft.Xna.Framework;
  15. using Microsoft.Xna.Framework.Audio;
  16. using Microsoft.Xna.Framework.Graphics;
  17. using Microsoft.Xna.Framework.Input;
  18. using Microsoft.Xna.Framework.Input.Touch;
  19. using Microsoft.Xna.Framework.Storage;
  20. using Microsoft.Xna.Framework.Content;
  21. using Microsoft.Xna.Framework.Media;
  22. #endregion
  23. namespace ChaseAndEvade
  24. {
  25. /// <summary>
  26. /// Sample showing how to implement simple chase, evade, and wander AI behaviors.
  27. /// The behaviors are based on the TurnToFace function, which was explained in
  28. /// AI Sample 1: Aiming.
  29. /// </summary>
  30. public class ChaseAndEvadeGame : Game
  31. {
  32. /// <summary>
  33. /// TankAiState is used to keep track of what the tank is currently doing.
  34. /// </summary>
  35. enum TankAiState
  36. {
  37. // chasing the cat
  38. Chasing,
  39. // the tank has gotten close enough that the cat that it can stop chasing it
  40. Caught,
  41. // the tank can't "see" the cat, and is wandering around.
  42. Wander
  43. }
  44. /// <summary>
  45. /// MouseAiState is used to keep track of what the mouse is currently doing.
  46. /// </summary>
  47. enum MouseAiState
  48. {
  49. // evading the cat
  50. Evading,
  51. // the mouse can't see the "cat", and it's wandering around.
  52. Wander
  53. }
  54. #region Constants
  55. // The following values control the different characteristics of the characters
  56. // in this sample, including their speed, turning rates. distances are specified
  57. // in pixels, angles are specified in radians.
  58. // how fast can the cat move?
  59. const float MaxCatSpeed = 7.5f;
  60. // how fast can the tank move?
  61. const float MaxTankSpeed = 5.0f;
  62. // how fast can he turn?
  63. const float TankTurnSpeed = 0.10f;
  64. // this value controls the distance at which the tank will start to chase the
  65. // cat.
  66. const float TankChaseDistance = 250.0f;
  67. // TankCaughtDistance controls the distance at which the tank will stop because
  68. // he has "caught" the cat.
  69. const float TankCaughtDistance = 60.0f;
  70. // this constant is used to avoid hysteresis, which is common in ai programming.
  71. // see the doc for more details.
  72. const float TankHysteresis = 15.0f;
  73. // how fast can the mouse move?
  74. const float MaxMouseSpeed = 8.5f;
  75. // and how fast can it turn?
  76. const float MouseTurnSpeed = 0.20f;
  77. // MouseEvadeDistance controls the distance at which the mouse will flee from
  78. // cat. If the mouse is further than "MouseEvadeDistance" pixels away, he will
  79. // consider himself safe.
  80. const float MouseEvadeDistance = 200.0f;
  81. // this constant is similar to TankHysteresis. The value is larger than the
  82. // tank's hysteresis value because the mouse is faster than the tank: with a
  83. // higher velocity, small fluctuations are much more visible.
  84. const float MouseHysteresis = 60.0f;
  85. #endregion
  86. #region Fields
  87. GraphicsDeviceManager graphics;
  88. SpriteBatch spriteBatch;
  89. SpriteFont spriteFont;
  90. Texture2D tankTexture;
  91. Vector2 tankTextureCenter;
  92. Vector2 tankPosition;
  93. TankAiState tankState = TankAiState.Wander;
  94. float tankOrientation;
  95. Vector2 tankWanderDirection;
  96. Texture2D catTexture;
  97. Vector2 catTextureCenter;
  98. Vector2 catPosition;
  99. Texture2D mouseTexture;
  100. Vector2 mouseTextureCenter;
  101. Vector2 mousePosition;
  102. MouseAiState mouseState = MouseAiState.Wander;
  103. float mouseOrientation;
  104. Vector2 mouseWanderDirection;
  105. Random random = new Random ();
  106. #endregion
  107. #region Initialization
  108. public ChaseAndEvadeGame ()
  109. {
  110. graphics = new GraphicsDeviceManager (this);
  111. Content.RootDirectory = "Content";
  112. #if WINDOWS_PHONE
  113. graphics.SupportedOrientations = DisplayOrientation.Portrait;
  114. graphics.PreferredBackBufferWidth = 480;
  115. graphics.PreferredBackBufferHeight = 800;
  116. TargetElapsedTime = TimeSpan.FromTicks(333333);
  117. #elif !MONOMAC
  118. graphics.PreferredBackBufferWidth = 320;
  119. graphics.PreferredBackBufferHeight = 480;
  120. #endif
  121. #if WINDOWS || MONOMAC || LINUX
  122. graphics.IsFullScreen = false;
  123. #else
  124. graphics.IsFullScreen = true;
  125. #endif
  126. }
  127. /// <summary>
  128. /// Overridden from the base Game.Initialize. Once the GraphicsDevice is setup,
  129. /// we'll use the viewport to initialize some values.
  130. /// </summary>
  131. protected override void Initialize ()
  132. {
  133. base.Initialize ();
  134. // once base.Initialize has finished, the GraphicsDevice will have been
  135. // created, and we'll know how big the Viewport is. We want the tank, cat
  136. // and mouse to be spread out across the screen, so we'll use the viewport
  137. // to figure out where they should be.
  138. Viewport vp = graphics.GraphicsDevice.Viewport;
  139. tankPosition = new Vector2 (vp.Width / 4, vp.Height / 2);
  140. catPosition = new Vector2 (vp.Width / 2, vp.Height / 2);
  141. mousePosition = new Vector2 (3 * vp.Width / 4, vp.Height / 2);
  142. }
  143. /// <summary>
  144. /// Load your graphics content.
  145. /// </summary>
  146. protected override void LoadContent ()
  147. {
  148. // create a SpriteBatch, and load the textures and font that we'll need
  149. // during the game.
  150. spriteBatch = new SpriteBatch (graphics.GraphicsDevice);
  151. spriteFont = Content.Load<SpriteFont> ("Arial");
  152. tankTexture = Content.Load<Texture2D> ("tank");
  153. catTexture = Content.Load<Texture2D> ("cat");
  154. mouseTexture = Content.Load<Texture2D> ("mouse");
  155. // once all the content is loaded, we can calculate the centers of each
  156. // of the textures that we loaded. Just like in the previous sample in
  157. // this series, the aiming sample, we want spriteBatch to draw the
  158. // textures centered on their position vectors. SpriteBatch.Draw will
  159. // center the sprite on the vector that we pass in as the "origin"
  160. // parameter, so we'll just calculate that to be the middle of
  161. // the texture.
  162. tankTextureCenter =
  163. new Vector2 (tankTexture.Width / 2, tankTexture.Height / 2);
  164. catTextureCenter =
  165. new Vector2 (catTexture.Width / 2, catTexture.Height / 2);
  166. mouseTextureCenter =
  167. new Vector2 (mouseTexture.Width / 2, mouseTexture.Height / 2);
  168. }
  169. #endregion
  170. #region Update and Draw
  171. /// <summary>
  172. /// Allows the game to run logic.
  173. /// </summary>
  174. protected override void Update (GameTime gameTime)
  175. {
  176. // handle input will read the controller input, and update the cat
  177. // to move according to the user's whim.
  178. HandleInput ();
  179. // UpdateTank will run the AI code that controls the tank's movement...
  180. UpdateTank ();
  181. // ... and UpdateMouse does the same thing for the mouse.
  182. UpdateMouse ();
  183. // Once we've finished that, we'll use the ClampToViewport helper function
  184. // to clamp everyone's position so that they stay on the screen.
  185. tankPosition = ClampToViewport (tankPosition);
  186. catPosition = ClampToViewport (catPosition);
  187. mousePosition = ClampToViewport (mousePosition);
  188. base.Update (gameTime);
  189. }
  190. /// <summary>
  191. /// This function takes a Vector2 as input, and returns that vector "clamped"
  192. /// to the current graphics viewport. We use this function to make sure that
  193. /// no one can go off of the screen.
  194. /// </summary>
  195. /// <param name="vector">an input vector</param>
  196. /// <returns>the input vector, clamped between the minimum and maximum of the
  197. /// viewport.</returns>
  198. private Vector2 ClampToViewport (Vector2 vector)
  199. {
  200. Viewport vp = graphics.GraphicsDevice.Viewport;
  201. vector.X = MathHelper.Clamp (vector.X, vp.X, vp.X + vp.Width);
  202. vector.Y = MathHelper.Clamp (vector.Y, vp.Y, vp.Y + vp.Height);
  203. return vector;
  204. }
  205. /// <summary>
  206. /// This function contains the code that controls the mouse. It decides what the
  207. /// mouse should do based on the position of the cat: if the cat is too close,
  208. /// it will attempt to flee. Otherwise, it will idly wander around the screen.
  209. ///
  210. /// </summary>
  211. private void UpdateMouse ()
  212. {
  213. // first, calculate how far away the mouse is from the cat, and use that
  214. // information to decide how to behave. If they are too close, the mouse
  215. // will switch to "active" mode - fleeing. if they are far apart, the mouse
  216. // will switch to "idle" mode, where it roams around the screen.
  217. // we use a hysteresis constant in the decision making process, as described
  218. // in the accompanying doc file.
  219. float distanceFromCat = Vector2.Distance (mousePosition, catPosition);
  220. // the cat is a safe distance away, so the mouse should idle:
  221. if (distanceFromCat > MouseEvadeDistance + MouseHysteresis) {
  222. mouseState = MouseAiState.Wander;
  223. }
  224. // the cat is too close; the mouse should run:
  225. else if (distanceFromCat < MouseEvadeDistance - MouseHysteresis) {
  226. mouseState = MouseAiState.Evading;
  227. }
  228. // if neither of those if blocks hit, we are in the "hysteresis" range,
  229. // and the mouse will continue doing whatever it is doing now.
  230. // the mouse will move at a different speed depending on what state it
  231. // is in. when idle it won't move at full speed, but when actively evading
  232. // it will move as fast as it can. this variable is used to track which
  233. // speed the mouse should be moving.
  234. float currentMouseSpeed;
  235. // the second step of the Update is to change the mouse's orientation based
  236. // on its current state.
  237. if (mouseState == MouseAiState.Evading) {
  238. // If the mouse is "active," it is trying to evade the cat. The evasion
  239. // behavior is accomplished by using the TurnToFace function to turn
  240. // towards a point on a straight line facing away from the cat. In other
  241. // words, if the cat is point A, and the mouse is point B, the "seek
  242. // point" is C.
  243. // C
  244. // B
  245. // A
  246. Vector2 seekPosition = 2 * mousePosition - catPosition;
  247. // Use the TurnToFace function, which we introduced in the AI Series 1:
  248. // Aiming sample, to turn the mouse towards the seekPosition. Now when
  249. // the mouse moves forward, it'll be trying to move in a straight line
  250. // away from the cat.
  251. mouseOrientation = TurnToFace (mousePosition, seekPosition,
  252. mouseOrientation, MouseTurnSpeed);
  253. // set currentMouseSpeed to MaxMouseSpeed - the mouse should run as fast
  254. // as it can.
  255. currentMouseSpeed = MaxMouseSpeed;
  256. } else {
  257. // if the mouse isn't trying to evade the cat, it should just meander
  258. // around the screen. we'll use the Wander function, which the mouse and
  259. // tank share, to accomplish this. mouseWanderDirection and
  260. // mouseOrientation are passed by ref so that the wander function can
  261. // modify them. for more information on ref parameters, see
  262. // http://msdn2.microsoft.com/en-us/library/14akc2c7(VS.80).aspx
  263. Wander (mousePosition, ref mouseWanderDirection, ref mouseOrientation,
  264. MouseTurnSpeed);
  265. // if the mouse is wandering, it should only move at 25% of its maximum
  266. // speed.
  267. currentMouseSpeed = .25f * MaxMouseSpeed;
  268. }
  269. // The final step is to move the mouse forward based on its current
  270. // orientation. First, we construct a "heading" vector from the orientation
  271. // angle. To do this, we'll use Cosine and Sine to tell us the x and y
  272. // components of the heading vector. See the accompanying doc for more
  273. // information.
  274. Vector2 heading = new Vector2 (
  275. (float)Math.Cos (mouseOrientation), (float)Math.Sin (mouseOrientation));
  276. // by multiplying the heading and speed, we can get a velocity vector. the
  277. // velocity vector is then added to the mouse's current position, moving him
  278. // forward.
  279. mousePosition += heading * currentMouseSpeed;
  280. }
  281. /// <summary>
  282. /// UpdateTank runs the AI code that will update the tank's orientation and
  283. /// position. It is very similar to UpdateMouse, but is slightly more
  284. /// complicated: where mouse only has two states, idle and active, the Tank has
  285. /// three.
  286. /// </summary>
  287. private void UpdateTank ()
  288. {
  289. // However, the tank's behavior is more complicated than the mouse's, and so
  290. // the decision making process is a little different.
  291. // First we have to use the current state to decide what the thresholds are
  292. // for changing state, as described in the doc.
  293. float tankChaseThreshold = TankChaseDistance;
  294. float tankCaughtThreshold = TankCaughtDistance;
  295. // if the tank is idle, he prefers to stay idle. we do this by making the
  296. // chase distance smaller, so the tank will be less likely to begin chasing
  297. // the cat.
  298. if (tankState == TankAiState.Wander) {
  299. tankChaseThreshold -= TankHysteresis / 2;
  300. }
  301. // similarly, if the tank is active, he prefers to stay active. we
  302. // accomplish this by increasing the range of values that will cause the
  303. // tank to go into the active state.
  304. else if (tankState == TankAiState.Chasing) {
  305. tankChaseThreshold += TankHysteresis / 2;
  306. tankCaughtThreshold -= TankHysteresis / 2;
  307. }
  308. // the same logic is applied to the finished state.
  309. else if (tankState == TankAiState.Caught) {
  310. tankCaughtThreshold += TankHysteresis / 2;
  311. }
  312. // Second, now that we know what the thresholds are, we compare the tank's
  313. // distance from the cat against the thresholds to decide what the tank's
  314. // current state is.
  315. float distanceFromCat = Vector2.Distance (tankPosition, catPosition);
  316. if (distanceFromCat > tankChaseThreshold) {
  317. // just like the mouse, if the tank is far away from the cat, it should
  318. // idle.
  319. tankState = TankAiState.Wander;
  320. } else if (distanceFromCat > tankCaughtThreshold) {
  321. tankState = TankAiState.Chasing;
  322. } else {
  323. tankState = TankAiState.Caught;
  324. }
  325. // Third, once we know what state we're in, act on that state.
  326. float currentTankSpeed;
  327. if (tankState == TankAiState.Chasing) {
  328. // the tank wants to chase the cat, so it will just use the TurnToFace
  329. // function to turn towards the cat's position. Then, when the tank
  330. // moves forward, he will chase the cat.
  331. tankOrientation = TurnToFace (tankPosition, catPosition, tankOrientation,
  332. TankTurnSpeed);
  333. currentTankSpeed = MaxTankSpeed;
  334. } else if (tankState == TankAiState.Wander) {
  335. // wander works just like the mouse's.
  336. Wander (tankPosition, ref tankWanderDirection, ref tankOrientation,
  337. TankTurnSpeed);
  338. currentTankSpeed = .25f * MaxTankSpeed;
  339. } else {
  340. // this part is different from the mouse. if the tank catches the cat,
  341. // it should stop. otherwise it will run right by, then spin around and
  342. // try to catch it all over again. The end result is that it will kind
  343. // of "run laps" around the cat, which looks funny, but is not what
  344. // we're after.
  345. currentTankSpeed = 0.0f;
  346. }
  347. // this calculation is also just like the mouse's: we construct a heading
  348. // vector based on the tank's orientation, and then make the tank move along
  349. // that heading.
  350. Vector2 heading = new Vector2 (
  351. (float)Math.Cos (tankOrientation), (float)Math.Sin (tankOrientation));
  352. tankPosition += heading * currentTankSpeed;
  353. }
  354. /// <summary>
  355. /// Wander contains functionality that is shared between both the mouse and the
  356. /// tank, and does just what its name implies: makes them wander around the
  357. /// screen. The specifics of the function are described in more detail in the
  358. /// accompanying doc.
  359. /// </summary>
  360. /// <param name="position">the position of the character that is wandering
  361. /// </param>
  362. /// <param name="wanderDirection">the direction that the character is currently
  363. /// wandering. this parameter is passed by reference because it is an input and
  364. /// output parameter: Wander accepts it as input, and will update it as well.
  365. /// </param>
  366. /// <param name="orientation">the character's orientation. this parameter is
  367. /// also passed by reference and is an input/output parameter.</param>
  368. /// <param name="turnSpeed">the character's maximum turning speed.</param>
  369. private void Wander (Vector2 position, ref Vector2 wanderDirection,
  370. ref float orientation, float turnSpeed)
  371. {
  372. // The wander effect is accomplished by having the character aim in a random
  373. // direction. Every frame, this random direction is slightly modified.
  374. // Finally, to keep the characters on the center of the screen, we have them
  375. // turn to face the screen center. The further they are from the screen
  376. // center, the more they will aim back towards it.
  377. // the first step of the wander behavior is to use the random number
  378. // generator to offset the current wanderDirection by some random amount.
  379. // .25 is a bit of a magic number, but it controls how erratic the wander
  380. // behavior is. Larger numbers will make the characters "wobble" more,
  381. // smaller numbers will make them more stable. we want just enough
  382. // wobbliness to be interesting without looking odd.
  383. wanderDirection.X +=
  384. MathHelper.Lerp (-.25f, .25f, (float)random.NextDouble ());
  385. wanderDirection.Y +=
  386. MathHelper.Lerp (-.25f, .25f, (float)random.NextDouble ());
  387. // we'll renormalize the wander direction, ...
  388. if (wanderDirection != Vector2.Zero) {
  389. wanderDirection.Normalize ();
  390. }
  391. // ... and then turn to face in the wander direction. We don't turn at the
  392. // maximum turning speed, but at 15% of it. Again, this is a bit of a magic
  393. // number: it works well for this sample, but feel free to tweak it.
  394. orientation = TurnToFace (position, position + wanderDirection, orientation,
  395. .15f * turnSpeed);
  396. // next, we'll turn the characters back towards the center of the screen, to
  397. // prevent them from getting stuck on the edges of the screen.
  398. Vector2 screenCenter = Vector2.Zero;
  399. screenCenter.X = graphics.GraphicsDevice.Viewport.Width / 2;
  400. screenCenter.Y = graphics.GraphicsDevice.Viewport.Height / 2;
  401. // Here we are creating a curve that we can apply to the turnSpeed. This
  402. // curve will make it so that if we are close to the center of the screen,
  403. // we won't turn very much. However, the further we are from the screen
  404. // center, the more we turn. At most, we will turn at 30% of our maximum
  405. // turn speed. This too is a "magic number" which works well for the sample.
  406. // Feel free to play around with this one as well: smaller values will make
  407. // the characters explore further away from the center, but they may get
  408. // stuck on the walls. Larger numbers will hold the characters to center of
  409. // the screen. If the number is too large, the characters may end up
  410. // "orbiting" the center.
  411. float distanceFromScreenCenter = Vector2.Distance (screenCenter, position);
  412. float MaxDistanceFromScreenCenter =
  413. Math.Min (screenCenter.Y, screenCenter.X);
  414. float normalizedDistance =
  415. distanceFromScreenCenter / MaxDistanceFromScreenCenter;
  416. float turnToCenterSpeed = .3f * normalizedDistance * normalizedDistance *
  417. turnSpeed;
  418. // once we've calculated how much we want to turn towards the center, we can
  419. // use the TurnToFace function to actually do the work.
  420. orientation = TurnToFace (position, screenCenter, orientation,
  421. turnToCenterSpeed);
  422. }
  423. /// <summary>
  424. /// Calculates the angle that an object should face, given its position, its
  425. /// target's position, its current angle, and its maximum turning speed.
  426. /// </summary>
  427. private static float TurnToFace (Vector2 position, Vector2 faceThis,
  428. float currentAngle, float turnSpeed)
  429. {
  430. // consider this diagram:
  431. // B
  432. // /|
  433. // / |
  434. // / | y
  435. // / o |
  436. // A--------
  437. // x
  438. //
  439. // where A is the position of the object, B is the position of the target,
  440. // and "o" is the angle that the object should be facing in order to
  441. // point at the target. we need to know what o is. using trig, we know that
  442. // tan(theta) = opposite / adjacent
  443. // tan(o) = y / x
  444. // if we take the arctan of both sides of this equation...
  445. // arctan( tan(o) ) = arctan( y / x )
  446. // o = arctan( y / x )
  447. // so, we can use x and y to find o, our "desiredAngle."
  448. // x and y are just the differences in position between the two objects.
  449. float x = faceThis.X - position.X;
  450. float y = faceThis.Y - position.Y;
  451. // we'll use the Atan2 function. Atan will calculates the arc tangent of
  452. // y / x for us, and has the added benefit that it will use the signs of x
  453. // and y to determine what cartesian quadrant to put the result in.
  454. // http://msdn2.microsoft.com/en-us/library/system.math.atan2.aspx
  455. float desiredAngle = (float)Math.Atan2 (y, x);
  456. // so now we know where we WANT to be facing, and where we ARE facing...
  457. // if we weren't constrained by turnSpeed, this would be easy: we'd just
  458. // return desiredAngle.
  459. // instead, we have to calculate how much we WANT to turn, and then make
  460. // sure that's not more than turnSpeed.
  461. // first, figure out how much we want to turn, using WrapAngle to get our
  462. // result from -Pi to Pi ( -180 degrees to 180 degrees )
  463. float difference = WrapAngle (desiredAngle - currentAngle);
  464. // clamp that between -turnSpeed and turnSpeed.
  465. difference = MathHelper.Clamp (difference, -turnSpeed, turnSpeed);
  466. // so, the closest we can get to our target is currentAngle + difference.
  467. // return that, using WrapAngle again.
  468. return WrapAngle (currentAngle + difference);
  469. }
  470. /// <summary>
  471. /// Returns the angle expressed in radians between -Pi and Pi.
  472. /// <param name="radians">the angle to wrap, in radians.</param>
  473. /// <returns>the input value expressed in radians from -Pi to Pi.</returns>
  474. /// </summary>
  475. private static float WrapAngle (float radians)
  476. {
  477. while (radians < -MathHelper.Pi) {
  478. radians += MathHelper.TwoPi;
  479. }
  480. while (radians > MathHelper.Pi) {
  481. radians -= MathHelper.TwoPi;
  482. }
  483. return radians;
  484. }
  485. /// <summary>
  486. /// This is called when the game should draw itself. Nothing too fancy in here,
  487. /// we'll just call Begin on the SpriteBatch, and then draw the tank, cat, and
  488. /// mouse, and some overlay text. Once we're finished drawing, we'll call
  489. /// SpriteBatch.End.
  490. /// </summary>
  491. protected override void Draw (GameTime gameTime)
  492. {
  493. GraphicsDevice device = graphics.GraphicsDevice;
  494. device.Clear (Color.CornflowerBlue);
  495. spriteBatch.Begin ();
  496. // draw the tank, cat and mouse...
  497. spriteBatch.Draw (tankTexture, tankPosition, null, Color.White,
  498. tankOrientation, tankTextureCenter, 1.0f, SpriteEffects.None, 0.0f);
  499. spriteBatch.Draw (catTexture, catPosition, null, Color.White,
  500. 0.0f, catTextureCenter, 1.0f, SpriteEffects.None, 0.0f);
  501. spriteBatch.Draw (mouseTexture, mousePosition, null, Color.White,
  502. mouseOrientation, mouseTextureCenter, 1.0f, SpriteEffects.None, 0.0f);
  503. // and then draw some text showing the tank's and mouse's current state.
  504. // to make the text stand out more, we'll draw the text twice, once black
  505. // and once white, to create a drop shadow effect.
  506. Vector2 shadowOffset = Vector2.One;
  507. spriteBatch.DrawString (spriteFont, "Tank State: \n" + tankState.ToString (),
  508. new Vector2 (10, 10) + shadowOffset, Color.Black);
  509. spriteBatch.DrawString (spriteFont, "Tank State: \n" + tankState.ToString (),
  510. new Vector2 (10, 10), Color.White);
  511. spriteBatch.DrawString (spriteFont, "Mouse State: \n" + mouseState.ToString (),
  512. new Vector2 (10, 90) + shadowOffset, Color.Black);
  513. spriteBatch.DrawString (spriteFont, "Mouse State: \n" + mouseState.ToString (),
  514. new Vector2 (10, 90), Color.White);
  515. spriteBatch.End ();
  516. base.Draw (gameTime);
  517. }
  518. #endregion
  519. #region Handle Input
  520. /// <summary>
  521. /// Handles input for quitting the game.
  522. /// </summary>
  523. void HandleInput ()
  524. {
  525. #if WINDOWS_PHONE
  526. KeyboardState currentKeyboardState = new KeyboardState();
  527. #else
  528. KeyboardState currentKeyboardState = Keyboard.GetState ();
  529. MouseState currentMouseState = Mouse.GetState ();
  530. #endif
  531. #if IPHONE || PSM
  532. GamePadState currentGamePadState = GamePad.GetState (PlayerIndex.One);
  533. // Check for exit.
  534. if (currentKeyboardState.IsKeyDown (Keys.Escape) ||
  535. currentGamePadState.Buttons.Back == ButtonState.Pressed) {
  536. Exit ();
  537. }
  538. #else
  539. // Check for exit.
  540. if (currentKeyboardState.IsKeyDown (Keys.Escape)) {
  541. Exit ();
  542. }
  543. #endif
  544. // check to see if the user wants to move the cat. we'll create a vector
  545. // called catMovement, which will store the sum of all the user's inputs.
  546. Vector2 catMovement = Vector2.Zero;
  547. //Move toward the touch point. We slow down the cat when it gets within a distance of MaxCatSpeed to the touch point.
  548. float smoothStop = 1;
  549. #if IPHONE || PSM
  550. // check to see if the user wants to move the cat. we'll create a vector
  551. // called catMovement, which will store the sum of all the user's inputs.
  552. catMovement = currentGamePadState.ThumbSticks.Left;
  553. // flip y: on the thumbsticks, down is -1, but on the screen, down is bigger
  554. // numbers.
  555. catMovement.Y *= -1;
  556. if (currentKeyboardState.IsKeyDown (Keys.Left) ||
  557. currentGamePadState.DPad.Left == ButtonState.Pressed) {
  558. catMovement.X -= 1.0f;
  559. }
  560. if (currentKeyboardState.IsKeyDown (Keys.Right) ||
  561. currentGamePadState.DPad.Right == ButtonState.Pressed) {
  562. catMovement.X += 1.0f;
  563. }
  564. if (currentKeyboardState.IsKeyDown (Keys.Up) ||
  565. currentGamePadState.DPad.Up == ButtonState.Pressed) {
  566. catMovement.Y -= 1.0f;
  567. }
  568. if (currentKeyboardState.IsKeyDown (Keys.Down) ||
  569. currentGamePadState.DPad.Down == ButtonState.Pressed) {
  570. catMovement.Y += 1.0f;
  571. }
  572. TouchCollection currentTouchCollection = TouchPanel.GetState();
  573. // TODO if (currentTouchCollection != null )
  574. {
  575. if (currentTouchCollection.Count > 0)
  576. {
  577. Vector2 touchPosition = currentTouchCollection[0].Position;
  578. if (touchPosition != catPosition)
  579. {
  580. catMovement = touchPosition - catPosition;
  581. float delta = MaxCatSpeed - MathHelper.Clamp(catMovement.Length(), 0, MaxCatSpeed);
  582. smoothStop = 1 - delta / MaxCatSpeed;
  583. }
  584. }
  585. }
  586. #else
  587. if (currentKeyboardState.IsKeyDown (Keys.Left)) {
  588. catMovement.X -= 1.0f;
  589. }
  590. if (currentKeyboardState.IsKeyDown (Keys.Right)) {
  591. catMovement.X += 1.0f;
  592. }
  593. if (currentKeyboardState.IsKeyDown (Keys.Up)) {
  594. catMovement.Y -= 1.0f;
  595. }
  596. if (currentKeyboardState.IsKeyDown (Keys.Down)) {
  597. catMovement.Y += 1.0f;
  598. }
  599. Vector2 mousePosition = new Vector2 (currentMouseState.X, currentMouseState.Y);
  600. if (currentMouseState.LeftButton == ButtonState.Pressed && mousePosition != catPosition) {
  601. catMovement = mousePosition - catPosition;
  602. float delta = MaxCatSpeed - MathHelper.Clamp (catMovement.Length (), 0, MaxCatSpeed);
  603. smoothStop = 1 - delta / MaxCatSpeed;
  604. }
  605. #endif
  606. // normalize the user's input, so the cat can never be going faster than
  607. // CatSpeed.
  608. if (catMovement != Vector2.Zero) {
  609. catMovement.Normalize ();
  610. }
  611. catPosition += catMovement * MaxCatSpeed * smoothStop;
  612. }
  613. #endregion
  614. }
  615. }