Attaching a characters hands to a steering wheel

Hello reader!

I’m working on a new game that requires vehicles as part of the main mechanic. I’ve gone the extra “foot” to add rotation to the steering wheel when the wheels turn. This is cool, but I want the characters hands to be attached to the wheel, and move when the steering wheel moves.

https://giphy.com/gifs/dBHrz1bqARUp95w3jE/html5

I have attachments for each hand in place, I’m just unsure of how to attach the hands.

If anyone knows how to help me figure this out, any help would be greatly appreciated! Thanks!

20 Likes

I am thinking that you should be able to use AlignPosition to create the desired effect.
If you don’t mind, can you send a model of the car so that I can try to create a solution for you?

you could try simulating the limbs with physics constraints, namely ball&sockect and hinge, with the hands welded to the sides of the wheel

or an animation for moving the wheel to the left and right,

or an IK solver, I’d link you the wiki article on how to do that, but i can’t seem to find it :confused:

1 Like

It’s gone. Found this one though. FABRIK is really simple at its core, but gets a bit complicated with the Z-axis rotation.

Another fairly easy way to do this is to create an animation and manually change the increment depending on how hard the wheel is turned. I believe this is how most other games do it as well (the floaty hands).

2 Likes

Sure! Here’s the model. Car Chassis.rbxm (53.7 KB)

You could create fake arms and welds and attach those to the steering wheel.
And to make it look better, some calculations or animations to make it look a bit more realistic when the wheel turns sharp left or right.

Sorry for the extremely late reply, but I did take a crack at it using the new BodyMovers I mentioned earlier, and came to the conclusion that although they do work somewhat, they are a bit hilarious in how they work.

Example here:

https://gyazo.com/7a5ebe5e33f6c46cbb5b8e8b0f631c3e

That being said, I would try using the method described by @RubyEpicFox as it seems to be the best solution.

But of course, if by any chance you want this updated model to play around with, here is is:
HilariousHandAttachment.rbxm (57.1 KB)

9 Likes

I’ll see what I can do with this. Thanks!

Sorry for the really late reply but I think I have just what your looking for. I was able to make the arms point at the attachments you have in the steering wheel. The arms were too short to reach on their own so I made the character lean forward by editing the Waist joint. If you don’t want the character to lean forward and still reach the wheel you will need to adjust your model so that can happen. There is a variable in the code that controls how many degrees forward the character is leaning you can mess with that if you edit the car. If you want to make the turning more dramatic you can edit how much the steering wheel turns in your constraints properties I think, my code should still work with it

The only downside to this code is that the joint movement dont replicate to the server :confused: Something I suggest is that when the player gets on the car you could tell the server to get the character into the starting pose so its leaning forward with arms out so its touching the wheel. The actual steering part could be purely client side, if your making some kind of racing game its unlikely any other players will be paying close enough attention to notice that other player’s arms aren’t turning the wheel anyway.

You can copy and paste this into a localscript and this should work, here is my save file too just in case
ChassisSteering.rbxl (68.9 KB)

local plr = game.Players.LocalPlayer
local char = workspace:WaitForChild(plr.Name)
local handLeft = workspace.Chassis.Parts.SteeringWheel.HandLeft
local handRight = workspace.Chassis.Parts.SteeringWheel.HandRight

--These two angles were found by trial and error, adjust if you want
local characterLeanAngle = math.rad(20)--How many degrees the character is leaning forward
local neckAngle = math.rad(-15)--How many degrees the head is looking up/down

local steppedEvent

---Create Welds identical to Motor6Ds to override animations
function configureJointsForSteering()
	local rightShoulder = char:WaitForChild("RightUpperArm").RightShoulder
	local leftShoulder = char:WaitForChild("LeftUpperArm").LeftShoulder
	local rightElbow = char.RightLowerArm.RightElbow
	local leftElbow = char.LeftLowerArm.LeftElbow
	
	--Torso Weld is to keep the player leaning forward so the arms reach the steering wheel
	local torsoWeld = Instance.new("Weld")
	torsoWeld.Part0 = char.UpperTorso.Waist.Part0
	torsoWeld.Part1 = char.UpperTorso.Waist.Part1
	torsoWeld.C0 = char.UpperTorso.Waist.C0 *CFrame.Angles(-characterLeanAngle, 0, 0)
	torsoWeld.C1 = char.UpperTorso.Waist.C1
	torsoWeld.Name = "WaistWeld"
	torsoWeld.Parent = char.UpperTorso
	
	
	--Neck weld is to make the player's head look up, since the character is leaning forward the head is naturally looking down, so we need to adjust
	local neckWeld = Instance.new("Weld")
	neckWeld.Part0 = char.Head.Neck.Part0
	neckWeld.Part1 = char.Head.Neck.Part1
	neckWeld.C0 = char.Head.Neck.C0 *CFrame.Angles(-neckAngle, 0, 0)
	neckWeld.C1 = char.Head.Neck.C1
	neckWeld.Name = "NeckWeld"
	neckWeld.Parent = char.Head
	
	--Shoulder welds so we can make them move towards the steering wheel
	local rightShoulderWeld = Instance.new("Weld")
	rightShoulderWeld.Part0 = rightShoulder.Part0
	rightShoulderWeld.Part1 = rightShoulder.Part1
	rightShoulderWeld.C0 = rightShoulder.C0
	rightShoulderWeld.C1 = rightShoulder.C1
	rightShoulderWeld.Parent = rightShoulder.Parent
	rightShoulderWeld.Name = "RightShoulderWeld"
	rightShoulder = rightShoulderWeld
	
	local leftShoulderWeld = Instance.new("Weld")
	leftShoulderWeld.Part0 = leftShoulder.Part0
	leftShoulderWeld.Part1 = leftShoulder.Part1
	leftShoulderWeld.C0 = leftShoulder.C0
	leftShoulderWeld.C1 = leftShoulder.C1
	leftShoulderWeld.Parent = leftShoulder.Parent
	leftShoulderWeld.Name = "LeftShoulderWeld"
	leftShoulder = leftShoulderWeld
	
	--Elbow welds to keep the arms straight
	rightElbow = Instance.new("Weld")
	rightElbow.Part0 = rightShoulder.Part1
	rightElbow.Part1 = char.RightLowerArm
	rightElbow.C0 = CFrame.new(0, -rightElbow.Part1.Size.Y/2, 0)
	rightElbow.Name = "RightElbowWeld"
	rightElbow.Parent = char.RightLowerArm
	
	leftElbow = Instance.new("Weld")
	leftElbow.Part0 = leftShoulder.Part1
	leftElbow.Part1 = char.LeftLowerArm
	leftElbow.C0 = CFrame.new(0, -leftElbow.Part1.Size.Y/2, 0)
	leftElbow.Name = "LeftElbowWeld"
	leftElbow.Parent = char.LeftLowerArm
	
	
	return rightShoulder, leftShoulder
end


function cleanupJoints()
	local waistWeld = char.UpperTorso:FindFirstChild("WaistWeld")
	local neckWeld = char.Head:FindFirstChild("NeckWeld")
	local rs = char.RightUpperArm:FindFirstChild("RightShoulderWeld")
	local ls = char.LeftUpperArm:FindFirstChild("LeftShoulderWeld")
	local re = char.RightLowerArm:FindFirstChild("RightElbowWeld")
	local le = char.LeftLowerArm:FindFirstChild("LeftElbowWeld")
	
	if waistWeld then
		waistWeld:Destroy()
	end
	
	if neckWeld then
		neckWeld:Destroy()
	end
	
	if rs then
		rs:Destroy()
	end
	
	if ls then
		ls:Destroy()
	end
	
	if re then
		re:Destroy()
	end
	
	if le then
		le:Destroy()
	end
end

function handsOnWheel(rightShoulder, leftShoulder)
    --Find the positions on the wheel we want the hand to point to
	local rightTargetPosition = CFrame.new(rightShoulder.Part0.CFrame:PointToObjectSpace(handRight.WorldPosition)) *(rightShoulder.C0 - rightShoulder.C0.p)
	local leftTargetPosition = CFrame.new(leftShoulder.Part0.CFrame:PointToObjectSpace(handLeft.WorldPosition)) *(leftShoulder.C0 - leftShoulder.C0.p)
	
    --Adjust target positions so that the center of the hand will point to target position, otherwise the top left part of the hand will point to target position
	rightTargetPosition = rightTargetPosition *CFrame.new(-rightShoulder.Part1.Size.X/2, -rightShoulder.Part1.Size.Y/2, 0)
	leftTargetPosition = leftTargetPosition *CFrame.new(leftShoulder.Part1.Size.X/2, -leftShoulder.Part1.Size.Y/2, 0)
	
    --Rotate arms 90 degrees downward so that the end of the hand will point to target position
	local rightC0 = CFrame.new(rightShoulder.C0.p, rightTargetPosition.p) *CFrame.Angles(math.rad(90), 0, 0)
	local leftC0 = CFrame.new(leftShoulder.C0.p, leftTargetPosition.p) *CFrame.Angles(math.rad(90), 0, 0)
	
	rightShoulder.C0 = rightC0
	leftShoulder.C0 =  leftC0
end

--When someone sits in the vehicle seat
workspace.Chassis.VehicleSeat:GetPropertyChangedSignal("Occupant"):Connect(function()
	if workspace.Chassis.VehicleSeat.Occupant == char.Humanoid then
		local rightShoulder, leftShoulder = configureJointsForSteering()
		
		--Setup event that will move hands to the wheel
		steppedEvent = game["Run Service"].RenderStepped:Connect(function()
			handsOnWheel(rightShoulder, leftShoulder)
		end)
	else
		cleanupJoints()--Put joints back to normal
		
		if steppedEvent then
			steppedEvent:Disconnect()--Stop making the players hands stay on the wheel and stop memory leaks
		end
	end
end)
30 Likes

This is amazing! Thanks so much! I really appreciate the help :slight_smile:

4 Likes

I do agree my method sometimes looks a bit funny but that’s why I also included that you might need to make some animations or calculations
when attaching arms to the wheel to avoid this funny looking behavior.
I hope my method works well though.

1 Like

I think that to create a visually pleasing affect your best bet would be using inverse kinematics, and updating it every second. @LMH_Hutch did a super detailed tutorial on it. You can see it here:2 Joint 2 Limb Inverse Kinematics

1 Like

Hi I built upon your script and modified it to be server sided.


local char =  script.Parent
local handLeft = nil
local handRight = nil
local val = 0
--These two angles were found by trial and error, adjust if you want

local neckAngle = math.rad(-15)--How many degrees the head is looking up/down

local steppedEvent


---Create Welds identical to Motor6Ds to override animations
function configureJointsForSteering()
	
	local characterLeanAngle = math.rad(0)--How many degrees the character is leaning forward
	
	local rightShoulder = char:WaitForChild("RightUpperArm").RightShoulder
	local leftShoulder = char:WaitForChild("LeftUpperArm").LeftShoulder
	local rightElbow = char.RightLowerArm.RightElbow
	local leftElbow = char.LeftLowerArm.LeftElbow

	--Torso Weld is to keep the player leaning forward so the arms reach the steering wheel
	local torsoWeld = Instance.new("Weld")
	torsoWeld.Part0 = char.UpperTorso.Waist.Part0
	torsoWeld.Part1 = char.UpperTorso.Waist.Part1
	torsoWeld.C0 = char.UpperTorso.Waist.C0 *CFrame.Angles(-characterLeanAngle, 0, 0)
	torsoWeld.C1 = char.UpperTorso.Waist.C1
	torsoWeld.Name = "WaistWeld"
	torsoWeld.Parent = char.UpperTorso


	--Neck weld is to make the player's head look up, since the character is leaning forward the head is naturally looking down, so we need to adjust
	local neckWeld = Instance.new("Weld")
	neckWeld.Part0 = char.Head.Neck.Part0
	neckWeld.Part1 = char.Head.Neck.Part1
	neckWeld.C0 = char.Head.Neck.C0 *CFrame.Angles(-neckAngle, 0, 0)
	neckWeld.C1 = char.Head.Neck.C1
	neckWeld.Name = "NeckWeld"
	neckWeld.Parent = char.Head

	--Shoulder welds so we can make them move towards the steering wheel
	local rightShoulderWeld = Instance.new("Weld")
	rightShoulderWeld.Part0 = rightShoulder.Part0
	rightShoulderWeld.Part1 = rightShoulder.Part1
	rightShoulderWeld.C0 = rightShoulder.C0
	rightShoulderWeld.C1 = rightShoulder.C1
	rightShoulderWeld.Parent = rightShoulder.Parent
	rightShoulderWeld.Name = "RightShoulderWeld"
	rightShoulder = rightShoulderWeld

	local leftShoulderWeld = Instance.new("Weld")
	leftShoulderWeld.Part0 = leftShoulder.Part0
	leftShoulderWeld.Part1 = leftShoulder.Part1
	leftShoulderWeld.C0 = leftShoulder.C0
	leftShoulderWeld.C1 = leftShoulder.C1
	leftShoulderWeld.Parent = leftShoulder.Parent
	leftShoulderWeld.Name = "LeftShoulderWeld"
	leftShoulder = leftShoulderWeld

	--Elbow welds to keep the arms straight
	rightElbow = Instance.new("Weld")
	rightElbow.Part0 = rightShoulder.Part1
	rightElbow.Part1 = char.RightLowerArm
	rightElbow.C0 = CFrame.new(0, -rightElbow.Part1.Size.Y/2, 0)
	rightElbow.Name = "RightElbowWeld"
	rightElbow.Parent = char.RightLowerArm

	leftElbow = Instance.new("Weld")
	leftElbow.Part0 = leftShoulder.Part1
	leftElbow.Part1 = char.LeftLowerArm
	leftElbow.C0 = CFrame.new(0, -leftElbow.Part1.Size.Y/2, 0)
	leftElbow.Name = "LeftElbowWeld"
	leftElbow.Parent = char.LeftLowerArm


	return rightShoulder, leftShoulder
end


function cleanupJoints()
	local waistWeld = char.UpperTorso:FindFirstChild("WaistWeld")
	local neckWeld = char.Head:FindFirstChild("NeckWeld")
	local rs = char.RightUpperArm:FindFirstChild("RightShoulderWeld")
	local ls = char.LeftUpperArm:FindFirstChild("LeftShoulderWeld")
	local re = char.RightLowerArm:FindFirstChild("RightElbowWeld")
	local le = char.LeftLowerArm:FindFirstChild("LeftElbowWeld")

	if waistWeld then
		waistWeld:Destroy()
	end

	if neckWeld then
		neckWeld:Destroy()
	end

	if rs then
		rs:Destroy()
	end

	if ls then
		ls:Destroy()
	end

	if re then
		re:Destroy()
	end

	if le then
		le:Destroy()
	end
end

function handsOnWheel(rightShoulder, leftShoulder)
	--Find the positions on the wheel we want the hand to point to

	local rightShoulder = char:FindFirstChild("RightShoulderWeld", true)
	local leftShoulder = char:FindFirstChild("LeftShoulderWeld", true)
	
	local rightTargetPosition = CFrame.new(rightShoulder.Part0.CFrame:PointToObjectSpace(handRight.WorldPosition)) *(rightShoulder.C0 - rightShoulder.C0.p)
	local leftTargetPosition = CFrame.new(leftShoulder.Part0.CFrame:PointToObjectSpace(handLeft.WorldPosition)) *(leftShoulder.C0 - leftShoulder.C0.p)

	--Adjust target positions so that the center of the hand will point to target position, otherwise the top left part of the hand will point to target position
	rightTargetPosition = rightTargetPosition *CFrame.new(-rightShoulder.Part1.Size.X/2, -rightShoulder.Part1.Size.Y/2, 0)
	leftTargetPosition = leftTargetPosition *CFrame.new(leftShoulder.Part1.Size.X/2, -leftShoulder.Part1.Size.Y/2, 0)
	
	--Rotate arms 90 degrees downward so that the end of the hand will point to target position
	local rightC0 = CFrame.new(rightShoulder.C0.p, rightTargetPosition.p) *CFrame.Angles(math.rad(90), 0, 0)
	local leftC0 = CFrame.new(leftShoulder.C0.p, leftTargetPosition.p) *CFrame.Angles(math.rad(90), 0, 0)

	rightShoulder.C0 = rightC0
	leftShoulder.C0 =  leftC0
end

--When someone sits in the vehicle seat
script.Parent.Humanoid.Seated:Connect(function(bool, seat)
	if bool == true then
		if seat then
			handLeft = seat.Parent:FindFirstChild("LARM", true)  -- make sure u have attachment in the car called LARM 
			handRight = seat.Parent:FindFirstChild("RARM", true) -- make sure u have attachment in car called RARM for right arm
			
			local rightShoulder, leftShoulder = configureJointsForSteering()
			
		--Setup event that will move hands to the wheel
			steppedEvent = game:GetService("RunService").Heartbeat:Connect(function()
				handsOnWheel(rightShoulder, leftShoulder)
			end)
		end
	elseif bool == false then
		cleanupJoints()--Put joints back to normal
		val = 0
		if steppedEvent then
			steppedEvent:Disconnect()--Stop making the players hands stay on the wheel and stop memory leaks
		end
	end
end)

Next thing I want to achieve is to have the torso point towards the position in between the LARM (leftarm) attachment and the RARM (rightarm) attachment and maybe to bend the arms a bit until it looks like the hand is holding the attachment perfectly in shape.

There’s a lot to improve on so far. I appreciate your help.

8 Likes

Came across this post, which was quite helpful (the solution(s), specifically). While building out a Roblox project in my spare time, I do code/script in other languages in my ‘day job’. I’ve taken this code and significantly refactored it into more testable code (for unit testing, that is). Long functions that do ‘everything’ are good, but I’d challenge anyone writing any kind of code to consider breaking out simple things into ‘helper’ functions (start thinking about code re-use, and not having to write simple things over and over throughout your project). These simpler functions will also be more conducive to unit testing as well, if you want to start testing your code (you should, but it there is a learning curve).

Cheers

local RunService = game:GetService("RunService")
local vehicleSeat =  script.Parent
local character
local steppedEvent
local tempJoints = {}

--[[
	Using a table for some lookups.
	Table could be extended for other lookups as well
--]]
local bodyParts = {
	LeftLowerArm = {
		Name = "LeftLowerArm",
		Joint = "LeftElbow",
	},
	RightLowerArm = {
		Name = "RightLowerArm",
		Joint = "RightElbow"
	},
	LeftUpperArm = {
		Name = "LeftUpperArm",
		Joint = "LeftShoulder"
	},
	RightUpperArm = {
		Name = "RightUpperArm",
		Joint = "RightShoulder"
	}
}

-- Query the table for a part, return nil if not located
local function TableContains(queryTable,value)
	return queryTable[value] ~= nil
end

--[[
	Get required joint from table
	Uses helper function to determine if an element (part)
	is in the specified table
--]]
local function GetJoint(partString)
	print("CARSEAT - getting joint for: ",partString)
	local joint = nil
	local match = TableContains(bodyParts,partString)
	if match then
		joint = bodyParts[partString]["Joint"]
	end
	return joint
end

local function GetPlayerJointFromPart(character,playerPart)
	local playerJoint = nil
	if character then
		local joint = GetJoint(playerPart)
		if joint then
			playerJoint = character:WaitForChild(playerPart)[joint]
		end
	end
	return playerJoint
end

--Shoulder welds so we can make them move towards the steering wheel
local function WeldUpperArmsForSteering(character)
	local upperArmParts = {"RightUpperArm","LeftUpperArm"}
	local weld
	local welds = {}
	if character then
		for _,armPart in pairs(upperArmParts) do
			local joint = GetPlayerJointFromPart(character,armPart)
			weld = Instance.new("Weld")
			weld.Part0 = joint.Part0
			weld.Part1 = joint.Part1
			weld.C0 = joint.C0
			weld.C1 = joint.C1
			weld.Parent = joint.Parent
			weld.Name = joint.Name.."_weldTemp"
			table.insert(welds,weld)
		end
	end
	return welds
end

--Elbow welds to keep the arms straight
local function WeldLowerArmsForSteering(character)
	local lowerArmParts = {"RightLowerArm","LeftLowerArm"}
	local weld
	local welds ={}
	for _,armPart in pairs(lowerArmParts) do
		local joint = GetPlayerJointFromPart(character,armPart)
		weld = Instance.new("Weld")
		weld.Part0 = joint.Part1
		weld.Part1 = character[armPart]
		weld.C0 = CFrame.new(0, -joint.Part1.Size.Y/2, 0)
		weld.Name = joint.Name.."_weldTemp"
		weld.Parent = character[armPart]
		table.insert(welds,weld)
	end
	return welds
end

--[[
	helper function to joint two tables,
	currently assumes the tables are flat
--]]
local function ConcatTableToTable(destinationTable,sourceTable)
	for _,i in ipairs(sourceTable) do
		table.insert(destinationTable,i)
	end
	return destinationTable
end

local function WeldArmsForSteering(character)
	local welds = {}	
	ConcatTableToTable(welds,WeldUpperArmsForSteering(character))
	ConcatTableToTable(welds,WeldLowerArmsForSteering(character))
	for _,i in pairs(welds) do
		print("CARSEAT - just welded: ",i)
	end
	return welds
end

--Torso Weld is to keep the player leaning forward so the arms reach the steering wheel
local function WeldTorsoForSteering(character)
	local characterLeanAngle = math.rad(0) --How many degrees the character is leaning forward
	local torsoWeld = Instance.new("Weld")
	torsoWeld.Part0 = character.UpperTorso.Waist.Part0
	torsoWeld.Part1 = character.UpperTorso.Waist.Part1
	torsoWeld.C0 = character.UpperTorso.Waist.C0 *CFrame.Angles(-characterLeanAngle, 0, 0)
	torsoWeld.C1 = character.UpperTorso.Waist.C1
	torsoWeld.Name = "WaistWeld_weldTemp"
	torsoWeld.Parent = character.UpperTorso
	return torsoWeld
end

--[[
Neck weld is to make the player's head look up, 
since the character is leaning forward the head is naturally looking down, 
so we need to adjust
--]]
local function WeldNeckForSteering(character)
	local neckAngle = math.rad(-15) --How many degrees the head is looking up/down
	local neckWeld = Instance.new("Weld")
	neckWeld.Part0 = character.Head.Neck.Part0
	neckWeld.Part1 = character.Head.Neck.Part1
	neckWeld.C0 = character.Head.Neck.C0 *CFrame.Angles(-neckAngle, 0, 0)
	neckWeld.C1 = character.Head.Neck.C1
	neckWeld.Name = "NeckWeld_weldTemp"
	neckWeld.Parent = character.Head
	return neckWeld
end

--[[
	Create Welds identical to Motor6Ds to override animations.
	Here we are calling the individual functions per weld type/location
--]]
function ConfigureJointsForSteering(character)
	local tempWelds = {}
	local armWelds = WeldArmsForSteering(character)
	local torsoWeld = WeldTorsoForSteering(character)
	local neckWeld = WeldNeckForSteering(character)
	ConcatTableToTable(tempWelds,armWelds)
	table.insert(tempWelds,torsoWeld)
	table.insert(tempWelds,neckWeld)
	return tempWelds
end

-- Return a weld if found, return nil if not
local function GetCharacterWeld(character,weldQuery)
	local weld = character:FindFirstChild(weldQuery,true)
	if weld:IsA("Weld") then
		return weld
	end
	return nil
end

-- Get temp joints from player and destroy them
local function CleanupJointsFromSteering(character)
	local bodyParts = character:GetDescendants()
	for _,part in pairs(bodyParts) do
		if string.match(part.Name,"_weldTemp") and part:IsA("Weld") then
			print("found: ",part.Name)
			part:Destroy()
		end
	end
end

--[[
	Keep hands on steering wheel while player is in car.
	Could likely be refactored further, into testable code
--]]
local function HandsOnWheel(character,handLeft,handRight)
	-- get required welds
	local rightShoulder = character:FindFirstChild("RightShoulder_weldTemp", true)
	local leftShoulder = character:FindFirstChild("LeftShoulder_weldTemp", true)
	
	--Find the positions on the wheel we want the hand to point to
	local rightTargetPosition = CFrame.new(rightShoulder.Part0.CFrame:PointToObjectSpace(handRight.WorldPosition)) *(rightShoulder.C0 - rightShoulder.C0.p)
	local leftTargetPosition = CFrame.new(leftShoulder.Part0.CFrame:PointToObjectSpace(handLeft.WorldPosition)) *(leftShoulder.C0 - leftShoulder.C0.p)

	--[[
	Adjust target positions so that the center of the hand will point to target position, 
	otherwise the top left part of the hand will point to target position
	--]]
	rightTargetPosition = rightTargetPosition *CFrame.new(-rightShoulder.Part1.Size.X/2, -rightShoulder.Part1.Size.Y/2, 0)
	--leftTargetPosition = leftTargetPosition *CFrame.new(leftShoulder.Part1.Size.X/2, -leftShoulder.Part1.Size.Y/2, 0)
	leftTargetPosition = leftTargetPosition *CFrame.new(leftShoulder.Part1.Size.X/2, leftShoulder.Part1.Size.Y/2, 0)

	--Rotate arms 90 degrees downward so that the end of the hand will point to target position
	local rightC0 = CFrame.new(rightShoulder.C0.p, rightTargetPosition.p) *CFrame.Angles(math.rad(90), 0, 0)
	local leftC0 = CFrame.new(leftShoulder.C0.p, leftTargetPosition.p) *CFrame.Angles(math.rad(90), 0, 0)

	rightShoulder.C0 = rightC0
	leftShoulder.C0 =  leftC0
end

--[[
when someone sits in the vehicle seat,
detect the character and run all the things
--]]
vehicleSeat:GetPropertyChangedSignal("Occupant"):Connect(function()
	if vehicleSeat.Occupant ~= nil then
		print("CARSEAT - someone sat")
		character = vehicleSeat.Occupant.Parent
		print("CARSEAT - it was: ",character)
		
		--[[
			ensure the vehicle steering wheel contains two attachments,
			labelled LEFT_ARM and RIGHT_ARM
		--]]
		local handLeft = vehicleSeat.Parent:FindFirstChild("LEFT_ARM", true)
		local handRight = vehicleSeat.Parent:FindFirstChild("RIGHT_ARM", true)
		
		--[[
			Configure joints for stearing and add the joints to the tempJoints table
		--]]
		ConcatTableToTable(tempJoints,ConfigureJointsForSteering(character))

		steppedEvent = RunService.Heartbeat:Connect(function()
			HandsOnWheel(character,handLeft,handRight)
		end)
	else
		print("CARSEAT - dude leaving: ",character)
		for _,i in pairs(tempJoints) do
			print("CARSEAT - have to destroy: ",i)
		end
		--[[
			CleanupJointsFromSteering could be changed to feed in the tempJoints table here,
			and just loop through the table looking for the joint name.
			You'd use the helper function GetCharacterWeld to locate the weld
		--]]
		CleanupJointsFromSteering(character)
		-- reset variables to empty them
		character = nil
		tempJoints = {}
		-- terminate event
		if steppedEvent then
			steppedEvent:Disconnect()
		end
	end
end)

I know I’m very late, really really really REALLY sorry, but how do I load the file into a game?

Is your script compatible with R15?

Can you do a R6 version for that tho?

This version is pretty delayed

Yes it is… changed it back to be client-side for myself. Down-side is only the client see the movement.

Splitting out into functions rather than one big long script, however, is always more preferable.