Player.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. using System;
  2. using System.Linq;
  3. using Microsoft.Xna.Framework;
  4. using Microsoft.Xna.Framework.Graphics;
  5. using Microsoft.Xna.Framework.Input;
  6. namespace XNAPacMan {
  7. /// <summary>
  8. /// Defines the position of an entity (player, ghost) on the board.
  9. /// </summary>
  10. public struct Position {
  11. public Position(Point Tile, Point DeltaPixel) {
  12. this.Tile = Tile;
  13. this.DeltaPixel = DeltaPixel;
  14. }
  15. /// <summary>
  16. /// The tile the entity is on.
  17. /// </summary>
  18. public Point Tile;
  19. /// <summary>
  20. /// How many pixels the entity is off its nominal tile.
  21. /// </summary>
  22. public Point DeltaPixel;
  23. }
  24. public enum Direction { Up, Down, Left, Right };
  25. public enum State { Start, Normal, Dying };
  26. /// <summary>
  27. /// This is the yellow pac man that eat dots and gets killed
  28. /// repetitively unless you're good.
  29. /// </summary>
  30. public class Player {
  31. public Player(Game game) {
  32. Reset();
  33. this.game = game;
  34. updatesPerPixel_ = Constants.PacManSpeed();
  35. spriteBatch_ = (SpriteBatch)game.Services.GetService(typeof(SpriteBatch));
  36. eatingFrames_ = new Texture2D[] {
  37. game.Content.Load<Texture2D>("sprites/PacManEating1"),
  38. game.Content.Load<Texture2D>("sprites/PacManEating2"),
  39. game.Content.Load<Texture2D>("sprites/PacManEating3"),
  40. game.Content.Load<Texture2D>("sprites/PacManEating4"),
  41. game.Content.Load<Texture2D>("sprites/PacManEating5"),
  42. game.Content.Load<Texture2D>("sprites/PacManEating6"),
  43. game.Content.Load<Texture2D>("sprites/PacManEating7"),
  44. game.Content.Load<Texture2D>("sprites/PacManEating8"),
  45. game.Content.Load<Texture2D>("sprites/PacManEating9"),
  46. };
  47. dyingFrames_ = game.Content.Load<Texture2D>("sprites/DyingSheetNew");
  48. }
  49. /// <summary>
  50. /// Allows the Player to update itself.
  51. /// </summary>
  52. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  53. public void Update(GameTime gameTime) {
  54. // First, deal with keyboard input. Get only the direction keys.
  55. Keys[] validKeys = { Keys.Up, Keys.Down, Keys.Left, Keys.Right };
  56. Keys[] pressedKeys = (from k in Keyboard.GetState().GetPressedKeys()
  57. where validKeys.Contains(k)
  58. select k).ToArray();
  59. // If the player is pressing more than one key, we don't turn. Trying to filter keys would be
  60. // either bogus or overly complex for the needs of this game, and yes, this is from experience.
  61. if (pressedKeys.Length == 1) {
  62. // At level start, Pac Man is facing right, although he is drawn full. Pressing
  63. // Right simply makes him start moving in that direction; pressing Left make him change direction
  64. // first; this must be handled separately from the usual case.
  65. if (state_ == State.Start) {
  66. if (pressedKeys[0] == Keys.Left || pressedKeys[0] == Keys.Right) {
  67. state_ = State.Normal;
  68. }
  69. if (pressedKeys[0] == Keys.Left) {
  70. TryTurn(pressedKeys[0]);
  71. }
  72. }
  73. // Normal case : turn if required direction != current direction.
  74. else if ((direction_.ToString() != pressedKeys[0].ToString())) {
  75. TryTurn(pressedKeys[0]);
  76. }
  77. }
  78. TryMove();
  79. }
  80. /// <summary>
  81. /// Ensures that if the Pac Man moves, it is a legal move
  82. /// </summary>
  83. void TryMove() {
  84. if (state_ == State.Start) {
  85. return;
  86. }
  87. // If between two tiles, movement is always allowed since TryTurn()
  88. // always ensures direction is valid
  89. if (position_.DeltaPixel != Point.Zero) {
  90. DoMove();
  91. }
  92. // Special case : the tunnel.
  93. else if ((position_.Tile == new Point(0, 14) && direction_ == Direction.Left) ||
  94. (position_.Tile == new Point(27, 14) && direction_ == Direction.Right)) {
  95. DoMove();
  96. }
  97. // If on a tile, we only move if the next tile in our direction is open
  98. else if ((direction_ == Direction.Up && Grid.TileGrid[position_.Tile.X, position_.Tile.Y - 1].Type == TileTypes.Open) ||
  99. (direction_ == Direction.Down && Grid.TileGrid[position_.Tile.X, position_.Tile.Y + 1].Type == TileTypes.Open) ||
  100. (direction_ == Direction.Left && Grid.TileGrid[position_.Tile.X - 1, position_.Tile.Y].Type == TileTypes.Open) ||
  101. (direction_ == Direction.Right && Grid.TileGrid[position_.Tile.X + 1, position_.Tile.Y].Type == TileTypes.Open)) {
  102. DoMove();
  103. }
  104. }
  105. /// <summary>
  106. /// Effectively moves the Pac Man according to member variable direction_.
  107. /// </summary>
  108. void DoMove() {
  109. // Everytime the updateCount == updatesPerPixel, move one pixel in
  110. // the desired direction and reset the updateCount.
  111. updateCount_++;
  112. updateCount_ %= updatesPerPixel_;
  113. if (updateCount_ == 0) {
  114. // Now is a nice time to update our speed, like we do for the ghosts.
  115. if (Ghost.NextTile(direction_, position_).HasCrump) {
  116. updatesPerPixel_ = Constants.PacManSpeed() + 2;
  117. }
  118. else {
  119. updatesPerPixel_ = Constants.PacManSpeed();
  120. }
  121. // Move one pixel in the desired direction
  122. if (direction_ == Direction.Up) {
  123. position_.DeltaPixel.Y--;
  124. }
  125. else if (direction_ == Direction.Down) {
  126. position_.DeltaPixel.Y++;
  127. }
  128. else if (direction_ == Direction.Left) {
  129. position_.DeltaPixel.X--;
  130. }
  131. else if (direction_ == Direction.Right) {
  132. position_.DeltaPixel.X++;
  133. }
  134. // By moving one pixel we might have moved to another tile completely
  135. if (position_.DeltaPixel.X == 16) {
  136. // Special case : the tunnel
  137. if (position_.Tile.X == 27) {
  138. position_.Tile.X = 0;
  139. }
  140. else {
  141. position_.Tile.X++;
  142. }
  143. position_.DeltaPixel.X = 0;
  144. }
  145. else if (position_.DeltaPixel.X == -16) {
  146. // Special case : the tunnel
  147. if (position_.Tile.X == 0) {
  148. position_.Tile.X = 27;
  149. }
  150. else {
  151. position_.Tile.X--;
  152. }
  153. position_.DeltaPixel.X = 0;
  154. }
  155. else if (position_.DeltaPixel.Y == 16) {
  156. position_.Tile.Y++;
  157. position_.DeltaPixel.Y = 0;
  158. }
  159. else if (position_.DeltaPixel.Y == -16) {
  160. position_.Tile.Y--;
  161. position_.DeltaPixel.Y = 0;
  162. }
  163. }
  164. }
  165. /// <summary>
  166. /// Allows the Player to be drawn to the screen. Assumes spritebatch.begin() has been called, and
  167. /// spritebatch.end() will be called afterwards.
  168. /// </summary>
  169. /// <param name="gameTime">Provides a snapshot of timing values.</param>
  170. public void Draw(GameTime gameTime, Vector2 boardPosition) {
  171. // The position is taken as a function of the board position, the tile on which the pac man is, how many
  172. // pixels he is off from this tile, and the size of the pac man itself versus the size of a tile (16).
  173. Vector2 position;
  174. position.X = boardPosition.X + (position_.Tile.X * 16) + position_.DeltaPixel.X - ((eatingFrames_[0].Width - 16) / 2);
  175. position.Y = boardPosition.Y + (position_.Tile.Y * 16) + position_.DeltaPixel.Y - ((eatingFrames_[0].Height - 16) / 2);
  176. // At level start, just draw the full pac man and exit
  177. if (state_ == State.Start) {
  178. spriteBatch_.Draw(eatingFrames_[0], position, Color.White);
  179. }
  180. else if (state_ == State.Normal) {
  181. // The frame index is taken as a function of how much the pac man is off from a tile, the size of
  182. // a tile (always 16 pixels), and how many frames we use for the animation.
  183. int frame = Math.Abs(position_.DeltaPixel.X + position_.DeltaPixel.Y) / (16 / usedFramesIndex_.Length);
  184. frame = (int)MathHelper.Clamp(frame, 0, usedFramesIndex_.Length - 1);
  185. RenderSprite(eatingFrames_[usedFramesIndex_[frame]], null, boardPosition, position);
  186. }
  187. else if (state_ == State.Dying) {
  188. int timeBetweenFrames = 90; // Sound "Death" is 1811 milliseconds long, we have 20 frames to go through.
  189. //timer_ += gameTime.ElapsedRealTime;
  190. timer_ += gameTime.ElapsedGameTime;
  191. int index = (timer_.Seconds * 1000 + timer_.Milliseconds) / timeBetweenFrames;
  192. if (index > 19) {
  193. return;
  194. }
  195. RenderSprite(dyingFrames_, new Rectangle(26 * index, 0, 26, 26), boardPosition, position);
  196. }
  197. }
  198. /// <summary>
  199. /// Allows rendering across the tunnel, which is tricky.
  200. /// </summary>
  201. /// <param name="spriteSheet">Source texture</param>
  202. /// <param name="rectangle">Portion of the source to render. Pass null for rendering the whole texture.</param>
  203. /// <param name="boardPosition">Top-left pixel of the board in the screen</param>
  204. /// <param name="position">Where to render the texture.</param>
  205. void RenderSprite(Texture2D spriteSheet, Rectangle? rectangle, Vector2 boardPosition, Vector2 position) {
  206. Rectangle rect = rectangle == null ? new Rectangle(0, 0, spriteSheet.Width, spriteSheet.Height) :
  207. rectangle.Value;
  208. // What happens when we are crossing to the other end by the tunnel?
  209. // We detect if part of the pacman is rendered outside of the board.
  210. // First, to the left.
  211. if (position.X < boardPosition.X) {
  212. int deltaPixel = (int)(boardPosition.X - position.X); // Number of pixels off the board
  213. var leftPortion = new Rectangle(rect.X + deltaPixel, rect.Y, 26 - deltaPixel, 26);
  214. var leftPortionPosition = new Vector2(boardPosition.X, position.Y);
  215. var rightPortion = new Rectangle(rect.X, rect.Y, deltaPixel, 26);
  216. var rightPortionPosition = new Vector2(boardPosition.X + (16 * 28) - deltaPixel, position.Y);
  217. spriteBatch_.Draw(spriteSheet, leftPortionPosition, leftPortion, Color.White);
  218. spriteBatch_.Draw(spriteSheet, rightPortionPosition, rightPortion, Color.White);
  219. }
  220. // Next, to the right
  221. else if (position.X > (boardPosition.X + (16 * 28) - 26)) {
  222. int deltaPixel = (int)((position.X + 26) - (boardPosition.X + (16 * 28))); // Number of pixels off the board
  223. var leftPortion = new Rectangle(rect.X + 26 - deltaPixel, rect.Y, deltaPixel, 26);
  224. var leftPortionPosition = new Vector2(boardPosition.X, position.Y);
  225. var rightPortion = new Rectangle(rect.X, rect.Y, 26 - deltaPixel, 26);
  226. var rightPortionPosition = new Vector2(position.X, position.Y);
  227. spriteBatch_.Draw(spriteSheet, leftPortionPosition, leftPortion, Color.White);
  228. spriteBatch_.Draw(spriteSheet, rightPortionPosition, rightPortion, Color.White);
  229. }
  230. // Draw normally - in one piece
  231. else {
  232. spriteBatch_.Draw(spriteSheet, position, rect, Color.White);
  233. }
  234. }
  235. /// <summary>
  236. /// Should be called anytime the Pac Man needs to be reset (game start, level start)
  237. /// </summary>
  238. void Reset() {
  239. state_ = State.Start;
  240. direction_ = Direction.Right;
  241. usedFramesIndex_ = new int[] { 0, 1, 2 };
  242. position_ = new Position { Tile = new Point(13, 23), DeltaPixel = new Point(8, 0) };
  243. updateCount_ = 0;
  244. }
  245. /// <summary>
  246. /// Ensures that if the Pac Man turns, it's in a valid direction.
  247. /// </summary>
  248. /// <param name="input">Direction the player tries to steer the Pac Man towards</param>
  249. void TryTurn(Keys input) {
  250. // If we're between two tiles, we can only turn 180
  251. if (position_.DeltaPixel != Point.Zero) {
  252. if ((direction_ == Direction.Up && input == Keys.Down) ||
  253. (direction_ == Direction.Down && input == Keys.Up) ||
  254. (direction_ == Direction.Left && input == Keys.Right) ||
  255. (direction_ == Direction.Right && input == Keys.Left)) {
  256. // Turning 180 between two tiles implies destination tile is open,
  257. // no other validation to be done
  258. DoTurn(input);
  259. }
  260. }
  261. // Special case : the tunnel.
  262. else if ((input == Keys.Left && position_.Tile.X == 0) ||
  263. (input == Keys.Right && position_.Tile.X == 27)) {
  264. DoTurn(input);
  265. }
  266. // We're exactly on a tile; this is the "general" case
  267. // Do turn if the destination tile is open
  268. else if ((input == Keys.Up && Grid.TileGrid[position_.Tile.X, position_.Tile.Y - 1].Type == TileTypes.Open) ||
  269. (input == Keys.Down && Grid.TileGrid[position_.Tile.X, position_.Tile.Y + 1].Type == TileTypes.Open) ||
  270. (input == Keys.Left && Grid.TileGrid[position_.Tile.X - 1, position_.Tile.Y].Type == TileTypes.Open) ||
  271. (input == Keys.Right && Grid.TileGrid[position_.Tile.X + 1, position_.Tile.Y].Type == TileTypes.Open)) {
  272. DoTurn(input);
  273. }
  274. }
  275. /// <summary>
  276. /// This effectively makes Pac Man turn.
  277. /// We have to update the sprites used for animation,
  278. /// and if the Pac Man is between two tiles, change his Position.
  279. /// </summary>
  280. /// <param name="newDirection">Direction to turn towards</param>
  281. void DoTurn(Keys newDirection) {
  282. switch (newDirection) {
  283. case Keys.Up:
  284. direction_ = Direction.Up;
  285. usedFramesIndex_ = new int[] { 0, 7, 8 };
  286. if (position_.DeltaPixel != Point.Zero) {
  287. position_.Tile.Y += 1;
  288. position_.DeltaPixel.Y -= 16;
  289. }
  290. break;
  291. case Keys.Down:
  292. direction_ = Direction.Down;
  293. usedFramesIndex_ = new int[] { 0, 3, 4 };
  294. if (position_.DeltaPixel != Point.Zero) {
  295. position_.Tile.Y -= 1;
  296. position_.DeltaPixel.Y += 16;
  297. }
  298. break;
  299. case Keys.Left:
  300. direction_ = Direction.Left;
  301. usedFramesIndex_ = new int[] { 0, 5, 6 };
  302. if (position_.DeltaPixel != Point.Zero) {
  303. position_.Tile.X += 1;
  304. position_.DeltaPixel.X -= 16;
  305. }
  306. break;
  307. case Keys.Right:
  308. direction_ = Direction.Right;
  309. usedFramesIndex_ = new int[] { 0, 1, 2 };
  310. if (position_.DeltaPixel != Point.Zero) {
  311. position_.Tile.X -= 1;
  312. position_.DeltaPixel.X += 16;
  313. }
  314. break;
  315. }
  316. }
  317. Game game;
  318. TimeSpan timer_;
  319. SpriteBatch spriteBatch_;
  320. Texture2D dyingFrames_;
  321. Texture2D[] eatingFrames_;
  322. int[] usedFramesIndex_;
  323. int updatesPerPixel_;
  324. int updateCount_;
  325. Position position_;
  326. Direction direction_;
  327. State state_;
  328. public State State { get { return state_; } set { state_ = value; } }
  329. public Direction Direction { get { return direction_; } }
  330. public Position Position { get { return position_; } }
  331. }
  332. }