Dynamically Spawn Enemies

In this section, you will modify the generation algorithm to use the Enemies table and spawn a random enemy from that table. By using the data from the ASCII map to determine where enemies spawn, you can randomly pick an enemy from the Enemies table.

Add Enemies Custom Property

The MapBuilder API script will need a reference to the Enemies data table.

  1. In My APIs in Project Content, click on the MapBuilder script.
  2. In My Tables in Project Content, add the Enemies data table as a custom property to the MapBuilder script called Enemies.

Update MapBuilder API Script

The MapBuilder script will need to be updated to handle enemy spawning.

Add Enemies Data Table Reference

Add a reference to the Enemies data table custom property you added so the script can pick an enemy to spawn.

local ENEMIES = require(script:GetCustomProperty("Enemies"))
Code language: Lua (lua)

Create SpawnEnemies Function

Create a new function called SpawnEnemies. This function will receive a table as a parameter that will contain all the spawn points for enemies. It will loop over those spawn points and randomly select an enemy from the Enemies data table. The position of the enemy will be the position of the tile, and a small UP value is added to make sure the enemy doesn’t spawn in the tile below them.

When using the SpawnAsset function, an optional parameter can be added containing a table of options. One of those options allows you to specify what context the asset is spawned in. Because the MapBuilder script is in a static context, you will need to specify the context for the enemy. The enemy templates are networked, so setting the option networkContext to NETWORKED will prevent any errors and spawn the enemy in the correct context.

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

Update Spawn Function

The Spawn function will need updating so that the enemy spawn points are collected, and also a few extra checks need to be done to make sure a floor tile is spawned under the enemy.

A table called enemySpawnPoints is added above the loops. Each time the loop finds a map value that is E, it will insert the position of that tile into the table. A check is done to see if the extraTile variable is nil, if so it will be set to a floor tile so that the enemy has something to stand on. After the loop is completed, a call to SpawnEnemies is done that will handle the spawning.

A check is also added when the tile is used for spawning the player. If the extraTile is nil then a floor tile is spawned.

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.SpawnEnemies(enemySpawnPoints)
	MapBuilder.SpawnPlayers(spawnPoint)
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.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.SpawnEnemies(enemySpawnPoints)
	MapBuilder.SpawnPlayers(spawnPoint)
end

return MapBuilder
Code language: Lua (lua)

Test the Game

Test the game to make sure the enemies are spawned. Right now you can’t fight the enemies, and the pathfinding will make them walk through walls.

Scroll to Top