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.

185 lines
8.4 KiB

  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Graphics;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. namespace SemiColinGames {
  6. enum Terrain {
  7. Empty,
  8. Grass,
  9. GrassL,
  10. GrassR,
  11. Rock,
  12. RockL,
  13. RockR,
  14. Water,
  15. Block
  16. }
  17. class Tile {
  18. readonly Texture2D texture;
  19. readonly Rectangle textureSource;
  20. public Tile(Texture2D texture, Terrain terrain, Rectangle position) {
  21. this.texture = texture;
  22. Terrain = terrain;
  23. Position = position;
  24. this.textureSource = TextureSource();
  25. }
  26. public Rectangle Position { get; private set; }
  27. public Terrain Terrain { get; private set; }
  28. public void Draw(SpriteBatch spriteBatch, Camera camera) {
  29. Vector2 drawPos = new Vector2(Position.Left - camera.Left, Position.Top);
  30. spriteBatch.Draw(texture, drawPos, textureSource, Color.White);
  31. }
  32. private Rectangle TextureSource() {
  33. int size = World.TileSize;
  34. switch (Terrain) {
  35. case Terrain.Grass: {
  36. return new Rectangle(3 * size, 0 * size, size, size);
  37. }
  38. case Terrain.GrassL: {
  39. return new Rectangle(2 * size, 0 * size, size, size);
  40. }
  41. case Terrain.GrassR: {
  42. return new Rectangle(4 * size, 0 * size, size, size);
  43. }
  44. case Terrain.Rock: {
  45. return new Rectangle(3 * size, 1 * size, size, size);
  46. }
  47. case Terrain.RockL: {
  48. return new Rectangle(1 * size, 2 * size, size, size);
  49. }
  50. case Terrain.RockR: {
  51. return new Rectangle(5 * size, 2 * size, size, size);
  52. }
  53. case Terrain.Water: {
  54. return new Rectangle(9 * size, 2 * size, size, size);
  55. }
  56. case Terrain.Block: {
  57. return new Rectangle(6 * size, 3 * size, size, size);
  58. }
  59. case Terrain.Empty:
  60. default:
  61. return new Rectangle();
  62. }
  63. }
  64. }
  65. class World {
  66. public const int TileSize = 16;
  67. readonly Tile[] tiles;
  68. // Size of World in terms of tile grid.
  69. private readonly int tileWidth;
  70. private readonly int tileHeight;
  71. // Size of World in pixels.
  72. public int Width {
  73. get { return tileWidth * TileSize; }
  74. }
  75. public int Height {
  76. get { return tileHeight * TileSize; }
  77. }
  78. readonly string worldString = @"
  79. X
  80. .
  81. X <======> <==X X <=> <XX> XX .
  82. XXX .
  83. XXXX .
  84. XXXXX .
  85. X <X=X> <> <> <X> = <> X X X X <> X X XX X <=X> XXXXXX .
  86. <> [] [] XX XX XXX XX XXXXXXX
  87. <> [] [] [] XXX XXX XXXX XXX <> <> XXXXXXXX
  88. []12345678[]123456[]123456789[]1234567890 123456 123456 12345 1234 12345 1234 123XXXX XXXX1234XXXXX XXXX1234[]123 1234567[]XXXXXXXXX12345678
  89. ====================> <====..========..======..=========..=========> <=============> <==============================================================> <=======..==============..==============================
  90. ....................] [............................................] [.............] [..............................................................] [.......................................................";
  91. public World(Texture2D texture) {
  92. var tilesList = new List<Tile>();
  93. string[] worldDesc = worldString.Split('\n');
  94. tileWidth = worldDesc.AsQueryable().Max(a => a.Length);
  95. tileHeight = worldDesc.Length;
  96. Debug.WriteLine("world size: {0}x{1}", tileWidth, tileHeight);
  97. for (int i = 0; i < tileWidth; i++) {
  98. for (int j = 0; j < tileHeight; j++) {
  99. Terrain terrain = Terrain.Empty;
  100. if (i < worldDesc[j].Length) {
  101. switch (worldDesc[j][i]) {
  102. case '=':
  103. terrain = Terrain.Grass;
  104. break;
  105. case '<':
  106. terrain = Terrain.GrassL;
  107. break;
  108. case '>':
  109. terrain = Terrain.GrassR;
  110. break;
  111. case '.':
  112. terrain = Terrain.Rock;
  113. break;
  114. case '[':
  115. terrain = Terrain.RockL;
  116. break;
  117. case ']':
  118. terrain = Terrain.RockR;
  119. break;
  120. case '~':
  121. terrain = Terrain.Water;
  122. break;
  123. case 'X':
  124. terrain = Terrain.Block;
  125. break;
  126. default:
  127. terrain = Terrain.Empty;
  128. break;
  129. }
  130. }
  131. if (terrain != Terrain.Empty) {
  132. var position = new Rectangle(i * TileSize, j * TileSize, TileSize, TileSize);
  133. tilesList.Add(new Tile(texture, terrain, position));
  134. }
  135. }
  136. }
  137. tiles = tilesList.ToArray();
  138. // Because we added tiles from left to right, the CollisionTargets are sorted by x-position.
  139. // We maintain this invariant so that it's possible to efficiently find CollisionTargets that
  140. // are nearby a given x-position.
  141. CollisionTargets = new AABB[tiles.Length + 2];
  142. // Add a synthetic collisionTarget on the left side of the world.
  143. CollisionTargets[0] = new AABB(new Vector2(-1, 0), new Vector2(1, float.MaxValue));
  144. // Now add all the normal collisionTargets for every static terrain tile.
  145. Vector2 halfSize = new Vector2(TileSize / 2, TileSize / 2);
  146. for (int i = 0; i < tiles.Length; i++) {
  147. Vector2 center = new Vector2(
  148. tiles[i].Position.Left + halfSize.X, tiles[i].Position.Top + halfSize.Y);
  149. CollisionTargets[i + 1] = new AABB(center, halfSize);
  150. }
  151. // Add a final synthetic collisionTarget on the right side of the world.
  152. CollisionTargets[tiles.Length + 1] = new AABB(
  153. new Vector2(Width + 1, 0), new Vector2(1, float.MaxValue));
  154. }
  155. public void Draw(SpriteBatch spriteBatch, Camera camera) {
  156. foreach (Tile t in tiles) {
  157. t.Draw(spriteBatch, camera);
  158. }
  159. }
  160. public AABB[] CollisionTargets { get; }
  161. }
  162. }