Hey, and thanks for reading in advance.
I’m working on a small-scale tower defense game of sorts, and while I’ve got the enemy pathing down to something functional, I’m looking to refine it a bit.
Currently, the zombies use the yellow goal parts (and their offset from it) to determine which direction to move and for how long. What I’m looking to do is create the capability for a zombie to move on the inside/outside of the path without needing to create extra goal points or input manual coordinates - my goal is for the AI to calculate the requested path on-the-fly.
If anyone has any tips or advice on how I can produce this, I’d be grateful.
Current AI:
local SS = game.ServerStorage
local RS = game.ReplicatedStorage
local Figure = script.Parent
local Humanoid = Figure:WaitForChild("Humanoid")
local Animator = Humanoid:WaitForChild("Animator")
local Root = Figure:WaitForChild("HumanoidRootPart")
local path = workspace.PathSet
local Display = script.Display
--
local ugly = {5379168692, 133322927, 1158091668, 185301839, 1158094454}
local order = {path.Start}
local goals = {}
local isDead = false
local inCombat = false
local lastAttack = tick() - 1
local attackRate = 1
local MinDamage = 1
local MaxDamage = 3
--
local function NewAnim(id, name)
local Animation = Instance.new("Animation")
Animation.AnimationId = "rbxassetid://"..id
Animation.Name = name or Figure.Name.."Animation"
Animation.Parent = Humanoid
return Animator:LoadAnimation(Animation)
end
--
local attack = NewAnim(6038549630, "Attack")
local blockFolder = Instance.new("Folder")
blockFolder.Name = "Blockers"
blockFolder.Parent = Figure
local nextGoal = Instance.new("ObjectValue")
nextGoal.Name = "NextGoal"
nextGoal.Parent = Figure
local toNext = Instance.new("NumberValue")
toNext.Name = "ToNext"
toNext.Parent = Figure
--
local function Swipe(target)
local tRoot = target:FindFirstChild("HumanoidRootPart")
local gore = {4988621662, 4988621968, 4988622242, 4988625180}
local hit = nil
hit = attack:GetMarkerReachedSignal("Hit"):Connect(function()
if target.Parent then
target.Humanoid:TakeDamage(math.random(MinDamage, MaxDamage))
RS.Remotes.EffectSpoof:FireAllClients("Sound", {id = gore[math.random(1, #gore)], v = .3, pos = tRoot})
end
hit:Disconnect()
end)
if tRoot then
local tPos = target.HumanoidRootPart.Position
Root.CFrame = CFrame.new(Root.Position, Vector3.new(tPos.X, Root.Position.Y, tPos.Z))
end
attack:Play()
RS.Remotes.EffectSpoof:FireAllClients("Sound", {id = ugly[1], v = .3, pos = Figure.PrimaryPart})
attack.Stopped:Wait()
hit:Disconnect()
end
local function moveTo(targetPoint, andThen)
local targetReached = false
local connection
connection = Humanoid.MoveToFinished:Connect(function(reached)
if #blockFolder:GetChildren() <= 0 and not inCombat then
targetReached = true; connection:Disconnect()
connection = nil
if andThen then
andThen()
end
end
end)
Humanoid:MoveTo(targetPoint)
while not targetReached do
local blockers = blockFolder:GetChildren()
if isDead then return
elseif #blockers > 0 or inCombat then
repeat
Humanoid:MoveTo(Root.Position)
blockers = blockFolder:GetChildren()
if (tick() - lastAttack) >= attackRate and inCombat then
if #blockers > 0 then
local target = blockers[1].Value
lastAttack = tick(); Swipe(target)
else inCombat = false
end
else wait()
end
until #blockers <= 0 and not inCombat
end
Humanoid:MoveTo(targetPoint)
toNext.Value = (targetPoint - Root.Position).Magnitude
wait()
end
if connection then
connection:Disconnect()
connection = nil
end
end
--
Display.Parent = Figure:WaitForChild("Head")
Humanoid.HealthChanged:Connect(function(health)
Display.HP.Bar.Size = UDim2.new(health/Figure.Humanoid.MaxHealth, 0, 1, 0)
end)
Humanoid.Died:Connect(function()
if not isDead then isDead = true
Display.Enabled = false
game:GetService("Debris"):AddItem(Figure, 1)
attack:Stop()
end
end)
Figure.CombatStart.Event:Connect(function()
inCombat = true
end)
--
for _,part in pairs(Figure:GetDescendants()) do
if part:IsA("BasePart") then
part:SetNetworkOwner(nil)
end
end
spawn(function()
while Figure.Parent and wait() do
local interval = math.random(3, 12)
local now = tick()
repeat wait() until tick() - now >= interval
or not Figure.Parent
if Figure.Parent and math.random(1, 100) <= 50 then
RS.Remotes.EffectSpoof:FireAllClients("Sound", {
id = ugly[math.random(1, #ugly)], pos = Figure.PrimaryPart,
v = .3, p = 1.6, s = 4
})
end
end
end)
--
for _,goal in pairs(path:GetChildren()) do
if goal.Name:find("Goal") then
table.insert(goals, goal)
end
end
table.sort(goals, function(a, b)
local nA = tonumber(a.Name:sub(5, -1))
local nB = tonumber(b.Name:sub(5, -1))
return nA < nB
end)
for _,goal in pairs(goals) do
table.insert(order, goal)
end
table.insert(order, path.Exit)
for i,goal in ipairs(order) do
if isDead then break
elseif goal.Name ~= "Start" then
local offset = CFrame.new()
local previous = order[i - 1]
if Figure:FindFirstChild("SpawnOffset") then
offset = Figure.SpawnOffset.Value
end
local fromLast = (goal.Position - previous.Position).Unit
local walkTo = CFrame.new(goal.Position, goal.Position + fromLast) * offset
local dir = (walkTo.Position - Root.Position).Unit
nextGoal.Value = goal
moveTo(walkTo.Position + dir)
end
end
if not isDead then
Figure:Destroy()
end