Updating the MapBuilder Script

A client script will need to read the values of custom properties that are on the Generated Map folder so it can display the progress to the player. To do this, you will need to add custom properties and update the MapBuilder script.

Add Dynamic Custom Properties

Two custom properties will need to be added to track the progress of the grid and mesh for the nav mesh.

  1. Add a Float custom property to the Generated Map folder called grid and set it as dynamic by right-clicking on the custom property and select Enable Dynamic Property.
  2. Add a Float custom property to the Generated Map folder called mesh and mark it as dynamic by right-clicking on the custom property and select Enable Dynamic Property.

Update MapBuilder Script

The MapBuilder script needs to be updated so that it sets the dynamic properties while the nav mesh is being generated.

Update GenerateNavMesh Function

The GenerateNavMesh function needs to be updated so it can set the dynamic properties based on the progress of the grid and mesh for the nav mesh.

The broadcast event NavMeshProgress will be broadcasted from the nav mesh generator and contain the progress. There are 2 stages to the nav mesh which can be checked by looking at the isGrid parameter of the function. The v value is used for the dynamic properties, which is a value between 0 and 1.

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)
endCode language: Lua (lua)

Update SpawnPlayersAndEnemies Function

The SpawnPlayersAndEnemies function needs to be updated so that it can set the mesh dynamic property to 1, meaning the nav mesh has been fully generated.

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

Update Spawn Function

The Spawn function needs to be updated so that when it calls the SpawnPlayersAndEnemies and GenerateNavMesh functions, the container is also passed in so the dynamic properties can be set.

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)
endCode 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.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)
end

return MapBuilderCode language: Lua (lua)
Scroll to Top