Hello, thanks for taking the time to read my post.
I am creating a system in which enemies spawned in will automatically chase the player. When originally making this, I created pretty simple way to achieve the intended functionality, but soon started having doubts about it.
So I decided to attempt another method, and as I worked on it, I couldn’t help but feel stupid. It worked, but it definitely seems more performance-intensive and I was struggling to understand why I should not just stick with the first idea. Both can be improved upon, but do currently work. Before I continue this work, I would just like some opinions about which idea i should focus more on in the long-term.
The first idea I had was incredibly simple and done completely on the Server. The following function was called on an enemy model whenever it was cloned into the workspace:
function NPCFollowModule.FollowPlayer(npcHumanoid, player)
local humanoidAlive = true
local testDB = false
local character = player.Character
local humanoid = character:FindFirstChild("Humanoid")
local rootPart = character:FindFirstChild("HumanoidRootPart")
if humanoid then
npcHumanoid:MoveTo(rootPart.Position)
end
task.spawn(function()
while humanoidAlive do
task.wait(.25)
npcHumanoid:MoveTo(rootPart.Position)
end
--warn("Loop is broken")
end)
npcHumanoid.Touched:Connect(function(hitPart)
if not testDB then
testDB = not testDB
if hitPart.Parent.Name == player.Name then
print("Damage time")
end
task.wait(1)
testDB = not testDB
end
end)
npcHumanoid.Died:Connect(function()
--warn("NPC humanoid has died! Loop should break!")
humanoidAlive = not humanoidAlive
end)
end
As stated earlier, it is pretty simple. I just started having doubts about it’s scalability specifically.
So then I thought of another method that goes as follows.
Part is created on client and then bound to RunService that follows in front of the player.
-- client side
local newRegion3 = Region3Module.CreateNewRegion3(player)
local newPart = Instance.new("Part")
newPart.CanCollide = false
newPart.Anchored = true
newPart.Transparency = .5
newPart.Size = Vector3.new(math.abs(newRegion3.Size.X), math.abs(newRegion3.Size.Y), math.abs(newRegion3.Size.Z))
newPart.CFrame = newRegion3.CFrame
newPart.Parent = workspace
Coroutine is created (that runs until player death or round is finished) that constantly sends this part’s information into a ModuleScript that sends the part properties to the Server. (Yes, I am aware that the Module function is not strictly necessary and I could just add the code into the LocalScript).
-- Client:
-- Create a coroutine that will run the EnemyDectection logic every 1 second
local enemyDetectionCoroutine = coroutine.create(function()
while true do
EnemyDetectionModule.DetectEnemiesWithinPart(newPart, character, humanoid)
task.wait(.5)
end
end)
-- Coroutine is resumed further down in the script
ModuleScript that uses the information:
function EnemyDetectionModule.DetectEnemiesWithinPart(partToUse : Part, character : Model, humanoid : Humanoid)
-- Create a dictionary of the part's properties to send to the Server
local partProperties = {
Size = partToUse.Size,
Position = partToUse.Position,
Shape = partToUse.Shape,
Anchored = true,
CanCollide = partToUse.CanCollide,
}
-- Send this table to the Server to create a dummy part to check for collisions
RemoteEventModule.DetectEnemiesEvent:FireServer(partProperties, character, humanoid)
end
This is when I began to feel like I was making a mistake using this method because it required constant client → server communication. I am unaware of exactly how important this is, but I figured if I could just do it all on the Server, why even use this method.
Lastly, here is the Server-Side script that uses the passed part properties:
-- Local function that will make the NPCs chase the player and attempt to remove them from the hitTable upon death
local function enemyMovement(characterTable, character : Model)
-- Loop through the table and connect move events
for _, enemyModel in characterTable do
local enemyHumanoid = enemyModel:FindFirstChild("Humanoid")
enemyHumanoid:MoveTo(character:FindFirstChild("HumanoidRootPart").Position)
end
end
-- Local function to use for testing hitbox detection with enemies
local function detectEnemies(player : Player, partProperties, character : Model, humanoid : Humanoid)
-- player's HRP
local humanoidRootPart
-- define humanoid state
local humanoidState
-- make sure the humanoid sent, get its state
if humanoid then
humanoidState = humanoid:GetState()
end
-- check if the humanoid state is still alive, if not then do return end
if humanoidState == Enum.HumanoidStateType.Dead then return end
-- Create the dummy part based off the client sent information table
local hitboxPart = Instance.new("Part")
-- define part properties
for propertyName, propertyValue in pairs(partProperties) do
hitboxPart[propertyName] = propertyValue
end
-- Set the newly created part's collision group
hitboxPart.CollisionGroup = "HitboxGroup"
-- Test detection with the above code
local partsHit = workspace:GetPartsInPart(hitboxPart, overlapParams)
for i, partHit in partsHit do
if partHit.Parent:FindFirstChildOfClass("Humanoid") and not table.find(hitCharacters, partHit.Parent) and partHit.Parent.Name ~= player.Name then
-- TODO:_REWORK_REMOVING_THE_PLAYER_FROM_DETECTION
table.insert(hitCharacters, partHit.Parent)
end
end
enemyMovement(hitCharacters, character)
end
-- Connect Client To Server Remote Event To Detection Function
RemoteEventModule.DetectEnemiesEvent.OnServerEvent:Connect(detectEnemies)
Basically, a part will be consistently created/removed from the Server using the client part’s properties, and then I use :GetPartsInParts to detect collisions with enemies that use the same collision group as the created part.
I know that this method does not have all the functionality as the first, but since I know how to achieve that functionality, I wanted to ask for some opinions on the matter before coding this in.
While I was making the second method, I could not help thinking “Why am I doing this/Why is this method any better”?
I just wanted to ask for some opinions/maybe see how you all achieved something similar to this. Thanks in advance,