Replicating R6 torso bending to the server

So I made this post yesterday asking how I could bend the player’s torso to follow the mouse on an r6 rig. I’ve managed to get it working, however, I had trouble with replicating the torso movement to the server. In order to solve this, I fire a remote event every heartbeat which runs the code server side.
I’m sure there must be a more efficient way of replicating the torso movement to the server though.

The script hierarchy
image

Local script

local RunService = game:GetService("RunService")
local Camera = workspace.CurrentCamera
local mouse = game.Players.LocalPlayer:GetMouse()


script:WaitForChild("SetUp"):FireServer(Camera.CFrame, Camera.CameraSubject)

RunService.Heartbeat:Connect(function(dt)
	script:WaitForChild("RemoteEvent"):FireServer(mouse.Hit.Position)
end)

Server script

-- Game services
local RunService = game:GetService("RunService")
local Point
local playerMousePositionDictionary = {}

script.Parent:WaitForChild("RemoteEvent").OnServerEvent:Connect(function(playerThatFired, mousePosition)
	playerMousePositionDictionary [playerThatFired] = mousePosition
end)

script.Parent:WaitForChild("SetUp").OnServerEvent:Connect(function(Player, CamCFrame, CamSubject)
	local Character = Player.Character or Player.CharacterAdded:Wait()
	--Old R15 code
	local Head = Character:WaitForChild("Head")
	--local Neck = Head:WaitForChild("Neck")

	--local Torso = Character:WaitForChild("UpperTorso")
	--local Waist = Torso:WaitForChild("Waist")

	--New R6 stuff
	local Humanoid = Character:WaitForChild("Humanoid")
	local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")

	local Torso = Character:WaitForChild("Torso")
	local Neck = Torso:WaitForChild("Neck")

	local Waist = HumanoidRootPart:WaitForChild("RootJoint")

	local RHip = Torso:WaitForChild("Right Hip")
	local LHip = Torso:WaitForChild("Left Hip")

	local LHipOriginC0 = LHip.C0
	local RHipOriginC0 = RHip.C0

	local NeckOriginC0 = Neck.C0
	local WaistOriginC0 = Waist.C0
	
	RunService.Heartbeat:Connect(function(dt)
		Point = playerMousePositionDictionary[Player] or Vector3.new(0,0,0)
		
		local CameraCFrame = CamCFrame

		if Character:FindFirstChild("Torso") and Character:FindFirstChild("Head") then
			local TorsoLookVector = Torso.CFrame.lookVector
			local HeadPosition = Head.CFrame.p

			if Neck and Waist then
				if CamSubject:IsDescendantOf(Character) or CamSubject:IsDescendantOf(Player) then

					local Distance = (Head.CFrame.p - Point).magnitude
					
					local Difference = Head.CFrame.Y - Point.Y


					local goalNeckCFrame = CFrame.Angles(-(math.atan(Difference / Distance) * 0.5), (((HeadPosition - Point).Unit):Cross(TorsoLookVector)).Y * 1, 0)
					Neck.C0 = Neck.C0:lerp(goalNeckCFrame*NeckOriginC0, 0.5 / 2).Rotation + NeckOriginC0.Position

					local xAxisWaistRotation = -(math.atan(Difference / Distance) * 0.5)	-- Change 0.5 to change amount of rotation
					local yAxisWaistRotation = (((HeadPosition - Point).Unit):Cross(TorsoLookVector)).Y * 0.5
					local rotationWaistCFrame = CFrame.Angles(xAxisWaistRotation, yAxisWaistRotation, 0)
					local goalWaistCFrame = rotationWaistCFrame*WaistOriginC0
					Waist.C0 = Waist.C0:lerp(goalWaistCFrame, 0.5 / 2).Rotation + WaistOriginC0.Position


					local currentLegCounterCFrame = Waist.C0*WaistOriginC0:Inverse()

					local legsCounterCFrame = currentLegCounterCFrame:Inverse()

					RHip.C0 =  legsCounterCFrame*RHipOriginC0
					LHip.C0 = legsCounterCFrame*LHipOriginC0
				end
			end
		end	
	end)
end)
2 Likes

A few things, firstly you should calculate everything on the client and send it to the server instead of only sending mouse position to the server and calculating from there. That way you’re doing less work on the server while still getting the results. This means you can pretty much get rid of the SetUp remote as it won’t be calculating anything, and you can instead set the torso bend in the RemoteEvent remote.

Secondly, you should fire the RemoteEvent remote to the server every maybe 10 heartbeats, instead of sending it every heartbeat. Then, you can Lerp on the server to make it smooth without needing to spam lots of remotes.

Finally, your script hierarchy definitely needs improving. I’d rename things so its easier to understand what the local script and server script are doing. Then I’d move that server script into somewhere like ServerScriptService, as if its inside a local script then I’m assuming its under the Player or the Character, which you don’t want server scripts in the Player, and you wouldn’t put something like this in the Character. Also I’d move the RemoteEvents into ReplicatedStorage so both the Server and Client can reach them.

This is all very well and good, I’ve seen the exact same being said by egomoose in this post, however, I don’t understand how I would implement your suggestions like interpolating the angles that are being sent to the server every few heartbeats.

Ignore that, I haven’t had the time to clean up my code yet.

Instead of simply setting the CFrame of the C0, you can use :Lerp() to interpolate the CFrames, which I believe is already happening in your code from these lines

If it looks off, I’d mess around with the second argument to :Lerp(), which is sort of like a time in this situation.

I’d like to think I know what you mean but I really don’t.
So I placed the calculation of all that maths stuff in a local script, and every 0.2 seconds, it fires a remote event which is picked up by the server script and these changes are applied to the server.

Server script

script.Parent.RemoteEvent.OnServerEvent:Connect(function(player, goalWaistCFrame, WaistOriginC0, goalNeckCFrame, NeckOriginC0, legsCounterCFrame, RHipOriginC0, LHipOriginC0)
	local HumanoidRootPart = player.Character:WaitForChild("HumanoidRootPart")

	local Torso = player.Character:WaitForChild("Torso")
	local Neck = Torso:WaitForChild("Neck")

	local Waist = HumanoidRootPart:WaitForChild("RootJoint")

	local RHip = Torso:WaitForChild("Right Hip")
	local LHip = Torso:WaitForChild("Left Hip")

	
	
	Waist.C0 = Waist.C0:lerp(goalWaistCFrame, 0.5 / 2).Rotation + WaistOriginC0.Position
	Neck.C0 = Neck.C0:lerp(goalNeckCFrame*NeckOriginC0, 0.5 / 2).Rotation + NeckOriginC0.Position
	
	
	RHip.C0 =  legsCounterCFrame*RHipOriginC0
	LHip.C0 = legsCounterCFrame*LHipOriginC0
end)

This doesn’t update the player torso smoothly at all.

It seems like the Lerp isn’t actually what its setting the C0 to, its just using the Lerp as a calculation. I’m not in studio so I don’t know if this will work but try this:

	Waist.C0 = Waist.C0:lerp(Waist.C0:lerp(goalWaistCFrame, 0.5 / 2).Rotation + WaistOriginC0.Position, .5)
	Neck.C0 = Neck.C0:lerp(Neck.C0:lerp(goalNeckCFrame*NeckOriginC0, 0.5 / 2).Rotation + NeckOriginC0.Position, .5)

To change the smoothness, experiment with that .5 at the end and see which numbers look better.

Again this doesn’t seem to be changing anything, player rotation is still very jittery.
The .5 at the end changes how far the M6D rotates, this definitely doesn’t affect “smoothness”.

Bump, any suggestions on how I could improve my code is welcome.
Bump

I decided to completely scrap my original code and followed egomoose’s method using springs instead.
Amazingly, script activity dropped from 1-2% with my old code, to basically 0%.

Here’s a basic mock-up of the code which is essentially bits copied and pasted from egomoose’s fps tutorial:

local script

local camera = workspace.CurrentCamera

wait(0.5)
script.setup:FireServer()

while task.wait(0.2) do
	script.tiltAt:FireServer(math.asin(camera.CFrame.LookVector.y));
end

server script

local states = {
	[Enum.HumanoidStateType.Climbing] = true;
	[Enum.HumanoidStateType.Swimming] = true;
}
local springs = {};
local cleanup = {};

local spring = require(script.Parent.spring)

script.Parent.tiltAt.OnServerEvent:Connect(function(player, theta)
	if (springs[player]) then
		springs[player].tilt.target = theta;
	end
end)

script.Parent.setup.OnServerEvent:Connect(function(player)
	local tilt = spring.new(0, 0, 0, 10, 1);
	springs[player] = {};
	springs[player].tilt = tilt;
	
	local character = player.Character;
	local humanoid = character:WaitForChild("Humanoid");
	
	local torso = player.Character:WaitForChild("Torso");
	local hrp = player.Character:WaitForChild("HumanoidRootPart");

	local neck = torso:WaitForChild("Neck");
	local root = hrp:WaitForChild("RootJoint");
	local rShoulder = torso:WaitForChild("Right Shoulder");
	local lShoulder = torso:WaitForChild("Left Shoulder");
	local rHip = torso:WaitForChild("Right Hip");
	local lHip = torso:WaitForChild("Left Hip");

	local neckC0 = neck.C0;
	local rootC0 = root.C0;
	local rShoulderC0 = rShoulder.C0;
	local lShoulderC0 = lShoulder.C0;
	local rHipC0 = rHip.C0;
	local lHipC0 = lHip.C0;
	
	local function tiltFunc(theta)
		neck.C0 = neckC0 * CFrame.fromEulerAnglesYXZ(-theta, 0, 0);
		root.C0 = rootC0 * CFrame.new(0, 0, -1) * CFrame.fromEulerAnglesYXZ(-theta, 0, 0) * CFrame.new(0, 0, 1);
		rShoulder.C0 = rShoulderC0 * CFrame.fromEulerAnglesYXZ(0, 0, theta);
		lShoulder.C0 = lShoulderC0 * CFrame.fromEulerAnglesYXZ(0, 0, -theta);
		rHip.C0 = rHipC0 * CFrame.fromEulerAnglesYXZ(0, 0, -theta);
		lHip.C0 = lHipC0 * CFrame.fromEulerAnglesYXZ(0, 0, theta);
	end
	
	local heartBeat = game:GetService("RunService").Heartbeat:Connect(function(dt)
		tilt:update(dt);
		tiltFunc(states[humanoid:GetState()] and 0 or tilt.p / 2);
	end)

end)

image

There are obvious ways of improving this, like disconnecting functions on player death or simply cleaning up the code and hierarchy to make it look tidier, but these are all trivial matters that anyone could do.