VERC: The Opposing Force Opening Sequence Last edited 1 month ago2022-09-29 07:55:39 UTC


One of the most impressive sequences in Gearbox Software's Opposing Force was the opening Osprey ride. It sets the mood nicely, and serves as a good introduction for your return to Black Mesa. If it's been a while since you've played the beginning of Op4, I suggest you load it up now and watch the Osprey ride. It is especially interesting when analyzed from a mapping perspective - Gearbox dug into their bag of tricks to pull this one off.

Depending on your experience with mapping for Half-Life and Opposing Force, it may or may not be obvious to you how the Osprey ride is set up - the player and the soldiers remain stationary in a 'simulated' Osprey, while everything else outside moves past them, giving the very convincing feel of motion. Gearbox used many moving entities to make the rocks and canyons move past the player for the entire sequence. Clever, no?

The approach I'll take to examine the Op4 Osprey ride is to create a similar sequence using a simulated train. The example map I'll describe is based on the map I submitted for the Valve ERC's Train Ride Contest, which I designed as an alternate perspective to Op4's original opening. It's not meant to be an exact re-creation, but it should serve its purpose as a good example map.

The Opposing Force entities and models really lend themselves to this type of sequence, more so than standard Half-Life. First, there's the trigger_playerfreeze, which allows you to freeze the player in place while still allowing him or her to look around but not jump or shoot. Second, there are the models used in Op4's opening Osprey ride: the sitting grunts and the standing commander. These files are named intro_*.mdl, and are typically used as a monster_generic), since there are no specific monster entities defined for these models, and thus they have no AI, so they must be controlled entirely through scripted_sequences and scripted_sentences. Also worth noting is that the models of the sitting grunts have no backs; that is, their backs are not modeled and they should be placed so that they can only be viewed from the front. Third, the monster_human_grunt_ally in Op4 includes a number of sequences where the grunt is sitting down, perfect for a simulated ride. Finally, there are two miniature models, also used as monster_generics: mini_apache.mdl and mini_osprey.mdl. They provide an easy way to give the perception of depth without having to make a humongous map using the regular-size Apache and Osprey models, which are roughly 3 times larger than the corresponding mini models.

Setup - The "Train"

I'll begin by describing the player's immediate surroundings; in this case, it's our simulated train car. Since we'll have the trigger_playerfreeze activated the entire time, we'll only need to worry about the areas that the player has a direct view of. It's best to open the map file so you can follow along.

The general setup of the train interior is straightforward - there's a desk, radio equipment, some crates and lockers, etc. Note that the placement of the green crates at the front of the train just blocks the player's view out the front door - this is no coincidence. I wanted the player to only see out the two windows, but still "know" that the commander grunt was standing at a doorway. The angle and pitch of the light_environment were selected specifically for that purpose. By the way, those windows are both func_breakable, which we'll trigger during one of the upcoming explosions for some added ambience.

Entities in The Player's Vicinity

The Grunts

Setup - The Scenery

Here I'll describe the overall setup of the map, as well as the moving scenery. If you look at the diagram of the map's setup below you'll see that I used two sets of func_trains for the rocks/canyon walls. Gearbox used a combination of func_trains and func_rotatings for the canyon walls in the Op4 intro, and in fact there are a number of moving entities that could be used in this situation with a little ingenuity.
Overall setup of the map. (top view)Overall setup of the map. (top view)
Another key point that should be noted about the overall setup diagram is the player's view. Since we are using a trigger_playerfreeze, this view will not change as the map progresses. This gives us a lot of control over what the player sees and what he/she does not. In fact, 2 of the 3 Ospreys explode outside of the player's view, saving us a lot of extra entity work.

The Close Rocks

We'll set up the rocks close to the train as a series of identical func_trains. What I actually have is 5 func_trains traveling through 6 path_corners. When each func_train reaches the last path_corner, it teleports instantly to the first path_corner, using the first path_corner's teleport flag. Note that all these trains are named identically (close_rocks), and each one has a different starting path_corner (First stop target). So all we have to do is target the trains once (which is done with the multi_manager mm_start at the beginning of the level), and they will move indefinitely in the player's view. Note that the trains fit together seamlessly and travel at the exact same speed (550) so that it looks like a continuous piece of scenery scrolling by. One other thing you may notice is that the textures facing the player are scaled 3x in the x-direction - this adds to the feeling of motion/speed.
The rocks closest to the player. (top view)The rocks closest to the player. (top view)

The Far Rocks

The more distant rocks are set up identically to the close rocks, except this time we have separate, different rocks instead of consecutive identical rocks. I used David Hyde's excellent utility GenSurf to make these small rock formations. Again, all these func_trains are named identically (mid_rocks), each has a different starting path_corner, and their speeds are all set to 85.
Author's note: the following paragraph is a description of a particular trick I used in this map, but it is not essential in understanding how the map works. It is included here for completeness.

Note that some of the rocks have what looks like a blue entity above them - this is actually a small 16x16x16 brush textured with the {INVISIBLE texture. These func_trains also have the Render Mode set to Solid and the FX Amount set to 255, similar to the properties used in any masked texture (railings, ladders, etc.). This makes the blue part invisible to the player. The reason for adding these invisible brushes to some of the func_trains is to control where the center of each func_train lies in the vertical (z) axis. I wanted all of those rock formations to share the same set of path_corners, but they are all of different height - this means that their centers are at different heights. (Note: the reference point on a func_train is its physical center, as determined on all 3 axes - i.e. the midpoint of the train on the x, y, and z axes). What I needed to do was to shift the height of some of the trains' midpoint on the z-axis so that all the trains' centers lined up at the same height, so they could all use the same set of path_corners.

Setup - The Aircraft

As I mentioned above, Opposing Force includes a couple of miniature versions of normally huge models - the mini_osprey and the mini_apache. We'll use these in the level to easily give the perception of depth to the player without making our level ridiculously big. All the aircraft are monster_generics in this level, and to enable us to control their speed as well as target one with some beams, we will move them around using invisible func_trains. You could also build your own aircraft (or alien ships) with brushes, then make them func_trains or tracktrains, which is what Gearbox did for the alien ships that attacked in the Op4 Osprey ride.

Take a look at the picture below. It shows a couple of the func_trains that will carry the Apache and the Ospreys. All I've done is make a box without a top big enough to hold the monster_generic entity, and textured it in the AAATRIGGER texture. This box is made into a func_train, given a name (e.g. train_far_apache), with the Render Mode set to Texture and the FX Amount set to 0, which will make it invisible to the player.
The func_trains to carry the Apache and Ospreys.The func_trains to carry the Apache and Ospreys.
There are a couple of important notes about the aircraft. First, notice that each monster_generic is named. They are given names so that we can remove them from the game using the killtarget field of a trigger_relay. Second, notice that there is one aircraft-carrying func_train with an origin brush, and an "empty" func_train right above it with an origin brush. The top train, which will be above the player's field of view, is meant to simulate the alien ship that shoots down the last Osprey. What we will end up doing is triggering these 2 func_trains at the same time and give them matching speeds. Then when the time comes for the last Osprey to be attacked, we can use the top train as the source of env_beams and the bottom func_train as the target - func_trains need to include origin brushes if you are going to use them as sources/targets for env_beams. Finally, notice that every func_train has its own set of path_corners. I could have made all of them (except the "alien ship") use the same set of path_corners, but I wanted to trigger certain events at certain times using the corners, and I didn't want the other trains to trigger the event early or repeatedly. For example, the first Osprey to pass triggers its destruction when it hits a certain path_corner, and I didn't want the Apache to trigger it early since it flies by first.

The last point to note about the Apache and Ospreys is that we need to animate them. If all we did was place the monster_generics in the map they wouldn't be animated - that is, their rotors would not be spinning. The animating is done with scripted_sequences, one for each model. The key settings for the scripted_sequences,:

Setup - Putting it all Together

If you've played the example map and you've managed to read this far, you hopefully have a pretty good idea for how all the sequences fit together. For a reference, here is an outline of the basic events: Well, there you have it. It's really not complicated when you break it down piece by piece. I've found that the keys to putting together a large sequence like this are to test each part separately, to get that one part working, and then to integrate it into your overall map. And if you are having trouble understanding how all the parts of my example map work, it's best to look at one sequence or one group of entities at a time. But once you understand some of the tricks used here, you'll be well on your way to creating your own action-packed Opposing Force sequences, like the damn good one in Operation: Sandblast. If I do say so my damn self. :)


For a more concrete illustration, check out the example map linked below.
This article was originally published on Valve Editing Resource Collective (VERC).
The archived page is available here.
TWHL only publishes archived articles from defunct websites, or with permission. For more information on TWHL's archiving efforts, please visit the TWHL Archiving Project page.

1 Comment

Commented 3 weeks ago2022-11-05 18:56:40 UTC Comment #104885
thanks for the tutorial. but I don't understand how to remove the rotor sound from my level, can you help me with that?

You must log in to post a comment. You can login or register a new account.