FlockingSample.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. #region File Description
  2. //-----------------------------------------------------------------------------
  3. // FlockingSample.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. using System.Collections.Generic;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Content;
  14. using Microsoft.Xna.Framework.Graphics;
  15. using Microsoft.Xna.Framework.Input;
  16. using Microsoft.Xna.Framework.Input.Touch;
  17. #endregion
  18. namespace Flocking
  19. {
  20. #region FlockingAIParameters
  21. public struct AIParameters
  22. {
  23. /// <summary>
  24. /// how far away the animals see each other
  25. /// </summary>
  26. public float DetectionDistance;
  27. /// <summary>
  28. /// seperate from animals inside this distance
  29. /// </summary>
  30. public float SeparationDistance;
  31. /// <summary>
  32. /// how much the animal tends to move in it's previous direction
  33. /// </summary>
  34. public float MoveInOldDirectionInfluence;
  35. /// <summary>
  36. /// how much the animal tends to move with animals in it's detection distance
  37. /// </summary>
  38. public float MoveInFlockDirectionInfluence;
  39. /// <summary>
  40. /// how much the animal tends to move randomly
  41. /// </summary>
  42. public float MoveInRandomDirectionInfluence;
  43. /// <summary>
  44. /// how quickly the animal can turn
  45. /// </summary>
  46. public float MaxTurnRadians;
  47. /// <summary>
  48. /// how much each nearby animal influences it's behavior
  49. /// </summary>
  50. public float PerMemberWeight;
  51. /// <summary>
  52. /// how much dangerous animals influence it's behavior
  53. /// </summary>
  54. public float PerDangerWeight;
  55. }
  56. #endregion
  57. /// <summary>
  58. /// This is the main type for your game
  59. /// </summary>
  60. public class FlockingSample : Microsoft.Xna.Framework.Game
  61. {
  62. #region Constants
  63. // X location to start drawing the HUD from
  64. const int hudLocX = 200;
  65. // Y location to start drawing the HUD from
  66. const int hudLocY = 30;
  67. // Min value for the distance sliders
  68. const float sliderMin = 0.0f;
  69. // Max value for the distance sliders
  70. const float sliderMax = 100f;
  71. // Width of the slider button
  72. const int sliderButtonWidth = 10;
  73. // Default value for the AI parameters
  74. const float detectionDefault = 70.0f;
  75. const float separationDefault = 50.0f;
  76. const float moveInOldDirInfluenceDefault = 1.0f;
  77. const float moveInFlockDirInfluenceDefault = 1.0f;
  78. const float moveInRandomDirInfluenceDefault = 0.05f;
  79. const float maxTurnRadiansDefault = 6.0f;
  80. const float perMemberWeightDefault = 1.0f;
  81. const float perDangerWeightDefault = 50.0f;
  82. #endregion
  83. #region Fields
  84. GraphicsDeviceManager graphics;
  85. SpriteBatch spriteBatch;
  86. InputState inputState;
  87. SpriteFont hudFont;
  88. // Do we need to update AI parameers this Update
  89. bool aiParameterUpdate = false;
  90. bool moveCat = false;
  91. #if WINDOWS || XBOX
  92. Texture2D bButton;
  93. Texture2D xButton;
  94. Texture2D yButton;
  95. #endif
  96. Texture2D onePixelWhite;
  97. Texture2D birdTexture;
  98. Texture2D catTexture;
  99. Cat cat;
  100. Flock flock;
  101. AIParameters flockParams;
  102. // Definte the dimensions of the controls
  103. Rectangle barDetectionDistance = new Rectangle(205, 45, 85, 40);
  104. Rectangle barSeparationDistance = new Rectangle(205, 125, 85, 40);
  105. Rectangle buttonResetDistance = new Rectangle(105, 205, 140, 40);
  106. Rectangle buttonResetFlock = new Rectangle(105, 285, 140, 40);
  107. Rectangle buttonToggleCat = new Rectangle(105, 365, 140, 40);
  108. int selectionNum;
  109. #endregion
  110. #region Initialization
  111. public FlockingSample()
  112. {
  113. graphics = new GraphicsDeviceManager(this);
  114. Content.RootDirectory = "Content";
  115. #if WINDOWS_PHONE || IOS
  116. // Frame rate is 30 fps by default for Windows Phone.
  117. TargetElapsedTime = TimeSpan.FromTicks(333333);
  118. graphics.IsFullScreen = true;
  119. #endif
  120. inputState = new InputState();
  121. flock = null;
  122. cat = null;
  123. flockParams = new AIParameters();
  124. ResetAIParams();
  125. }
  126. /// <summary>
  127. /// Allows the game to perform any initialization it needs to before starting
  128. /// to run. This is where it can query for any required services and load any
  129. /// non-graphic related content. Calling base.Initialize will enumerate
  130. /// through any components and initialize them as well.
  131. /// </summary>
  132. protected override void Initialize()
  133. {
  134. // Enable the gestures we care about. you must set EnabledGestures before
  135. // you can use any of the other gesture APIs.
  136. TouchPanel.EnabledGestures =
  137. GestureType.Tap |
  138. GestureType.FreeDrag;
  139. base.Initialize();
  140. }
  141. /// <summary>
  142. /// LoadContent will be called once per game and is the place to load
  143. /// all of your content.
  144. /// </summary>
  145. protected override void LoadContent()
  146. {
  147. spriteBatch = new SpriteBatch(GraphicsDevice);
  148. catTexture = Content.Load<Texture2D>("cat");
  149. birdTexture = Content.Load<Texture2D>("mouse");
  150. #if WINDOWS || XBOX
  151. bButton = Content.Load<Texture2D>("xboxControllerButtonB");
  152. xButton = Content.Load<Texture2D>("xboxControllerButtonX");
  153. yButton = Content.Load<Texture2D>("xboxControllerButtonY");
  154. #endif
  155. hudFont = Content.Load<SpriteFont>("HUDFont");
  156. onePixelWhite = new Texture2D(
  157. GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
  158. // TODO onePixelWhite.SetData<Color>(new Color[] { Color.White });
  159. }
  160. #endregion
  161. #region Handle Input
  162. /// <summary>
  163. /// Handles input for quitting the game.
  164. /// </summary>
  165. void HandleInput()
  166. {
  167. inputState.Update();
  168. // Check for exit.
  169. if (inputState.Exit)
  170. {
  171. Exit();
  172. }
  173. float dragDelta = 0f;
  174. // Check to see whether the user wants to modify their currently selected
  175. // weight.
  176. if (inputState.Up)
  177. {
  178. selectionNum--;
  179. if (selectionNum < 0)
  180. selectionNum = 1;
  181. }
  182. if (inputState.Down)
  183. {
  184. selectionNum = (selectionNum + 1) % 2;
  185. }
  186. // Update move for the cat
  187. if (cat != null)
  188. {
  189. cat.HandleInput(inputState);
  190. }
  191. // Turn the cat on or off
  192. if (inputState.ToggleCatButton)
  193. {
  194. ToggleCat();
  195. }
  196. // Resets flock parameters back to default
  197. if (inputState.ResetDistances)
  198. {
  199. ResetAIParams();
  200. aiParameterUpdate = true;
  201. }
  202. // Resets the location and orientation of the members of the flock
  203. if (inputState.ResetFlock)
  204. {
  205. flock.ResetFlock();
  206. aiParameterUpdate = true;
  207. }
  208. dragDelta = inputState.SliderMove;
  209. // Apply to the changeAmount to the currentlySelectedWeight
  210. switch (selectionNum)
  211. {
  212. case 0:
  213. flockParams.DetectionDistance += dragDelta;
  214. break;
  215. case 1:
  216. flockParams.SeparationDistance += dragDelta;
  217. break;
  218. default:
  219. break;
  220. }
  221. if (dragDelta != 0f)
  222. aiParameterUpdate = true;
  223. // By default we can move the cat but if a touch registers against a control do not move the cat
  224. moveCat = true;
  225. TouchCollection rawTouch = TouchPanel.GetState();
  226. // Use raw touch for the sliders
  227. if (rawTouch.Count > 0)
  228. {
  229. // Only grab the first one
  230. TouchLocation touchLocation = rawTouch[0];
  231. // Create a collidable rectangle to determine if we touched the controls
  232. Rectangle touchRectangle = new Rectangle((int)touchLocation.Position.X,
  233. (int)touchLocation.Position.Y, 20, 20);
  234. // Have the sliders rely on the raw touch to function properly
  235. SliderInputHelper(touchRectangle);
  236. }
  237. // Next we handle all of the gestures. since we may have multiple gestures available,
  238. // we use a loop to read in all of the gestures. this is important to make sure the
  239. // TouchPanel's queue doesn't get backed up with old data
  240. while (TouchPanel.IsGestureAvailable)
  241. {
  242. // Read the next gesture from the queue
  243. GestureSample gesture = TouchPanel.ReadGesture();
  244. // Create a collidable rectangle to determine if we touched the controls
  245. Rectangle touch = new Rectangle((int)gesture.Position.X, (int)gesture.Position.Y, 20, 20);
  246. // We can use the type of gesture to determine our behavior
  247. switch (gesture.GestureType)
  248. {
  249. case GestureType.Tap:
  250. if (buttonResetDistance.Intersects(touch))
  251. {
  252. // Resets flock parameters back to default
  253. ResetAIParams();
  254. aiParameterUpdate = true;
  255. moveCat = false;
  256. }
  257. else if (buttonResetFlock.Intersects(touch))
  258. {
  259. // Resets the location and orientation of the members of the flock
  260. flock.ResetFlock();
  261. aiParameterUpdate = true;
  262. moveCat = false;
  263. }
  264. else if (buttonToggleCat.Intersects(touch))
  265. {
  266. ToggleCat();
  267. moveCat = false;
  268. }
  269. break;
  270. }
  271. // Check if we can move the cat
  272. if (cat != null && moveCat)
  273. {
  274. // If we did not touch any controls then move the cat
  275. cat.Location = gesture.Position;
  276. }
  277. }
  278. // Clamp the slider values
  279. flockParams.DetectionDistance = MathHelper.Clamp(flockParams.DetectionDistance, sliderMin, sliderMax);
  280. flockParams.SeparationDistance = MathHelper.Clamp(flockParams.SeparationDistance, sliderMin, sliderMax);
  281. if (aiParameterUpdate)
  282. {
  283. flock.FlockParams = flockParams;
  284. }
  285. }
  286. /// <summary>
  287. /// Helper function that handles Slider interaction logic
  288. /// </summary>
  289. /// <param name="touchRectangle">Rectangle representing a touch</param>
  290. private void SliderInputHelper( Rectangle touchRectangle)
  291. {
  292. if (barDetectionDistance.Intersects(touchRectangle))
  293. {
  294. selectionNum = 0;
  295. aiParameterUpdate = true;
  296. moveCat = false;
  297. flockParams.DetectionDistance = touchRectangle.X - barDetectionDistance.X;
  298. }
  299. else if (barSeparationDistance.Intersects(touchRectangle))
  300. {
  301. selectionNum = 1;
  302. aiParameterUpdate = true;
  303. moveCat = false;
  304. flockParams.SeparationDistance = touchRectangle.X - barDetectionDistance.X;
  305. }
  306. }
  307. #endregion
  308. #region Update and Draw
  309. /// <summary>
  310. /// Allows the game to run logic such as updating the world,
  311. /// checking for collisions, gathering input, and playing audio.
  312. /// </summary>
  313. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  314. protected override void Update(GameTime gameTime)
  315. {
  316. HandleInput();
  317. if (cat != null)
  318. {
  319. cat.Update(gameTime);
  320. }
  321. if (flock != null)
  322. {
  323. flock.Update(gameTime, cat);
  324. }
  325. else
  326. {
  327. SpawnFlock();
  328. }
  329. base.Update(gameTime);
  330. }
  331. /// <summary>
  332. /// This is called when the game should draw itself.
  333. /// </summary>
  334. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  335. protected override void Draw(GameTime gameTime)
  336. {
  337. graphics.GraphicsDevice.Clear(Color.CornflowerBlue);
  338. spriteBatch.Begin();
  339. if (flock != null)
  340. {
  341. flock.Draw(spriteBatch, gameTime);
  342. }
  343. if (cat != null)
  344. {
  345. cat.Draw(spriteBatch, gameTime);
  346. }
  347. // Draw all the HUD elements
  348. DrawBar(barDetectionDistance, flockParams.DetectionDistance / 100f,
  349. "Detection Distance:", gameTime, selectionNum == 0);
  350. DrawBar(barSeparationDistance, flockParams.SeparationDistance / 100f,
  351. "Separation Distance:", gameTime, selectionNum == 1);
  352. #if WINDOWS_PHONE || IOS || PSM
  353. DrawButton(buttonResetDistance, "Reset Distance");
  354. DrawButton(buttonResetFlock, "Reset Flock");
  355. DrawButton(buttonToggleCat, "Add/Remove Cat");
  356. #else
  357. spriteBatch.Draw(bButton,
  358. new Vector2(hudLocX + 110.0f, hudLocY), Color.White);
  359. spriteBatch.Draw(xButton,
  360. new Vector2(hudLocX + 110.0f, hudLocY + 20.0f), Color.White);
  361. spriteBatch.Draw(yButton,
  362. new Vector2(hudLocX + 110.0f, hudLocY + 40.0f), Color.White);
  363. spriteBatch.DrawString(hudFont, "Reset Distances",
  364. new Vector2(hudLocX + 135.0f, hudLocY), Color.White);
  365. spriteBatch.DrawString(hudFont, "Reset flock",
  366. new Vector2(hudLocX + 135.0f, hudLocY+20.0f), Color.White);
  367. spriteBatch.DrawString(hudFont, "Spawn/remove cat",
  368. new Vector2(hudLocX + 135.0f, hudLocY+40.0f), Color.White);
  369. #endif
  370. spriteBatch.End();
  371. base.Draw(gameTime);
  372. }
  373. /// <summary>
  374. /// Helper function used by Draw. It is used to draw the buttons
  375. /// </summary>
  376. /// <param name="button"></param>
  377. /// <param name="label"></param>
  378. private void DrawButton(Rectangle button, string label)
  379. {
  380. spriteBatch.Draw(onePixelWhite, button, Color.Orange);
  381. spriteBatch.DrawString(hudFont, label, new Vector2(button.Left + 10, button.Top + 10), Color.Black);
  382. }
  383. /// <summary>
  384. /// Helper function used by Draw. It is used to draw the slider bars
  385. /// </summary>
  386. private void DrawBar(Rectangle bar, float barWidthNormalized, string label, GameTime gameTime, bool highlighted)
  387. {
  388. Color tintColor = Color.White;
  389. // If the bar is highlighted, we want to make it pulse with a red tint.
  390. if (highlighted)
  391. {
  392. // To do this, we'll first generate a value t, which we'll use to
  393. // determine how much tint to have.
  394. float t = (float)Math.Sin(10 * gameTime.TotalGameTime.TotalSeconds);
  395. // Sin varies from -1 to 1, and we want t to go from 0 to 1, so we'll
  396. // scale it now.
  397. t = .5f + .5f * t;
  398. // Finally, we'll calculate our tint color by using Lerp to generate
  399. // a color in between Red and White.
  400. tintColor = new Color(Vector4.Lerp(
  401. Color.Red.ToVector4(), Color.White.ToVector4(), t));
  402. }
  403. // Calculate how wide the bar should be, and then draw it.
  404. bar.Height /= 2;
  405. spriteBatch.Draw(onePixelWhite, bar, Color.White);
  406. // Draw the slider
  407. spriteBatch.Draw(onePixelWhite, new Rectangle(bar.X + (int)(bar.Width * barWidthNormalized),
  408. bar.Y - bar.Height / 2, sliderButtonWidth, bar.Height * 2), Color.Orange);
  409. // Finally, draw the label to the left of the bar.
  410. Vector2 labelSize = hudFont.MeasureString(label);
  411. Vector2 labelPosition = new Vector2(bar.X - 5 - labelSize.X, bar.Y);
  412. spriteBatch.DrawString(hudFont, label, labelPosition, tintColor);
  413. }
  414. #endregion
  415. #region Methods
  416. /// <summary>
  417. /// Create the bird flock
  418. /// </summary>
  419. /// <param name="theNum"></param>
  420. protected void SpawnFlock()
  421. {
  422. if (flock == null)
  423. {
  424. flock = new Flock(birdTexture, GraphicsDevice.Viewport.TitleSafeArea.Width,
  425. GraphicsDevice.Viewport.TitleSafeArea.Height, flockParams);
  426. }
  427. }
  428. /// <summary>
  429. /// Reset flock AI parameters
  430. /// </summary>
  431. private void ResetAIParams()
  432. {
  433. flockParams.DetectionDistance = detectionDefault;
  434. flockParams.SeparationDistance = separationDefault;
  435. flockParams.MoveInOldDirectionInfluence = moveInOldDirInfluenceDefault;
  436. flockParams.MoveInFlockDirectionInfluence = moveInFlockDirInfluenceDefault;
  437. flockParams.MoveInRandomDirectionInfluence = moveInRandomDirInfluenceDefault;
  438. flockParams.MaxTurnRadians = maxTurnRadiansDefault;
  439. flockParams.PerMemberWeight = perMemberWeightDefault;
  440. flockParams.PerDangerWeight = perDangerWeightDefault;
  441. }
  442. /// <summary>
  443. /// Create or remove the cat
  444. /// </summary>
  445. protected void ToggleCat()
  446. {
  447. if (cat == null)
  448. {
  449. cat = new Cat(catTexture, GraphicsDevice.Viewport.TitleSafeArea.Width,
  450. GraphicsDevice.Viewport.TitleSafeArea.Height);
  451. }
  452. else
  453. {
  454. cat = null;
  455. }
  456. }
  457. #endregion
  458. }
  459. }