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.

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