Detect a player's character position changing

I’m trying to detect on the server when a player’s character has teleported from PointA to an undefined PointB, and the distance between it.

I tried to achieve this by using GetPropertyChangedSignal(‘Position’) on the HumanoidRootPart, but this doesn’t seem to be working. It’s not firing the event when my character moves.

local Players = game:GetService('Players')

local VerifiedPlayers = {}

function SetupVerification(player)
    local Character = player.Character or player.CharacterAdded:Wait()
    Character = player.Character
    local HRP = Character:WaitForChild('HumanoidRootPart')
    local CurrentPosition = HRP.Position
    HRP:GetPropertyChangedSignal('Position'):Connect(function()
        local DistanceTravelled = (CurrentPosition - HRP.Position).magnitude
        print(DistanceTravelled)
        CurrentPosition = HRP.CFrame.Position
    end)
end

for _ , player in pairs (Players:GetPlayers()) do
    if not (VerifiedPlayers[player]) then
        VerifiedPlayers[player] = true
    end
    SetupVerification(player)
end

Players.PlayerAdded:Connect(function(player)
    if not (VerifiedPlayers[player]) then
        VerifiedPlayers[player] = true
    end
    SetupVerification(player)
end)

Players.PlayerRemoving:Connect(function(player)
    if (VerifiedPlayers[player]) then
        VerifiedPlayers[player] = nil
    end
end)
1 Like

Changed and its neighbor events will only fire for when a script explicitly changes something, so the fact that it never fires when you do standard movement via controls is to be expected.

Seeing your code, I recommend a loop connected to something like RunService.Heartbeat or RunService.Stepped (either works) to poll the position once every frame. In the context of your script, granted you remember to clean up your connection should the player reset or quit, you could actually just replace the HRP connection with the RunService connection.

Now as for firing when you teleport them? I’d think it would change. I think it only fires for the property you script though, so if you set CFrame, it’s going to fire for CFrame, not Position or Rotation.

1 Like

I was experimenting with ways to achieve what I needed and this is actually exactly the solution I tried out, and it works! I decided to check the player’s position every 1/5th of a second. How would you recommend cleaning up the RunService connections? The script I currently have is this:

local Players = game:GetService('Players')
local RunService = game:GetService('RunService')

local VerifiedPlayers = {}

function SetupVerification(player)
	local Character = player.Character or player.CharacterAdded:Wait()
	Character = player.Character
	local HRP = Character:WaitForChild('HumanoidRootPart')
	local CurrentPosition = HRP.Position
	local passedtime, debounce, steplength = 0, false, 0.2
	VerifiedPlayers[player].connection = RunService.Heartbeat:Connect(function(step)
		passedtime += step
		if passedtime > steplength then
			passedtime = 0
			debounce = false
		end
		if not (debounce) then
			debounce = true
			local CharPos = HRP.Position
			local DistanceTravelled = (CurrentPosition - CharPos).magnitude
			if DistanceTravelled > 0.1 then
				print(DistanceTravelled)
			end
			CurrentPosition = CharPos
		end
	end)
end

for _ , player in pairs (Players:GetPlayers()) do
	if not (VerifiedPlayers[player]) then
		VerifiedPlayers[player] = {}
	end
	SetupVerification(player)
end

Players.PlayerAdded:Connect(function(player)
	if not (VerifiedPlayers[player]) then
		VerifiedPlayers[player] = {}
	end
	SetupVerification(player)
end)

Players.PlayerRemoving:Connect(function(player)
	if (VerifiedPlayers[player]) then
		VerifiedPlayers[player].connection:Disconnect()
		VerifiedPlayers[player] = nil
	end
end)

Edit: Added a disconnect to it.

Yeah, that script there works. I had been writing a reply to preserve the boolean, but that there also works just fine.

As for whether or not you want to make the values of VerifiedPlayers the connections themselves is up to you (I’d personally rename the table at that point) or you can store it in a sub-table as .connection, but either way, that will work.

The only thing you need to remember to do is disconnect any old connections at the start of SetupVerification because connections don’t get GC’d unless the thing they are connected to is destroyed, and RunService can’t be destroyed, obviously. It’d cause stacking connections for every reset / reload.

Another thing is that you should run SetupVerification in player.CharacterAdded so that if they reset, it tracks the proper current character. I’d still bind it to the associated player (via Players:GetPlayerFromCharacter), but that is something to note.

1 Like

Thanks for the reminder and pointing that out. I was thinking of adding the CharacterAdded event inside SetupVerification. That way I only need to disconnect when the player leaves, I believe.

1 Like