A stealth-based 2D platformer where you don't have to kill anyone unless you want to. https://www.semicolin.games
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

177 lines
5.5 KiB

  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Graphics;
  3. using System.Collections.Generic;
  4. namespace SemiColinGames {
  5. class Player {
  6. enum Facing { Left, Right };
  7. enum Pose { Walking, Standing, Crouching, Stretching, SwordSwing, Jumping };
  8. private const int moveSpeed = 180;
  9. private const int jumpSpeed = -600;
  10. private const int gravity = 2400;
  11. private Texture2D texture;
  12. private const int spriteSize = 48;
  13. private const int spriteWidth = 7;
  14. private Point position = new Point(64, 16);
  15. private int jumps = 0;
  16. private Facing facing = Facing.Right;
  17. private Pose pose = Pose.Jumping;
  18. private double swordSwingTime = 0;
  19. private double jumpTime = 0;
  20. private float ySpeed = 0;
  21. private float totalModelTime = 0;
  22. public Player(Texture2D texture) {
  23. this.texture = texture;
  24. }
  25. public Point Position { get { return position; } }
  26. private Rectangle Bbox(Point position) {
  27. return new Rectangle(position.X - spriteWidth, position.Y - 7, spriteWidth * 2, 26);
  28. }
  29. public void Update(float modelTime, History<Input> input, List<Rectangle> collisionTargets) {
  30. totalModelTime += modelTime;
  31. Point oldPosition = position;
  32. Vector2 movement = HandleInput(modelTime, input);
  33. position = new Point((int) (oldPosition.X + movement.X), (int) (oldPosition.Y + movement.Y));
  34. Rectangle oldBbox = Bbox(oldPosition);
  35. Rectangle playerBbox = Bbox(position);
  36. bool standingOnGround = false;
  37. foreach (var rect in collisionTargets) {
  38. playerBbox = Bbox(position);
  39. // first we check for left-right collisions...
  40. if (playerBbox.Intersects(rect)) {
  41. if (oldBbox.Right <= rect.Left && playerBbox.Right > rect.Left) {
  42. position.X = rect.Left - spriteWidth;
  43. }
  44. if (oldBbox.Left >= rect.Right && playerBbox.Left < rect.Right) {
  45. position.X = rect.Right + spriteWidth;
  46. }
  47. playerBbox = Bbox(position);
  48. }
  49. // after fixing that, we check for hitting our head or hitting the ground.
  50. if (playerBbox.Intersects(rect)) {
  51. if (oldPosition.Y > position.Y) {
  52. int diff = playerBbox.Top - rect.Bottom;
  53. position.Y -= diff;
  54. // TODO: set ySpeed = 0 here so that bonking our head actually reduces hangtime?
  55. } else {
  56. standingOnGround = true;
  57. int diff = playerBbox.Bottom - rect.Top;
  58. position.Y -= diff;
  59. }
  60. } else {
  61. playerBbox.Height += 1;
  62. if (playerBbox.Intersects(rect)) {
  63. standingOnGround = true;
  64. Debug.AddRect(rect, Color.Cyan);
  65. } else {
  66. Debug.AddRect(rect, Color.Green);
  67. }
  68. }
  69. }
  70. if (standingOnGround) {
  71. jumps = 1;
  72. ySpeed = 0;
  73. Debug.AddRect(playerBbox, Color.Red);
  74. } else {
  75. jumps = 0;
  76. Debug.AddRect(playerBbox, Color.Orange);
  77. }
  78. if (movement.X > 0) {
  79. facing = Facing.Right;
  80. } else if (movement.X < 0) {
  81. facing = Facing.Left;
  82. }
  83. if (swordSwingTime > 0) {
  84. pose = Pose.SwordSwing;
  85. } else if (jumps == 0) {
  86. pose = Pose.Jumping;
  87. } else if (movement.X != 0) {
  88. pose = Pose.Walking;
  89. } else if (input[0].Motion.Y > 0) {
  90. pose = Pose.Stretching;
  91. } else if (input[0].Motion.Y < 0) {
  92. pose = Pose.Crouching;
  93. } else {
  94. pose = Pose.Standing;
  95. }
  96. }
  97. // Returns the desired (dx, dy) for the player to move this frame.
  98. Vector2 HandleInput(float modelTime, History<Input> input) {
  99. Vector2 result = new Vector2();
  100. result.X = (int) (input[0].Motion.X * moveSpeed * modelTime);
  101. if (input[0].Jump && !input[1].Jump && jumps > 0) {
  102. jumpTime = 0.3;
  103. jumps--;
  104. ySpeed = jumpSpeed;
  105. }
  106. if (input[0].Attack && !input[1].Attack && swordSwingTime <= 0) {
  107. swordSwingTime = 0.3;
  108. }
  109. result.Y = ySpeed * modelTime;
  110. ySpeed += gravity * modelTime;
  111. jumpTime -= modelTime;
  112. swordSwingTime -= modelTime;
  113. return result;
  114. }
  115. private int SpriteIndex(Pose pose) {
  116. int frameNum = ((int) (totalModelTime * 1000) / 125) % 4;
  117. if (frameNum == 3) {
  118. frameNum = 1;
  119. }
  120. switch (pose) {
  121. case Pose.Walking:
  122. return 6 + frameNum;
  123. case Pose.Stretching:
  124. return 18 + frameNum;
  125. case Pose.Jumping:
  126. if (jumpTime > 0.2) {
  127. return 15;
  128. } else if (jumpTime > 0.1) {
  129. return 16;
  130. } else {
  131. return 17;
  132. }
  133. case Pose.SwordSwing:
  134. if (swordSwingTime > 0.2) {
  135. return 30;
  136. } else if (swordSwingTime > 0.1) {
  137. return 31;
  138. } else {
  139. return 32;
  140. }
  141. case Pose.Crouching:
  142. return 25;
  143. case Pose.Standing:
  144. default:
  145. return 7;
  146. }
  147. }
  148. public void Draw(SpriteBatch spriteBatch, Camera camera) {
  149. int index = SpriteIndex(pose);
  150. Rectangle textureSource = new Rectangle(index * spriteSize, 0, spriteSize, spriteSize);
  151. Vector2 spriteCenter = new Vector2(spriteSize / 2, spriteSize / 2);
  152. SpriteEffects effect = facing == Facing.Right ?
  153. SpriteEffects.FlipHorizontally : SpriteEffects.None;
  154. Vector2 drawPos = new Vector2(position.X - camera.Left, position.Y);
  155. spriteBatch.Draw(texture, drawPos, textureSource, Color.White, 0f, spriteCenter,
  156. Vector2.One, effect, 0f);
  157. }
  158. }
  159. }