Hello! So, i would link an roblox file here, but this code requires all of the rest of the code to work. However, this code is a server side module script used in the Aero Game Framework. The way i designed my npc system was so that non hostile NPC’s, such as Major or Minor Story NPC’s or Shop NPC’s are local, but NPCs such as Enemy ones, Creatures, Guards… npc’s that can fight, are in the server.
When this code begins it loops through a folder in the Replicated storage and finds a type of Hostile NPC, then, it loops through a Hostile Spawn Folder in the workspace, and finds all the spawns with that Hostile NPC’s Name in it such as “Warlock Spawn”. Within that spawn is a number value for how many npc’s i want to spawn there… so it runs this code as many times as it takes to fill that up. At the end of the code i’m about to show, in the npc died function, it will call back the activate npcs function so it will find the spawn after an elated amount of time and respawn that npc.
My goal for positing here is to get some ideas on how to make these npc’s smarter cleaner, and how to make them a bit more immersive. There are many different types of npc’s so this code is very open ended for parameters depending on the npc that it is spawned so i dont have to rewrite the code for each NPC.
Some of my key issues include
*In the NPC wandering section, sometimes NPC’s tend to walk into eachother
*NPC’s, particularly the ones i’ve been testing right now which are magic based, often accidently attack eachother.
*NPC’s sometimes spawn on top of eachother.
I use the pathfinding service here… now, Hostile NPC coding is new to me… so any tips or ideas or suggestions would be awesome! Thanks!
function Hostile_NPC_Service:Hostile(NPCname, spawna, spawned)
--Primary Local Indexes--
local cooldown = false
--Spawnnpc--
local npc = Replicated_Hostile_assets[NPCname]:Clone()
local cln
pcall(function()
cln=spawna["ClassName"]
end)
if cln then
--if part or model
if spawna:IsA('BasePart') then
npc:SetPrimaryPartCFrame(spawna.CFrame+Vector3.new(math.random(-100,100),npc.HumanoidRootPart.Size.Y+0.25,math.random(-100,100)))
elseif spawna:IsA("Model") and spawna.PrimaryPart then
npc:SetPrimaryPartCFrame(spawna.PrimaryPart.CFrame+Vector3.new(math.random(-100,100),npc.HumanoidRootPart.Size.Y+0.25,math.random(-100,100)))
else
warn("Failed to recieve cframe of object: "..spawna.Name)
end
elseif spawna.p then
npc:SetPrimaryPartCFrame(spawna+Vector3.new(math.random(-100,100),npc.HumanoidRootPart.Size.Y+0.25,math.random(-100,100)))
else
warn("No object or CFrame given.")
end
spawned.Value = spawned.Value + 1
spawna.Transparency = 1
npc.Parent = Hostile_assets
npc.PrimaryPart = npc:WaitForChild("Head")
coroutine.resume(coroutine.create(function()
if npc:FindFirstChild("BodyHumanoid") then
Hostile_NPC_Service:NPC_Appearance(npc)
end
end))
local humanoid = npc:WaitForChild("Humanoid")
local torso = npc:WaitForChild("UpperTorso")
local OriginalPosition = npc:WaitForChild("HumanoidRootPart").Position
local target = npc:WaitForChild("Target")
local location = npc:WaitForChild("Location")
local a = Vector3.new(100,5,-100)
local b = Vector3.new(75,5,-200)
local debounce = true
local debounce2 = false
local list = game.Players:GetChildren()
local loco = settings.TargetDistance
if target.Value then
local targetTorso = target.Value:FindFirstChild("UpperTorso")
end
local Attack_Debounce = false
local Previous_Attack
local function attack(target)
if npc.Attack_Type.Value == "Magic" then
if Attack_Debounce == false then
Attack_Debounce = true
local RandomSpell
repeat wait()
RandomSpell = Spells[math.random(#Spells)]
until npc.MagInfo.Power.Value >= SpellLevels[RandomSpell] and RandomSpell ~= Previous_Attack
Previous_Attack = RandomSpell
NPC_Attack_Service:NPC_PowerValues(npc, RandomSpell, false, math.random(1,2), target)
Attack_Debounce = false
end
end
end
--Create Sound--
local Passive_Sound
if not Passive_Sound then
Passive_Sound = Instance.new("Sound")
Passive_Sound.Name = "NPC_Passive_Sound"
Passive_Sound.Parent = npc:WaitForChild("Head")
else
Passive_Sound = npc:WaitForChild("Head"):FindFirstChild("NPC_Passive_Sound")
end
--Check for the NPC's Line of sight.
local function findNearestTorso(pos)
local list = game.Workspace:children()
local torso = nil
local dist = settings.TargetEnemyDistance
local temp = nil
local human = nil
local temp2 = nil
for x = 1, #list do
temp2 = list[x]
if (temp2.className == "Model") and (temp2 ~= npc) and game.Players:FindFirstChild(temp2.Name) then
temp = temp2:findFirstChild("HumanoidRootPart")
human = temp2:findFirstChild("Humanoid")
if (temp ~= nil) and (human ~= nil) and (human.Health > 0) then
if (temp.Position - pos).magnitude < dist then
torso = temp
dist = (temp.Position - pos).magnitude
end
end
end
end
return torso
end
local raus,vaus,haus,saus
local WanderCoolDown
local PassiveSoundCoolDown
local render = game:GetService("RunService").Heartbeat:connect(function(dt)
local target = findNearestTorso(npc.HumanoidRootPart.Position)
TargetLocked = false
--local targetPos = target.Position
if not haus and not vaus and not PassiveSoundCoolDown and not TargetLocked then
PassiveSoundCoolDown = true
local PassiveSounds = Hostile_NPC_Dialogue[NPCname]["Passive_Sounds"]["Inactive"]
local RandomSoundId = Hostile_NPC_Dialogue[NPCname]["Passive_Sounds"]["Inactive"][math.random(#PassiveSounds)]
Passive_Sound.SoundId = RandomSoundId
Passive_Sound:Play()
wait(math.random(8,30))
PassiveSoundCoolDown = false
end
if target ~= nil and target.Name == "HumanoidRootPart" then
local NpcHRP = npc:WaitForChild("HumanoidRootPart")
local CharHRP = target
local dist = (NpcHRP.CFrame.p-CharHRP.CFrame.p).magnitude
if not cooldown and not vaus then
if dist <= settings.animationOffset then
local ray = Ray.new(npc.HumanoidRootPart.CFrame.p,npc.HumanoidRootPart.CFrame.LookVector*(settings.TargetEnemyDistance*2))
local object,surfaceposition = workspace:FindPartOnRay(ray,npc)
if object then
local target = object.Parent
if target:FindFirstChild("Humanoid") and game.Players:FindFirstChild(target.Name) then
cooldown=true
haus = true
saus = false
--local track = Hostile_NPC_Service:animate(507770239,npc.Humanoid)
local PassiveSounds = Hostile_NPC_Dialogue[NPCname]["Passive_Sounds"]["Active"]
local RandomSoundId = Hostile_NPC_Dialogue[NPCname]["Passive_Sounds"]["Active"][math.random(#PassiveSounds)]
print(RandomSoundId)
Passive_Sound.SoundId = RandomSoundId
Passive_Sound:Play()
wait(1.5)
--track:Stop()
spawn(function()
wait(settings.animationCooldownTime)
cooldown=false
end)
else
haus = false
end
end
else
local ray = Ray.new(npc.HumanoidRootPart.CFrame.p,npc.HumanoidRootPart.CFrame.LookVector*(settings.TargetEnemyDistance*2))
local object,surfaceposition = workspace:FindPartOnRay(ray,npc)
if object then
local target = object.Parent
if target:FindFirstChild("Humanoid") and game.Players:FindFirstChild(target.Name) then
haus = true
saus = false
else
haus = false
end
end
end
end
if npc.MagInfo.Energy.Value <= 0 then
wait(4)
npc.MagInfo.Energy.Value = npc.MagInfo.MaxEnergy.Value
end
if not vaus then
if dist <= settings.TargetEnemyDistance or haus == true then
--Raise the gui
if target ~= nil and not TargetLocked then -- found player
TargetLocked = true
npc.Humanoid.WalkSpeed = Hostile_NPC_Settings["HostileChaseSpeeds"][npc.Name]
local function ChasePlayer()
local path = PathfindingService:FindPathAsync(npc.UpperTorso.Position, target.Position)
local points = path:GetWaypoints()
if path.Status == Enum.PathStatus.Success then
for i,v in pairs(points) do
npc.Humanoid:MoveTo(v.Position)
npc.Humanoid.MoveToFinished:Wait()
if v.Action == Enum.PathWaypointAction.Jump then
npc.Humanoid.Jump = true
end
end
end
end
if dist >= Hostile_NPC_Settings["HostileHaltDistances"][npc.Name] then
ChasePlayer()
elseif dist >= Hostile_NPC_Settings["HostileHaltDistances"][npc.Name] and TargetLocked and target:FindFirstChild("HumanoidRootPart") then
npc.HumanoidRootPart.CFrame = CFrame.new(target.HumanoidRootPart.CFrame.p,npc.HumanoidRootPart.CFrame.p)
end
if not saus and haus then
attack(target)
end
end
raus=true
else -- No longer Target Locked
if target == nil and TargetLocked then
TargetLocked = false
haus = false
npc.Humanoid.WalkSpeed = 8
local path = PathfindingService:FindPathAsync(npc.UpperTorso.Position, OriginalPosition)
local points = path:GetWaypoints()
if path.Status == Enum.PathStatus.Success then
for i,v in pairs(points) do
npc.Humanoid:MoveTo(v.Position)
npc.Humanoid.MoveToFinished:Wait()
if v.Action == Enum.PathWaypointAction.Jump then
npc.Humanoid.Jump = true
end
end
end
--npc.Humanoid:MoveTo(OriginalPosition)
end
raus=false
end
end
else --Doesnt Deteect Anything
TargetLocked = false
haus = false
npc.Humanoid.WalkSpeed = 8
local path = PathfindingService:FindPathAsync(npc.UpperTorso.Position, OriginalPosition)
local points = path:GetWaypoints()
if path.Status == Enum.PathStatus.Success then
for i,v in pairs(points) do
npc.Humanoid:MoveTo(v.Position)
npc.Humanoid.MoveToFinished:Wait()
if v.Action == Enum.PathWaypointAction.Jump then
npc.Humanoid.Jump = true
end
end
end
end
--NPC Wandering Around
coroutine.resume(coroutine.create(function()
if not vaus and not haus and not TargetLocked and not WanderCoolDown then
WanderCoolDown = true
local RandomDestination = npc.HumanoidRootPart.Position + Vector3.new(math.random(-50,50),0,math.random(-50,50))
npc.Humanoid.WalkSpeed = 8
local path = PathfindingService:FindPathAsync(npc.UpperTorso.Position, RandomDestination)
local points = path:GetWaypoints()
if path.Status == Enum.PathStatus.Success then
for i,v in pairs(points) do
npc.Humanoid:MoveTo(v.Position)
npc.Humanoid.MoveToFinished:Wait()
if v.Action == Enum.PathWaypointAction.Jump then
npc.Humanoid.Jump = true
end
end
end
wait(8)
WanderCoolDown = false
end
end))
end)
npc.Humanoid.Died:Connect(function()
render:disconnect()
spawned.Value = spawned.Value - 1
wait(5)
for i,v in pairs(npc:GetDescendants()) do
coroutine.resume(coroutine.create(function()
if v:IsA'BasePart' then
local Smoke = Instance.new("ParticleEmitter")
Smoke.Name = "SpellSmoke"
Smoke.Color = ColorSequence.new(ColorSettings[npc.MagInfo.Architype.Value])
Smoke.Texture = "http://www.roblox.com/asset/?id=445231898"
Smoke.LightEmission = 0.5
Smoke.Size = NumberSequence.new({NumberSequenceKeypoint.new(0, .2, 0), NumberSequenceKeypoint.new(1, .7, 0)})
Smoke.Transparency = NumberSequence.new({NumberSequenceKeypoint.new(0, 0, 0), NumberSequenceKeypoint.new(0.75, 0.25, 0), NumberSequenceKeypoint.new(1, 1, 0)})
Smoke.ZOffset = 1
Smoke.Acceleration = Vector3.new(4,4,0)
Smoke.Drag = 10
Smoke.VelocityInheritance = 1
Smoke.Lifetime = NumberRange.new(4,6)
Smoke.Rate = 2000
Smoke.Rotation = NumberRange.new(0,360)
Smoke.RotSpeed = NumberRange.new(-20,20)
Smoke.Speed = NumberRange.new(0,0)
Smoke.SpreadAngle = Vector2.new(100,100)
Smoke.Parent = v
Smoke.Enabled = true
if v.Name == "Head" then
v.face.Transparency = 1
end
wait(2)
v.Transparency = 1
Smoke.Enabled = false
end
end))
end
wait(10)
npc:Destroy()
wait(settings.NPCRegenTime)
Hostile_NPC_Service:Activate()
end)
end