Check those Neighbors

In this section, you will be adding new code to the MapBuilder API that was created in an earlier lesson. The additional code you will add will help improve your generation algorithm so you can check neighboring tiles for specific patterns that will allow you to spawn a tile to improve the look of the map.

Add Type Enum

Having an easy way of accessing some of the tiles from the Tiles data table will help later when checking the value of a tile in the map. An Enum is a table that will have a list of values that can be accessed using a constant. For example, MapBuilder.Type.WallCorner would return the id value. This is much better than typing Tiles[3] which does not give any clue as to which tile it is. As more tiles get added to the data table, this will be far more useful.

When defining the MapBuilder table, add a new property called Type that can be set to an empty table for now.

Manually adding all the types to the Type table would be tedious. So what you can do, is loop over all the rows in the Tiles data table and add them automatically to the Type table. The key value from the row will be the key that will act as the constant, and the value will be the id from the row.

local MapBuilder = {

	Type = {}

}

for key, row in pairs(TILES) do
	MapBuilder.Type[row.key] = row.id
end
Code language: Lua (lua)

Create GetTile Function

Create a new function called GetTile. This function will receive the map, row, and column which will try to find the row and column passed in. Because there is a chance of the row or column being out of bounds, this will handle that case as well by making sure the foundRow exists.

function MapBuilder.GetTile(map, row, col)
	local foundRow = map[row]

	if foundRow then
		return foundRow[col]
	end

	return nil
end
Code language: Lua (lua)

Create GetNeighbors Function

Create a new function called GetNeighbors. This function will return a table that contains the tiles for the cardinal directions. By adding or taking away from either the row or column values, you can get the North, South, East, and West tiles from the current tile in the loop found in the Spawn function.

function MapBuilder.GetNeighbors(map, row, column)
	return {

		NORTH = MapBuilder.GetTile(map, row - 1, column),
		SOUTH = MapBuilder.GetTile(map, row + 1, column),
		EAST = MapBuilder.GetTile(map, row, column + 1),
		WEST = MapBuilder.GetTile(map, row, column - 1)

	}
end
Code language: Lua (lua)

Create CheckForWallCorner Function

Create a new function called CheckForWallCorner. This function is going to look for a pattern by going around the cardinal directions. Now that you have a way to get the neighbors of a tile, you can check if those neighbors match the 3 tile corner pattern. As you work around the cardinal directions, the tile rotation will also need to be rotated so the spawned tile faces the correct way. The function will return the tileRow, the rotation, and a boolean to indicate a floor tile should be spawned as well.

Looking at the first pattern check, let us break it down to see what it is checking.

  • A check to the WEST is done first to see if that neighbor is a wall.
  • A check to the NORTH is done to see if that neighbor is a wall.
  • The current tile in the loop is checked to make sure it is not a wall.
  • A check to the EAST is done to make sure it is not nil.
  • A check to the SOUTH is done to make sure it is not nil.

The nil checking is to make sure that a tile exists. If you don’t do these checks, your map will still generate, but you will see some Wall Corners appear in spots that don’t make sense.

Tile Rotation

If the children of your tile have been rotated, then the rotation for the Z will be different for you. Change the rotation Z value until you find which values work for your tile.

function MapBuilder.CheckForWallCorner(map, neighbors, row, col)
	local tileRow = nil
	local rotation = Rotation.New()

	if neighbors.WEST == MapBuilder.Type.Wall and neighbors.NORTH == MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall and neighbors.EAST ~= nil and neighbors.SOUTH ~= nil then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = 0
	elseif neighbors.NORTH == MapBuilder.Type.Wall and neighbors.EAST == MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall and neighbors.WEST ~= nil and neighbors.SOUTH ~= nil then
	 	tileRow = TILES[MapBuilder.Type.WallCorner]
	 	rotation.z = 90
	elseif neighbors.EAST == MapBuilder.Type.Wall and neighbors.SOUTH == MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall and neighbors.NORTH ~= nil and neighbors.WEST ~= nil then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = 180
	elseif neighbors.SOUTH == MapBuilder.Type.Wall and neighbors.WEST == MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall and neighbors.EAST ~= nil and neighbors.NORTH ~= nil  then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = 270
	end

	return tileRow, rotation, true
end
Code language: Lua (lua)

Create SpawnExtraTile Function

There will be cases where an extra tile will need to be spawned with the current tile. For example, when spawning a Wall Corner tile, a Floor tile will need to be spawned as well, or there will be a hole in the floor. In this case, the function will just be spawning a Floor tile, but this may change later as the algorithm improves.

Create a new function called SpawnExtraTile. This function will take in the params table from the Spawn function so it can reuse those options.

function MapBuilder.SpawnExtraTile(params, container)
	params.scale.z = 1
	params.rotation = Rotation.New()
		
	container:SpawnSharedAsset(TILES[MapBuilder.Type.Floor].tile, params)
endCode language: JavaScript (javascript)

Update Spawn Function

The Spawn function will need to be updated so it can get the neighbors and work out if a wall corner should be spawned or not. Most of this code is the same, the only difference is that it is calling a few functions from within the Spawn function, and checking to make sure it can spawn an extra tile.

function MapBuilder.Spawn(map, width, height, size, container)
	if not Environment.IsServer() then
		return
	end

	for row = 1, height do
		for col = 1, width do
			local neighbors = MapBuilder.GetNeighbors(map, row, col)
			local tileRow, rotation, spawnFloor = MapBuilder.CheckForWallCorner(map, neighbors, row, col)

			if tileRow == nil then
				tileRow = TILES[map[row][col]]
			end		
	
			if tileRow ~= nil then
				local scale = Vector3.New(size / 100, size / 100, size / 100)

				if tileRow.floor then
					scale.z = 1
				end

				local x = -(row * size) + (height * size) / 2
				local y = (col * size) - (width * size) / 2
			
				local params = {

					scale = scale,
					position = Vector3.New(x, y, 0),
					rotation = rotation

				}

				container:SpawnSharedAsset(tileRow.tile, params)

				if(spawnFloor) then
					MapBuilder.SpawnExtraTile(params, container)
				end
			end
		end
	end
end
Code language: Lua (lua)

The MapBuilder API Script

local ASCIIParser = require(script:GetCustomProperty("ASCIIParser"))
local TILES = require(script:GetCustomProperty("Tiles"))

local MapBuilder = {

	Type = {}

}

for key, row in pairs(TILES) do
	MapBuilder.Type[row.key] = row.id
end

function MapBuilder.Build(opts)
	local map, mapStr = ASCIIParser.BuildMap(opts.map, opts.width, opts.height)

	MapBuilder.Spawn(map, opts.width, opts.height, opts.size, opts.container)
end

function MapBuilder.GetTile(map, row, col)
	local foundRow = map[row]

	if foundRow then
		return foundRow[col]
	end

	return nil
end

function MapBuilder.GetNeighbors(map, row, column)
	return {

		NORTH = MapBuilder.GetTile(map, row - 1, column),
		SOUTH = MapBuilder.GetTile(map, row + 1, column),
		EAST = MapBuilder.GetTile(map, row, column + 1),
		WEST = MapBuilder.GetTile(map, row, column - 1)

	}
end

function MapBuilder.CheckForWallCorner(map, neighbors, row, col)
	local tileRow = nil
	local rotation = Rotation.New()

	if neighbors.WEST == MapBuilder.Type.Wall and neighbors.NORTH == MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall and neighbors.EAST ~= nil and neighbors.SOUTH ~= nil then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = 0
	elseif neighbors.NORTH == MapBuilder.Type.Wall and neighbors.EAST == MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall and neighbors.WEST ~= nil and neighbors.SOUTH ~= nil then
		tileRow = TILES[MapBuilder.Type.WallCorner]
	 	rotation.z = 90
	elseif neighbors.EAST == MapBuilder.Type.Wall and neighbors.SOUTH == MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall and neighbors.NORTH ~= nil and neighbors.WEST ~= nil then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = 180
	elseif neighbors.SOUTH == MapBuilder.Type.Wall and neighbors.WEST == MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall and neighbors.EAST ~= nil and neighbors.NORTH ~= nil  then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = 270
	end

	return tileRow, rotation, true
end

function MapBuilder.SpawnExtraTile(params, container)
	params.scale.z = 1
	params.rotation = Rotation.New()
		
	container:SpawnSharedAsset(TILES[MapBuilder.Type.Floor].tile, params)
end

function MapBuilder.Spawn(map, width, height, size, container)
	if not Environment.IsServer() then
		return
	end

	for row = 1, height do
		for col = 1, width do
			local neighbors = MapBuilder.GetNeighbors(map, row, col)
			local tileRow, rotation, spawnFloor = MapBuilder.CheckForWallCorner(map, neighbors, row, col)

			if tileRow == nil then
				tileRow = TILES[map[row][col]]
			end		
	
			if tileRow ~= nil then
				local scale = Vector3.New(size / 100, size / 100, size / 100)

				if tileRow.floor then
					scale.z = 1
				end

				local x = -(row * size) + (height * size) / 2
				local y = (col * size) - (width * size) / 2
			
				local params = {

					scale = scale,
					position = Vector3.New(x, y, 0),
					rotation = rotation

				}

				container:SpawnSharedAsset(tileRow.tile, params)

				if(spawnFloor) then
					MapBuilder.SpawnExtraTile(params, container)
				end
			end
		end
	end
end

return MapBuilder
Code language: Lua (lua)

Test the Game

Test the game to make sure that corners are now using a different tile. You will see dead ends are using the wall corner tile as well. This will be addressed in the next lesson.

Scroll to Top