[Tutorial] The complete guide: how to make a train

Apr 14, 2013
662
344
Introduction

The question "how do I make a train?" is asked a lot. Everybody wants to have a train in their map, because dynamic elements are cool, and trains are the best dynamic element in tf2. (that's a fact, not an opinion). From ctf_well and cp_freight, to cp_snowplow and koth_trainsawlazer- Many of the maps in the tf2 world feature trains. In this tutorial I'll go over creating a train and fitting it to your needs. This tutorial is very long, because I'll give in depth information and tips about all the entities we're using, so that you'll understand exactly what you're doing, and can later use that understanding to create your own train, or better- a whole new setup for something completely different! (I used it to make an elevator for my sd_ map, for example).

Freight3.png


What entities we'll be using?

Let's begin!

Now we'll build a basic train, like featured in cp_well, or cp_freight. It would only go in a straight line. We'll go later over curved tracks and how those work.
First, make a prop_dynamic. This would be your train model (the train you'll see in-game).
Set it's world model to a train engine. You can make more prop dynamic entities that would be the train cars, if you want to.

:cordonenable: IMPORTANT! :cordonenable: The train must be built facing from left to right. However, that won't define it's in-game position, but rather it's orientation relative to the tracks we'll soon place. So you don't need to rotate your entire map. You just need to build the tracks in the right direction (we'll soon talk about how to do that), and the train- facing right. Don't forget this tip!

tut1_zpswbfm3d1a.png


Next, make a brush, textured nodraw. it's height should be from the ground to the top of the lowest train part (car or engine), the length should be the train's lenght, and it's width- the train width. Place it over the train props.
Tie the brush to an entity (ctrl+t). Set the entity to "func_tracktrain". That's our train entity! Give it a name, like "train" (w/o quotations). Set all the train prop_dynamic props, so their "parent" property would be set to train. Child entities move together with their parents, so now all the train props would move together with the func_tracktrain entity.
We now need to set some properties for the func_tracktrain. double click it, and set the render mode to "don't render" (that way it would be invisible and won't cast shadows on your beautiful train). Set "max speed" to whatever speed you want your train to have (trains tend to have only one speed- the max speed. "Those drivers never lift their foot off the pedal, I'm telling ya"). I personally use 800. Set damage on crush to a very high number (like 99999).
Set the Move sound property to something like ambient\train.wav or ambient\slow_train.wav, or whatever you want. (in the sound browser window, you can search by putting keywords like "train" in the "filter" textbox, just like in the texture browser).
Then head to the "flags" tab and check "fixed orientation" and "Is unblockable by player".

tut2_zpssba2glmc.png


:helpers: NOTE :helpers: You will need to change another property, if you use a different sized track prop than the usual track props in tf2. You see, by default, the tracks in tf2 are 4 hammer units high (and by track I mean the track prop, not the actual path_track). If yours have a different height, you'll need to change the "Height above track" property to that height.

And now add a brush in front of the train, textured with trigger texture, and tie it to an entity (ctrl+t). Set the entity class to trigger_hurt, set the damage type to train. That would set the kill icon to the train one.
Trainico.png

Set the "damage" property to a very big number (like 9999999). That would kill all players touching the front end of the train, no matter how much they are healed.
Parent the trigger to the func_tracktrain. Then open the flags tab, and check the flag "Everything (not including physics debris)". That would be used so that the trigger also damages engineer machinery. (And I don't remember for sure, but I think it also has to do with killing Ubered players)

tut3_zpstkyalzqk.png


Now the train needs to move. For that, we'll have to define a track. Place an entity, and set it's class to "path_track". This is an entity used to define the path of objects that move, in the source engine (like rd_ bots, payload carts, etc.) The way path tracks work, is by "connecting" them. Two connected path tracks would form a path. Payloads might have hundreds of path_tracks, but we'll only need two for now.
The path_track needs to be located where you want the train to start, but you must remember that the train is not a real life train, it's a brush entity. Unlike real life trains, brush entity trains move from their origin. (marked with a little red circle when selected, by default it's set to the center of the brush). So you must think where you want the origin to be when the train starts, Not the front!!! Take that into consideration when planning the train area in hammer. Also remember to put the path_track in the proper height- the height should be the func_tracktrain origin's height.

:cordonenable: IMPORTANT! :cordonenable: The train's start and end spots should be well hidden in your map. (In a tunnel like cp_freight, or a warehouse like cp_well. It's up to you). If it won't be hidden, people would see your train suddenly appear, or disappear.

tut4_zpsc807f3pp.png


Set the path track name to something simple, but for ease of usage later, make it end with "_01". (I'll name mine "path_train_01"). Now, all the default properties are good for us.

Now, here's a cool trick- If you shift+drag your path track, hammer would automatically assume that the new path track is the next path_track for this path, and would connect them without you having to do a thing! It would also automatically rename the new path track, so that's cool too. Shift+drag a new path_track, and place it where you want the train to end it's track. (Same as before- end=where the origin should be at the end)

After you've done that, open the 1st path_track properties, and in the "flags" tab, check "teleport to THIS path_track". That marks this path_track as the first.
Open the func_tracktrain properties again, and set the "first stop target" to the first path_track name. That tells the func_tracktrain what is the path_track path it should follow.

And now for the final setup before we are finished with the basic train components- we'll need to set an output for the train to go back to the first path_track, after it reached the end. (Otherwise it would just stay at the edge of the path, and won't go again)
Open the last path_track's properties window, select the "outputs" tab, and make a new output-
OnPass (meaning "when the train passes") train [Name of your train] TeleportToPathTrack (self explanatory) Path_train_01 [Name of 1st path track]

tut5_zps5qlws70l.png


That's it! you have a train! but the train won't yet work, because you never told it when to start. And here's a great part- Everything can cause a train to start! (And I mean everything). From picking up an intel or capping a point, to stepping into a trigger volume. But for staying consistent with ctf_well, cp_freight and the rest of the maps, we'll use a timer.

Place an entity somewhere in your map (it doesn't matter where, but I reccomend putting it near the train for ease of access). Set it's class to logic_timer. Give it a name ("timer"). Now you'll need to chose how you want the timer to work- using random times, or pre-set times. You will set the "Use random times" property accordingly. Then you'll set the refire interval if you chose pre-set times, or the minimum/maximum time if you chose random times. (times are in seconds obviously).

tut6_zpsdmoce9i3.png


Then, on the outputs tab, add an output to the timer.
OnTimer (meaning "when the timer triggers") train [Name of your train] StartForward (Start driving in the path track direction)
That will tell the train to start driving. You can trigger this output (StartForward) from wherever you want- as I said, it can be a control point, a trigger, or anything else.
You may want to set the delay of the output to a few seconds, if you want to add the hazard bells sound to activate before the train comes, like in ctf_well. In that case, you'll need to add an ambient_generic entity (the entity used to play sound files in your map) with a name ("train_bells") and the sound property set to the ringing bells sound (ambient/railroad_bells.wav), and then add an output from the timer,
OnTimer (When timer triggers) train_bells (name of ambient_generic) PlaySound
To make sure that the bells don't play forever, add another output:
OnTimer (When timer triggers) train_bells (name of ambient_generic) StopSound
Make sure you set the delay properly. The delay means how much time the output will wait between the time it was triggered (in this case-triggered by the clock) until it gets "sent". You want to give the train something around ~1 second and the bells StopSound something around ~5 seconds.
tut7_zpseaaeadat.png


Finishing up

Well, from here, you just need to compile. (F9). Make sure everything works, and enjoy your train!

533DB7CDEF762032E1309632B4E5DA6C2F714083


And just to sum this tutorial up, you've just learned how to make elevators, lifts, moving spaceships, or anything that needs to move around in the map.
Yes that's right. ITS THE SAME BASIC SETUP FOR ALL MOVING THINGS. (not including doors, which are done differently)
You just need to change the things you parent to the func_tracktrain (an elevator or a spaceship, in this case), and the trigger for the movement starting (a trigger brush for the elevator, a logic_auto for the spaceship).
What are you waiting for? Open hammer and fill your map with dynamic moving things!

If you have a question, a suggestion, If you found a mistake, or suddenly got an urge to show off your amazing train, be sure to add those in the comments below.

Useful train links:
Freyja's lazy train prefab. Also featuring gates, which I didn't go over.
Another tutorial, which is more Half-Life oriented, and less complete IMO, but still relevant
 
Last edited:
Apr 14, 2013
662
344
Part II- more advanced train creations and options

Now that you know how to make a basic train, it's time to talk about other things you can do.
This post might update with more subjects as time passes.

Train gates

If you ever played a map featuring trains, you must have seen the gates. The gates are usually yellow, and open when the train passes, after a few seconds the lights blink.

What entities would we be needing?
  • prop dynamic- as we said earlier- it's a more advanced version of prop_static, with more properties
  • prop_static
  • func_door_rotating- a simple solution for rotation of stuff, mostly props
  • env_lightglow- a cool effect, that mimics the glow around a light, but doesn't actually emit light. It is used mostly together with a light entity that does emit light (light_spot or something). Can be replaced with env_sprite.
  • light- a simple light entity
How would it work?

Let's build the gates. Make 2 prop_dynamic entities, and set their model to a gate door model. cp_well, cp_freight and most other train maps use this model: models/props_trainyard/train_gate002.mdl.
Remember to name your gate props, I named mine traingate_prop_start_left and traingate_prop_start_right.
Next, place them inside a gate frame. We'll use train_gate002's frame: models/props_trainyard/train_gate001b.mdl.

p2_2_zpsqcrukxve.png



Now let's build the actual doors. those would be two brushes painted with a nodraw texture, placed over the 2 gate props, and sized to the exact size of the gate props- from the frame's side where the hinges connect, to the edge of the gate. Repeat the following steps for both brushes:
Tie the brush to an entity (ctrl+t), set the entity type to func_door_rotating
Set the following properties:
  • name- something simple that you'll remember. I chose traingate_start_left and traingate_start_right
  • render mode- don't render (even though it's nodraw, you still don't want it to be rendered, as nodraw can cast shadows
  • speed- something around 300, but this is really up to you. Remember you want it to be rather fast, so don't go too low, under 200.
  • distance- This is how much the door rotates, and to what direction. Remember hammer counts angles counter-clockwise. So the door that should rotate 90 degrees clockwise, would be set with the value of -90, and the door that should rotate counter-clockwise would have a value of 90.
That's it. Now go back to the props, and set their parents to the proper func_door's name, so that the props would move with the actual (invisible) door.

We now have The entrance gate. If you want, you can build another gate for the end of the track, and repeat all the steps we would soon describe, for that gate too. But for the sake of simplicity, I would only refer to the entrance gate in the tutorial from now on.

The gates are purely cosmetic, they don't actually block the train's movement. So if we don't want the train to accidentally pass through the gates, we need to make sure the timing is perfect.
Now there are two ways to achieve that: the pre-calculated way (requires more math and physics), and the path_track way (requires more compiles, trials and errors)

We would use our old logic_timer to trigger the gates. Previously, the train triggered 1 second after the bells started playing. Now we would make the timing more accurate, we might need to change this timing.
First, you need to do some math. Pull out your calculators everyone, school is nearly here! time to practice some mathematics!
You need to find the distance between the front end of the train to the gate. You can get this length by taking the distance between the front of the train, to the train's origin (remember- the train moves from it's origin!) and reducing it from the distance from the 1st path_track to the gate.
Here's an example- the length from 1st path track the gate, is 1024 hammer units, the distance from the front of the train to the origin is 256 hammer units. 1024-256=768. So the distance from the front of the train to the gate (after the train teleports to the path track) would be 768 hammer units.
p2_1_zpsrue2sxji.png

Now use that distance, and your train speed, to calculate the time it would take the train to get to the gate
For you physicians, this is what I mean:
speed%20triangle.jpg

For those who have no idea what I'm talking about, I'll explain- you can find the time, by dividing the distance we found out, by the velocity of the train. So if the distance is 768 hammer units, and the train speed property is 800 units per second, the time it would take is 0.96 sec which is ≈1 second

In this case, we would want the gates to finish opening before a second passes. So we would trigger the gates to open after half a second. The gates should open after about 1 quarter of a second, more or less, which leaves just about enough time for the train to get close to the gates. In other words- set the gates to open about half a second before the train reaches the gates
OnTimer (meaning "when the timer triggers") traingate_start* [This is a very cool trick I'll explain in a second!!] Open (open the gate) with a delay of 0.5 (or whatever number you find right).
Compile the map, see if you like the results, and if not, slightly adjust timings and speed of the gate door_rotatings.

:eye1:COOL TRICK!!!:eye1: When using outputs to trigger two or more entities, who have the same name, but a different suffix (like setup_gate_1 and setup_gate_2), you can use the * character instead of the suffix (like setup_gate*) and by that, you call all entities with that name, no matter what suffix they have, to trigger! cool, isn't it?
:cordonenable:WARNING!:cordonenable: Make sure that you keep track of your gate names when using this trick, so you don't trigger stuff you don't want to trigger.

Place (using shift+drag) a new path_track (which you copied from the 1st path_track).
I recommend you place it 512 units from the gate (on the out-of-the-map side of the gate- so that the train passes through it before it reaches the gate).
Add to the path track an output:
OnPass (meaning "when the train passes") traingate_start* [This is a very cool trick I'll explain in a second!!] Open (open the gate)

:eye1:COOL TRICK!!!:eye1: When using outputs to trigger two or more entities, who have the same name, but a different suffix (like setup_gate_1 and setup_gate_2), you can use the * character instead of the suffix (like setup_gate*) and by that, you call all entities with that name, no matter what suffix they have, to trigger! cool, isn't it?
:cordonenable:WARNING!:cordonenable: Make sure that you keep track of your gate names when using this trick, so you don't trigger stuff you don't want to trigger.

Now compile the map, and look at the timing. For making it easier to do, I recommend temporarily changing the logic_timer min and max timer intervals to much smaller numbers, so you won't have to wait so long until the train arrives.
Adjust the distance between the new path_track to the gates. Bigger distance=more time for the gates to open. Smaller distance=less time.
.

Soon I'll add the final part, talking about the flashing warning lights, but now I really have to go.
 
Last edited:

Kraken

Few more zeros and ones for the site to proccess
Dec 21, 2014
430
121
I always wondered how the trains were made. Also you should mention about the trains own collision(in the train model) and possibly tell how to shut it down (for people who don't know about it.
 

henke37

aa
Sep 23, 2011
2,075
515
Trains don't move from the center. They move from their origin, which just happens to default to the center.

And I think that it would be nice to know how to do the blinking lights.
 
Apr 14, 2013
662
344
Trains don't move from the center. They move from their origin, which just happens to default to the center.

And I think that it would be nice to know how to do the blinking lights.

Thanks. I corrected that now.
I might add later a part about blinking lights. But not now, I've done enough for today...
 

ChuckSpurgeon

L2: Junior Member
Jan 6, 2014
52
10
This is TERRIFIC! Thanks so much for this!
 

Vel0city

func_fish
aa
Dec 6, 2014
1,947
1,589
One other thing though: if you want the train sound (not the bells, the actual train sound riding and sounding its horn) to actually play from the train you should put your func_tracktrain as the sound location in the ambient_generic. This way the train brush becomes the speaker, otherwise your train sound will only play from the ambient_generic's location.
 

YM

LVL100 YM
aa
Dec 5, 2007
7,158
6,079
Important for trains that go around curves:
The origin is the BACK axle.
The "distance between wheels" is the distance from the back axle to the front axle.

The back wheel stays on the track exactly, when the front wheel passes a path_track with a new angle, it starts rotating towards the new angle. It doesn't stay exactly on the path, it just rotates at a fixed speed to the new angle. The result means the front axle drifts a little, but as long as your angles aren't too extreme it's a reasonable approximation.
 
Apr 14, 2013
662
344
One other thing though: if you want the train sound (not the bells, the actual train sound riding and sounding its horn) to actually play from the train you should put your func_tracktrain as the sound location in the ambient_generic. This way the train brush becomes the speaker, otherwise your train sound will only play from the ambient_generic's location.

That's true, but I find it more intuitive to set the train sound property of the func_tracktrain to the train sound. That's why I wrote that method instead of the ambient_generic method, but they are both valid ways to add the train sound.

Important for trains that go around curves:
The origin is the BACK axle.
The "distance between wheels" is the distance from the back axle to the front axle.

The back wheel stays on the track exactly, when the front wheel passes a path_track with a new angle, it starts rotating towards the new angle. It doesn't stay exactly on the path, it just rotates at a fixed speed to the new angle. The result means the front axle drifts a little, but as long as your angles aren't too extreme it's a reasonable approximation.

Thanks for the information! When I eventually get to writing the expansion I'll add that information as well
 
Last edited:
Apr 14, 2013
662
344
When I put in the output to teleport the train back to the first path_track, it doesnt recognize TeleportToPathTrack and declares the output unfinished.

Make sure you typed everything correctly
It should say
OnPass [name of the func_tracktrain] TeleportToPathTrack [name of first path_track]
 

YM

LVL100 YM
aa
Dec 5, 2007
7,158
6,079
The train needs to face east. That's not the direction of the path, tracks, that's the direction the func_track train is pointing when you make it in hammer.

The picture from the first post perfectly illustrates this:


:cordonenable: IMPORTANT! :cordonenable: The train must be built facing from left to right. However, that won't define it's in-game position, but rather it's orientation relative to the tracks we'll soon place. So you don't need to rotate your entire map. You just need to build the tracks in the right direction (we'll soon talk about how to do that), and the train- facing right. Don't forget this tip!

tut1_zpswbfm3d1a.png
 

Portalman4

L1: Registered
Jun 4, 2015
22
1
3q5BVnX

Everything is set up correctly (as far as I know). I am stealing/inspired by Crash's transportation train idea.
 

YM

LVL100 YM
aa
Dec 5, 2007
7,158
6,079
Add a path_track before the start (and link it to the starting one) the train will never use this path track but it should correct the angle it uses when it teleports to the first track.
 

YM

LVL100 YM
aa
Dec 5, 2007
7,158
6,079
By fixed speed I mean the speed is constant over the turn. I am not sure if the rotation speed changes with train speed, I'd guess it does but I'd have to check
 

Jawesome99

L1: Registered
Aug 29, 2015
5
1
Umm, help?
705379724F29B3A394DE67BFD3E13B9E7D2E06A3
I followed this guide, step by step. Both trains are in their ending locations but don't ever teleport back. The train bells also keep playing infinitly.
 
Apr 14, 2013
662
344
Make sure that the last path_track has this output, as mentioned in the tutorial
OnPass (meaning "when the train passes") train [Name of your train] TeleportToPathTrack (self explanatory)Path_train_01 [Name of 1st path track]

As for the infinite noise, you'll need something to trigger it to stop. I haven't mentioned it (I would fix it in a second), I'll recommend adding another output for that logic_timer I called "timer", which would be set as follows:
OnTimer (When timer triggers) train_bells (name of ambient_generic) StopSound (with a delay of 5-6 seconds)