Preface
I’ve been experimenting with raycasting and CFraming recently in order to become more well-rounded, and I was interested in creating platforms that Characters could automatically move with. During this process, I stumbled across a topic that tyridge77 and badcc provided insight on, as they’ve implemented trains in the respective games they work on (The Wild West & Jailbreak).
Utilizing the information provided there, including placebo code from a couple of posts, I put together something that I was satisfied with until realizing that Characters were unable to leave the moving platform in the direction it’s moving in when exceeding the Humanoid’s WalkSpeed.
Video Examples
Video 1 Description: The first video demonstrates the moving platforms in my testing place at various speeds. The yellow, blue, and red platforms are the only parts contained in the raycast whitelist; the translucent parts that my Character walks on are not included.
Video 1 Notes: I understand that the Character is unable to leave the platform because of how physics take over when the raycast does not find any whitelisted part to teleport the Character above. When the platform is slower than the Character, the platform cannot keep up with the Character, and vice versa. However, I am unsure of how I can accurately compensate for this without accidentally teleporting the Character when it’s not necessary.
Video 2 Description: In the second video, I went into Jailbreak and The Wild West in order to run similar tests that I did in my place. While I’m unsure of the speed that the trains were moving at, it’s clear that both of the trains exceeded the WalkSpeed of the Humanoid when walking or sprinting.
Video 2 Notes: The behavior of Jailbreak’s raycasting is similar to what was demonstrated in my test place – this is especially clear at the very front of the train. However, I was baffled by The Wild West because it allowed the Character to smoothly exit the boundaries of the train in the direction it’s moving in, even though the train is moving faster than the Character is able to.
Code & Test Place
The code for the raycasting & teleporting of the Character along with the Tweening of the moving platforms can be found below. While I understand how the code works, take note that I am not adept with raycasting & CFraming. Because this is the first project I’ve worked on that revolves around these concepts, there are likely many nuances that I’ll be unaware of until I garner more experience.
Raycasting Test Place (3.20.2021).rbxl (27.7 KB)
Character Raycasting code
The code I have below was derived/based on this topic in regards to moving platforms.
(Extra lines of code were omitted from this, including the Raycast Visualization)
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local lastObjectRaycasted -- Last part that was hit by a raycast
local lastObjectCFrame -- The part's CFrame at the time
local Function
local Function2
local Whitelist = {workspace.MovableItems.MovingParts}
Function = RunService.Heartbeat:Connect(function()
local Character = player.Character or player.CharacterAdded:Wait()
local HumanoidRootPart = player.Character.HumanoidRootPart
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = Whitelist
raycastParams.FilterType = Enum.RaycastFilterType.Whitelist
-- I plan on making this more efficient in the future
local raycastParams2 = RaycastParams.new()
raycastParams2.FilterDescendantsInstances = {lastObjectRaycasted}
raycastParams2.FilterType = Enum.RaycastFilterType.Whitelist
-- Two separate RaycastResults in case a previous Instance is still stored
local RaycastResult = workspace:Raycast(HumanoidRootPart.CFrame.Position, Vector3.new(0,-20,0), raycastParams)
local lastObjectRaycastedRaycastResult = workspace:Raycast(HumanoidRootPart.CFrame.Position, Vector3.new(0,-20,0), raycastParams2)
local updatedInfo
-- If Character is still above the "lastObjectRaycasted", then...
if lastObjectRaycasted and lastObjectRaycastedRaycastResult then
updatedInfo = lastObjectRaycastedRaycastResult -- Stores Raycast Result into updatedInfo
else -- Otherwise, updatedInfo is set to the Raycast Result that detects any descendant of the "MovingParts" folder
updatedInfo = RaycastResult
lastObjectRaycasted = nil
end
if updatedInfo then -- If a raycast found a part in the whitelist...
local raycastInstance = updatedInfo.Instance
if not lastObjectCFrame then -- Prevents relativeDistance from erroring if no value is stored already
lastObjectCFrame = raycastInstance.CFrame
end
local currentCFrame
if lastObjectRaycasted and raycastInstance ~= lastObjectRaycasted then
currentCFrame = lastObjectRaycasted.CFrame -- Updates currentCFrame to the last object that was stored if it doesn't align with the current raycastInstance
else
currentCFrame = raycastInstance.CFrame
end
local relativeDistance = currentCFrame * lastObjectCFrame:Inverse() -- Finds the relativeDistance from the CFrame of the current Instance & the previous one that was stored
HumanoidRootPart.CFrame = relativeDistance * HumanoidRootPart.CFrame
lastObjectRaycasted = raycastInstance
lastObjectCFrame = raycastInstance.CFrame
else -- If raycast didn't return a result, the lastObjectCFrame will be cleared
lastObjectCFrame = nil
end
if not Function2 then
Function2 = player.Character.Humanoid.Died:Connect(function()
Function:Disconnect()
Function2:Disconnect()
end)
end
end)
Moving Platform code
-- This tweens every descendant that is a BasePart in the MovableItems folder
local TweenService = game:GetService("TweenService")
local MovingPlatformTween = TweenInfo.new(
20,
--[[
20 seconds = 20 studs a second
25 seconds = 16 studs a second
400/15 seconds = 15 studs a second
]]--
Enum.EasingStyle.Linear,
Enum.EasingDirection.In,
5,
true,
20
)
for _,Item in ipairs(workspace.MovableItems:GetDescendants()) do
if Item:IsA("BasePart") then
local Goal = {
CFrame = Item.CFrame + Vector3.new(0, 0, -400)
}
local MoveItem = TweenService:Create(Item, MovingPlatformTween, Goal)
MoveItem:Play()
end
end
I appreciate you taking the time to read this!