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 bottom2
- Name the one at the top
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.
- One, we will name
In our
DropTrack
track, we will create aPointToPoint2
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
tofalse
as we don't want this to be a full circuit track.
In our
DropTrackTrain
track, we will create aMovingSection
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
ObjectValue
s for ourRootTrack
andSectionTrack
.- 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
toDropTrack
. - Set our
SectionTrack
to the track which connects to the top of our drop track.
- Why ObjectValues?
SectionOffsetType
IntValue- Set to
0
as we will just using a position at the top of the drop track for our shape.
- Set to
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).
- Since our track isn't a "coaster" track, physics calculations using
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
- Binding syncs time and state between a parent AnimationPlayer and it's "children"
- Why do we need to do this?
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