Creating a Roof

In this section, you will create a roof tile that will be spawned after all the map generation has been completed so the roof size can be sized dynamically to cover the map.

Create Roof Tile

Open the Tiles scene and created a new roof tile. The roof tile can be made out of a 1 sided plane that is rotated so you can still see the map generation from above the roof.

  1. Create a new group in the Hierarchy called Roof.
  2. Search for plane 1 sided in Core Content to find Plane 1m – One Sided and add it as a child of the Roof group in the Hierarchy.
  3. Rename the plane to Tile
  4. Rotate the plane 180 degrees on the Y axis.
  5. Give the tile a material similar to the wall materials.
  6. Create a template of the roof and add it to the My Tiles folder in Project Content.

Add Roof Tile to Tiles Data Table

The new Roof tile will need to be added to the Tiles data table so it can be accessed from the MapBuilder script later.

  1. In My Tables in Project Content, double click on the Tiles data table to open it.
  2. Add a new row to the data table.
  3. Set the id column to -2.
  4. Add the template to the tile column.
  5. Set the key column to Roof.

Update MapBuilder API Script

The MapBuilder script will need to be updated so that it can spawn the roof tile and size it correctly based on the width and height of the generated map.

Create SpawnRoof Function

Create a new function called SpawnRoof that will spawn the roof tile and set the scale and position so it covers the top of the dungeon so it becomes a sealed map. The scale of the Z axis can be left at 1, but the X and Y will be scaled based on the height and width. Noticed that the height is used in the X calculation. This is because the direction of the rows is created in the world forward direction.

The Z position of the roof is moved down a little bit to make sure no gaps appear. This will depend on what mesh you use for your wall tiles, so this may need tweaking.

function MapBuilder.SpawnRoof(width, height, size, container)
	container:SpawnSharedAsset(TILES[MapBuilder.Type.Roof].tile, {
	
		scale = Vector3.New(size / 100 * height, size / 100 * width, 1),
		position = Vector3.New(-size / 2, size / 2, size - 60)
	
	})
end
Code language: Lua (lua)

Update Spawn Function

The Spawn function will need to call the SpawnRoof function after the map spawning has been done.

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

	local spawnPoint = nil
	local enemySpawnPoints = {}

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

			tileRow, rotation, extraTile = MapBuilder.CheckForDeadEnd(map, neighbors, row, col)
			
			if tileRow == nil then
				tileRow, rotation, extraTile = MapBuilder.CheckForWallCorner(map, neighbors, row, col)
			end

			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

				}

				if tileRow.tile ~= nil then
					container:SpawnSharedAsset(tileRow.tile, params)
				end

				if map[row][col] == MapBuilder.Type.Spawn then
					spawnPoint = params.position

					if extraTile == nil then
						extraTile = TILES[MapBuilder.Type.Floor]
					end
				elseif map[row][col] == MapBuilder.Type.Enemy then
					table.insert(enemySpawnPoints, params.position)

					if extraTile == nil then
						extraTile = TILES[MapBuilder.Type.Floor]
					end
				end

				if extraTile ~= nil then
					MapBuilder.SpawnExtraTile(extraTile, params, container)
				end
			end
		end
	end

	MapBuilder.SpawnPlayersAndEnemies(spawnPoint, enemySpawnPoints, container)
	MapBuilder.GenerateNavMesh(width, height, size, container)
	MapBuilder.SpawnRoof(width, height, size, container)
end
Code language: Lua (lua)

The MapBuilder Script

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

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 extraTile = 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

	if tileRow ~= nil then
		extraTile = TILES[MapBuilder.Type.Floor]
	end

	return tileRow, rotation, extraTile
end

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

	if neighbors.WEST == MapBuilder.Type.Wall and neighbors.NORTH == MapBuilder.Type.Wall and neighbors.EAST == MapBuilder.Type.Wall and neighbors.SOUTH ~= MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = 45
	elseif neighbors.NORTH == MapBuilder.Type.Wall and neighbors.EAST == MapBuilder.Type.Wall and neighbors.SOUTH == MapBuilder.Type.Wall and neighbors.WEST ~= MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = 135
	elseif neighbors.EAST == MapBuilder.Type.Wall and neighbors.SOUTH == MapBuilder.Type.Wall and neighbors.WEST == MapBuilder.Type.Wall and neighbors.NORTH ~= MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = -135
	elseif neighbors.SOUTH == MapBuilder.Type.Wall and neighbors.WEST == MapBuilder.Type.Wall and neighbors.NORTH == MapBuilder.Type.Wall and neighbors.EAST ~= MapBuilder.Type.Wall and map[row][col] ~= MapBuilder.Type.Wall then
		tileRow = TILES[MapBuilder.Type.WallCorner]
		rotation.z = -45
	end

	if tileRow ~= nil then
		extraTile = TILES[MapBuilder.Type.Floor]
	end

	return tileRow, rotation, extraTile
end

function MapBuilder.SpawnExtraTile(extraTile, params, container)
	if extraTile == nil then
		return
	end

	if MapBuilder.Type.Floor == extraTile.id then
		params.scale.z = 1
		params.rotation = Rotation.New()
	end

	container:SpawnSharedAsset(extraTile.tile, params)
end

function MapBuilder.SpawnRoof(width, height, size, container)
	container:SpawnSharedAsset(TILES[MapBuilder.Type.Roof].tile, {
	
		scale = Vector3.New(size / 100 * height, size / 100 * width, 1),
		position = Vector3.New(-size / 2, size / 2, size - 60)
	
	})
end

function MapBuilder.SpawnPlayers(spawnPoint)
	if spawnPoint == nil or not Environment.IsServer() then
		return
	end

	Task.Wait()
	Events.Broadcast("SpawnPlayers", spawnPoint, TILES[MapBuilder.Type.Spawn].rotation)
end

function MapBuilder.SpawnEnemies(spawnPoints, container)
	for index, point in ipairs(spawnPoints) do
		local enemyAsset = ENEMIES[math.random(#ENEMIES)].asset
		local enemy = World.SpawnAsset(enemyAsset, {
			
			position = point + (Vector3.UP * 50),
			networkContext = NetworkContextType.NETWORKED,
			rotation = Rotation.New(0, 0, math.random(0, 360))
		
		})
	end
end

function MapBuilder.GenerateNavMesh(width, height, size, container)
	_G.NavMeshArea = {

		scale = Vector3.New(size / 100 * height, size / 100 * width, size / 100),
		position = Vector3.New(-size / 2, size / 2, size / 2)

	}

	Events.Connect("NavMeshProgress", function(v, isGrid)
		if isGrid then
			container:SetCustomProperty("grid", v)
		else
			container:SetCustomProperty("grid", 1)
			container:SetCustomProperty("mesh", v)
		end
	end)
end

function MapBuilder.SpawnPlayersAndEnemies(spawnPoint, enemySpawnPoints, container)
	Events.Connect("NavMeshGenerated", function()
		container:SetCustomProperty("mesh", 1)
		MapBuilder.SpawnEnemies(enemySpawnPoints)
		MapBuilder.SpawnPlayers(spawnPoint)
	end)
end

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

	local spawnPoint = nil
	local enemySpawnPoints = {}

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

			tileRow, rotation, extraTile = MapBuilder.CheckForDeadEnd(map, neighbors, row, col)
			
			if tileRow == nil then
				tileRow, rotation, extraTile = MapBuilder.CheckForWallCorner(map, neighbors, row, col)
			end

			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

				}

				if tileRow.tile ~= nil then
					container:SpawnSharedAsset(tileRow.tile, params)
				end

				if map[row][col] == MapBuilder.Type.Spawn then
					spawnPoint = params.position

					if extraTile == nil then
						extraTile = TILES[MapBuilder.Type.Floor]
					end
				elseif map[row][col] == MapBuilder.Type.Enemy then
					table.insert(enemySpawnPoints, params.position)

					if extraTile == nil then
						extraTile = TILES[MapBuilder.Type.Floor]
					end
				end

				if extraTile ~= nil then
					MapBuilder.SpawnExtraTile(extraTile, params, container)
				end
			end
		end
	end

	MapBuilder.SpawnPlayersAndEnemies(spawnPoint, enemySpawnPoints, container)
	MapBuilder.GenerateNavMesh(width, height, size, container)
	MapBuilder.SpawnRoof(width, height, size, container)
end

return MapBuilder
Code language: Lua (lua)

Test the Game

Test the game to make sure the roof is spawned and is at the correct height.

Scroll to Top