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.

193 lines
6.4 KiB

  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Content;
  3. using Microsoft.Xna.Framework.Graphics;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. namespace SemiColinGames {
  8. enum Terrain {
  9. Grass,
  10. GrassL,
  11. GrassR,
  12. Rock,
  13. RockL,
  14. RockR,
  15. Water,
  16. Block
  17. }
  18. enum TileSet {
  19. Cemetery,
  20. Crypt,
  21. Dungeon,
  22. Forest,
  23. Garden,
  24. Grassland,
  25. Ruins,
  26. Sewer,
  27. Temple,
  28. Village,
  29. }
  30. class TileFactory {
  31. struct TextureSource {
  32. public Texture2D texture;
  33. public Rectangle source;
  34. public TextureSource(Texture2D texture, Rectangle source) {
  35. this.texture = texture;
  36. this.source = source;
  37. }
  38. }
  39. static readonly Dictionary<TileSet, string> tileSetToContentPath =
  40. new Dictionary<TileSet, string>() {
  41. { TileSet.Cemetery, "tiles/anokolisa/cemetery" },
  42. { TileSet.Crypt, "tiles/anokolisa/crypt" },
  43. { TileSet.Dungeon, "tiles/anokolisa/dungeon" },
  44. { TileSet.Forest, "tiles/anokolisa/forest" },
  45. { TileSet.Garden, "tiles/anokolisa/garden" },
  46. { TileSet.Grassland, "tiles/anokolisa/grassland" },
  47. { TileSet.Ruins, "tiles/anokolisa/ruins" },
  48. { TileSet.Sewer, "tiles/anokolisa/sewer" },
  49. { TileSet.Temple, "tiles/anokolisa/temple" },
  50. { TileSet.Village, "tiles/anokolisa/village" },
  51. };
  52. readonly Dictionary<Terrain, TextureSource> terrainToTexture =
  53. new Dictionary<Terrain, TextureSource>();
  54. readonly Texture2D[] textures;
  55. public TileFactory(ContentManager content) {
  56. Array tileSets = Enum.GetValues(typeof(TileSet));
  57. textures = new Texture2D[tileSets.Length];
  58. foreach (TileSet tileSet in tileSets) {
  59. textures[(int) tileSet] = content.Load<Texture2D>(tileSetToContentPath[tileSet]);
  60. }
  61. terrainToTexture[Terrain.Grass] = GetTextureSource(TileSet.Grassland, 3, 0);
  62. terrainToTexture[Terrain.GrassL] = GetTextureSource(TileSet.Grassland, 2, 0);
  63. terrainToTexture[Terrain.GrassR] = GetTextureSource(TileSet.Grassland, 4, 0);
  64. terrainToTexture[Terrain.Rock] = GetTextureSource(TileSet.Grassland, 3, 1);
  65. terrainToTexture[Terrain.RockL] = GetTextureSource(TileSet.Grassland, 1, 2);
  66. terrainToTexture[Terrain.RockR] = GetTextureSource(TileSet.Grassland, 5, 2);
  67. terrainToTexture[Terrain.Water] = GetTextureSource(TileSet.Grassland, 9, 2);
  68. terrainToTexture[Terrain.Block] = GetTextureSource(TileSet.Grassland, 6, 3);
  69. }
  70. public Tile MakeTile(Terrain terrain, Rectangle position) {
  71. TextureSource textureSource = terrainToTexture[terrain];
  72. return new Tile(terrain, position, textureSource.texture, textureSource.source);
  73. }
  74. private TextureSource GetTextureSource(TileSet tileSet, int x, int y) {
  75. Texture2D texture = textures[(int) tileSet];
  76. int size = World.TileSize;
  77. Rectangle position = new Rectangle(x * size, y * size, size, size);
  78. return new TextureSource(texture, position);
  79. }
  80. }
  81. class Tile {
  82. readonly Texture2D texture;
  83. readonly Rectangle textureSource;
  84. public Tile(Terrain terrain, Rectangle position, Texture2D texture, Rectangle textureSource) {
  85. Terrain = terrain;
  86. Position = position;
  87. this.texture = texture;
  88. this.textureSource = textureSource;
  89. }
  90. public Rectangle Position { get; private set; }
  91. public Terrain Terrain { get; private set; }
  92. public void Draw(SpriteBatch spriteBatch) {
  93. spriteBatch.Draw(texture, Position.Location.ToVector2(), textureSource, Color.White);
  94. }
  95. }
  96. class World {
  97. public const int TileSize = 16;
  98. readonly Tile[] tiles;
  99. // Size of World in terms of tile grid.
  100. private readonly int tileWidth;
  101. private readonly int tileHeight;
  102. // Size of World in pixels.
  103. public int Width {
  104. get { return tileWidth * TileSize; }
  105. }
  106. public int Height {
  107. get { return tileHeight * TileSize; }
  108. }
  109. private static readonly Dictionary<char, Terrain> charToTerrain =
  110. new Dictionary<char, Terrain>() {
  111. { '=', Terrain.Grass },
  112. { '<', Terrain.GrassL },
  113. { '>', Terrain.GrassR },
  114. { '.', Terrain.Rock },
  115. { '[', Terrain.RockL },
  116. { ']', Terrain.RockR },
  117. { '~', Terrain.Water },
  118. { 'X', Terrain.Block }
  119. };
  120. public World(ContentManager content, string levelSpecification) {
  121. TileFactory factory = new TileFactory(content);
  122. var tilesList = new List<Tile>();
  123. string[] worldDesc = levelSpecification.Split('\n');
  124. tileWidth = worldDesc.AsQueryable().Max(a => a.Length);
  125. tileHeight = worldDesc.Length;
  126. Debug.WriteLine("world size: {0}x{1}", tileWidth, tileHeight);
  127. for (int i = 0; i < tileWidth; i++) {
  128. for (int j = 0; j < tileHeight; j++) {
  129. if (i < worldDesc[j].Length) {
  130. char key = worldDesc[j][i];
  131. if (charToTerrain.ContainsKey(key)) {
  132. Terrain terrain = charToTerrain[key];
  133. var position = new Rectangle(i * TileSize, j * TileSize, TileSize, TileSize);
  134. tilesList.Add(factory.MakeTile(terrain, position));
  135. }
  136. }
  137. }
  138. }
  139. tiles = tilesList.ToArray();
  140. // Because we added tiles from left to right, the CollisionTargets are sorted by x-position.
  141. // We maintain this invariant so that it's possible to efficiently find CollisionTargets that
  142. // are nearby a given x-position.
  143. CollisionTargets = new AABB[tiles.Length + 2];
  144. // Add a synthetic collisionTarget on the left side of the world.
  145. CollisionTargets[0] = new AABB(new Vector2(-1, 0), new Vector2(1, float.MaxValue));
  146. // Now add all the normal collisionTargets for every static terrain tile.
  147. Vector2 halfSize = new Vector2(TileSize / 2, TileSize / 2);
  148. for (int i = 0; i < tiles.Length; i++) {
  149. Vector2 center = new Vector2(
  150. tiles[i].Position.Left + halfSize.X, tiles[i].Position.Top + halfSize.Y);
  151. CollisionTargets[i + 1] = new AABB(center, halfSize);
  152. }
  153. // Add a final synthetic collisionTarget on the right side of the world.
  154. CollisionTargets[tiles.Length + 1] = new AABB(
  155. new Vector2(Width + 1, 0), new Vector2(1, float.MaxValue));
  156. }
  157. public void Draw(SpriteBatch spriteBatch) {
  158. foreach (Tile t in tiles) {
  159. t.Draw(spriteBatch);
  160. }
  161. }
  162. public AABB[] CollisionTargets { get; }
  163. }
  164. }