Game1.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. //-----------------------------------------------------------------------------
  2. // Game.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.Audio;
  11. using Microsoft.Xna.Framework.Content;
  12. using Microsoft.Xna.Framework.Graphics;
  13. using Microsoft.Xna.Framework.Input;
  14. namespace PerPixelCollision
  15. {
  16. /// <summary>
  17. /// This is the main type for your game
  18. /// </summary>
  19. public class PerPixelCollisionGame : Microsoft.Xna.Framework.Game
  20. {
  21. GraphicsDeviceManager graphics;
  22. // The images we will draw
  23. Texture2D personTexture;
  24. Texture2D blockTexture;
  25. // The color data for the images; used for per pixel collision
  26. Color[] personTextureData;
  27. Color[] blockTextureData;
  28. // The images will be drawn with this SpriteBatch
  29. SpriteBatch spriteBatch;
  30. // Person
  31. Vector2 personPosition;
  32. const int PersonMoveSpeed = 5;
  33. // Blocks
  34. List<Vector2> blockPositions = new List<Vector2>();
  35. float BlockSpawnProbability = 0.01f;
  36. const int BlockFallSpeed = 2;
  37. Random random = new Random();
  38. // For when a collision is detected
  39. bool personHit = false;
  40. // The sub-rectangle of the drawable area which should be visible on all TVs
  41. Rectangle safeBounds;
  42. // Percentage of the screen on every side is the safe area
  43. const float SafeAreaPortion = 0.05f;
  44. public PerPixelCollisionGame()
  45. {
  46. graphics = new GraphicsDeviceManager(this);
  47. Content.RootDirectory = "Content";
  48. }
  49. /// <summary>
  50. /// Allows the game to perform any initialization it needs to before starting to
  51. /// run. This is where it can query for any required services and load any
  52. /// non-graphic related content. Calling base.Initialize will enumerate through
  53. /// any components and initialize them as well.
  54. /// </summary>
  55. protected override void Initialize()
  56. {
  57. base.Initialize();
  58. // Calculate safe bounds based on current resolution
  59. Viewport viewport = graphics.GraphicsDevice.Viewport;
  60. safeBounds = new Rectangle(
  61. (int)(viewport.Width * SafeAreaPortion),
  62. (int)(viewport.Height * SafeAreaPortion),
  63. (int)(viewport.Width * (1 - 2 * SafeAreaPortion)),
  64. (int)(viewport.Height * (1 - 2 * SafeAreaPortion)));
  65. // Start the player in the center along the bottom of the screen
  66. personPosition.X = (safeBounds.Width - personTexture.Width) / 2;
  67. personPosition.Y = safeBounds.Height - personTexture.Height;
  68. }
  69. /// <summary>
  70. /// Load your graphics content.
  71. /// </summary>
  72. protected override void LoadContent()
  73. {
  74. // Load textures
  75. blockTexture = Content.Load<Texture2D>("Block");
  76. personTexture = Content.Load<Texture2D>("Person");
  77. // Extract collision data
  78. blockTextureData =
  79. new Color[blockTexture.Width * blockTexture.Height];
  80. blockTexture.GetData(blockTextureData);
  81. personTextureData =
  82. new Color[personTexture.Width * personTexture.Height];
  83. personTexture.GetData(personTextureData);
  84. // Create a sprite batch to draw those textures
  85. spriteBatch = new SpriteBatch(graphics.GraphicsDevice);
  86. }
  87. /// <summary>
  88. /// Allows the game to run logic such as updating the world,
  89. /// checking for collisions, gathering input and playing audio.
  90. /// </summary>
  91. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  92. protected override void Update(GameTime gameTime)
  93. {
  94. // Get input
  95. KeyboardState keyboard = Keyboard.GetState();
  96. GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
  97. // Allows the game to exit
  98. if (gamePad.Buttons.Back == ButtonState.Pressed ||
  99. keyboard.IsKeyDown(Keys.Escape))
  100. {
  101. this.Exit();
  102. }
  103. // Move the player left and right with arrow keys or d-pad
  104. if (keyboard.IsKeyDown(Keys.Left) ||
  105. gamePad.DPad.Left == ButtonState.Pressed)
  106. {
  107. personPosition.X -= PersonMoveSpeed;
  108. }
  109. if (keyboard.IsKeyDown(Keys.Right) ||
  110. gamePad.DPad.Right == ButtonState.Pressed)
  111. {
  112. personPosition.X += PersonMoveSpeed;
  113. }
  114. // Prevent the person from moving off of the screen
  115. personPosition.X = MathHelper.Clamp(personPosition.X,
  116. safeBounds.Left, safeBounds.Right - personTexture.Width);
  117. // Spawn new falling blocks
  118. if (random.NextDouble() < BlockSpawnProbability)
  119. {
  120. float x = (float)random.NextDouble() *
  121. (Window.ClientBounds.Width - blockTexture.Width);
  122. blockPositions.Add(new Vector2(x, -blockTexture.Height));
  123. }
  124. // Get the bounding rectangle of the person
  125. Rectangle personRectangle =
  126. new Rectangle((int)personPosition.X, (int)personPosition.Y,
  127. personTexture.Width, personTexture.Height);
  128. // Update each block
  129. personHit = false;
  130. for (int i = 0; i < blockPositions.Count; i++)
  131. {
  132. // Animate this block falling
  133. blockPositions[i] =
  134. new Vector2(blockPositions[i].X,
  135. blockPositions[i].Y + BlockFallSpeed);
  136. // Get the bounding rectangle of this block
  137. Rectangle blockRectangle =
  138. new Rectangle((int)blockPositions[i].X, (int)blockPositions[i].Y,
  139. blockTexture.Width, blockTexture.Height);
  140. // Check collision with person
  141. if (IntersectPixels(personRectangle, personTextureData,
  142. blockRectangle, blockTextureData))
  143. {
  144. personHit = true;
  145. }
  146. // Remove this block if it have fallen off the screen
  147. if (blockPositions[i].Y > Window.ClientBounds.Height)
  148. {
  149. blockPositions.RemoveAt(i);
  150. // When removing a block, the next block will have the same index
  151. // as the current block. Decrement i to prevent skipping a block.
  152. i--;
  153. }
  154. }
  155. base.Update(gameTime);
  156. }
  157. /// <summary>
  158. /// This is called when the game should draw itself.
  159. /// </summary>
  160. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  161. protected override void Draw(GameTime gameTime)
  162. {
  163. GraphicsDevice device = graphics.GraphicsDevice;
  164. // Change the background to red when the person was hit by a block
  165. if (personHit)
  166. {
  167. device.Clear(Color.Red);
  168. }
  169. else
  170. {
  171. device.Clear(Color.CornflowerBlue);
  172. }
  173. spriteBatch.Begin();
  174. // Draw person
  175. spriteBatch.Draw(personTexture, personPosition, Color.White);
  176. // Draw blocks
  177. foreach (Vector2 blockPosition in blockPositions)
  178. spriteBatch.Draw(blockTexture, blockPosition, Color.White);
  179. spriteBatch.End();
  180. base.Draw(gameTime);
  181. }
  182. /// <summary>
  183. /// Determines if there is overlap of the non-transparent pixels
  184. /// between two sprites.
  185. /// </summary>
  186. /// <param name="rectangleA">Bounding rectangle of the first sprite</param>
  187. /// <param name="dataA">Pixel data of the first sprite</param>
  188. /// <param name="rectangleB">Bouding rectangle of the second sprite</param>
  189. /// <param name="dataB">Pixel data of the second sprite</param>
  190. /// <returns>True if non-transparent pixels overlap; false otherwise</returns>
  191. static bool IntersectPixels(Rectangle rectangleA, Color[] dataA,
  192. Rectangle rectangleB, Color[] dataB)
  193. {
  194. // Find the bounds of the rectangle intersection
  195. int top = Math.Max(rectangleA.Top, rectangleB.Top);
  196. int bottom = Math.Min(rectangleA.Bottom, rectangleB.Bottom);
  197. int left = Math.Max(rectangleA.Left, rectangleB.Left);
  198. int right = Math.Min(rectangleA.Right, rectangleB.Right);
  199. // Check every point within the intersection bounds
  200. for (int y = top; y < bottom; y++)
  201. {
  202. for (int x = left; x < right; x++)
  203. {
  204. // Get the color of both pixels at this point
  205. Color colorA = dataA[(x - rectangleA.Left) +
  206. (y - rectangleA.Top) * rectangleA.Width];
  207. Color colorB = dataB[(x - rectangleB.Left) +
  208. (y - rectangleB.Top) * rectangleB.Width];
  209. // If both pixels are not completely transparent,
  210. if (colorA.A != 0 && colorB.A != 0)
  211. {
  212. // then an intersection has been found
  213. return true;
  214. }
  215. }
  216. }
  217. // No intersection found
  218. return false;
  219. }
  220. }
  221. }