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)