Mapping for dummies 2: hands-on tutorials. Basic entity dive-in.

[ATTACH=full]5209[/ATTACH]

With the previous two-part tutorial, we have learned to use the editor to a bare minimum. We toyed around with the different tools available and made our attempt at designing a reasonable scene. Nothing, however, was happened within it.

In this tutorial we will introduce the basics of working with entities, remembering the usage of point entities and coupling it with brush entities. We will also cover entity I/O briefly, and we will lay the basic foundations of what you need to build interesting maps in the Source Engine. For this purpose, I have designed a simple dev textured map that we will use as our base to create a few entities. Download the version that is not solved, entity_tutorial.zip (containing our entity_tutorial.vmf) here or in the attachments below.

When you load the map in Hammer, your first sight should be of a room with a few spawnpoints, a rock brick wall and a big orange brush textured with the ‘Trigger’ material. For reference, it should look like this:

[ATTACH=full]5211[/ATTACH]

We will make sure that we are using the correct version by checking the entities actually in the map. For this, we will use the entity report menu. This menu is very helpful when your mapping projects start to grow, so it is a good idea to know that it exists and to use it if you start losing your mind at times. To access it, click on the ‘Map’ menu in the top bar and then open up the ‘Entity report…’. You should get something like:

[ATTACH=full]5210[/ATTACH]

Silly me! I forgot to mention that we also have a light placed, specifically an environmental one. We didn’t cover this type of lighting in particular, so you might want to change the values and study where does the light come from. When you do, you will notice that it has to do with the ‘SKYBOX’ texture on top of us, from which environmental light is casted when the map is compiled. We also see a ‘func_buyzone’ and, if we click on that name in the report, we will see that the brush textured with ‘Trigger’ will light up: in that area players will be able to buy weapons.

Now that we have some grasp of this initial area, we should start getting our hands dirty. We will create our first brush entity ever, selecting the brown-brick textured wall. With it selected, we want to make it become an entity. For this, we can use the menu on the side:

[ATTACH=full]5212[/ATTACH]

Or we can use the Ctrl + T hotkey. For your own productivity’s sake, you should probably get accustomed to using this hotkey. Now a dialog will show, exactly the same as the one we have already seen when dealing with the properties of point entities. As entity class, we notice it says ‘func_detail’, a few of which were listed in the entity report. We will put our mouse cursor there and change that to ‘func_breakable’. A bunch of properties will then pop up:

[ATTACH=full]5213[/ATTACH]

We will only change two of them, as pictured. In particular, I set the strength to be 100 and the material type to be cinderblock. You can try variations on this, compiling every time, to see the effect they have on the way the entity behaves. In fact, even if you’re only following the tutorial step by step, you should compile at this point and hop into the map. Can you break the wall after a few shoots? Don’t set too high of a strength or it’ll be too hard!

After this very simple starter we reach a little area with two different paths and brushes blocking them. You should also notice a button-looking thing in the middle of the room. We will set it up so that both of them are doors that open after 5 seconds of pressing the button. Furthermore, when the button is pressed, a console message will appear – does this remind you of some particular gamemode?

We will go in the order of the actions we want. First, we need the entity that will let us print out console messages. For that, we will create a point entity (you can remember how in the previous tutorial!), whose class will be point_servercommand in particular. We will give this entity a name, I usually go for ‘Console’ (without the 's) since I mostly use it for… Console to talk!

[ATTACH=full]5214[/ATTACH]

As you’ll notice, the position in which you place this entity is meaningless. Now, we will start with the doors. We want to have an example of each door type we can build: either sliding or rotating. As such, we will turn the white blocker into a sliding door and the grey blocker into a rotating door.
In the same vein as with our previous func_breakable, we will select the brush and turn it into an entity. Use the hotkey this time, at least if you didn’t before. The classname will be func_door this time around:

[ATTACH=full]5215[/ATTACH]
We will set the flags up and configure a few of the properties. In particular, we will give it a name: SlidingDoor, a start sound for when it starts moving (I decided to use a random, funny sounding one), the delay before reset (-1 in our case, so it stays open) and the lip (that is, the amount of units which the door will not fully go into; 32 in our case). When this is all set up, we have a door that doesn't open when you touch it (like a default one) and will slide all the way but up to 32 units.

In the same vein, we will set up our rotating door. We will give it a name: RotatingDoor, and a few properties (as specified in the next picture). Since rotation works counter-clockwise and we’d like the door to fit in the little space in the wall it has ahead, we will also set the flags to go into the reverse direction:

[ATTACH=full]5216[/ATTACH]

In Source, entities rotate with respect to their origin. For entities, the origin is usually initialized at their center. Because of this, the door will rotate in a very odd way if we don’t change it around. In this particular case, we will move the origin to the corner so that rotation happens with respect to it. To move the origin, we just need to move the little circular handle (in the top view), as shown in the next image:

[ATTACH=full]5226[/ATTACH]

With this done, we have properly set up the three entities. We will now turn the button brush into a func_button, and set its flags so it doesn’t move. Furthermore, we will set up the input/output. For that, we will click on the ‘output’ tab in the entity information view, and add a few fields, as shown below:

[ATTACH=full]5217[/ATTACH]
Entity I/O (input/output) is very simple in Source and, once you get accustomed to it, you realize how easy it is to build complex situations with it. We have set it up exactly as we wanted: when the button is pressed, that is, [B]OnPressed[/B], we first send a [B]Command[/B] through the [B]Console[/B] entity, in particular a [B]say[/B] command with some message. With a 5 second delay, the [B]RotatingDoor[/B] and [B]SlidingDoor[/B] entities receive an input to [B]Open[/B].

Compile the map and check if everything goes as expected. Make sure you configured the entity attributes appropiately. When it does, move onto the next area, where both paths merge.

[ATTACH=full]5218[/ATTACH]

You shall see an area that will remind you of an sliderace map (you can see an example of those kinds of maps in ze_random, at the end of the skyscrapper level).

In this area we will introduce triggers. Triggers are volumetric brush entities that can fire I/O or have an effect on players, pushing, hurting or setting gravity on them, among other things. We will create our first sliderace area by using a push trigger. For that, create a brush covering the whole race track with the ‘Trigger’ material. Although trigger entities don’t need to use this material, it is a good practice to do so for your own sanity, since it will let you distinguish what’s going on easily. When you’re done, you should have something like this:

[ATTACH=full]5219[/ATTACH]
Again, turn the brush into an entity and set the class to be 'trigger_push'. We will need to adjust the force it applies, as well as the direction. Furthermore, push triggers have a small catch: they don't affect players by default. To solve that, go into the flags section and tick the 'clients' option. Clients are players in Source Engine lingo.

When that’s done, we will need to adjust the direction of the applied push. Since most of the time entities both look and apply their effects in the 2D top view plane, Source is conceived so this is easy to do. If we look at the top view, we want the push to go ‘upwards’ in it, so we will use the direction gizmo marked in red in the next picture:

[ATTACH=full]5220[/ATTACH]
With it, we point in the direction we want to be going (in the top view) and we're done. Now, we want to make sure that we can make it to the other side of the big hole that rests there, but we also want to die if we fall into the pit. As an exercise to the reader, you will have to set up three different triggers here: one to set low gravity if you're coming from the sliderace, one to set normal gravity if you either fall or are coming from the opposite direction, and one to kill those that fall.

For this task, you will need to use trigger_gravity and trigger_hurt. If you want two different brushes to be associated to the same brush entity (i.e., a breakable with several parts or the two parts of the gravity trigger meant to set default gravity), hold down the control (Ctrl) key while selecting the brushes before you turn them into entities. The result of this small challenge should look similar to this:

[ATTACH=full]5221[/ATTACH]

With the trigger that sets low gravity being selected, the two-brush trigger that sets default gravity adjacent to it and the hurt trigger in the bottom of them pit.

Finally, we will add one trigger to go back to the beginning after we win. That way, we will be able to cycle through our map and make sure everything is alright. For this we will need to set up a teleport destination. We will use a point entity, which we will name, placed at the area where we first started.

[ATTACH=full]5222[/ATTACH]
Afterwards, we will place a small trigger in the end area. This should be fairly easy to you by this point. You will have to select the destination and, since we want to face in the direction of our trigger to make sure it doesn't feel confusing or nauseating, we will also enable that option:
[ATTACH=full]5224[/ATTACH]

If we followed all the steps correctly, our small course-like map will be finished! You can make sure if you did by checking the solved version, which you will find here or in the attachments below.


CHALLENGES (growing difficulty):

  • We have left out one very important kind of trigger: trigger_multiple. It is typically used to manage trigger-based I/O. Set triggers on each area that display through console where you just entered. Notice that you can keep using the same point_servercommand entity, there is no need to make multiple instances of it.
  • Our course map is too short and not very challenging. Make an area in which you have to jump on rotating cylinders to advance. You should use the func_rotating entity, and it will be advisable to place a trigger_hurt below so you die if you fall. You will have to configure the properties on your own, for which you might use the Valve Dev Wiki (some information in it might be out of date or plain wrong, beware!)
  • Through parenting and using func_tracktrain, make a killer train that wanders our little map. It must include the train (a prop_dynamic entity), the tracktrain (which will move according to some preset tracks, path_tracks) and a trigger_hurt. Use the wiki and combine this entities to build our killer train. For simplicity, you can have it running in circles in the spawn area, so you can test it easily. Figure out how to connect everything.
  • BONUS: Use logic entities (logic_auto, logic_case, …) alongside with sound entities (ambient_generic) to give the killer train randomized killing sounds. Can you make it start and stop at random too? (check logic_timer!) How would you make it breakable so you can ‘Kill’ the killer train?
3 Likes

Reserved for the actual mapping challenges. We will have some challenges (with rewards, if Morell agrees) to create working prototypes for certain kinds of maps. I don’t expect the prototypes to be fully playable (and they probably won’t be tested to the servers if you only do what I propose), but they will give you very good grasps of what it is to actually develop something playable (and how enjoyable that is!)

1 Like

Nice, it’s starting to be hard now ^^
Next week i follow your tutorial, just for pleasure :wink:

Hello envi, very good tutorial, I’m currently finishing the challenges, but one thing I didn’t understand is why we need a trigger_gravity to set normal gravity.

Thank you for the tutorial,

Try removing the triggers that set gravity back to normal. If you die with low gravity enabled, what happens next round if you respawn?

I already asked for this/stressed it in the previous tutorial, but as you go through the challenges it is a good idea to post what you’ve done here for others to see. Not only will you encourage others to try (people are challenged by pictures more than by words), but you will also be able to get constructive feedback and learn from others.

I will create a thread for the ‘freeform’ mapping challenges soon, with prizes going to those that participate (1 free supporter month for entries that fulfill the minimum requirements laid for any of the freeform challenges). Stay tuned for that and tell anybody that is just getting into mapping to get some attention going.

The way I have it setup there is no way of dying inside the trigger_gravity,

and after the gap the gravity becomes normal again.

The gravity multiplier keeps being applied after you leave the trigger and even if you die. Try jumping after you land (and using a lower gravity value, like 0.1). Gravity can only become normal again if you make it so.

I just checked to make sure it still works like this in CS:GO, and it does. This is also the reason why Wanderers had issues some time ago with people having low gravity after finishing the map: the mapper didn’t reset gravity after the end of the round, so players kept the lower gravity.

Thank you for the answer, and I went back to test the map again and it worked as I said. If any client is inside the trigger he gets 400 gravity (800*.5) and whenever he leaves the trigger it resets back to 800, maybe I changed some setting by mistake that originates this but I understand why you used two trigger_gravity. But now the map has another problem. At the end, where there is the teleport (my teleport destination and the size of the trigger is equal to yours) I get teleported back ±20 blocks the first time and if i go into the teleporter again, and only after going the second time do I get teleported to my EndTeleport (this happened every time, I was only getting teleported to the EndTeleport after already going in once). (The info_teleport_destination entity only has it’s name modified as well as where it is facing)

Sorry for the trouble,

Post your compiled BSP here so I can see exactly what you mean, as it seems like a weird issue. Unless you have multiple entities named ‘EndTeleport’, perhaps mistakenly, the teleport should only move the player to the teleport destination.

It is not finished yet, I needed a rest from studying for my finals and decided to do your tutorial, I hope I can finish the challenges when my finals are over. The right side has a hole where I will add more things, so either noclip or go left (I already have the rotating entities left in case you want to check them out).

I gave it a quick look with EntSpy and, as I mentioned in my previous posts, you have two entities with the same name. Teleports can have multiple targets with the same targetname so entities teleported get spread through an area (pretty useful to, for instance, move zombies into different hold entrances in a map), but in this case it led to your issue.

Renamed the trigger_multiple and all is working fine now, as you said it would.
Thank you for the help,