Code Snippet: Map Generation

28 04 2015

Overview

My capstone project, a dual-stick shooter called “Endless Swarm,” employed procedurally generated levels. To improve the appearance of these levels I developed a system which would select appropriate tiles to piece together a 3D level from a 2D character array. The system is contained in a single MonoBehavior script which accepts as inputs the aforementioned character array and a positional offset at which to create the map.

The generator works by looping through each character in the array and counting the number of each type of tile found adjacent to the current one. E.G. A tile might have one wall tile next to it, and three floor tiles. Tiles diagonal to the origin are not considered. This, combined with the type of tile the algorithm is currently considering, is enough to determine which object or objects need to be placed in 3D space.

The code is organized into two core methods and a number of helper methods. The first, Build() is the publicly exposed method, and is responsible for loading the array, and making the call to ConvertMap(), a method which converts the character array to an array of a TileType enumeration. Then it loops through all the tiles; if a floor is found it calculates the location and creates the floor. Otherwise, if it sees a wall, it calls into the second key method, BuildWallPiece().

public void Build(char[,] input, float xOffset = 0.0f, float yOffset = 0.0f) {
    TileType[,] map = ConvertMap(input);
    int mapWidth = map.GetLength(0);
    int mapHeight = map.GetLength(1);
    for(int x=0; x<mapWidth; ++x) {
        for(int y=0; y<mapHeight; ++y) {
            TileType type = GetTileType(map, x, y);
            if(type == TileType.Floor) {
                Vector3 position = new Vector3((-x - xOffset) * TileSize, 0, (y + yOffset) * TileSize);
                CreateFloor(position);
                floorPositions.Add (position);  //add floor tile positions to a series of lists...
                spawnPositions.Add (position);
                enemyPositions.Add (position);
            } else if (type == TileType.Wall) {
                BuildWallPiece(map, x, y, xOffset, yOffset);
            }
        }
    }
}
private TileType[,] ConvertMap(char[,] input) {
    TileType[,] map = new MapBuilder.TileType[input.GetLength(1), input.GetLength(0)];
    for(int x=0; x<input.GetLength(1); ++x) {
        for(int y=0; y<input.GetLength(0); ++y) {
            var type = MapBuilder.TileType.Void;
            if(input[y,x]=='F') type = MapBuilder.TileType.Floor;
            if(input[y,x]=='W') type = MapBuilder.TileType.Wall;
            int xPos = input.GetLength(1) - (1 + x);
            map[xPos, y] = type;
        }
    }
    return map;
}

BuildWallPiece() takes a similar set of arguments, as well as the coordinates of the tile it is to consider. The method is responsible for counting the adjacent peices and determining which objects to place. Helper methods are used to place the objects into the world.

public void BuildWallPiece(TileType[,] map, int x, int y, float xOffset, float yOffset) {
    Vector3 position = new Vector3((-x - xOffset) * TileSize, 0, (y + yOffset) * TileSize);
    
    TileType north, south, east, west;
    north = GetTileType(map, x, y - 1);
    south = GetTileType(map, x, y + 1);
    east = GetTileType(map, x + 1, y);
    west = GetTileType(map, x - 1, y);
    
    int numWalls = 0, numFloors = 0;
    if(north == TileType.Wall) ++numWalls;
    else if(north == TileType.Floor) ++numFloors;
    if(south == TileType.Wall) ++numWalls;
    else if(south == TileType.Floor) ++numFloors;
    if(east == TileType.Wall) ++numWalls;
    else if(east == TileType.Floor) ++numFloors;
    if(west == TileType.Wall) ++numWalls;
    else if(west == TileType.Floor) ++numFloors;
    
    if(numFloors == 1) {
        float angle = 0;
        if(north == TileType.Floor) angle = 90;
        else if(east == TileType.Floor) angle = 180;
        else if(south == TileType.Floor) angle = 270;
        
        CreateWall1(angle, position);
    } else if(numFloors == 2) {
        if(north == TileType.Floor && south == TileType.Floor) {
            CreateWall1(90, position);
            CreateWall1(270, position);
        } else if (east == TileType.Floor && west  == TileType.Floor) {
            CreateWall1(0, position); 
            CreateWall1(180, position);
        } else if(north == TileType.Floor && west == TileType.Floor) {
            CreateWall2(0, position);
        } else if(north == TileType.Floor && east == TileType.Floor) {
            CreateWall2(90, position);
        } else if(south == TileType.Floor && east == TileType.Floor) {
            CreateWall2(180, position);
        } else if(south == TileType.Floor && west == TileType.Floor) {
            CreateWall2(270, position);
        }
    } else if(numFloors == 3) {
        if(north != TileType.Floor) {
            CreateWall3(0, position);
        } else if(east != TileType.Floor) {
            CreateWall3(90, position);
        } else if(south != TileType.Floor) {
            CreateWall3(180, position);
        } else if(west != TileType.Floor) {
            CreateWall3(270, position);
        }
    } else if(numFloors == 4) {
        CreateWall4(position);
    }
    
    if(numWalls > 1) {
        if(south == TileType.Wall && west == TileType.Wall) {
            CreateWall5(0, position);
        }
        if(north == TileType.Wall && west == TileType.Wall) {
            CreateWall5(90, position);
        }
        if(north == TileType.Wall && east == TileType.Wall) {
            CreateWall5(180, position);
        }
        if(south == TileType.Wall && east == TileType.Wall) {
            CreateWall5(270, position);
        }
    }
}

Complete Source

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MapBuilder : MonoBehaviour {
    public Transform Floor, Wall1, Wall2, Wall3, Wall4, Wall5;
    public float TileSize = 5.0f;
    public List<Vector3> floorPositions = new List<Vector3> ();
    public List<Vector3> spawnPositions = new List<Vector3> ();
    public List<Vector3> enemyPositions = new List<Vector3> ();
    
    public enum TileType { Floor, Wall, Void };
    
    public void Build(char[,] input, float xOffset = 0.0f, float yOffset = 0.0f) {
        TileType[,] map = ConvertMap(input);
        int mapWidth = map.GetLength(0);
        int mapHeight = map.GetLength(1);
        for(int x=0; x<mapWidth; ++x) {
            for(int y=0; y<mapHeight; ++y) {
                TileType type = GetTileType(map, x, y);
                if(type == TileType.Floor) {
                    Vector3 position = new Vector3((-x - xOffset) * TileSize, 0, (y + yOffset) * TileSize);
                    CreateFloor(position);
                    floorPositions.Add (position);  //add floor tile positions to a series of lists...
                    spawnPositions.Add (position);
                    enemyPositions.Add (position);
                } else if (type == TileType.Wall) {
                    BuildWallPiece(map, x, y, xOffset, yOffset);
                }
            }
        }
    }
    
    public void BuildWallPiece(TileType[,] map, int x, int y, float xOffset, float yOffset) {
        Vector3 position = new Vector3((-x - xOffset) * TileSize, 0, (y + yOffset) * TileSize);
        
        TileType north, south, east, west;
        north = GetTileType(map, x, y - 1);
        south = GetTileType(map, x, y + 1);
        east = GetTileType(map, x + 1, y);
        west = GetTileType(map, x - 1, y);
        
        int numWalls = 0, numFloors = 0;
        if(north == TileType.Wall) ++numWalls;
        else if(north == TileType.Floor) ++numFloors;
        if(south == TileType.Wall) ++numWalls;
        else if(south == TileType.Floor) ++numFloors;
        if(east == TileType.Wall) ++numWalls;
        else if(east == TileType.Floor) ++numFloors;
        if(west == TileType.Wall) ++numWalls;
        else if(west == TileType.Floor) ++numFloors;
        
        if(numFloors == 1) {
            float angle = 0;
            if(north == TileType.Floor) angle = 90;
            else if(east == TileType.Floor) angle = 180;
            else if(south == TileType.Floor) angle = 270;
            
            CreateWall1(angle, position);
        } else if(numFloors == 2) {
            if(north == TileType.Floor && south == TileType.Floor) {
                CreateWall1(90, position);
                CreateWall1(270, position);
            } else if (east == TileType.Floor && west  == TileType.Floor) {
                CreateWall1(0, position); 
                CreateWall1(180, position);
            } else if(north == TileType.Floor && west == TileType.Floor) {
                CreateWall2(0, position);
            } else if(north == TileType.Floor && east == TileType.Floor) {
                CreateWall2(90, position);
            } else if(south == TileType.Floor && east == TileType.Floor) {
                CreateWall2(180, position);
            } else if(south == TileType.Floor && west == TileType.Floor) {
                CreateWall2(270, position);
            }
        } else if(numFloors == 3) {
            if(north != TileType.Floor) {
                CreateWall3(0, position);
            } else if(east != TileType.Floor) {
                CreateWall3(90, position);
            } else if(south != TileType.Floor) {
                CreateWall3(180, position);
            } else if(west != TileType.Floor) {
                CreateWall3(270, position);
            }
        } else if(numFloors == 4) {
            CreateWall4(position);
        }
        
        if(numWalls > 1) {
            if(south == TileType.Wall && west == TileType.Wall) {
                CreateWall5(0, position);
            }
            if(north == TileType.Wall && west == TileType.Wall) {
                CreateWall5(90, position);
            }
            if(north == TileType.Wall && east == TileType.Wall) {
                CreateWall5(180, position);
            }
            if(south == TileType.Wall && east == TileType.Wall) {
                CreateWall5(270, position);
            }
        }
    }
    
    public TileType GetTileType(TileType[,] map, int x, int y) {
        if(x < 0 || x >= map.GetLength(0) || y < 0 || y >= map.GetLength(1)) {
            return TileType.Void;
        } else {
            return map[x, y];
        }
    }
    
    private TileType[,] ConvertMap(char[,] input) {
        TileType[,] map = new MapBuilder.TileType[input.GetLength(1), input.GetLength(0)];
        for(int x=0; x<input.GetLength(1); ++x) {
            for(int y=0; y<input.GetLength(0); ++y) {
                var type = MapBuilder.TileType.Void;
                if(input[y,x]=='F') type = MapBuilder.TileType.Floor;
                if(input[y,x]=='W') type = MapBuilder.TileType.Wall;
                int xPos = input.GetLength(1) - (1 + x);
                map[xPos, y] = type;
            }
        }
        return map;
    }
    
    private void CreateFloor(Vector3 position) {
        Quaternion rotation = Quaternion.AngleAxis(0, Vector3.up);
        var piece = GameObject.Instantiate(Floor, position, rotation) as Transform;
        piece.parent = transform;
    }
    private void CreateWall1(float angle, Vector3 position) {
        Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.up);
        var piece = GameObject.Instantiate(Wall1, position, rotation) as Transform;
        piece.parent = transform;
    }
    private void CreateWall2(float angle, Vector3 position) {
        Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.up);
        var piece = GameObject.Instantiate(Wall2, position, rotation) as Transform;
        piece.parent = transform;
    }
    private void CreateWall3(float angle, Vector3 position) {
        Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.up);
        var piece = GameObject.Instantiate(Wall3, position, rotation) as Transform;
        piece.parent = transform;
    }
    private void CreateWall4(Vector3 position) {
        Quaternion rotation = Quaternion.AngleAxis(0, Vector3.up);
        var piece = GameObject.Instantiate(Wall4, position, rotation) as Transform;
        piece.parent = transform;
    }
    private void CreateWall5(float angle, Vector3 position) {
        Quaternion rotation = Quaternion.AngleAxis(angle, Vector3.up);
        var piece = GameObject.Instantiate(Wall5, position, rotation) as Transform;
        piece.parent = transform;
    }
}
Advertisements

Actions

Information

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: