Can Anyone Give Me Some Feedback About My Enemy Spawning System?

Hello, thank you for taking the time to read my post. I would consider myself a beginner programmer and only recently began the process of seriously trying to learn. I have yet to make anything substantial, but I am now attempting to.

I have recently created a system that allows players to get into combat with enemy NPCs. I am trying to create a turn-based combat game, with mechanics similar to Fear and Hunger, i,e limb dismemberment. Just providing this explanation in case anyone wonders why combat is started via a .Touched event, as it is like that in the aforementioned game.

Here is a brief overview of the relevant portions of my script(s).

Main and only ServerScript:

-- Loop through the AreaOneTable and connect each model's HumanoidRootPart to the .Touched function inside the EnemyTouched Module
for _, v in ipairs(EnemyTouched.AreaOneTable) do
	v:FindFirstChild("HumanoidRootPart").Touched:Connect(function(partTouched)
		if partTouched.Parent:FindFirstChild("Humanoid") then
			EnemyTouched.AreaOneEnemy(partTouched.Parent, v)
		end
	end)
end

The corresponding EnemyTouched Module’s relevant sections:

function EnemyTouched.AreaOneEnemy(partTouched, enemy : Model)
	if not areaOneDB then
		areaOneDB							= not areaOneDB
		local enemyTable					= EnemyDatabase.GetAndReturnVariations(enemy.Name)
		
		-- Define Needed Variables
		local rig : Model					= partTouched
		local humanoid : Humanoid			= partTouched:FindFirstChild("Humanoid")
		local humanoidRootPart : Part		= rig:FindFirstChild("HumanoidRootPart")
		local player : Player				= game:GetService("Players"):GetPlayerFromCharacter(rig)
		
		if player then
			RemoteEventModule.event:FireClient(player, RemoteEventModule.eventTable1)
		end
		
		task.wait(1.5)
		
		teleportPlayer(humanoidRootPart, enemy, enemyTable,"Area1")
		enemy.Parent = ReplicatedStorage.EnemyStorage
		
		task.wait(.5)
		areaOneDB = not areaOneDB
	elseif areaOneDB then
	
	end
end

The remote event that fires starts what is essentially a “loading screen”.

The corresponding teleportPlayer() function that I would like the most feedback on:

local function teleportPlayer(humanoidRoot : Part, enemy : Model, enemyTable, whichArea : string)
	if whichArea == "Area1" then
		local number = enemy:GetAttribute("NumberOfEnemies")
		local baseEnemy = ReplicatedStorage.EnemiesToClone[enemy.Name]:FindFirstChild(enemy.Name)
		
		-- Clone the base enemy
		local enemyClone		= baseEnemy:Clone()
		local cloneRoot			= enemyClone:FindFirstChild("HumanoidRootPart")
		enemyClone.Parent		= workspace.FightAreas.Area1.Battleground1.Enemies

		local posStr			= "EnemySpawn" .. tostring(1)

		cloneRoot.CFrame		= CFrame.new(workspace.FightAreas.Area1.Battleground1[posStr].Position) * CFrame.Angles(0, 0, 0)
		
		-- Attempt to spawn some variations
		for i = 2, number do
			print(i)
			local spawnVariation			= math.random(0, 3) >= 2 -- 66% chance to spawn a variation
			
			if spawnVariation then
				local randomVariationName	= getRandomKey(enemyTable.Variations)
				
				local varToClone			= ReplicatedStorage.EnemiesToClone[enemy.Name].Variations:FindFirstChild(randomVariationName)
				
				-- Check if the variation exists:
				if varToClone then
					local clone				= varToClone:Clone()
					local root				= clone:FindFirstChild("HumanoidRootPart")
					clone.Parent			= workspace
					local posStr			= "EnemySpawn" .. tostring(i)
					root.CFrame				= CFrame.new(workspace.FightAreas.Area1.Battleground1[posStr].Position) * CFrame.Angles(0, 0, 0)
					
				else
					warn("This variation was not found!")
				end
			else
				print("Spawn the base enemy...")
				local enemyClone			= baseEnemy:Clone()
				local cloneRoot				= enemyClone:FindFirstChild("HumanoidRootPart")
				enemyClone.Parent			= workspace.FightAreas.Area1.Battleground1.Enemies
				local posStr				= "EnemySpawn" .. tostring(i)
				cloneRoot.CFrame			= CFrame.new(workspace.FightAreas.Area1.Battleground1[posStr].Position) * CFrame.Angles(0, 0, 0)
				
			end
		end
		
		-- Teleport the player
		humanoidRoot.CFrame					= CFrame.new(workspace.FightAreas.Area1.Battleground1.PlayerSpawn1.Position) * CFrame.Angles(0, math.rad(180), 0)
	end
end

This is the main function that I have concerns about. As seen above, I want to have a system in my game in which running into one enemy in the overworld has a chance to spawn more than one on a battlefield, in addition to specific variations for each enemy.

For one, as I plan on having different areas (or locations where the player fights), that eventually this one function will have possibly too many “if area == fightArea then” statements, and thought I am not certain, I would think this is inefficient.

Speaking of inefficient, I have noticed it is definitely not as fast as I would want it to be, and have had to compensate the task.wait()s in other functions to lengthen the loading screen to avoid players “loading in” when not teleported or not all enemies have spawned.

I also plan on having all combat calculations done on the server, so there will likely have to be another function and/or bindable event that sends all enemy stats (alongside the player’s) to a separate ModuleScript which handles combat, which I would think makes performance worse.

Lastly, it does feel a bit redundant having to type out the process of cloning various models at least three separate times in the same function. I considered using local functions instead of having it all typed out like this, but I am unsure if that would really improve anything.

Lastly, I did have some concerns about how this would scale up when using custom models, that I do intend to implement in the future. As it stands now, this has only been tested using R15 rigs.

Really, I would just be curious to hear thoughts on how certain things could be done better, or how you specifically would handle any portion of this process.

Thanks again,

1 Like

the main area of improvement that I would change is the way that teleportPlayer handles different areas
If you are going to add more areas, it would save a lot of code to not have a huge elseif

if whichArea == "Area1" then
elseif whichArea == "Area2" then
elseif whichArea == "Area3" then--this sucks

I would make a table where each area can store its unique information like workspace.FightAreas.Area1
and a function that handles enemy & player teleporting taking the area information as parameters