Last active
December 3, 2019 15:48
-
-
Save RobertBouillon/3e1890b00f09144e39eeccabe3e05230 to your computer and use it in GitHub Desktop.
2D Map Generator C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text.RegularExpressions; | |
using Texture = System.Object; | |
namespace Core3Sandbox | |
{ | |
[Flags] | |
public enum Faces | |
{ | |
None = 0, | |
Up = 1, | |
Down = 2, | |
Left = 4, | |
Right = 8, | |
All = Up + Down + Left + Right | |
} | |
public enum TileMaterial | |
{ | |
Stone, | |
Dirt, | |
TheTearsOfMyEnemies | |
} | |
public class Tile | |
{ | |
int X { get; set; } | |
int Y { get; set; } | |
public TileMaterial Material { get; } | |
public Tile(int x, int y, TileMaterial material) | |
{ | |
X = x; | |
Y = y; | |
Material = material; | |
} | |
public override string ToString() => $"Tile of {Material}"; | |
} | |
public class Wall : Tile | |
{ | |
public Faces Faces { get; } | |
public Wall(int x, int y, TileMaterial material, Faces faces) : base(x, y, material) => Faces = faces; | |
public override string ToString() => $"Wall of {Material} - Faces: {Faces}"; | |
} | |
//Use this class to load your tiles from files or whatever for your UI. | |
//For example, if your tile is STONE and has a corner in the bottom-right, this will try to | |
public class TileSet | |
{ | |
private static Regex _fileNameParser = new Regex(@"(?<material>\w+)_(?<faces>\d{1,2})", RegexOptions.Compiled); | |
private static Texture LoadTexture(string fileName) => throw new NotImplementedException("Your game engine determines how you should load textures. Override this to load the texture how your game engine needs it."); | |
private Dictionary<(TileMaterial, Faces), Texture> _cache; | |
private static IEnumerable<(TileMaterial Material, Faces Faces, Texture Texture)> LoadTextures(string name) => | |
Directory.GetFiles($@"Textures\{name}") | |
.Select(x => _fileNameParser.Match(x)) | |
.Where(x => x.Success) | |
.Select(x => (Material: Enum.Parse<TileMaterial>(x.Groups["material"].Value), Faces: (Faces)Int32.Parse(x.Groups["faces"].Value), Texture: LoadTexture(x.Value))); | |
public string Name { get; } | |
public object GetTexture(TileMaterial material, Faces faces) => LoadTexture($@"{Name}\{material}_{(int)faces}"); | |
public TileSet(string name) | |
{ | |
#region Validation | |
if (String.IsNullOrWhiteSpace(name)) | |
throw new ArgumentNullException(nameof(name)); | |
#endregion | |
Name = name; | |
_cache = LoadTextures(name).ToDictionary(x => (x.Material, x.Faces), x => x.Texture); | |
} | |
public Texture this[TileMaterial material, Faces faces] => _cache[(material, faces)]; | |
} | |
public class Map : IEnumerable<Tile> | |
{ | |
#region Map Generation | |
public static Map Generate(int width, int height, int? seed = null) | |
{ | |
var map = SeedNewMap(width, height, seed); | |
RefineMap(map); | |
var faces = CalculateFaces(map); | |
Tile[,] tiles = new Tile[width, height]; | |
for (int x = 0; x < width; x++) | |
for (int y = 0; y < height; y++) | |
tiles[x, y] = (!map[x, y]) ? new Tile(x, y, TileMaterial.Dirt) : | |
faces[x, y] == Faces.None ? new Tile(x, y, TileMaterial.Stone) : | |
new Wall(x, y, TileMaterial.Stone, faces[x, y]); | |
return new Map(tiles); | |
} | |
private static Faces[,] CalculateFaces(bool[,] map) | |
{ | |
var width = map.GetUpperBound(0) + 1; | |
var height = map.GetUpperBound(1) + 1; | |
var faces = new Faces[width, height]; | |
for (int x = 0; x < width - 1; x++) | |
for (int y = 0; y < height; y++) | |
if (!map[x, y]) | |
faces[x + 1, y] |= Faces.Left; | |
for (int x = 1; x < width; x++) | |
for (int y = 0; y < height; y++) | |
if (!map[x, y]) | |
faces[x - 1, y] |= Faces.Right; | |
for (int x = 0; x < width; x++) | |
for (int y = 0; y < height - 1; y++) | |
if (!map[x, y]) | |
faces[x, y + 1] |= Faces.Up; | |
for (int x = 0; x < width; x++) | |
for (int y = 1; y < height; y++) | |
if (!map[x, y]) | |
faces[x, y - 1] |= Faces.Down; | |
return faces; | |
} | |
private static void RefineMap(bool[,] map, int passes = 5) | |
{ | |
var width = map.GetUpperBound(0) + 1; | |
var height = map.GetUpperBound(1) + 1; | |
var counts = new int[width, height]; | |
for (int pass = 0; pass < passes; pass++) | |
{ | |
for (int x = 0; x < width; x++) | |
for (int y = 0; y < height; y++) | |
{ | |
var xx_max = Math.Min(width - 1, x + 1); | |
var yy_max = Math.Min(height - 1, y + 1); | |
for (var xx = Math.Max(0, x - 1); xx <= xx_max; xx++) | |
for (var yy = Math.Max(0, y - 1); yy <= yy_max; yy++) | |
if (map[xx, yy]) | |
counts[xx, yy] += 1; | |
} | |
for (int x = 0; x < width; x++) | |
for (int y = 0; y < height; y++) | |
map[x, y] = counts[x, y] >= 5; | |
for (int x = 0; x < width; x++) | |
for (int y = 0; y < height; y++) | |
counts[x, y] = 0; | |
} | |
} | |
private static bool[,] SeedNewMap(int width, int height, int? seed) | |
{ | |
Random rnd = new Random(seed ?? (int)DateTime.Now.Ticks); | |
var map = new bool[width, height]; | |
for (int x = 0; x < width; x++) | |
for (int y = 0; y < height; y++) | |
map[x, y] = rnd.NextDouble() > 0.5; | |
return map; | |
} | |
#endregion | |
private Tile[,] _tiles; | |
public int Width => _tiles.GetUpperBound(0) + 1; | |
public int Height => _tiles.GetUpperBound(1) + 1; | |
internal Map(Tile[,] tiles) | |
{ | |
#region Validation | |
const int MAX_SIZE = 50000; | |
if (tiles is null) | |
throw new ArgumentNullException(nameof(tiles)); | |
//Sanity check. Limit is arbitrary | |
if (tiles.GetUpperBound(0) > MAX_SIZE) | |
throw new ArgumentOutOfRangeException($"Cannot create a map with a width greater than {MAX_SIZE}"); | |
if (tiles.GetUpperBound(1) > MAX_SIZE) | |
throw new ArgumentOutOfRangeException($"Cannot create a map with a height greater than {MAX_SIZE}"); | |
#endregion | |
_tiles = tiles; | |
} | |
public Tile this[int x, int y] => _tiles[x, y]; | |
public Texture GetTexture(TileSet tileSet, int x, int y) | |
{ | |
var tile = this[x, y]; | |
return tile is Wall wall ? tileSet[wall.Material, wall.Faces] : tileSet[tile.Material, Faces.None]; | |
} | |
public IEnumerator<Tile> GetEnumerator() => Enumerable.Range(0, Width).SelectMany(x => Enumerable.Range(0, Height).Select(y => _tiles[x, y])).GetEnumerator(); | |
IEnumerator IEnumerable.GetEnumerator() => _tiles.GetEnumerator(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment