[In this article from Game Developer magazine's 2012 Career Guide, developer Ben Evans provides an in-depth tutorial on making your own role-playing game in Valve's Source Engine.]
If you want to get into game development, modding an existing game is a good way to start exploring design ideas and building prototypes without reinventing the wheel. In this tutorial, we're going to use Valve Software's Source Engine (the same game engine that powers Portal, Left 4 Dead, Half-Life, and Team Fortress 2) to prototype a mod with RPG-esque quests within Half-Life 2: Episode 2.
Note that this tutorial assumes you have a basic knowledge of Source SDK's Hammer Editor and Face Poser tools, so it's definitely an intermediate-level project. You should be comfortable with building and compiling maps, placing entities, and setting up triggers before following this tutorial, and if you want to add custom dialogue, you'll also need to know how build and save your own scenes in Face Poser. It's okay if you've never used the Source SDK tools before-there are a ton of free resources online that you can use to learn how to use them. Start with the Valve Developer Wiki which hosts a lot of very useful tutorials and references, and then check out design3 (disclaimer: I am a design3 contributor) for step-by-step instructional videos on the Source Engine. The minimum requirements to run these tools are the same as the requirements for Half-Life 2: Episode Two: a 1.7 GHz processor, 512MB RAM, DirectX 8.1 level graphics card, and Windows XP or newer. You will also need a microphone that plugs into your PC.
Before we get started, let's make sure we have everything we need. First, you'll need a copy of Half-Life 2: Episode Two installed. We're going to use this game because it's the most up-to-date and flexible Source engine single-player game available for modding. If you don't have Episode Two, Half-Life 2 will be compatible with most of the elements in this tutorial as well.
If you want to add custom dialogue to your RPG, you'll need some audio editing software. Audacity is a free, open-source audio-editing application that we're going to use to record your own dialogue for your mod.
We'll also need a Phoneme Extractor patch that will help make Face Poser work better with Windows 7/Vista. Download it and follow the installation instructions. Without that patch, the tool that associates dialogue text strings with sound files won't work.
Next, grab GCFScape. GCFScape allows us to open up the cache files that come with Steam games so we can view and extract the files within them. We'll be using it to extract audio for our mod.
If you haven't already, you'll need to install the Source SDK, so open up the Steam Client, go to the Tools tab, and install it. You will need your own map to work with; we'll refer to it as "main map." It can be anything from just a few simple rooms up to a wide-open landscape. Figure 1 is a screenshot of the level I use in this tutorial-if you'd like to use this level for the tutorial, go ahead and download it here.
Figure 1: Our sample level in the Hammer Editor
Right now our level is just a rough prototype that hasn't been fully textured or detailed yet. It's always best to start rough and refine the details as you finalize the level, so just come up with a basic level structure to start with for your own RPG. Also, remember to save regularly! Some of the new features in Hammer are not fully supported in the Episode Two version and may crash your editor.
Outlining the quest structure
For this tutorial, let's focus on the questing aspect of a role-playing game-building a character-progression system with experience points would have to be a completely separate guide. Our game level will have three non-player characters (NPCs) giving two quests each. We can use them to give quests, provide information, fight against the player, or just add life to the environment.
To keep our levels organized, let's use a naming convention prefix for our separate groups of entities, as explained in Figure 2.
Figure 2: This naming convention will make it easier for you to stay organized.
This will make finding the entities much easier, especially when working on individual quests. For example, adding the Level_prefix to entities that only affect the level keeps them all in one location in the list and out of the way of the quest entities.
Before we start making quests, we need to break them down into smaller components. Figure 3 is a table that lists the order in which the components are used in each quest. Note that "" and "" listings refer to the actual game logic within the quests and the trigger to activate the following quest (if there is one).
Figure 3: The anatomy of a quest.
Don't worry about figuring out all of this yet-we're only making a list of what we need right now. This is a list for just one quest, so you can tell we'll have a lot of entities to keep track of by the time we're done. To keep our viewports (and minds) uncluttered, we're going to use Visible Groups (VisGroups) to separate and hide each quest. VisGroups let you define groups of brushes and entities and hide/show them quickly. If the VisGroup is hidden when the map is compiled, everything in that group will be skipped in the process and won't appear in the game. This is very useful because it allows you to selectively view individual groups of objects by themselves so that you can focus on them without all the other stuff getting in the way. We'll start using them in the next section.
Build a quest template with instances
We're going to use a helpful tool known as instancing within the Hammer Editor. Instancing allows your main map to reference other maps and includes them in the build. We'll be making a "quest instance," which will be the template for every other quest we add later. Once we have our first instance, we can quickly duplicate it, place it in the map, and tweak its settings for each new quest. We can use an instance many times and change properties to make each one unique in order to save a lot of time during development. These instances will control the gameplay and logic of our RPG because they will contain the dialogue and character actions in the game. Unfortunately, Episode Two doesn't fully support instances, so we have to convert them into the level before compiling (which we'll cover when we're ready to compile at the end of this tutorial).
In order to set up our quest instance, we're going to need a new map file. Within Hammer, go to File, New to create a new map, and then save the map as "quest_instance" in a folder named "instances" within the same location as your main map.
Figure 4 shows how our quest_instance should look after we add all the necessary entities. We'll cover those necessary entities next.
Figure 4: Our basic quest instance template.
It's best to place your entities as close to the origin coordinates (0,0,0) as possible. We also want to have them sitting on top of the X/Y axis because when the instance is imported into the main map it will be oriented around the func_instance entity, which means you might lose sight of entities under the world if they are too low. You can check the location of your entities by using the top and side viewports to determine where they are. If needed, use the Selection Tool to place them above the X/Y axis. Our instance also gives all the entities inside it a prefix of our choice. We'll leave the triggers until the end of placing all the entities and leave some values out until later, so don't worry if it looks like we missed something.
We're going to need all of the entities mentioned in the list in Figure 3. Let's start by placing a NPC. With the Entity Tool selected, find npc_citizen on the drop-down list and place it at the origin coordinates. This will be our quest giver. Hit Alt + Enter to open his properties and make his name "giver." Also change Prevent picking up weapons? to Yes. Hit Apply and then go into his Flags panel and check Not Commandable, or else the quest giver will follow your player around the map. We'll also want to enable Don't drop weapons and Ignore player push (don't give way to player).
Next we're going to need a trigger that tells us when the player has approached our quest giver. Create a brush that is 64 units tall, 32 units wide, and 4 units thick, and then place it just in front of our NPC. We'll change its texture to "nodraw" so that it doesn't get rendered in the game. To change the texture, just select the brush, switch to the Toggle texture application tool, click the Browse button, filter for nodraw, double-click the nodraw texture, and click Apply. Then tie the trigger to an entity by pressing Ctrl + T and selecting func_button from the class list. Let's name it "trigger_start" and change the speed to 0. Hit Apply to save the changes we've just made.
Next, add a sprite that will alert the player that the NPC has a quest. Place an env_sprite entity above your NPC's head and open up the Object Properties window by hitting Alt + Enter. Name it "Sprite." We're also going to need to change the render mode so that the sprite renders properly in the game. Within the Object Properties window, locate the Render Mode option box and select World Space Glow from the drop-down menu. You can change the sprite image in this menu if you have one that you want to add to your game, but for now the standard sprite will do. Also, make sure that Start on is checked in the Flags tab.
Now we're going to add some dialogue. Find logic_choreographed_scene in the entity list and place it next to our NPC. We're going to need at least two of these for each quest, one for the start dialogue and one for the end dialogue, so copy and paste a second one above the first. Name one scene_start and the other scene_end. We won't add the actual dialogue just yet, but we'll cover that soon.
We will also need an audio notification that the player has accepted the quest and that it's begun. Create an ambient_generic entity, place it near your NPC, and name it sound_start. If you don't already have one you want to use, try using elevbell1.wav by typing "plats\elevbell1.wav" into the Sound Name field. Then go to the Flags tab and make sure all three options are checked.
After that we're going to add the text that explains the quest to the instance. Add two separate game_text entities and name them "text_start" and "text_end." The Hold Time for both will need to be a high value, like 99999, so that the message won't disappear until we kill that entity. We also want the text to be off to the side of the screen where it won't obstruct the player's view, so set the X and Y fields to 0.1 for both game_text entities. Text_start will be the message that displays during the quest and text_end will be the message that tells the player to return to the quest giver or a certain area.
We're going to need some relays to keep our triggers organized. A relay is an entity that activates other entities when it gets triggered. They're used by level designers to keep triggered events that occur at the same time together in one place, and they can easily be enabled and disabled. Create two logic_relay entities and place them near your NPC. Name them "relay_content" and "relay_complete." These will be the relays that activate all of the entities in the quest. The specifics of the actual quest are up to you to create for the player to complete. You'll need to set up a condition for the player to meet, like "get to a specific location" or "collect a certain number of objects." You can use entities such as math_counters, or brush entities such as trigger_once to check whether the condition has been met. If those entities find that the condition has been met, they will then trigger the relay_complete entity that we've set up. The relay_complete will then trigger all the entities needed to run the end of the quest. Make sure that "relay_complete" is triggered when your quest section is completed.
Now we need the audio notification for the end of the level. Add another ambient_generic entity and name it "sound_complete." Set it to a sound that indicates the quest has been completed, such as a bell or cash register. To keep it simple we'll use "plats\elevbell1.wav" again for now.
We'll need to add one more trigger in front of the NPC. This will be the same as the starting trigger we made earlier, so just copy and paste trigger_start. Name this new trigger "trigger_end" and make sure that Starts locked is checked in the Flags tab. Make sure both triggers are not overlapping each other at all and that trigger_start is in front by using your top and side viewports to place them. Also, make sure that they are in front of the NPC's hitbox, or else the game cannot tell if you are selecting the trigger or button. To check the hitbox location, just select your NPC and a yellow wireframe bounding box should appear around it.
Let's set up all of the triggers listed in Figure 5. For now, you don't have to worry about the triggers in [brackets] because we'll add them later. Select the listed entities, open their Object Properties, go to the Outputs tab, and add the designated outputs to each entity using the table below for reference. Be sure to hit Apply to save each output!
Figure 5: Our trigger listing.
That's how we set up our quest instance. Now we can take that instance and put it into different places around our map. Right now, our instance is essentially a basic quest template that we can easily modify and add new elements to.
Now that we have our instance set up, let's add copies of it at different points in our main map so we can start making our actual quests. Place a func_instance where you want your first quest to start. Open up its Object Properties window and under VMF Filename browse for your quest_instance map file. (Note that browsing can be a bit buggy, so you may have to type in the relative file path to "quest_instance" yourself.) You can now copy and paste these entities around your level. Go ahead and duplicate the instance until you've got as many quests as you want.
Select your first quest instance and collapse it by going to Instancing, Collapse, Selection. Hammer will then import all the entities and give them a prefix, normally "AutoInstance-". Everything will be selected when it has been collapsed, so we're going to organize them into VisGroups to make the quest-building process easier. Bring up the Object Properties window and click on the VisGroup tab. You should see something like Figure 6.
Figure 6: Organize Visible Groups in the Object Properties window.
Don't worry about the "sewer" and "main level" entries-I'm using them in my own map to break up the space, but you don't have to have them in yours. Hit the Edit groups button and you'll see the window shown in Figure 7.
Figure 7: Assign brushes and entities to Visible Groups.
From here you can create new VisGroups to use in your own map. Press the New group button and you'll see a new entry on the list. Click on it and then rename it using the name field to Quest_n, where n is the number of your quest. Click Close after you've done that and you should see the new VisGroup appear as a new option on the main list. Check the box to add entities to that group, hit Apply, and click Close. You can now hide/unhide that group by checking or unchecking it in the VisGroup control panel, as seen in Figure 8. Do this for the rest of your quests as well.
Figure 8: Toggling Visible Groups.
Importing your own audio
Our RPG is going to be extremely boring without any recorded dialogue to convey the story, so we're going to use Audacity to record your own dialogue. You must have a microphone for this part. Due to the way the Face Poser application works, you're going to want to record one sentence at a time. Recording audio in Audacity is easy. All you need to do is get your mic set up correctly, hit the red button to record, and save your audio as a .wav file when you're finished. If you have any problems, Audacity's help menus have lots of useful information. When you're done recording your dialogue, create a folder named "RPG" and save each sentence as a .wav file into it. Move that RPG folder to C:/Program Files/Steam/steamapps//half-life episode two/ep2/sounds/. It's important to save the audio into a folder one level above the sounds folder because the engine will not read the sound file otherwise.
Next, let's create a sound_script file so that the engine will be able to reference our sounds in the editors and the game. Launch GCFScape, go to File, Open, go over to your steamapps folder, and open up the "episode two content.gcf" file. After GCFScape has loaded that file, we'll need to find a file named "game_sounds_manifest.txt," which is located in the /steamapps//ep2/scripts/ folder. Right-click on the file and click Extract, and place the manifest within /steamapps//half-life 2 episode two/ep2/scripts. We're done with GCFScape, so you can close it now.
Go to the scripts folder and open up the game_sounds_manifest.txt file we just extracted. In this text file we can see all of the sound scripts that the game will use when it loads, and we need to add our own to this list. Under the last line of code, within the last bracket, add your own entry like Listing 1.
Now we're going to create the "rpg_sounds.txt" file that we are referencing, so save the manifest file and close it now.
In the scripts folder, right-click and select New, Text Document. Rename this new text file to "rpg_sounds.txt." After you rename the text file, open it up and add the code in Listing 2.
The first line is the name of the sound that will appear in Face Poser: "rpg _sounds" is the prefix, and the second part is the name. You can change this naming convention to whatever you like. The next four lines tell the engine how to deal with the sounds, and we'll leave them as they are for now. The last line is the .wav file it will load and contains a relative path to the "sound" folder, located in /steamapps//half-life 2 episode two/ep2/sound/. All you need to do is put the folder with your audio files into that "sound" folder and make sure the file names and extensions match what it says in the script file. Save and close the text file when you are done.
If you're running Windows Vista or Windows 7, we'll need to help Face Poser out a little bit. As mentioned earlier, you'll need to download and install the Phoneme Extractor patch in order to do this. Follow the instructions and replace the necessary files. This patch will allow Face Poser to correctly translate the audio files and sound scripts into the phoneme facial expressions that will be animated on the characters. It's important to note that Source SDK refreshes itself each time it is launched, so this patch fix needs to be done each time Source SDK is opened. Yes, it is a bit of a pain, but it's way easier and faster than trying to line up phonemes to the audio manually.
With Face Poser open, we're going to start a new scene. Under the Choreography menu tab click "New," which will create a new scene to work with. It'll prompt you to save the scene, so name it something you can remember, and then place it in /steamapps//half-life episode two/ep2/Scenes/RPG_MOD/. It'll make it easier to find later on if you give the scene name a prefix, such as "RPG_". Next, Face Poser will ask you to name your first Actor. Name your actor "!_target" where is a number between 1 and 8, because we can have up to eight individual NPCs in each scene. Hammer will use whichever NPC you select in the editor based on this name. We currently only have one character, so name the actor "!_target1." Right-click on the Actor name that has appeared in the Choreography window and then go to New, Channel to bring up the Create Channel box. Name this channel "Audio." We'll need to make a second channel for animations, so repeat the last step and name it "Anim."
Now we're going to sort out the audio. In the list of tabs at the bottom of the Face Poser screen, double-click Phoneme Editor and it will bring up a new window. Click on Load and select the .wav file you want to add to the character. The file will be displayed as a waveform diagram in the Phoneme Editor window after it is loaded. Press the Re-extract button, and Face Poser will prompt us to write out the sentence text of the same dialogue audio clip we selected. Write out the line and then press OK. The words you entered should now be placed at the appropriate points over the waveform, so right-click on the waveform and hit Commit extraction to apply the phonemes to your waveform. If you play it by pressing your space bar you should see the Actor in the window move their mouth. Hit Save on the Phoneme Editor window and then close it.
We need to add that .wav file to our actor, so right-click on the Audio channel and click WAV File, and then check Show All Sounds. We need to locate the sound name that we gave our .wav file in the sound script file; in our example it will be named "CitizenHomie/FemaleCitizen_Line1-try2 - Processed.wav," but you should look for whatever name you added into your own sound script. You can use the filter field to help you find it. Once you find it, select it and hit OK. This will place it on your Audio channel. When you play through the sound, it should make your actor's mouth animate to speak the dialogue. Look at Figure 9 for an example of what everything should look like once you're done.
Figure 9: Once your dialogue animation is set up in Choreography, it will look like this.
If you wanted to add gestures, facial expressions, and other animations, you'd do that now. We won't cover that in this tutorial, but there are plenty of resources online that explain how to do those things, such as design3's Source Engine section. Next save your scene by going to Choreography, Save. Now that we have our scene saved, we're ready to attach it to our NPC.
Applying custom audio to NPCs
Now we need to set our dialogue up in our level. We're going to add our scene to the scene_start and scene_end entities we created while building our instance earlier. Hit Alt + Enter to open up the Object Properties window of the scene_start entity and you'll see an option named "Scene file." Select this option and then browse for your scene under the "scenes" folder. We'll need to assign an NPC to use the scene, so select the Target 1 option and choose the NPC with the name of your actor in Face Poser. Hit Apply to save these changes. Then repeat that same process for the scene_end entity.
All we need to do now is trigger the scene to start playing. We'll need to trigger the scene from another entity at the point where we want the scene to be acted out. For example, you can use a trigger_once brush with the output of "On trigger, entities named scene_start, Start." Our quest instance that we set up earlier has two button brushes that should already start our scenes, so just double-click on the brush to open up the Properties panel and change the Scene File field to the scene you want your NPC to act out.
In order for this RPG to work, we need a system that allows each quest to know when the player has completed a specified task. This is so the game can progress and won't just stop dead in its tracks. The best thing to do is have a single entity that performs all the necessary outputs once the quest is completed. We'll use our relay_complete entity for this task.
Here's an example of a quest:
- Player needs to kill five enemies.
- On each kill, a math_counter entity increases by one.
- When it hits its max limit, five in this example, it outputs a trigger to our logic_relay.
- logic_relay runs all necessary outputs like stopping enemies spawning, activating the quest giver, activating sprites, and so on.
Using relays keeps the number of outputs low and in one place instead of spread across multiple entities, which can get very confusing on complex maps.
Setting up autosave
Everybody hates it when a single crash erases their last hour of gameplay, so it's vital to have an autosave feature just in case it happens to our players. Fortunately, autosave is easy to implement. All we need is a logic_autosave entity. Find it on the entity list and place it in your map near any other logic entities you have, such as environmental lighting or auto_logic. The placement isn't too important, but it's good to have similar entities located close to together to make them easier to find. Let's name it "Global_autosave." The autosave entity works off inputs from other entities, so we're going to make our end relays for each quest output a trigger to our autosave in order to save the game. Open up the Outputs tab on our relay_complete and add this output: "On Trigger" the entities named "Global_autosave" via this input "Save." And that's all there is to it! Now the game should save the player's progress every time we finish a quest.
Our game is going to have to end at some point, and it'll look bad if it just cuts back to the main menu, so we're going to use another group of entities to roll the credits first. Create the entities shown in Figure 10 and place them in a suitable place in the level, somewhere that is easy to access and remember.
Figure 10: You'll need to use these entities to make your ending credits sequence.
Once you've made the core entities for the ending credits, all you need to do is link them with triggers. Open up the output tab on your end_relay and create these new outputs: On trigger, env_fade, fade, 0 seconds On trigger, end_text, display, 1 seconds, On trigger, point_clientcommand, disconnect, 5 seconds. This will make the screen fade to black, your credits will fade in, and then a few seconds later the game will disconnect the player using the client command.
Now you just need to compile your map. Compiling is the process of packaging everything and making a .BSP file that the game can read. To compile, just go to File, Run Map. This will bring up the Run Map options window with some different compile options, such as launching the game after the compile finishes. Just hit OK to compile the map and you're good to go!
Now you should have the basic tools you need to build your own RPG prototype. We've shown you how to create quests, incorporate your own dialogue, and use logic to enhance your game, so now it's your job to keep testing and refining those quests, level, and dialogue decisions. You can make any sort of quest you can possibly imagine, or add a multiplayer co-op component like the Synergy mod, or even use elements from other Valve games like Left 4 Dead. Get in there and start modding!
Ben Evans is a game developer with experience in level design, 3D art, and coding. He's a frequent design3 contributor, especially when it comes to Source engine tutorials. He is also the current lead programmer at Stupor Studios and can be found on Twitter as @The_BenEvans.
Special thanks to Ross Scott, the director of the Machinima series Civil Protection and Freeman's Mind, for providing dialogue writing and audio recording.