FlockingSample.cs 19 KB

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