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.

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