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.

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