I have been trying to put together a weeping angel ai that works with multiple players and weeping angels for a few days now. In my current system what I am doing is having alocal script inside starterCharacter scripts loop through each angel to check and see if it is on the players screen and if it is then checking to see if a raycast from the players camera can reach it. If botha are true the script fires an event meant to anchor all parts of the angel. The angels themselves are always pathfinding to the player and in addition to that have a part change its position to whatever the angel’s next waypoint to detect whether the player will see it if it moves to said waypoint(This is to prevent the angels from moving on the players screen before they get a chance to anchor upon detection) The current issue is that when I use multiple Angel NPCs often times they can still move even when in the players view, and in addition to that it seems that they all freeze only when a certain few are in the players view when the ones out of the players view should still be moving. This is the problem I am trying to solve. I will leave links to videos down below as well as the scripts used.
Video With a single NPC
Video With Multiple NPCS
**Local Script: **
local runService = game:GetService("RunService")
local collectionService = game:GetService("CollectionService")
local workService = game:GetService("Workspace")
local players = game:GetService("Players")
local RS = game:GetService("ReplicatedStorage")
local player = players.LocalPlayer
local camera = workService.CurrentCamera
--local statue = game.Workspace:WaitForChild("statues"):WaitForChild("statueTester")
--local gazeDetection = statue:WaitForChild("gazeDetection")
--local gazeDetection2 = statue:WaitForChild("gazeDetection2")
--local statueHRP = statue:WaitForChild("HumanoidRootPart")
local seenEvent = RS:WaitForChild("Events"):WaitForChild("seenEvent")
local statues = collectionService:GetTagged("statue")
local rayResult = false
runService.Heartbeat:Connect(function(deltaTime: number)
for i, value in pairs(statues) do
local _, onScreenGaze = camera:WorldToViewportPoint(value:WaitForChild("gazeDetection").Position)
local _, OSGx = camera:WorldToViewportPoint(value.gazeDetection.Position + Vector3.new(3,0,0))
local _, OSGy = camera:WorldToViewportPoint(value.gazeDetection.Position + Vector3.new(0,3,0))
local _, OSGz = camera:WorldToViewportPoint(value.gazeDetection.Position + Vector3.new(0,0,2))
local _, OSGx2 = camera:WorldToViewportPoint(value.gazeDetection.Position + Vector3.new(-3,0,0))
local _, OSGy2 = camera:WorldToViewportPoint(value.gazeDetection.Position + Vector3.new(0,-3,0))
local _, OSGz2 = camera:WorldToViewportPoint(value.gazeDetection.Position + Vector3.new(0,0,-3))
local _, onScreenLArm = camera:WorldToViewportPoint(value["Left Arm"].Position)
local _, onScreenRArm = camera:WorldToViewportPoint(value["Right Arm"].Position)
local _, onScreenHead = camera:WorldToViewportPoint(value.Head.Position)
local _, onScreenHRP = camera:WorldToViewportPoint(value.HumanoidRootPart.Position)
local _, onScreenTorso = camera:WorldToViewportPoint(value.Torso.Position)
local onScreen = onScreenGaze or OSGx or OSGy or OSGz or OSGx2 or OSGy2 or OSGz2 or onScreenHead or onScreenLArm or onScreenRArm or onScreenHRP or onScreenTorso
local rayOrigin = camera.CFrame.Position
local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = {player.Character}--filters you
rayParams.FilterType = Enum.RaycastFilterType.Exclude
for i, v in pairs(statues) do
if v ~= value then
table.insert(rayParams.FilterDescendantsInstances, v)--filters other statues
end
end
-- raycasts to each part of statue from camera
if onScreen then
local rayCastGaze = workService:Raycast(rayOrigin, (value.gazeDetection.Position - rayOrigin).Unit * (value.gazeDetection.Position-rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, ((value.gazeDetection.Position + Vector3.new(3,0,0)) - rayOrigin).Unit * ((value.gazeDetection.Position + Vector3.new(2,0,0)) - rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, ((value.gazeDetection.Position + Vector3.new(0,3,0)) - rayOrigin).Unit * ((value.gazeDetection.Position + Vector3.new(0,2,0)) - rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, ((value.gazeDetection.Position + Vector3.new(0,0,3)) - rayOrigin).Unit * ((value.gazeDetection.Position + Vector3.new(0,0,2)) - rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, ((value.gazeDetection.Position + Vector3.new(-3,0,0)) - rayOrigin).Unit * ((value.gazeDetection.Position + Vector3.new(2,0,0)) - rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, ((value.gazeDetection.Position + Vector3.new(0,-3,0)) - rayOrigin).Unit * ((value.gazeDetection.Position + Vector3.new(0,2,0)) - rayOrigin).Magnitude, rayParams)
local otherRays = workService:Raycast(rayOrigin, (value["Left Arm"].Position - rayOrigin).Unit * (value["Left Arm"].Position-rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, (value["Right Arm"].Position - rayOrigin).Unit * (value["Right Arm"].Position-rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, (value.Head.Position - rayOrigin).Unit * (value.Head.Position-rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, (value.HumanoidRootPart.Position - rayOrigin).Unit * (value.HumanoidRootPart.Position-rayOrigin).Magnitude, rayParams)
or workService:Raycast(rayOrigin, (value.Torso.Position - rayOrigin).Unit * (value.Torso.Position-rayOrigin).Magnitude, rayParams)
rayResult = rayCastGaze.Instance or otherRays.Instance
end
if onScreen and rayResult:FindFirstAncestor("statueTester") then
seenEvent:FireServer("Freeze", value)
else
seenEvent:FireServer("Move", value)
end
end
end)
**Anchor Script: **
local rs = game:GetService("ReplicatedStorage")
local seenEvent = rs:WaitForChild("Events"):WaitForChild("seenEvent")
local statue = script.Parent.Parent
local moveValue = statue:WaitForChild("moveValue")
local looking = {}
statue.PrimaryPart:SetNetworkOwner(nil)
seenEvent.OnServerEvent:Connect(function (player, status, value)
print(value, " ", statue)
if status == "Freeze" and statue == value then
if not table.find(looking, player.UserId) then
table.insert(looking, player.UserId)
end
if #looking > 0 then
moveValue.Value = false
for i, v in pairs(statue:GetChildren()) do
if v:IsA("BasePart") and v.Name ~= "gazeDetection" then
v.Transparency = 0
v.Anchored = true
end
end
end
elseif status == "Move" and statue == value then
if table.find(looking, player.UserId) then
table.remove(looking, table.find(looking, player.UserId))
end
if #looking <= 0 then
moveValue.Value = true
for i, v in pairs(statue:GetChildren()) do
if v:IsA("BasePart") and v.Name ~= "gazeDetection" then
v.Transparency = 1
v.Anchored = false
end
end
end
end
end)
**Move Script: **
local PFS = game:GetService("PathfindingService")
local players = game:GetService("Players")
local RS = game:GetService("RunService")
local statue = script.Parent.Parent
local humanoid = statue:WaitForChild("Humanoid")
local gazeDetection = statue:WaitForChild("gazeDetection")
local moveValue = statue:WaitForChild("moveValue")
statue.PrimaryPart:SetNetworkOwner(nil)
local walkSpeed = statue:GetAttribute("WalkSpeed")
local sprintSpeed = statue:GetAttribute("SprintSpeed")
local damage = statue:GetAttribute("Damage")
local waypoints = game.Workspace.waypoints:GetChildren()
local function getPath(destination)
local path = PFS:CreatePath({
AgentHeight = 6;
AgentRadius = 6;
AgentCanJump = false;
AgentCanClimb = false;
Costs = {
--adding things later
}
})
path:ComputeAsync(statue.HumanoidRootPart.Position, destination.Position)
return path
end
local function findNearestTarget()
local nearestTarget
local maxDistance = 1000
for index, player in pairs(players:GetPlayers()) do
if player.Character then
if player.Character:FindFirstChild("HumanoidRootPart") then
local target = player.Character:FindFirstChild("HumanoidRootPart")
local distance = (target.Position - statue.HumanoidRootPart.Position).Magnitude
if distance < maxDistance then
nearestTarget = target
maxDistance = distance
end
end
end
end
if nearestTarget == nil then
local waypoints = game.Workspace:WaitForChild("waypoints"):GetChildren()
nearestTarget = waypoints[math.random(1,#waypoints)]
end
return nearestTarget
end
local function attack(target)
local distance = (statue.HumanoidRootPart.Position - target.Position).Magnitude
if distance > 3 then
humanoid:MoveTo(target.Position)
elseif distance < 3 then
target.Parent.Humanoid:TakeDamage(damage)
end
end
local function walkTo(destination)
local path = getPath(destination)
if path.Status == Enum.PathStatus.Success then
local pathWay = path:GetWaypoints()
for index, waypoint in pairs(pathWay) do
local target = findNearestTarget()
if (statue.HumanoidRootPart.Position - target.Position).Magnitude < 15 and target.Parent:FindFirstChild("Humanoid") then
statue.Humanoid.WalkSpeed = walkSpeed
attack(target)
else
if moveValue.Value == true then
if pathWay[index+2]~= nil and (statue.HumanoidRootPart.Position - target.Position).Magnitude > 15 then
gazeDetection.Position = pathWay[index+2].Position + Vector3.new(0,3,0)
statue.Humanoid.WalkSpeed = sprintSpeed
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
elseif pathWay[index+1] ~= nil and (statue.HumanoidRootPart.Position - target.Position).Magnitude > 10 then
gazeDetection.Position = pathWay[index+1].Position + Vector3.new(0,3,0)
statue.Humanoid.WalkSpeed = sprintSpeed
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
else
gazeDetection.Position = waypoint.Position + Vector3.new(0,3,0)
statue.Humanoid.WalkSpeed = sprintSpeed
humanoid:MoveTo(waypoint.Position)
humanoid.MoveToFinished:Wait()
end
end
--if moveValue.Value == true then
-- statue.Humanoid.WalkSpeed = sprintSpeed
-- humanoid:MoveTo(waypoint.Position)
-- humanoid.MoveToFinished:Wait()
--end
end
end
else
humanoid:MoveTo(destination.Position - (statue.HumanoidRootPart.CFrame.LookVector*10))
end
end
while wait(0.01) do
local target = findNearestTarget()
walkTo(target)
end