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
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)
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:
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”.
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)
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.