i have a script for an entity that floats toward the player, and it works fine. the problem arises when i try to detect a change in the entity’s parent (from ReplicatedStorage to the workspace) to trigger an ambient sound within the HumanoidRootPart. the detection function never gets called. all the necessary children are present, no infinite yield warnings, and no errors in the output. i ran into this issue while trying to clone these entities. it’s been a real head-scratcher.
the script that clones the models and parents them into the workspace
local Players = game:GetService("Players") -- unused for now
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local creatures = ReplicatedStorage:FindFirstChild("evil creatures")
local creatureSpawns = workspace:FindFirstChild("evil creature spawns")
local remoteFolder = ReplicatedStorage:FindFirstChild("remotes") -- remotes for later
function getRandomChild(parent)
local children = parent:GetChildren()
local randomChild = math.random(1, #children)
if children[randomChild] then
return children[randomChild] -- return the child instance, not the index
end
return nil
end
function wakeCreatures(creatureNum)
for i = 1, creatureNum do
local randomCreature = getRandomChild(creatures)
local randomSpawn = getRandomChild(creatureSpawns)
if randomCreature then
local newCreature = randomCreature:Clone()
newCreature.Parent = workspace
newCreature:PivotTo(randomSpawn.CFrame)
end
end
end
wakeCreatures(math.random(38, 64)) -- wakey wakey
the entity script:
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local goober = script.Parent
local personoid = goober:FindFirstChildWhichIsA("Humanoid")
local root = goober:FindFirstChild("HumanoidRootPart")
local bodyForce = root:FindFirstChildWhichIsA("BodyForce")
local bodyPos = root:FindFirstChildWhichIsA("BodyPosition")
local ambientSound = root:FindFirstChild("the him...")
local oldParent = goober.Parent
local waypoints
local nextWaypointIndex
local currentTime = 0
local path = PathfindingService:CreatePath()
-- stuck detection variables
local stuckCheckTime = 5 -- number of seconds before considering it stuck
local lastPosition = root.Position
local stuckTimer = 0
local isStuck = false
personoid:ChangeState(Enum.HumanoidStateType.Physics) -- yummers
local constants = {
DEBUG_MODE = false;
ROCKET_FIRE_CHANCE = 0.11;
SCAN_DIST = 1250;
UPDATE_TIME = 0.5;
WAYPOINT_INTERVAL = 4;
PATHING_DATA = {
D = 1550,
MaxForce = Vector3.one * 23,
P = 17000,
};
MOVEMENT_DATA = {
D = 1250,
MaxForce = Vector3.one * 3,
P = 10000,
};
}
-- utility functions
local function getDistance(a, b)
return (a - b).Magnitude
end
local function updateForce()
bodyForce.Force = Vector3.new(0, root:GetMass() * workspace.Gravity - 5, 0)
end
local function checkParent() -- does this never fire? nothing is ever printed
local newParent = goober.Parent
print(`checking parent... old parent: {oldParent}, new parent: {newParent}`)
if oldParent ~= newParent then
print(`parent changed`)
ambientSound:Play()
oldParent = newParent
end
end
-- target finding + movement
local function getNearestRoot()
local nearestChar = nil
local shortestDist = constants.SCAN_DIST
for _, player in Players:GetPlayers() do
local character = player.Character
if character and character:FindFirstChild("HumanoidRootPart") then
local plrRoot = character.HumanoidRootPart
local charPos = plrRoot.Position
local dist = getDistance(charPos, root.Position)
if dist < shortestDist then
shortestDist = dist
nearestChar = character
end
end
end
return nearestChar and nearestChar:FindFirstChild("HumanoidRootPart")
end
local function performRaycast()
local plrRoot = getNearestRoot()
if plrRoot then
local direction = (plrRoot.Position - root.Position)
if direction.Magnitude > constants.SCAN_DIST then
direction = direction.Unit * constants.SCAN_DIST
end
local balloonParams = RaycastParams.new()
balloonParams.RespectCanCollide = true
balloonParams.FilterType = Enum.RaycastFilterType.Exclude
balloonParams.FilterDescendantsInstances = {goober, plrRoot.Parent}
local result = workspace:Raycast(root.Position, direction, balloonParams)
return result ~= nil
end
return false
end
local function adjustForGround(position)
local rayOrigin = position + Vector3.new(0, 10, 0)
local rayDirection = Vector3.new(0, -50, 0)
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {goober} -- prevent it from floating upwards
local result = workspace:Raycast(rayOrigin, rayDirection, params)
if result then
return Vector3.new(position.X, result.Position.Y + 3.5, position.Z)
else
return position + Vector3.new(0, 3.5, 0)
end
end
local function runPath()
root:SetNetworkOwner(nil)
local nearestRoot = getNearestRoot()
if nearestRoot then
path:ComputeAsync(root.Position, nearestRoot.Position)
waypoints = path:GetWaypoints()
nextWaypointIndex = 2
if #waypoints < 2 or path.Status == Enum.PathStatus.NoPath then
bodyPos.Position = nearestRoot.Position
return
end
if nextWaypointIndex <= #waypoints then
local waypoint = waypoints[nextWaypointIndex]
if (waypoint.Position - root.Position).Magnitude < 5 then
nextWaypointIndex += constants.WAYPOINT_INTERVAL
end
if nextWaypointIndex > #waypoints then
nextWaypointIndex = #waypoints
end
local adjustedPosition = adjustForGround(waypoints[nextWaypointIndex].Position)
bodyPos.Position = adjustedPosition
end
end
end
local function checkIfStuck(dt)
local distanceMoved = (root.Position - lastPosition).Magnitude
if distanceMoved < 0.2 then
stuckTimer += dt
else
stuckTimer = 0
end
lastPosition = root.Position
if stuckTimer >= stuckCheckTime then
isStuck = true
local behindPosition = root.CFrame.Position - root.CFrame.LookVector * 6
root:ApplyImpulse((behindPosition * Vector3.new(math.random(-4, 4), math.random(-5, -2), math.random(-4, 4))) / 11)
else
isStuck = false
end
end
local function floatTowardsPlayer()
local shouldPathfind = performRaycast()
local nearestRoot = getNearestRoot()
if nearestRoot then
if shouldPathfind then
runPath()
for key, value in pairs(constants.PATHING_DATA) do
bodyPos[key] = value
end
else
bodyPos.Position = nearestRoot.Position
for key, value in pairs(constants.MOVEMENT_DATA) do
bodyPos[key] = value
end
end
else
bodyPos.Position = root.Position
end
end
-- why does this not work (the model is being cloned, but this script is ALSO being cloned)
goober:GetPropertyChangedSignal("Parent"):Connect(function()
print(`calling parent change function`)
checkParent()
end)
-- runtime
updateForce()
workspace:GetPropertyChangedSignal("Gravity"):Connect(updateForce)
root:GetPropertyChangedSignal("Mass"):Connect(updateForce)
RunService.Heartbeat:Connect(function(dt)
currentTime += dt
checkIfStuck(dt) -- hello gordon
if currentTime >= constants.UPDATE_TIME then
currentTime = 0
floatTowardsPlayer()
end
end)