Game1.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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. using System.Collections.Generic;
  12. using Microsoft.Xna.Framework;
  13. using Microsoft.Xna.Framework.Audio;
  14. using Microsoft.Xna.Framework.Content;
  15. using Microsoft.Xna.Framework.Graphics;
  16. using Microsoft.Xna.Framework.Input;
  17. using Microsoft.Xna.Framework.Storage;
  18. #endregion
  19. namespace TransformedCollision
  20. {
  21. /// <summary>
  22. /// This is the main type for your game
  23. /// </summary>
  24. public class TransformedCollisionGame : Microsoft.Xna.Framework.Game
  25. {
  26. GraphicsDeviceManager graphics;
  27. // The images we will draw
  28. Texture2D personTexture;
  29. Texture2D blockTexture;
  30. // The color data for the images; used for per pixel collision
  31. Color[] personTextureData;
  32. Color[] blockTextureData;
  33. // The images will be drawn with this SpriteBatch
  34. SpriteBatch spriteBatch;
  35. // Person
  36. Vector2 personPosition;
  37. const int PersonMoveSpeed = 5;
  38. // Blocks
  39. List<Block> blocks = new List<Block>();
  40. float BlockSpawnProbability = 0.01f;
  41. const int BlockFallSpeed = 1;
  42. const float BlockRotateSpeed = 0.005f;
  43. Vector2 blockOrigin;
  44. Random random = new Random();
  45. // For when a collision is detected
  46. bool personHit = false;
  47. // The sub-rectangle of the drawable area which should be visible on all TVs
  48. Rectangle safeBounds;
  49. // Percentage of the screen on every side is the safe area
  50. const float SafeAreaPortion = 0.05f;
  51. public TransformedCollisionGame()
  52. {
  53. graphics = new GraphicsDeviceManager(this);
  54. Content.RootDirectory = "Content";
  55. }
  56. /// <summary>
  57. /// Allows the game to perform any initialization it needs to before starting to
  58. /// run. This is where it can query for any required services and load any
  59. /// non-graphic related content. Calling base.Initialize will enumerate through
  60. /// any components and initialize them as well.
  61. /// </summary>
  62. protected override void Initialize()
  63. {
  64. base.Initialize();
  65. // Calculate safe bounds based on current resolution
  66. Viewport viewport = graphics.GraphicsDevice.Viewport;
  67. safeBounds = new Rectangle(
  68. (int)(viewport.Width * SafeAreaPortion),
  69. (int)(viewport.Height * SafeAreaPortion),
  70. (int)(viewport.Width * (1 - 2 * SafeAreaPortion)),
  71. (int)(viewport.Height * (1 - 2 * SafeAreaPortion)));
  72. // Start the player in the center along the bottom of the screen
  73. personPosition.X = (safeBounds.Width - personTexture.Width) / 2;
  74. personPosition.Y = safeBounds.Height - personTexture.Height;
  75. }
  76. /// <summary>
  77. /// Load your graphics content.
  78. /// </summary>
  79. protected override void LoadContent()
  80. {
  81. // Load textures
  82. blockTexture = Content.Load<Texture2D>("SpinnerBlock");
  83. personTexture = Content.Load<Texture2D>("Person");
  84. // Extract collision data
  85. blockTextureData =
  86. new Color[blockTexture.Width * blockTexture.Height];
  87. blockTexture.GetData(blockTextureData);
  88. personTextureData =
  89. new Color[personTexture.Width * personTexture.Height];
  90. personTexture.GetData(personTextureData);
  91. // Calculate the block origin
  92. blockOrigin =
  93. new Vector2(blockTexture.Width / 2, blockTexture.Height / 2);
  94. // Create a sprite batch to draw those textures
  95. spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
  96. }
  97. /// <summary>
  98. /// Allows the game to run logic such as updating the world,
  99. /// checking for collisions, gathering input and playing audio.
  100. /// </summary>
  101. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  102. protected override void Update(GameTime gameTime)
  103. {
  104. // Get input
  105. KeyboardState keyboard = Keyboard.GetState();
  106. GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
  107. // Allows the game to exit
  108. if (gamePad.Buttons.Back == ButtonState.Pressed ||
  109. keyboard.IsKeyDown(Keys.Escape))
  110. {
  111. this.Exit();
  112. }
  113. // Move the player left and right with arrow keys or d-pad
  114. if (keyboard.IsKeyDown(Keys.Left) ||
  115. gamePad.DPad.Left == ButtonState.Pressed)
  116. {
  117. personPosition.X -= PersonMoveSpeed;
  118. }
  119. if (keyboard.IsKeyDown(Keys.Right) ||
  120. gamePad.DPad.Right == ButtonState.Pressed)
  121. {
  122. personPosition.X += PersonMoveSpeed;
  123. }
  124. // Prevent the person from moving off of the screen
  125. personPosition.X = MathHelper.Clamp(personPosition.X,
  126. safeBounds.Left, safeBounds.Right - personTexture.Width);
  127. // Update the person's transform
  128. Matrix personTransform =
  129. Matrix.CreateTranslation(new Vector3(personPosition, 0.0f));
  130. // Spawn new falling blocks
  131. if (random.NextDouble() < BlockSpawnProbability)
  132. {
  133. Block newBlock = new Block();
  134. // at a random position just above the screen
  135. float x = (float)random.NextDouble() *
  136. (Window.ClientBounds.Width - blockTexture.Width);
  137. newBlock.Position = new Vector2(x, -blockTexture.Height);
  138. // with a random rotation
  139. newBlock.Rotation = (float)random.NextDouble() * MathHelper.TwoPi;
  140. blocks.Add(newBlock);
  141. }
  142. // Get the bounding rectangle of the person
  143. Rectangle personRectangle =
  144. new Rectangle((int)personPosition.X, (int)personPosition.Y,
  145. personTexture.Width, personTexture.Height);
  146. // Update each block
  147. personHit = false;
  148. for (int i = 0; i < blocks.Count; i++)
  149. {
  150. // Animate this block falling
  151. blocks[i].Position += new Vector2(0.0f, BlockFallSpeed);
  152. blocks[i].Rotation += BlockRotateSpeed;
  153. // Build the block's transform
  154. Matrix blockTransform =
  155. Matrix.CreateTranslation(new Vector3(-blockOrigin, 0.0f)) *
  156. // Matrix.CreateScale(block.Scale) * would go here
  157. Matrix.CreateRotationZ(blocks[i].Rotation) *
  158. Matrix.CreateTranslation(new Vector3(blocks[i].Position, 0.0f));
  159. // Calculate the bounding rectangle of this block in world space
  160. Rectangle blockRectangle = CalculateBoundingRectangle(
  161. new Rectangle(0, 0, blockTexture.Width, blockTexture.Height),
  162. blockTransform);
  163. // The per-pixel check is expensive, so check the bounding rectangles
  164. // first to prevent testing pixels when collisions are impossible.
  165. if (personRectangle.Intersects(blockRectangle))
  166. {
  167. // Check collision with person
  168. if (IntersectPixels(personTransform, personTexture.Width,
  169. personTexture.Height, personTextureData,
  170. blockTransform, blockTexture.Width,
  171. blockTexture.Height, blockTextureData))
  172. {
  173. personHit = true;
  174. }
  175. }
  176. // Remove this block if it have fallen off the screen
  177. if (blocks[i].Position.Y >
  178. Window.ClientBounds.Height + blockOrigin.Length())
  179. {
  180. blocks.RemoveAt(i);
  181. // When removing a block, the next block will have the same index
  182. // as the current block. Decrement i to prevent skipping a block.
  183. i--;
  184. }
  185. }
  186. base.Update(gameTime);
  187. }
  188. /// <summary>
  189. /// This is called when the game should draw itself.
  190. /// </summary>
  191. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  192. protected override void Draw(GameTime gameTime)
  193. {
  194. GraphicsDevice device = graphics.GraphicsDevice;
  195. // Change the background to red when the person was hit by a block
  196. if (personHit)
  197. {
  198. device.Clear(Color.Red);
  199. }
  200. else
  201. {
  202. device.Clear(Color.CornflowerBlue);
  203. }
  204. spriteBatch.Begin();
  205. // Draw person
  206. spriteBatch.Draw(personTexture, personPosition, Color.White);
  207. // Draw blocks
  208. foreach (Block block in blocks)
  209. {
  210. spriteBatch.Draw(blockTexture, block.Position, null, Color.White,
  211. block.Rotation, blockOrigin, 1.0f, SpriteEffects.None, 0.0f);
  212. }
  213. spriteBatch.End();
  214. base.Draw(gameTime);
  215. }
  216. /// <summary>
  217. /// Determines if there is overlap of the non-transparent pixels
  218. /// between two sprites.
  219. /// </summary>
  220. /// <param name="rectangleA">Bounding rectangle of the first sprite</param>
  221. /// <param name="dataA">Pixel data of the first sprite</param>
  222. /// <param name="rectangleB">Bouding rectangle of the second sprite</param>
  223. /// <param name="dataB">Pixel data of the second sprite</param>
  224. /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
  225. public static bool IntersectPixels(Rectangle rectangleA, Color[] dataA,
  226. Rectangle rectangleB, Color[] dataB)
  227. {
  228. // Find the bounds of the rectangle intersection
  229. int top = Math.Max(rectangleA.Top, rectangleB.Top);
  230. int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
  231. int left = Math.Max(rectangleA.Left, rectangleB.Left);
  232. int right = Math.Min(rectangleA.Right, rectangleB.Right);
  233. // Check every point within the intersection bounds
  234. for (int y = top; y < bottom; y++)
  235. {
  236. for (int x = left; x < right; x++)
  237. {
  238. // Get the color of both pixels at this point
  239. Color colorA = dataA[(x - rectangleA.Left) +
  240. (y - rectangleA.Top) * rectangleA.Width];
  241. Color colorB = dataB[(x - rectangleB.Left) +
  242. (y - rectangleB.Top) * rectangleB.Width];
  243. // If both pixels are not completely transparent,
  244. if (colorA.A != 0 && colorB.A != 0)
  245. {
  246. // then an intersection has been found
  247. return true;
  248. }
  249. }
  250. }
  251. // No intersection found
  252. return false;
  253. }
  254. /// <summary>
  255. /// Determines if there is overlap of the non-transparent pixels between two
  256. /// sprites.
  257. /// </summary>
  258. /// <param name="transformA">World transform of the first sprite.</param>
  259. /// <param name="widthA">Width of the first sprite's texture.</param>
  260. /// <param name="heightA">Height of the first sprite's texture.</param>
  261. /// <param name="dataA">Pixel color data of the first sprite.</param>
  262. /// <param name="transformB">World transform of the second sprite.</param>
  263. /// <param name="widthB">Width of the second sprite's texture.</param>
  264. /// <param name="heightB">Height of the second sprite's texture.</param>
  265. /// <param name="dataB">Pixel color data of the second sprite.</param>
  266. /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
  267. public static bool IntersectPixels(
  268. Matrix transformA, int widthA, int heightA, Color[] dataA,
  269. Matrix transformB, int widthB, int heightB, Color[] dataB)
  270. {
  271. // Calculate a matrix which transforms from A's local space into
  272. // world space and then into B's local space
  273. Matrix transformAToB = transformA * Matrix.Invert(transformB);
  274. // When a point moves in A's local space, it moves in B's local space with a
  275. // fixed direction and distance proportional to the movement in A.
  276. // This algorithm steps through A one pixel at a time along A's X and Y axes
  277. // Calculate the analogous steps in B:
  278. Vector2 stepX = Vector2.TransformNormal(Vector2.UnitX, transformAToB);
  279. Vector2 stepY = Vector2.TransformNormal(Vector2.UnitY, transformAToB);
  280. // Calculate the top left corner of A in B's local space
  281. // This variable will be reused to keep track of the start of each row
  282. Vector2 yPosInB = Vector2.Transform(Vector2.Zero, transformAToB);
  283. // For each row of pixels in A
  284. for (int yA = 0; yA < heightA; yA++)
  285. {
  286. // Start at the beginning of the row
  287. Vector2 posInB = yPosInB;
  288. // For each pixel in this row
  289. for (int xA = 0; xA < widthA; xA++)
  290. {
  291. // Round to the nearest pixel
  292. int xB = (int)Math.Round(posInB.X);
  293. int yB = (int)Math.Round(posInB.Y);
  294. // If the pixel lies within the bounds of B
  295. if (0 <= xB && xB < widthB &&
  296. 0 <= yB && yB < heightB)
  297. {
  298. // Get the colors of the overlapping pixels
  299. Color colorA = dataA[xA + yA * widthA];
  300. Color colorB = dataB[xB + yB * widthB];
  301. // If both pixels are not completely transparent,
  302. if (colorA.A != 0 && colorB.A != 0)
  303. {
  304. // then an intersection has been found
  305. return true;
  306. }
  307. }
  308. // Move to the next pixel in the row
  309. posInB += stepX;
  310. }
  311. // Move to the next row
  312. yPosInB += stepY;
  313. }
  314. // No intersection found
  315. return false;
  316. }
  317. /// <summary>
  318. /// Calculates an axis aligned rectangle which fully contains an arbitrarily
  319. /// transformed axis aligned rectangle.
  320. /// </summary>
  321. /// <param name="rectangle">Original bounding rectangle.</param>
  322. /// <param name="transform">World transform of the rectangle.</param>
  323. /// <returns>A new rectangle which contains the trasnformed rectangle.</returns>
  324. public static Rectangle CalculateBoundingRectangle(Rectangle rectangle,
  325. Matrix transform)
  326. {
  327. // Get all four corners in local space
  328. Vector2 leftTop = new Vector2(rectangle.Left, rectangle.Top);
  329. Vector2 rightTop = new Vector2(rectangle.Right, rectangle.Top);
  330. Vector2 leftBottom = new Vector2(rectangle.Left, rectangle.Bottom);
  331. Vector2 rightBottom = new Vector2(rectangle.Right, rectangle.Bottom);
  332. // Transform all four corners into work space
  333. Vector2.Transform(ref leftTop, ref transform, out leftTop);
  334. Vector2.Transform(ref rightTop, ref transform, out rightTop);
  335. Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
  336. Vector2.Transform(ref rightBottom, ref transform, out rightBottom);
  337. // Find the minimum and maximum extents of the rectangle in world space
  338. Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop),
  339. Vector2.Min(leftBottom, rightBottom));
  340. Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop),
  341. Vector2.Max(leftBottom, rightBottom));
  342. // Return that as a rectangle
  343. return new Rectangle((int)min.X, (int)min.Y,
  344. (int)(max.X - min.X), (int)(max.Y - min.Y));
  345. }
  346. }
  347. }