Hmm… I thought of something right before you replied; When looking through the scripts to change the default attribute values to fit my game, I noticed you enable PlatformStand on the Humanoid; Maybe sitting overrides that, so I’d have to re-enable PlatformStand upon exiting a seat.
No problem; I saw your post in the topic about Roblox’s upcoming new character controller and from what I’ve tested of it, I feel your script does basically everything I’d like Roblox’s official one to do.
I’ll keep your movement script in my game since it’s an improvement over the default to me.
One last question, though. My game uses a custom Animate script replacement; How easy would it be to convert it to a compatible ModuleScript to use in place of this movement system’s AnimateModule? Apologies if this is getting too off-topic.
My custom Animate script
-- Player Animation LocalScript replacement, initially based on GnomeCode's custom character animation script but more fleshed-out.
-- SERVICES
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- REFERENCES
local LocalPlayer = Players.LocalPlayer
local AnimationRemote = ReplicatedStorage.Remotes.ManageAnimations
local rCurrentSeat = nil -- Chair LocalPlayer is currently sitting in.
local rSeatWeld = nil -- The seat's "SeatWeld" weld, created by sitting on it.
local SeatWeldPlayer = nil -- Player object reference, gotten using GetPlayerFromCharacter on the weld's second part.
while not LocalPlayer:GetAttribute("Loaded_LoadScreen") do
task.wait(0.5)
end
-- Animation references. (Don't reference their Value or the animations won't be changable mid-game!)
local rStandAnim = script:WaitForChild("StandAnim",3)
local rSitPose = script:WaitForChild("SitPose",3)
local rToolHoldPose = script:WaitForChild("ToolHoldPose",3)
local rRunAnim = script:WaitForChild("RunAnim",3)
local rClimbAnim = script:WaitForChild("ClimbAnim",3)
local rJumpAnim = script:WaitForChild("JumpAnim",3)
local rFallAnim = script:WaitForChild("FallAnim",3)
local rLandAnim = script:WaitForChild("LandAnim",3)
local LocalHumanoid = LocalPlayer.Character.Humanoid
-- Constantly monitor this player's Character for a tool; If they carry a tool with a handle, show the "holding tool" pose.
-- TODO: Re-submit the "holding tool pose" animation with Movement priority, so it just "works" with any sitting animation.
task.spawn(function()
local PlayerTool = nil
local PlayerToolHandle = nil
local DefaultToolPoseActive = false
while LocalPlayer.Character.Humanoid do
PlayerTool = LocalPlayer.Character:FindFirstChildOfClass("Tool")
if PlayerTool then
PlayerToolHandle = PlayerTool:FindFirstChild("Handle") -- Find the tool's Handle and make sure it's a descendant of BasePart.
if PlayerToolHandle and PlayerToolHandle:IsA("BasePart") then
if not DefaultToolPoseActive then
DefaultToolPoseActive = true
AnimationRemote.PlayAnimation:FireServer(nil, rToolHoldPose.Value, nil, 0.333, 21) -- Show the tool-holding pose.
end
end
elseif DefaultToolPoseActive then -- If the player isn't holding a tool, lower the player's arm.
DefaultToolPoseActive = false
AnimationRemote.StopAnimation:FireServer(nil, 21, nil, nil, 0.25)
end
task.wait()
end
end)
local CurrentSpeedAnimation = 0
LocalHumanoid.Running:Connect(function(speed)
if speed > 0 then -- If running, stop any standing animations then play the running loop.
AnimationRemote.StopAnimation:FireServer(nil, 13, nil, nil, 0.25) -- Standing
AnimationRemote.StopAnimation:FireServer(nil, 14, nil, nil, 0.25) -- Idle
AnimationRemote.PlayAnimation:FireServer(nil, rRunAnim.Value, nil, 0.2, 16)
else -- Otherwise, do the opposite, and stop all movement-related animations before playing the standing loop.
AnimationRemote.StopAnimation:FireServer(nil, 15, nil, nil, 0.25) -- Walking
AnimationRemote.StopAnimation:FireServer(nil, 16, nil, nil, 0.25) -- Running
AnimationRemote.PlayAnimation:FireServer(nil, rStandAnim.Value, nil, 0.15, 13)
end
end)
-- Plays and stops player animations using ManageAnimations based on built-in state transitions.
LocalHumanoid.StateChanged:Connect(function(_oldState, _newState)
-- First, check what state the player was in before this, and stop that animation.
if _oldState == Enum.HumanoidStateType.Seated then
AnimationRemote.StopAnimation:FireServer(nil, 12, nil, nil, 0.175)
SeatWeldPlayer = nil -- Invalidate seat-related variables.
rCurrentSeat = nil
rSeatWeld = nil
elseif _oldState == Enum.HumanoidStateType.Running then -- Running is used for both standing AND moving, confusingly.
AnimationRemote.StopAnimation:FireServer(nil, 13, nil, nil, 0.25) -- Standing
AnimationRemote.StopAnimation:FireServer(nil, 14, nil, nil, 0.25) -- Idle
AnimationRemote.StopAnimation:FireServer(nil, 15, nil, nil, 0.25) -- Walking
AnimationRemote.StopAnimation:FireServer(nil, 16, nil, nil, 0.25) -- Running
AnimationRemote.StopAnimation:FireServer(nil, 19, nil, nil, 0.125) -- Landing
elseif _oldState == Enum.HumanoidStateType.Freefall then
AnimationRemote.StopAnimation:FireServer(nil, 18, nil, nil, 0.25) -- Falling
elseif _oldState == Enum.HumanoidStateType.Climbing then
AnimationRemote.StopAnimation:FireServer(nil, 20, nil, nil, 0.25) -- Climbing
end
if _newState == Enum.HumanoidStateType.Seated then
-- Now, this is a bit unnecessarily complex, but check the entire workspace looking for a SeatWeld that links to this player.
-- If so, check if the seat the player's in is supposed to use a custom sitting animation, showing that instead of the default
-- pose if it's requested.
print("Sitting started! Scanning workspace for SeatWeld instances.")
for _, _instance in ipairs(workspace:GetDescendants()) do
if _instance:IsA("Weld") and _instance.Name == "SeatWeld" then -- If this is a Weld named SeatWeld, try to grab its player.
print("Valid SeatWeld found at ", _instance:GetFullName(), ". Trying to find out what player 'owns' it.")
rCurrentSeat = _instance.Parent
rSeatWeld = _instance
SeatWeldPlayer = Players:GetPlayerFromCharacter(_instance.Part1.Parent)
print("Player associated with this weld should be", _instance.Part1.Parent:GetFullName(), ".")
if SeatWeldPlayer then
if SeatWeldPlayer.UserId == LocalPlayer.UserId then -- If the detected player has the same ID, we've found the Weld we're looking for!
print("Found you! " .. SeatWeldPlayer.DisplayName .. "'s sitting on " .. rCurrentSeat:GetFullName() .. "!")
break
else -- If it isn't, nil the variable so it isn't detected when this for-loop ends.
print(rCurrentSeat:GetFullName() .. " isn't being sat on by you.")
SeatWeldPlayer = nil
end
end
end
end
-- If this player's seat weld was found and this chair has a custom sitting animation, decide what animation to play based on that.
local rSitAnim = rCurrentSeat:FindFirstChild("SitAnimation")
if SeatWeldPlayer and rSitAnim then
local SitAnimList = {}
-- Check if this is an array (comma-separated list with no spaces) or single animation ID (integer).
-- Though this is always an array, it only contains a single index if this is an IntValue. StringValues use more indices, to be
-- chosen randomly when firing the RemoteEvent below.
if rSitAnim:IsA("IntValue") then SitAnimList[1] = rSitAnim.Value
elseif rSitAnim:IsA("StringValue") then
local temp_commaSeparatedAnimList = string.split(rSitAnim.Value,",")
-- Split the string value into parts, then add each ID to the animation list array. A random animation will be chosen to play.
for i = 1, #temp_commaSeparatedAnimList, 1 do
SitAnimList[i] = tonumber(temp_commaSeparatedAnimList[i])
end
end
AnimationRemote.PlayAnimation:FireServer(nil, SitAnimList[(math.random(0,255)%#SitAnimList)+1], nil, 0.25, 12) -- Pick an animation to use.
else
AnimationRemote.PlayAnimation:FireServer(nil, rSitPose.Value, nil, 0.25, 12) -- Show the default sitting pose.
end
elseif _newState == Enum.HumanoidStateType.Jumping then
AnimationRemote.PlayAnimation:FireServer(nil, rJumpAnim.Value, nil, 0, 17)
elseif _newState == Enum.HumanoidStateType.Freefall then
AnimationRemote.PlayAnimation:FireServer(nil, rFallAnim.Value, nil, 0.125, 18)
elseif _newState == Enum.HumanoidStateType.Landed then
AnimationRemote.PlayAnimation:FireServer(nil, rLandAnim.Value, nil, 0.075, 19)
end
end)