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.

222 lines
8.8 KiB

  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Graphics;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. namespace SemiColinGames {
  7. class Terrain {
  8. public static Terrain FromSymbol(char symbol) {
  9. if (mapping.ContainsKey(symbol)) {
  10. return mapping[symbol];
  11. } else {
  12. return null;
  13. }
  14. }
  15. private static Dictionary<char, Terrain> mapping = new Dictionary<char, Terrain>();
  16. public static Terrain Grass = new Terrain('=', true);
  17. public static Terrain GrassL = new Terrain('<', true);
  18. public static Terrain GrassR = new Terrain('>', true);
  19. public static Terrain Rock = new Terrain('.', true);
  20. public static Terrain RockL = new Terrain('[', true);
  21. public static Terrain RockR = new Terrain(']', true);
  22. public static Terrain WaterL = new Terrain('~', false);
  23. public static Terrain WaterR = new Terrain('`', false);
  24. public static Terrain Block = new Terrain('X', true);
  25. public static Terrain Spike = new Terrain('^', true);
  26. public static Terrain Wood = new Terrain('_', true);
  27. public static Terrain WoodL = new Terrain('(', true);
  28. public static Terrain WoodR = new Terrain(')', true);
  29. public static Terrain WoodVert = new Terrain('|', false);
  30. public static Terrain WoodVertL = new Terrain('/', false);
  31. public static Terrain WoodVertR = new Terrain('\\', false);
  32. public static Terrain WoodBottom = new Terrain('v', false);
  33. public static Terrain FenceL = new Terrain('d', false);
  34. public static Terrain Fence = new Terrain('f', false);
  35. public static Terrain FencePost = new Terrain('x', false);
  36. public static Terrain FenceR = new Terrain('b', false);
  37. public static Terrain VineTop = new Terrain('{', false);
  38. public static Terrain VineMid = new Terrain(';', false);
  39. public static Terrain VineBottom = new Terrain('}', false);
  40. public static Terrain GrassTall = new Terrain('q', false);
  41. public static Terrain GrassShort = new Terrain('w', false);
  42. public static Terrain Shoots = new Terrain('e', false);
  43. public static Terrain Bush = new Terrain('r', false);
  44. public static Terrain Mushroom = new Terrain('t', false);
  45. public bool IsObstacle { get; private set; }
  46. private Terrain(char symbol, bool isObstacle) {
  47. if (mapping.ContainsKey(symbol)) {
  48. throw new ArgumentException("already have a terrain with symbol " + symbol);
  49. }
  50. IsObstacle = isObstacle;
  51. mapping[symbol] = this;
  52. }
  53. }
  54. class TileFactory {
  55. struct TextureSource {
  56. public Texture2D texture;
  57. public Rectangle source;
  58. public TextureSource(Texture2D texture, Rectangle source) {
  59. this.texture = texture;
  60. this.source = source;
  61. }
  62. }
  63. readonly Dictionary<Terrain, TextureSource> terrainToTexture;
  64. public TileFactory() {
  65. terrainToTexture = new Dictionary<Terrain, TextureSource>() {
  66. { Terrain.GrassL, GetTextureSource(Textures.Grassland, 2, 0) },
  67. { Terrain.Grass, GetTextureSource(Textures.Grassland, 3, 0) },
  68. { Terrain.GrassR, GetTextureSource(Textures.Grassland, 4, 0) },
  69. { Terrain.Rock, GetTextureSource(Textures.Grassland, 3, 1) },
  70. { Terrain.RockL, GetTextureSource(Textures.Grassland, 1, 2) },
  71. { Terrain.RockR, GetTextureSource(Textures.Grassland, 5, 2) },
  72. { Terrain.WaterL, GetTextureSource(Textures.Grassland, 9, 2) },
  73. { Terrain.WaterR, GetTextureSource(Textures.Grassland, 10, 2) },
  74. { Terrain.Block, GetTextureSource(Textures.Ruins, 2, 0) },
  75. { Terrain.Spike, GetTextureSource(Textures.Grassland, 11, 8) },
  76. { Terrain.Wood, GetTextureSource(Textures.Grassland, 10, 3) },
  77. { Terrain.WoodL, GetTextureSource(Textures.Grassland, 9, 3) },
  78. { Terrain.WoodR, GetTextureSource(Textures.Grassland, 12, 3) },
  79. { Terrain.WoodVert, GetTextureSource(Textures.Grassland, 9, 5) },
  80. { Terrain.WoodVertL, GetTextureSource(Textures.Grassland, 9, 4) },
  81. { Terrain.WoodVertR, GetTextureSource(Textures.Grassland, 12, 4) },
  82. { Terrain.WoodBottom, GetTextureSource(Textures.Grassland, 10, 5) },
  83. { Terrain.FenceL, GetTextureSource(Textures.Grassland, 5, 4) },
  84. { Terrain.Fence, GetTextureSource(Textures.Grassland, 6, 4) },
  85. { Terrain.FencePost, GetTextureSource(Textures.Grassland, 7, 4) },
  86. { Terrain.FenceR, GetTextureSource(Textures.Grassland, 8, 4) },
  87. { Terrain.VineTop, GetTextureSource(Textures.Ruins, 12, 5) },
  88. { Terrain.VineMid, GetTextureSource(Textures.Ruins, 12, 6) },
  89. { Terrain.VineBottom, GetTextureSource(Textures.Ruins, 12, 7) },
  90. { Terrain.GrassTall, GetTextureSource(Textures.Grassland, 13, 0) },
  91. { Terrain.GrassShort, GetTextureSource(Textures.Grassland, 14, 0) },
  92. { Terrain.Shoots, GetTextureSource(Textures.Grassland, 15, 0) },
  93. { Terrain.Bush, GetTextureSource(Textures.Grassland, 13, 2) },
  94. { Terrain.Mushroom, GetTextureSource(Textures.Grassland, 17, 2) },
  95. };
  96. }
  97. public Tile MakeTile(Terrain terrain, Rectangle position) {
  98. TextureSource textureSource = terrainToTexture[terrain];
  99. return new Tile(terrain, position, textureSource.texture, textureSource.source);
  100. }
  101. private TextureSource GetTextureSource(Texture2D texture, int x, int y) {
  102. int size = World.TileSize;
  103. Rectangle position = new Rectangle(x * size, y * size, size, size);
  104. return new TextureSource(texture, position);
  105. }
  106. }
  107. class Tile {
  108. readonly Texture2D texture;
  109. readonly Rectangle textureSource;
  110. public Tile(Terrain terrain, Rectangle position, Texture2D texture, Rectangle textureSource) {
  111. Terrain = terrain;
  112. Position = position;
  113. this.texture = texture;
  114. this.textureSource = textureSource;
  115. }
  116. public Rectangle Position { get; private set; }
  117. public Terrain Terrain { get; private set; }
  118. public void Draw(SpriteBatch spriteBatch) {
  119. spriteBatch.Draw(texture, Position.Location.ToVector2(), textureSource, Color.White);
  120. }
  121. }
  122. class World {
  123. public const int TileSize = 16;
  124. readonly Tile[] tiles;
  125. readonly Tile[] decorations;
  126. // Size of World in terms of tile grid.
  127. private readonly int tileWidth;
  128. private readonly int tileHeight;
  129. // Size of World in pixels.
  130. public int Width {
  131. get { return tileWidth * TileSize; }
  132. }
  133. public int Height {
  134. get { return tileHeight * TileSize; }
  135. }
  136. public World(string levelSpecification) {
  137. TileFactory factory = new TileFactory();
  138. var tilesList = new List<Tile>();
  139. var decorationsList = new List<Tile>();
  140. string[] worldDesc = levelSpecification.Split('\n');
  141. tileWidth = worldDesc.AsQueryable().Max(a => a.Length);
  142. tileHeight = worldDesc.Length;
  143. Debug.WriteLine("world size: {0}x{1}", tileWidth, tileHeight);
  144. for (int i = 0; i < tileWidth; i++) {
  145. for (int j = 0; j < tileHeight; j++) {
  146. if (i < worldDesc[j].Length) {
  147. char key = worldDesc[j][i];
  148. Terrain terrain = Terrain.FromSymbol(key);
  149. if (terrain != null) {
  150. var position = new Rectangle(i * TileSize, j * TileSize, TileSize, TileSize);
  151. Tile tile = factory.MakeTile(terrain, position);
  152. if (tile.Terrain.IsObstacle) {
  153. tilesList.Add(tile);
  154. } else {
  155. decorationsList.Add(tile);
  156. }
  157. }
  158. }
  159. }
  160. }
  161. tiles = tilesList.ToArray();
  162. decorations = decorationsList.ToArray();
  163. // Because we added tiles from left to right, the CollisionTargets are sorted by x-position.
  164. // We maintain this invariant so that it's possible to efficiently find CollisionTargets that
  165. // are nearby a given x-position.
  166. CollisionTargets = new AABB[tiles.Length + 2];
  167. // Add a synthetic collisionTarget on the left side of the world.
  168. CollisionTargets[0] = new AABB(new Vector2(-1, 0), new Vector2(1, float.MaxValue));
  169. // Now add all the normal collisionTargets for every static terrain tile.
  170. Vector2 halfSize = new Vector2(TileSize / 2, TileSize / 2);
  171. for (int i = 0; i < tiles.Length; i++) {
  172. Vector2 center = new Vector2(
  173. tiles[i].Position.Left + halfSize.X, tiles[i].Position.Top + halfSize.Y);
  174. CollisionTargets[i + 1] = new AABB(center, halfSize);
  175. }
  176. // Add a final synthetic collisionTarget on the right side of the world.
  177. CollisionTargets[tiles.Length + 1] = new AABB(
  178. new Vector2(Width + 1, 0), new Vector2(1, float.MaxValue));
  179. }
  180. public void DrawBackground(SpriteBatch spriteBatch) {
  181. foreach (Tile t in decorations) {
  182. t.Draw(spriteBatch);
  183. }
  184. }
  185. public void DrawForeground(SpriteBatch spriteBatch) {
  186. foreach (Tile t in tiles) {
  187. t.Draw(spriteBatch);
  188. }
  189. }
  190. public AABB[] CollisionTargets { get; }
  191. }
  192. }