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.

157 lines
5.0 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. enum Terrain {
  8. Grass,
  9. GrassL,
  10. GrassR,
  11. Rock,
  12. RockL,
  13. RockR,
  14. Water,
  15. Block
  16. }
  17. class TileFactory {
  18. struct TextureSource {
  19. public Texture2D texture;
  20. public Rectangle source;
  21. public TextureSource(Texture2D texture, Rectangle source) {
  22. this.texture = texture;
  23. this.source = source;
  24. }
  25. }
  26. readonly Dictionary<Terrain, TextureSource> terrainToTexture;
  27. public TileFactory() {
  28. terrainToTexture = new Dictionary<Terrain, TextureSource>() {
  29. { Terrain.GrassL, GetTextureSource(Textures.Grassland, 2, 0) },
  30. { Terrain.Grass, GetTextureSource(Textures.Grassland, 3, 0) },
  31. { Terrain.GrassR, GetTextureSource(Textures.Grassland, 4, 0) },
  32. { Terrain.Rock, GetTextureSource(Textures.Grassland, 3, 1) },
  33. { Terrain.RockL, GetTextureSource(Textures.Grassland, 1, 2) },
  34. { Terrain.RockR, GetTextureSource(Textures.Grassland, 5, 2) },
  35. { Terrain.Water, GetTextureSource(Textures.Grassland, 9, 2) },
  36. { Terrain.Block, GetTextureSource(Textures.Grassland, 6, 3) },
  37. };
  38. }
  39. public Tile MakeTile(Terrain terrain, Rectangle position) {
  40. TextureSource textureSource = terrainToTexture[terrain];
  41. return new Tile(terrain, position, textureSource.texture, textureSource.source);
  42. }
  43. private TextureSource GetTextureSource(Texture2D texture, int x, int y) {
  44. int size = World.TileSize;
  45. Rectangle position = new Rectangle(x * size, y * size, size, size);
  46. return new TextureSource(texture, position);
  47. }
  48. }
  49. class Tile {
  50. readonly Texture2D texture;
  51. readonly Rectangle textureSource;
  52. public Tile(Terrain terrain, Rectangle position, Texture2D texture, Rectangle textureSource) {
  53. Terrain = terrain;
  54. Position = position;
  55. this.texture = texture;
  56. this.textureSource = textureSource;
  57. }
  58. public Rectangle Position { get; private set; }
  59. public Terrain Terrain { get; private set; }
  60. public void Draw(SpriteBatch spriteBatch) {
  61. spriteBatch.Draw(texture, Position.Location.ToVector2(), textureSource, Color.White);
  62. }
  63. }
  64. class World {
  65. public const int TileSize = 16;
  66. readonly Tile[] tiles;
  67. // Size of World in terms of tile grid.
  68. private readonly int tileWidth;
  69. private readonly int tileHeight;
  70. // Size of World in pixels.
  71. public int Width {
  72. get { return tileWidth * TileSize; }
  73. }
  74. public int Height {
  75. get { return tileHeight * TileSize; }
  76. }
  77. private static readonly Dictionary<char, Terrain> charToTerrain =
  78. new Dictionary<char, Terrain>() {
  79. { '=', Terrain.Grass },
  80. { '<', Terrain.GrassL },
  81. { '>', Terrain.GrassR },
  82. { '.', Terrain.Rock },
  83. { '[', Terrain.RockL },
  84. { ']', Terrain.RockR },
  85. { '~', Terrain.Water },
  86. { 'X', Terrain.Block }
  87. };
  88. public World(string levelSpecification) {
  89. TileFactory factory = new TileFactory();
  90. var tilesList = new List<Tile>();
  91. string[] worldDesc = levelSpecification.Split('\n');
  92. tileWidth = worldDesc.AsQueryable().Max(a => a.Length);
  93. tileHeight = worldDesc.Length;
  94. Debug.WriteLine("world size: {0}x{1}", tileWidth, tileHeight);
  95. for (int i = 0; i < tileWidth; i++) {
  96. for (int j = 0; j < tileHeight; j++) {
  97. if (i < worldDesc[j].Length) {
  98. char key = worldDesc[j][i];
  99. if (charToTerrain.ContainsKey(key)) {
  100. Terrain terrain = charToTerrain[key];
  101. var position = new Rectangle(i * TileSize, j * TileSize, TileSize, TileSize);
  102. tilesList.Add(factory.MakeTile(terrain, position));
  103. }
  104. }
  105. }
  106. }
  107. tiles = tilesList.ToArray();
  108. // Because we added tiles from left to right, the CollisionTargets are sorted by x-position.
  109. // We maintain this invariant so that it's possible to efficiently find CollisionTargets that
  110. // are nearby a given x-position.
  111. CollisionTargets = new AABB[tiles.Length + 2];
  112. // Add a synthetic collisionTarget on the left side of the world.
  113. CollisionTargets[0] = new AABB(new Vector2(-1, 0), new Vector2(1, float.MaxValue));
  114. // Now add all the normal collisionTargets for every static terrain tile.
  115. Vector2 halfSize = new Vector2(TileSize / 2, TileSize / 2);
  116. for (int i = 0; i < tiles.Length; i++) {
  117. Vector2 center = new Vector2(
  118. tiles[i].Position.Left + halfSize.X, tiles[i].Position.Top + halfSize.Y);
  119. CollisionTargets[i + 1] = new AABB(center, halfSize);
  120. }
  121. // Add a final synthetic collisionTarget on the right side of the world.
  122. CollisionTargets[tiles.Length + 1] = new AABB(
  123. new Vector2(Width + 1, 0), new Vector2(1, float.MaxValue));
  124. }
  125. public void Draw(SpriteBatch spriteBatch) {
  126. foreach (Tile t in tiles) {
  127. t.Draw(spriteBatch);
  128. }
  129. }
  130. public AABB[] CollisionTargets { get; }
  131. }
  132. }