Skip to main content

Drop Track

Setup

Setup other tracks

  • Enough length so that the train will fit in this length
  • Tracks overlap each other vertically in each length

Drop Track Setup

Setup Points for the Drop Track "Track"

  • Get where you want your train to stop at the top of a drop track

    • We will use the top for most of the drop track positioning since that is where our train enters.
    • If we were doing an elevator lift or element that enters from the bottom, we would use that instead.
  • Create a Part

    • Position and Rotate part at the position
  • Duplicate part, and move it down along the Y Axis until it meets the bottom track

    • Assuming track is level, it should be easy as moving it by X amount
    • If not, well this will get more complex
  • Group both parts

    • Name the one at the top 1 and the one at the bottom 2
  • In your RideData for your ride, we will create two new tracks.

    • One, we will name DropTrack, which will be used by the moving track that we will setup later.
    • The other, we will name DropTrackTrain. This track will be used exclusively by our train model.
  • In our DropTrack track, we will create a PointToPoint2 track.

    • Docs
    • Our parts that we positioned, grouped, and numbered earlier will be our Points that we will use for the track.
    • Will we set IsCircuited to false as we don't want this to be a full circuit track.
  • In our DropTrackTrain track, we will create a MovingSection track.

    • Docs
    • Info about MovingSectionTrack here
      • Combines two tracks so that the "shape" of one track can be used as an offset from another track.
      • Mainly works with trains as trains bend with the track
    • We will use ObjectValues for our RootTrack and SectionTrack.
      • Why ObjectValues?
        • Makes setting it up simpler
        • We don't have to maintain other tracks inside of our MovingSection track, we can just edit the others.
      • Set our RootTrack to DropTrack.
      • Set our SectionTrack to the track which connects to the top of our drop track.
    • SectionOffsetType IntValue
      • Set to 0 as we will just using a position at the top of the drop track for our shape.
    • SectionPosition NumberValue
      • Get the position on the track that most accurately matches where the train will stop.
      • Keep track of this as this is what we will be using as the position our train will stop at

Drop Track Model Setup

Assumes it's at the top.

Group your drop track model together.

Duplicate the top point from our Drop Track Points and parent it into our drop track model.

Set this as the model's PrimaryPart

Recorder Program Setup

If you haven't already, initialize our tracks in the recorder program.

local tracks = {}
tracks[mainCFrameTrack.name] = mainCFrameTrack
tracks[dropTrackTrack.name] = dropTrackTrack
tracks[dropTrackTrainTrack.name] = dropTrackTrainTrack
  • Record our train's animation to our drop track

Record Drop Track Animation

  • Record the animation for our drop track

    • Not the train dropping, but the drop track dropping.
    • We will then integrate it into our train
  • Use recordAndExportSegment since we want to

local dropTrackAnimationRecorder = AnimationRecorder.new(animationsFolder, tracks)
local dropTrackPhysicsState = createPhysicsState()

dropTrackAnimationRecorder:recordAndExportSegment("DropTrackAnimation", function(keyframeRecorder, triggerRecorder)
local START_POSITION = 0
local END_POSITION = dropTrackTrack.length
local TWEEN_START_OFFSET = 2.5

local TRACK = dropTrackTrack
local TRACK_SECTIONS_DATA = dropTrackTrackSectionsData

local PHYSICS_STATE = dropTrackPhysicsState

local startKeyframe = KeyframeBuilder.new()
:withPosition(START_POSITION)
:withTrack(TRACK.name)
:withTrackSpeed(PHYSICS_STATE.currentSpeed)
:withModelDirection(true)
:withLength(RECORD_INTERVAL)
:withTransitionType(TransitionType.Linear)
:build()
:unwrap()

keyframeRecorder:push(startKeyframe)

-- simulate physics
local physicsSimulationState = physicsSimulatorV1.SimulationStateBuilder.new()
:withCFrameTrack(TRACK)
:withTrackSectionsData(TRACK_SECTIONS_DATA)
:withCurrentPosition(keyframeRecorder:getLastPosition())
:withModelDirection(true)
:withClampPositionToTrackLength(true)
:withGravity(GRAVITY)
:withFriction(FRICTION)
:withTimeInterval(TIME_INTERVAL)
:withStopPosition(END_POSITION - TWEEN_START_OFFSET, "Stop", false)
:withStopWhenDirectionChanges() -- stop immediately when valleying
:withUpdateType(PhysicsUpdateType.ConstantAngle(math.rad(90)))
:build() -- builds the simulation state
:unwrap()

local physicsRecorder, _simulationStopType = physicsSimulatorV1.simulate(physicsSimulationState, PHYSICS_STATE)

keyframeRecorder:append(physicsRecorder)

if TWEEN_START_OFFSET ~= 0 then
-- spring to a stop
local springSimulationState = springSimulator.SimulationStateBuilder.new()
:withCFrameTrack(TRACK)
:withClampPositionToTrackLength(true)
:withCurrentPosition(keyframeRecorder:getLastPosition())
:withAngularFrequency(1)
:withDampingRatio(1)
:withGoal(END_POSITION)
:withMaxSpeed(keyframeRecorder:getLast().trackSpeed)
:build()
:unwrap()

local springRecorder = springSimulator.simulate(springSimulationState, PHYSICS_STATE)

keyframeRecorder:append(springRecorder)
end

-- record passed triggers
local passedTriggerRecorder = GetPassedTriggersBuilder.new()
:withKeyframeRecorder(keyframeRecorder)
:withCFrameTrack(TRACK)
--:withTrigger("Trigger1", 100)
--:withTrigger("Trigger2", 200)
:build()
:unwrap()

triggerRecorder:combine(passedTriggerRecorder)
end)
  • Start position is 0, stop position is track length
  • :withUpdateType(PhysicsUpdateType.ConstantAngle(math.rad(90)))
    • Since our track isn't a "coaster" track, physics calculations using .WithPositionOffsets for it will not be accurate.
      • It uses the vertical angle at given points of the track to calculate how the train will accelerate with gravity.
      • Our DropTrack is not angled properly for that (nor do we want to edit it to do so).
    • .ConstantAngle() is a solution to this
      • Assumes track is always that angle
      • For our drop track, we will assume it is vertical (or 90 degrees).

Record Train Drop Track Animation

local cloneKeyframe = api.keyframe.clone

animationRecorder:recordSegment(function(keyframeRecorder, triggerRecorder)
triggerRecorder:add("DropTrackStart", keyframeRecorder:getLength())

for _, keyframe in ipairs(dropTrackAnimationRecorder:getKeyframes()) do
local clonedKeyframe = cloneKeyframe(keyframe)
clonedKeyframe.track = dropTrackTrainTrack.name
keyframeRecorder:push(clonedKeyframe)
end

triggerRecorder:add("DropTrackStop", keyframeRecorder:getLength())
end, true)
  • Cloning the animation by cloning the keyframes
  • Changing the track property of each keyframe to be the one we made for the train

RidePlayer_Server Setup

local dropTrackAnimPlayer = rideManager:getAnimationPlayer("DropTrack")
dropTrackAnimPlayer:setAnimation("DropTrackAnimation") -- set start animation

local trainAnimPlayers = rideManager:getAnimationPlayersWithTags("Train")
for _, animPlayer in ipairs(trainAnimPlayers) do
animPlayer:getTrigger("DropTrackStart"):Connect(function()
local currentAnim = rideManager:getAnimation(animPlayer.animationPlayer.currentAnimationName)
local dropTrackTriggerPosition = currentAnim:getFirstTrigger("DropTrackStart").position

dropTrackAnimPlayer:bindToAnimationPlayerTime(animPlayer, -dropTrackTriggerPosition)
dropTrackAnimPlayer:setAnimation("DropTrackAnimation")
dropTrackAnimPlayer:play()
end)

animPlayer:getTrigger("DropTrackStop"):Connect(function()
dropTrackAnimPlayer:stop()
dropTrackAnimPlayer:unbindFromAnimationPlayer()

task.wait(10) -- wait until train has left track

dropTrackAnimPlayer:setAnimation("DropTrackAnimation")
end)
end
  • Binding to AnimationPlayers

    • Why do we need to do this?
      • Binding syncs time and state between a parent AnimationPlayer and it's "children"
        • When we bind, we are basically setting the parent of the AnimationPlayer we called :bind on to the target animationPlayer
      • Basically playing the animation we cloned and modified earlier at the same time
  • Finally, when the animation completes, a.k.a. where we marked "DropTrackEnd", we unbind

Record Drop Track Reset

local dropTrackAnimPlayer = rideManager:getAnimationPlayer("DropTrack")
dropTrackAnimPlayer:setAnimation("DropTrackAnimation") -- set start animation

local trainAnimPlayers = rideManager:getAnimationPlayersWithTags("Train")
for _, animPlayer in ipairs(trainAnimPlayers) do
animPlayer:getTrigger("DropTrackStart"):Connect(function()
local currentAnim = rideManager:getAnimation(animPlayer.animationPlayer.currentAnimationName)
local dropTrackTriggerPosition = currentAnim:getFirstTrigger("DropTrackStart").position

dropTrackAnimPlayer:bindToAnimationPlayerTime(animPlayer, -dropTrackTriggerPosition)
dropTrackAnimPlayer:setAnimation("DropTrackAnimation")
dropTrackAnimPlayer:play()
end)

animPlayer:getTrigger("DropTrackStop"):Connect(function()
dropTrackAnimPlayer:stop()
dropTrackAnimPlayer:unbindFromAnimationPlayer()

task.wait(8) -- wait until train has left track

dropTrackAnimPlayer:setAnimation("DropTrackReturnAnimation")
dropTrackAnimPlayer:play()
end)
end