Bending player torso on an R6 rig

I want the character’s torso to bend up or down based on the position of the mouse, similar to this post.
There is plenty of code out there that deals with this, however, all of it is for R15 characters.
I’ve looked around online as well, and nothing has worked so far, so I’m asking for help with basically zero knowledge on the subject.

10 Likes

Its because moving the torso (like on the R15 rig) is impossible on the R6 Rig, as there is no waist joint.
The closest effect you’ll get with an R6 Rig is moving the head (and arms if youre making a gun game)

3 Likes

Yeah it’s more difficult for R6 like @Chark_Proto said.

However it’s still possible,

The issue is changing the rotation Torso motor changes the rotation of the legs.

However, to counteract this I have added an inverse rotation so it works.

The script I adapted is from @CleverSource in this post, a lot of things needed to be changed as well such as the CFrame order with rotation * OriginalC0 in order to apply the rotation in the correct direction.

Edit: Whoops forgot the .Rotation and + .Position which made the head move around
Code:

local RunService = game:GetService("RunService")

local Player = game.Players.LocalPlayer
local PlayerMouse = Player:GetMouse()

local Camera = workspace.CurrentCamera

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

Neck.MaxVelocity = 1/3

RunService.RenderStepped:Connect(function() 
	local CameraCFrame = Camera.CoordinateFrame

	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 Camera.CameraSubject:IsDescendantOf(Character) or Camera.CameraSubject:IsDescendantOf(Player) then
				local Point = PlayerMouse.Hit.p

				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)
				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)
67 Likes

This is great, however, I’m wondering how I would be able to replicate this change to the server without exhausting a remote event?

2 Likes

Thanks,

To replicate this change you can just send the mouse position over and then fire all clients perhaps in a loop every 1 or 0.2 seconds

while task.wait(0.2) do
someRemote:Fire(mouse.Hit.Position)

To replicate you can run the code on the client for each players

Instead of using .LocalPlayer use the player from game.Players.PlayerAdded then run the code for every .CharacterAdded.

Instead of using the local mouse use a dictionary containing the mouse position per player that is received from the server.

Initially since there is no mouse position you can default to Vector3.new(0,0,0) as a fail safe or a position in front of the character.

local playerMousePositionDictionary = {}

remote.OnClientEvent(function(playerThatFired, mousePosition)
playerMousePositionDictionary [playerThatFired] = mousePosition
end)

--In the renderstep code for each player that joins
game.PlayerAdded:Connect(function(player)
				local Point = playerMousePositionDictionary[player] or Vector3.new(0,0,0)

end))

3 Likes

Update on this, having

		game.Players.PlayerAdded:Connect(function(player)
			Point = playerMousePositionDictionary[player] or Vector3.new(0,0,0)
		end)

in runservice still continues to return nil for Point.
The code so far is structured very similar to how egomoose did it in this post which I had mentioned before.

1 Like

My method of replication that im thinking of is a bit different then EgoMoose.

The main idea im going for is having the local player change the C0 of the other players for replication.

A reference for this is what I did for my R6 IKPF system with player added and character added.

EgoMooses method is to change it directly on server which also works.

I can show it when I get the time im outside rn.

I tried having a look at your code for your R6 IKPF system, it’s a lot and it’s quite overwhelming.
Could you give the main gist of how I could implement the changes egomoose’s way?

So I’ve managed to stitch the code together to work, but the fact that it fires the remote event every heartbeat kinda worries me in terms of game performance.
I’m hoping you’ll actually reply with how you would do it with your own method.

Server

-- 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)
		--game.Players.PlayerAdded:Connect(function(player)
			Point = playerMousePositionDictionary[Player] or Vector3.new(0,0,0)
		--end)
		
		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
					--print(Point)
					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)

Local

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)

Here is my method which does it on the client. Which sends the data at a rate of every 0.2 seconds.

The advantage of this method is that all the C0 stuff happens on the client and hence is not burdened by the server.

Cons is that the server does not replicate so hitboxes and stuff might be a little off.

However another issue I found is that the interpolation done by the lerping is not as smooth since there is a delay in mouse.Position of 0.2 seconds perhaps delta time or the lerp alpha number should be changed.

--StarterPlayerScripts
--Only animates the C0 for other players keep another local script to animate the local player character
--Handle Replication Data
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local RE = ReplicatedStorage:WaitForChild("RemoteEvent")

local PlayersMouseData = {}

local RE2 = ReplicatedStorage:WaitForChild("OnClient")
RE2.OnClientEvent:Connect(function(player, mousePosData)
--Make sure to clean this up to prevent memory leak with OnPlayerRemoved set data to nil
	PlayersMouseData[player] = mousePosData
end)

--Use Replication Data

local Players = game:GetService("Players")

local localPlayer = Players.LocalPlayer

local function initRunService(somePlayer)
	local RunService = game:GetService("RunService")

	local Character = somePlayer.Character or somePlayer.CharacterAdded:Wait()

	local Head = Character:WaitForChild("Head")

	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

	Neck.MaxVelocity = 1/3
	
	local connection
	connection = RunService.RenderStepped:Connect(function(deltaTime) 
		if Character.Parent == nil then
			--clean up function
			connection:Disconnect()
		end
			
		if Character:FindFirstChild("Torso") and Character:FindFirstChild("Head") then
			local TorsoLookVector = Torso.CFrame.lookVector
			local HeadPosition = Head.CFrame.p

			if Neck and Waist then
				local Point = PlayersMouseData[somePlayer] or Vector3.new(0,0,0)
				
				assert(Point, "What its missing")
				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)
				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

local playerAdded = function(player)
	if localPlayer == player then
		return
	end
	player.CharacterAdded:Connect(function(character)
		initRunService(player)
	end)
end

Players.PlayerAdded:Connect(playerAdded)

for i, v in pairs(Players:GetPlayers()) do
	playerAdded(v)
end

--Begin transmitting data

local PlayerMouse = localPlayer:GetMouse()

while true do
	RE:FireServer(PlayerMouse.Hit.Position)
	print("Fire serverrr", RE)
	task.wait(0.2)
end

Server Script:

local Players = game:GetService("Players")

local function fireAllClientsExcept(remote, exceptionPlayer, ...)
	for _, player in pairs(Players:GetPlayers()) do
		if player == exceptionPlayer then
			continue
		end
		remote:FireClient(player, ...)
	end
end

--local RE = Instance.new("RemoteEvent", game.ReplicatedStorage)

--local RE2 = Instance.new("RemoteEvent")
--RE2.Name = "OnClient"
--RE2.Parent = game.ReplicatedStorage
local RE = game.ReplicatedStorage.RemoteEvent
local RE2 = game.ReplicatedStorage.OnClient

RE.OnServerEvent:Connect(function(player, mousePosition)
	fireAllClientsExcept(RE2, player, player, mousePosition)
	print("Onserver event")
end)
8 Likes

looks really good, but how can I make it so the torso isn’t turning with the mouse, but instead, the camera?

Thanks (With credits to CleverSource as well for the initial script),

To make it turn with the camera you can replace the look at point which is the mouse position obtained from mouse.Hit in the original line of code:

And replace that with this do this instead:

local camera = workspace.CurrentCamera
local cameraCF = camera.CFrame
local cameraPoint = (HumanoidRootPart.CFrame*CFrame.new(0,1,0)).Position + cameraCF.LookVector*5 --Position new look at point at around the original head location and move it where it is looking in front by 5 studs.
--You can adjust the 5 
1 Like

Sorry, I’m a major beginner, where do I replace that code? And what do I replace it with?

i wonder do you use local script or just script?

Very good code! I have somewhat cleaned it up but in camelCase and I don’t just want to leech of this project so, I’ll post what I did here.

It is starterCharacterScript so therefore it gets cleaned up when character gets reset. No need for Disconnecting or stuff like that.

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

local localPlayer = Players.LocalPlayer
local mouse = localPlayer:GetMouse()

local camera = workspace.CurrentCamera

local character = script.Parent

local head = character:WaitForChild("Head")
local rootPart = character:WaitForChild("HumanoidRootPart")
local torso = character:WaitForChild("Torso")

local neckMotor = torso:WaitForChild("Neck")
local waistMotor = rootPart:WaitForChild("RootJoint")
local rightHipMotor = torso:WaitForChild("Right Hip")
local leftHipMotor = torso:WaitForChild("Left Hip")

local neckMotorOrigin0 = neckMotor.C0
local waistMotorOrigin0 = waistMotor.C0
local leftHipMotorOrigin0 = leftHipMotor.C0
local rightHipMotorOrigin0 = rightHipMotor.C0

neckMotor.MaxVelocity = 1/3

local function updateCharacterMouseFollow(deltaTime)
	local cameraCFrame = camera.CoordinateFrame

	if torso and head and neckMotor and waistMotor then
		-- //You could add check if its Parented to nil, because thats what happens when its destroyed,
		-- //Or you could keep it like this so the code doesn't error, and will work with other yet parented motors.
		
		-- //https://devforum.roblox.com/t/frame-rate-independent-lerp/352375/23
		local lerpAlpha = 1 - math.exp(-6 * deltaTime)
	
		local torsoLookVector = torso.CFrame.lookVector
		local headPosition = head.CFrame.p

		local mouseHitPosition = mouse.Hit.p

		local mouseHitHeadDist = (head.CFrame.p - mouseHitPosition).magnitude
		local YDifference = head.CFrame.Y - mouseHitPosition.Y
		local YDiffmouseHeadDistUnit = (headPosition - mouseHitPosition).Unit

		local XWaistRotation = -(math.atan(YDifference / mouseHitHeadDist) * 0.5)
		local YWaistRotation = (YDiffmouseHeadDistUnit:Cross(torsoLookVector)).Y * 0.5

		local rotationWaistCFrame = CFrame.Angles(XWaistRotation, YWaistRotation, 0)
		local goalWaistCFrame = rotationWaistCFrame * waistMotorOrigin0

		local currentLegCounterCFrame = waistMotor.C0 * waistMotorOrigin0:Inverse()
		local legsCounterCFrame = currentLegCounterCFrame:Inverse() * CFrame.Angles(0, math.rad(180), 0)
		-- //I had issue with legs being rotated in opposite direction therefore I added ^. It fixes it for me.

		local goalNeckCFrame = CFrame.Angles(
			-(math.atan(YDifference / mouseHitHeadDist) * 0.5),
			YDiffmouseHeadDistUnit:Cross(torsoLookVector).Y * 1,
			0
		)

		neckMotor.C0 = neckMotor.C0:lerp(goalNeckCFrame * neckMotorOrigin0, lerpAlpha).Rotation + neckMotorOrigin0.Position
		waistMotor.C0 = waistMotor.C0:lerp(goalWaistCFrame, lerpAlpha).Rotation + waistMotorOrigin0.Position

		rightHipMotor.C0 = legsCounterCFrame * leftHipMotorOrigin0
		leftHipMotor.C0 = legsCounterCFrame * rightHipMotorOrigin0
	end	
end

RunService.RenderStepped:Connect(updateCharacterMouseFollow)
6 Likes

Hey, even though this is quite old; How can I manage to change it so instead of it getting the Mouse location, it just gets where the player is looking at, like how you described to @unsinfulways

Just a follow up, if you or someone could help me change it so instead of it getting the players mouse, it gets where the player looks like in the main script. It’ll be better if possible.

1 Like

just replace the mouse parts with camera.lookvector

1 Like