TrackWorx is a system to create any kind of game that needs things to run on rails.
Whether it’s a gentle monorail ride, a mine-cart obstacle course, a roller coaster ride, a puzzle game or something more exotic, TrackWorx can help you. I’ll keep this guide up-to-date as new features/updates/questions come along.
Head over to TrackWorx Showcase to see it in action!
Transform: Anything that can has a position on a track and knows how to move along it.
Vehicle: A transform that knows how to move around the track with acceleration/braking, taking any coupled vehicles with it.
Track Section: A piece of track placed in the world that a transform can attach to. Typically a single mesh entity (eg MonorailStraight).
Blocker: A transform that prevents vehicles from moving through it. Typically used for signals.
Rider: A player that’s riding in a vehicle that has a seat.
Seat: A location on a vehicle that a rider can sit.
Prototype: The setup for a new type of track or a new type of vehicle.
Stitching: The process that combines multiple track sections together to make a complete run of track.
Module: A script that extends the behaviour of an existing script (eg a track section module, or a vehicle module).
Points: A set of points; a junction on a railway, also known as a switch, switchtrack or turnout.
Track is made up of individual sections, each section is an entity associated with a path. Sections can be anything you like, but typically they’ll be a track mesh (eg MonorailStraight) - you could make your own track pieces out of voxels, or even have invisible track (eg for a plane ride). TrackWorx comes with track setups for the Monorail and Minecart meshes.
The TrackWorx layout engine stitches track sections together to make a complete track. Vehicles can be placed on a track and made to move; they will automatically follow the track.
Track sections provide events for when a vehicle enters or exits them; you could use these to trigger signals, scenics, explosions, change to a special tunnel camera etc.
You can move track sections around (eg to make a turntable, switch/points/turnout or even a lift), or spawn them dynamically - you just tell the layout engine that you’ve made a change, and it’ll update the stitching.
When laying track I would recommend using the Grid Snap feature in Advanced Mode to make it easier to line them up. Set to 4 for the monorail and 5 for minecart.
A few modules are provided to extend the functionality of track section. Just drop the scripts on any TWTrackSection entity.
- TWTrackSectionModuleBoost can accelerate and/or brake any vehicle on a track section. You can use this to create rollercoaster lift hills, braking runs, or a “launch” start.
- TWTrackSectionModuleVehicleStartStop has events for when a vehicle starts or stops on that track section
- TWTrackBlockSectionModuleLiftHill is similar to TWTrackSectionModuleBoost, but forces a fixed speed on an entire train to simulate the chain lift hill of a roller coaster. This module operates primarily in a new Block Section style - add it to the parent of all your lift hill tracks to have them act together. This is more efficient, but also allows them all to animate simultaneously. However it can still be used on an individual track section if needed.
Vehicles can also be anything you like - TrackWorx comes with vehicle setups for Monorail and Minecart, but you could create your own out of voxels, or use the planes, cars, toilets etc. You can couple multiple vehicles together to make a train, or let them run solo. Vehicles can be placed in the world, or spawned dynamically. They automatically snap to the closest track.
Vehicles optionally support rail physics, so they’ll accelerate down hills, slowdown on inclines (and possibly roll back), and reduce to a halt via friction.
Vehicle modules extend the functionality of vehicles. Just drop the scripts on any TWVehicle entity.
Two driver modules are provided. You don’t need to use either, but they make it easy to create powered vehicles (rather than those than just use rail physics)
- TWVehicleModuleAutoDriver will automatically drive its vehicle along the track, keeping to a designated speed and stopping for blockers.
- TWVehicleModuleManualDriver makes it easy for a player to take control, with optional safeties to keep to a max speed and stop for blockers.
- TWVehicleModuleRandomRouting will have the vehicle choose a random track to follow when it arrives at a set of points.
- TWVehicleModuleAnimation can play a movement animation on the vehicle (eg to make the wheels turn), updating the animation speed as the vehicle accelerates or slows.
- TWVehicleModuleSnapToTrack (experimental) allows vehicles that are ragdolling (ie have physics enabled) to snap to any track that they come close to. You could use this to create a “jump” mechanic for mine carts, where you want it to automatically snap back onto the track when the jump finishes.
- TWVehicleModuleShuttle switches a vehicle’s direction when it reaches the end of the track.
- TWVehicleModuleSound updates a sound entity’s volume and pitch based on the speed of the vehicle. Use this to create engine noise. Experimental - this can now also play track joint noises (ie clickety clack) as the vehicle travels.
A blocker can be created out of any
TWTransform. They can be placed anywhere on the track, turned on or off or moved around - useful for signals, the end of station platforms, or other vehicles on the line. Running out of track is also an automatic blocker, as is track that’s been set inactive. Vehicles with a TWVehicleModuleAutoDriver or TWVehicleModuleManualDriver will detect blockers ahead that are within their braking distance - once one is detected the driver will apply the brakes to stop in the right place.
The simplest blocker is a locator with a TWTransform - set the Track and T properties to attach it to a track piece, then tick Blocker, and untick TriggerTrackEvents (to stop it triggering TWTrackSection’s onEnter etc events that you normally only want for vehicles). You can enable/disable the blocker during play by changing calling SetBlocked or SetUnblocked on the transform.
You can place seat(s) on a vehicle, and allow riders (players) to “sit” in them. Riders can have a TWRiderCamera on their player template that they use to provide a suitable viewpoint on the vehicle. Optionally, each seat can specify a template to spawn onto any rider to enable special behaviours or UI, eg driver controls, speedometer etc.
Each track section can decide whether or not a rider can disembark while their vehicle is on it, and where the rider should teleport to if they do. This is useful in stations, where you can setup spawn points along the platform that closely match the track positions. Or on a rollercoaster you can prevent the rider from getting out whereever they like and only allow it in the station.
The TrackWorx core system is a single entity that you place in the root of your world. You only need one, no matter how many tracks you have in the world. Place all of your vehicle prototypes and track prototypes as children of the system. You can use the template (TWSystem) provided as it’s already setup with the three scripts it needs:
- TWSystem manages the prototypes - place prototypes as children of the system entity
- TWLayout manages the stitching of tracks and initialisation of vehicles
- TWHelpers is a collection of helper functions that make it easier to do things to vehicles from track events.
Add the templates TWMonorailPrototypes and/or TWMineCartPrototypes as children of the TWSystem to use those track/vehicle setups.
Every script and template that comes with TrackWorx is prefixed TW for easy searching. Script functions and variables that begin with an underscore _ are intended for internal use, and may change or not exist in future updates. On the off-chance that I do need to make breaking changes, I’ll point them out here and in the update notes.
You can use the templates provided with TrackWorx, or simply add a TWTrackSection script to any supported mesh entity - TrackWorx will detect the mesh used (eg MonorailStraight or MonorailCurve) and set it up correctly. Put the mesh down so that the ends line-up, and that’s it!
The stitching will stitch tracks that end in the same location (with a tiny tolerance) and whose ends are at approximately the same angle.
Vehicles are an entity with TWTransform and TWVehicle scripts on them - there are templates provided with these added for you. TrackWorx will detect any vehicles and snap them to the closest track.
Place down multiple vehicles and link them together by setting up prevVehicle and nextVehicle properties on each vehicle entity. Designate one vehicle as ‘isDriving’ - this is the one that controls the movement of the whole train - the rest will follow it along the rail.
Track types are created as “prototypes”. Each prototype tells TrackWorx System about the shape of that track piece type. TrackWorx comes with prototypes for Monorail and Minecart assets, but you can create new ones if you want a special track system for your game (eg voxel tracks).
Create a new mesh or voxel entity as a child of TrackWorx System, add a TWTrackSectionPrototype script and one of the TWPath* scripts - TWPathLine is a straight line, and TWPathSpline is a bezier spline for curves/slopes. Configure the path to match the shape of the mesh - you can use TWTrackSectionDebug to help visualise the path during preview by placing it on a mesh child of a TWTrackSection or the TWTrackSectionPrototype - the mesh “Bench” is a good choice for this.
By default TrackWorx System will use the mesh asset/voxel mesh asset on the entity as the ‘key’ to match it up with track sections you place in the world that use the same mesh. You can also set the key manually as a string if you’re doing something exotic that doesn’t use meshes (eg invisible sky track, or a boat course), but it’s advisable to use an invisible voxel mesh anyway to help with lining sections up so they stitch together.
Vehicles are very similar, but us TWVehiclePrototype instead. The only thing to configure is the “couplerOffset”, which tells the vehicle how far the ends of the vehicle are from its centre. This is used to adjust the distance between vehicles in a train.
You can call Ragdoll() on any TWTransform to detach it from its rail, and enable physics while preserving its existing momentum.
If you make a ‘Y’ shape with track, there are a few ways to indicate which route a vehicle should take:
Physically move the track (eg with TWMovableTrack) to disconnect all paths except one this is how real trains work!
Let the track section decide by weighing up the various options:
- First it’ll disregard any tracks that are not ‘active’
- From the remaining options it’ll choose the track with the highest ‘switchPriority’
- If it’s still undecided, it’ll use the first track in the list - this can be unreliable, so you don’t want to let it do this if possible.
- Dynamic Routing (experimental)
After static routing has decided on a default route, if there were multiple options available you can choose if you want to override it.
Implement a function GetBestTrackLink(options, default, result) in a script on the vehicle (TWVehicle has a similar GetDefaultBestTrackLink that you can use as an example). From the options available, choose the one you want, eg based on a previous route-finding calculation, and set “result.link = bestOption”. If you leave result.link as nil, it’ll go the default route. Be warned that this function may be called a lot more often than you think (eg when checking the line ahead for blockers), so you should keep it as simple as possible, and also ensure it returns the same result on server & client.
TWVehicleModuleRandomRouting uses Dynamic Routing to pick a random track to follow.
Add a locator with a TWMovableTrack script, and add the track piece(s) as children of it. The MovableTrack script can be setup with a list of position & rotation “stops”, and then ChangeToStop(stopNum) will move the locator to the chosen position & rotation. It’ll automatically restitch any track that’s a child of the “restitchTrack” entities as the track moves.
The world tree for a turntable with one movable track piece (“turntableStraight”) and four entry points might be setup like this:
- turntablePivot ← TWMovableTrack
The entry tracks are added to the TWMovableTrack as restitchTracks. They are optional, but it is much much more efficient to use them, otherwise the layout engine will try to restitch every piece of track in the world, rather than just those provided. It’ll restitch any children of the tracks selected, so you could group them all together and use that as the restitchTrack for simplicity.
If you’re in a situation where you just need a one-of-a-kind custom track section, you can add a TWPathLine or TWPathSpline directly on the track section it and configure the path just for that one piece. The path will override whatever’s setup on the prototype track. You can configure the TWPathSpline’s control points with locator entities rather than the vector properties (it’s less efficient, but it can be easier to visualise). You can also make the path move if you set the handle type to MovableLocators; this will be quite expensive and I’ve not found a reason to use it yet, but it seems like it could have a funky use!
There’s a lot I’d like to add, and I intend to expand upon the basic TrackWorx system. This is a rough list of things I’d like to do, but didn’t quite make it in to the first release:
- Easier coupling in the editor - in the same way that vehicles snap to their closest track, vehicles could also snap together
- More TWPath* types - eg arc, b-spline
- Track path roll - track paths can only run “flat” - you can’t make them barrel roll. Could be useful for planes or camera splines
- Track section roll - track sections only work propertly when they have zero roll - you can’t tilt them at an angle or place them upside-down. Vehicles will follow them, but they don’t rotate properly.
The TrackWorx package contains the bare minimum you need to get going and make vehicles go round a track. The showcase has a lot of ‘glue’ scripts to make the attractions fully functional (eg the signals, user control scripts/widgets), and I may release some of the reusable components separately to the core package.
What will you create?!