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.

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