Update MapBuilder API for Runtime AI Nav Mesh

The MapBuilder API will be responsible for starting the nav mesh generation after the map generation has been completed. Once the nav mesh has been generated, it will then spawn in the player and enemies.

The spawn logic for the player will need to be changed. This will be done in the next section.

Using the Runtime Nav Mesh

The Runtime Nav Mesh Community Content you have imported contains a way to dynamically set the nav mesh area, get information on the progress of the nav mesh generation, and when the nav mesh has been built.

The Nav Mesh Generation script will broadcast 2 events.

EventDescription
NavMeshProgressThis event will be fired while the nav mesh grid and mesh is being generated. This can be used to get the progress of the generation.
NavMeshGeneratedThis event will fire when the nav mesh generation has been completed.

Create GenerateNavMesh Function

Create a new function called GenerateNavMesh which will set the scale and position in the global table (_G) so the Nav Mesh Generator can look for those values and set nav mesh area. When these values are detected by the generator, it will start to generate the nav mesh.

The global table in Lua can be accessed using the _G variable. Values set in the global table can be read by other scripts in the same context.

function MapBuilder.GenerateNavMesh(width, height, size)
	_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
			print("Nav Mesh Progress [Grid]:", v)
		else
			print("Nav Mesh Progress [Mesh]:", v)
		end
	end)
end
Code language: Lua (lua)

Create SpawnPlayersAndEnemies Function

Create a new function called SpawnPlayersAndEnemies. This function will receive the player spawn point and the table of enemy spawn points. The function will connect to the NavMeshGenerated event that will be called after the nav mesh has been generated. This will then spawn the enemies and the players.

function MapBuilder.SpawnPlayersAndEnemies(spawnPoint, enemySpawnPoints)
	Events.Connect("NavMeshGenerated", function()
		MapBuilder.SpawnEnemies(enemySpawnPoints)
		MapBuilder.SpawnPlayers(spawnPoint)
	end)
end
Code language: Lua (lua)

Update Spawn Function

The Spawn function needs to be updated so it calls the new functions you have created after the loops have finished. The order of the functions is important to make sure the event for when the nav mesh has been generated is connected first.

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)
	MapBuilder.GenerateNavMesh(width, height, size)
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.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)
	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)
	_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
			print("Nav Mesh Progress [Grid]:", v)
		else
			print("Nav Mesh Progress [Mesh]:", v)
		end
	end)
end

function MapBuilder.SpawnPlayersAndEnemies(spawnPoint, enemySpawnPoints)
	Events.Connect("NavMeshGenerated", function()
		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)
	MapBuilder.GenerateNavMesh(width, height, size)
end

return MapBuilder
Code language: Lua (lua)
Scroll to Top