Track player movement

I want to “record” a Humanoid players exact movement including jump for (arbitrary) 5 seconds, and then I want to move an NPC Humanoid in that exact path.

I tried saving the players humanoidrootpart position in a table every hearbeat and then loop through the table calling humanoid:MoveTo() on the NPC. But it takes much longer for the NPC to travel than the player.

I want the NPC to follow the same Humanoid physics. As in if the player goes through water the NPC will change HumanoidState to water.

If anyone has any ideas or any leads please let me know thanks.

Ideas:
Rather than :MoveTo perhaps use :Move() with a players humanoid.MoveDirection instead as :MoveTo function attempts to want to “stop and arrive perfectly at each position” hence lose momentum and be slower.

Additionally try using renderstep as it runs faster and could perhaps capture more user input but hearbeat seems ok as well.

1 Like

I think your best shot at this is storing the players HumanoidRootPart CFrame inside of an array and iterating over that, setting the NPCs HumanoidRootPart CFrame. As for physics like the water state, I’m not sure. My best guess would be to just play the animations yourself as the NPC enters those states.

Pseudo-code? (ignore this, more so just me thinking out loud)

-- Client essentials
local player = game:GetService("Players").LocalPlayer
local character, humanoid, HRP

local function onCharacterAdded(newChar)
	character = newChar
	humanoid = character:WaitForChild("Humanoid")
	HRP = character:WaitForChild("HumanoidRootPart")
end
if player.Character then
	onCharacterAdded(player.Character)
end
player.CharacterAdded:Connect(onCharacterAdded)

-- Services
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")

-- Variables
local NPC = workspace:WaitForChild("NPC")
local cframeArray = {}
local recording = false
local index = 0

-- Functions
local function playback(startIndex)
	for i = startIndex, #cframeArray do
		NPC.HumanoidRootPart.CFrame = cframeArray[i]
		task.wait()
	end
end

local function clearRecording()
	table.clear(cframeArray)
	index = 0
end

local function handleInputs(inputObject, typing)
	if typing then return end
	if inputObject.KeyCode == Enum.KeyCode.R and not recording then
		recording = true
		print("Recording CFrames :D")
	elseif inputObject.KeyCode == Enum.KeyCode.R and recording then
		recording = false
		print("Stopped recording")
	elseif inputObject.KeyCode == Enum.KeyCode.C and not recording then
		clearRecording()
		print("Reset recording")
	elseif inputObject.KeyCode == Enum.KeyCode.P and not recording then
		print("Playing recording")
		playback(0)
	end
end

-- Events
UserInputService.InputBegan:Connect(handleInputs)

RunService.Heartbeat:Connect(function(dt)
	if recording then
		cframeArray[index] = HRP.CFrame
		index += 1
	end
end)

Was messing around with the code above just for fun. It was actually my first time making something like it, and surprisingly it worked first try. All you’d really have to do here to ‘simulate’ an actual character is play animations during appropriate times. Assuming you wanted it to be local… and activated by pressing keys… yeah.

3 Likes

Yeah, I want it done on the server.

This code looks interesting. Just so I’m clear, this NPC will move at the same speed as the humanoid? And this is each achieved because you save a cframe each heartbeat, and use task.wait and task.wait waits a heartbeat?

I tried setting the CFrame, with the animations the HumanoidStateType automatically switches when it goes in water but it doesn’t automatically play the moving animation.

Update:
Updating the CFrame every heartbeat works. The only thing is it doesn’t respect physics, even though it’s following the exact path I want it to respect physics.

It’s because I am using this in a pathfinding system as a fallback when ComputeAsync() fails. But if the player jumps on a moving part lets say and then the NPC tries to jump on that part but it’s not there anymore I want the NPC to fall not to “jump on air”

Also I’m sure the animations are easy to add, but I never used any animations so can you explain how to do that more thanks

It’d be much better to record the method instead of recording the result.

As frames can fluctuate its impossible to get 100% accuracy. Probably something like 80% accurate movement. Really thats what we need.

You can store this data inside an array them play it back. Its very simple method and if you wanted you could include more actions driving a vehicle sitting in a chair, equipping a tool or jumping. I just included jumping for this one.

I made a quick script to demonstrate this.

local RunService = game:GetService("RunService")


local RECORD_TIME = 10
local Record = {-- We will be using a dictionary to record this
	MoveDirection = {},
	Jump = {}
} 

-- Yeah its messy but whatever
local Player: Player = game.Players.PlayerAdded:Wait()
local Character = Player.CharacterAppearanceLoaded:Wait()
local Humanoid: Humanoid = Character:WaitForChild("Humanoid"):: Humanoid

task.wait(10) -- wait before recording
print("Recording")

local i = 1			-- iteration of recorder
local runTime = 0	-- RunTime of recorder

while (runTime < RECORD_TIME) do
	Record.MoveDirection[i] = Humanoid.MoveDirection
	Record.Jump[i] = Humanoid.Jump -- Save actions
	i += 1
	runTime += task.wait()
end

local HumanoidNPC = script.Parent.Humanoid
while i > 1 do -- Playback recorder
	i -= 1
	HumanoidNPC:Move(Record.MoveDirection[i])
	HumanoidNPC.Jump = Record.Jump[i] -- play actions
	task.wait()
end

No animations but…you get the idea. Note I did move a bit after the recording finished so thats why it didn’t align in the end.

4 Likes

Only problem is, it’s way too inaccurate. Put a few obstacles and try jumping over and around them and the Dummy completely misses everything.

So far the thing that worked is what @NoParameters said to update the cframe every heartbeat. The only problem is that it doesn’t respect physics.

How would I make it so that physics do apply?

I thought instead of moving the NPC to the exact CFrame the player traveled I would record the object space CFrame that the player is travelling and then offset it on the NPCs CFrame.

I tried that and it’s not working but I also don’t know how to use ToObjectSpace and ToWorldSpace properly.

Hate to break it to you but it is impossible for a part that is being changed every frame to respect physics.

I mean…take this example:

local RunService = game:GetService("RunService")

RunService.Stepped:Connect(function(runTime)
	script.Parent.CFrame = CFrame.new(runTime,10,0)
end)

https://gyazo.com/ef6df98cfba7ee630333997a235fd35d

The only feasible way you could do is to use AlignPosition and AlignOrientation to move the NPC to the target CFrame. They have the capability to do that. For it to work you have to make an attachment where the CFrame is parent it to the terrain and then move character that way(Don’t use Mode.OneAttachment cos it is broken). You could have this on top of the current action system if you wanted to but it isn’t necessary.

It could work maybe.

I’m aware, that’s why I am trying to figure out a workaround.

Okay thanks for your help I will try to make a system.

I have a question regarding this topic, is it also possible to replicate the position of the player’s arms/legs in order to make it seem more realistic?