|
SiN Doors part II - func_rotatingdoor by BlindTypist
|
5. func_rotatingdoor
|
This tutorial focuses on the more complicated (and often obscure) capabilites of doors. This time we will be using func_rotatingdoor: the notorious malfunctioning door entity. The door doesn't really spontaneously malfunction, it just requires a little extra "help" to work properly.
I will give a detailed explanation about how the door operates as well problems that may arise. The first half of the tutorial focuses on the details of func_rotatingdoors operation, the second half deals with scripting, shaping and binding. Whenever possible, I will provide you with alternative methods to perform the same action, and let you choose which works best for you.
Since we already created a room in Part I, we will just modify our current door.
|
Select the func_door we created in Part I.
Press ALT E to bring down the Edit menu and select Ungroup entity. The door isn't a func_door entity anymore, but a plain world brush instead.
While the door is selected, make it into a func_rotatingdoor entity.
|
Edit the door to the following specifications:
length~ 64 units
height~ 128 units
width~ 8 units.
texture~ Dam/wl_dam(inside) (or any texture you like)
Adjust the doorway brushes to accomodate our new door.
|
Origin: the func_rotatingdoor's hinge
|
Most of the key/value pairs and spawnflags of func_rotatingdoor are similar to func_door. The main difference is in the complex operation of func_rotatingdoor, which as the name implies, rotates on a hinge. When I first learned SinEd, I wrongfully assumed the default hinge position was on one edge of the door like all the doors in my house. But then I thought: "How will the game know which edge I want it to hinge upon?". The answer is simple: you have to tell it which edge.
The truth is, the origin acts as a hinge point. The origin provides the center of rotation (centroid) of the Sin entities. For the brush entities such as func_rotatingdoor, the centroid is located in the exact center by default.
|
The origin is actually a single point in 3 dimensional space which tells the game where the rotation axis will pass. Since it is invisible during gameplay, I illustrated the location of the default origin by stretching a red cube the full height of the door (right). This shows how it is used as a pivotal point, or hinge.
While some entities (like func_scriptobject for example) support full rotation on all three axes (X, Y and Z), the rotating door entities can only rotate on the vertical axis. Not setting the door's origin will make the door pivot around the centroid of the door brush by default: a sort of "revolving door" effect.
In order to have a properly working hinge, we need to move the origin to the edge of the door.
|
Moving the origin of the door
|
Assign a new origin by creating a brush about 24 x 24 x 128 units in size. The origin brush can be any size, but I prefer to oversize it and stretch it the full height of the door to make it easy to select. Apply the generic/misc/origin texture (the red box with the black "O").
Position the origin brush where you think the hinge should be, using the image on the left as an example. Keep in mind that the origin brush's centroid will become the exact center of rotation, so don't place it flush with the edge of the door.
You now have the required origin for your door but first, you have to make it part of that door entity. Compiling the map now would result in an "origin brushes not allowed in the world" error. This simply means you haven't assigned the origin brush to an entity yet.
|
Merging the origin brush with func_rotatingdoor
|
To make this origin brush a part of our func_rotatingdoor we need to merge it it into the existing door entity. First, make sure to deselect the origin brush if it's still selected (hit ESC). Then, select the func_rotatingdoor entity first and the origin brush second and press M (Select Entity Merge function).
* NOTE-
When merging one or more brushes to an entity, the first brush that is selected is designated as the primary selection: this is the entity to merge to. All the following brushes that are selected are the secondary selections: those will all become part of the primary selection when you hit the M key.
I used the method above as an opportunity to show you how to merge a new world brush into an existing entity. This feature is very useful and powerful but be careful: if you had selected the brushes in the reverse order in the above example, you would have actually merged the func_rotatingdoor brush into a plain world brush instead!
Why is this important? Let's say you had previously set something like 8 different key/value pairs on your door entity. By merging it into a world brush, all those settings would have been lost so you would have had to make the brush a func_rotatingdoor again and re-enter the key/value pairs again one by one. Not a disaster as such but real pain (especially for the sound settings). So when using this feature, be extra careful with the order of selection if you don't want to end up doing your work twice.
|
An alternate method to set the origin of the door
|
There is another way to change the origin by setting the origin key/value pair directly. In order to use this method, you will need to determine the X and Y coordinates of your hinge (the Z coordinate is irrelevant). These coordinates are always relative to the grid origin 0 0 0 (thus absolute).
To make this easier to calculate, you can change the grid setting to 1 unit increments by pressing 1.
Using the grid, decide where the hinge should be (the large green dot in the image on the left). Find the X coordinate by following the nearest vertical gridline (in blue). The grid numbers are displayed in 64 unit increments, so you may have to do some addition or subtraction to find the exact coordinate.
Use the nearest horizontal gridline (in red) to find the Y coordinate.
Now set the value of the origin key to reflect the XY coordinates:
origin: -20 64 0
|
* NOTE-
When entering the coordinate data for the origin key/value pair, X ALWAYS comes FIRST, followed by the Y coordinate. The Z coordinate is ignored so it can be any value. To keep things simple, just set it to 0.
In some instances this method may be easier, but remember, if you move or copy this door you MUST re-calculate the XY coordinates and re-set the origin key/value.
|
Don't forget to check the angle
|
Remember how important the angle key/value was in Part I? With func_rotatingdoor the angle key/value has a different role, but remains just as important to prevent erratic operation. For the most part, the angle defines the direction the door will open. In the composite illustrations below, the angle arrow is highlighted in blue.
|
Most func_rotatingdoor's have the arrow pointing AWAY from the hinge (toward the middle of the door). This will make the door open AWAY from the player, regardless of which side of the door the player is standing. This is the setting to use for common doors.
|
Pointing the arrow TOWARD the hinge will result in the door opening TOWARD the player, regardless of which side of the door the player is standing. This setting is useful for cabinetry and lockers on which the door must open outward.
|
Pointing the arrow in any other direction will make it ALWAYS open in the direction of the arrow. This type of setting can also be used in cabinetry and lockers, since you can "force" it to open a specific direction.
|
Here, we want the door to always open away from the player, so set the angle key/value to 270
|
* NOTE-
If the door texture is backward (ie, doorknob is on the wrong side), press the Tx Flip button: to flip the texture.
|
We're done
|
Whew! That may seem like alot of work, but after a while it becomes natural. Compile your map and test out the door. Notice the door always opens away from the player. You can go back into sinEd and change the angle key/value to see how the different angles affect the door, if you want to.
|
6. What about double doors?
|
Once you make one door, making a double-door seems like the next logical step. We could have done this in Part I, but I decided to put it off until now. Creating linked doors isn't difficult, it is just subject to different concerns. Once you know how to get two func_rotatingdoors to work properly, then getting two func_doors follows the exact same rules.
* NOTE-
Without going into too much detail about linking doors, here's how this works: when 2 doors touch, they are automatically linked ot treamed. One door is the master while the other is the slave. The master is the first door to spawn in the game and controls the operation of BOTH doors. There is no easy way to determine for sure which door is the master and which one is the slave but there is a way to control how your doors will operate: the DOOR_DONT_LINK spawnflag.
|
Preparing your door to be copied
|
Of course, in order to do this we'll need another door. The easiest solution is copying our existing door. This will also copy ALL the key/value pairs (except targetname) and spawnflags to the new door.
But before copying it, let's set the following key/value pairs and spawnflags of our existing door:
message: Security Zone- 5
sound_locked: player/blade/locked.wav
targetname: door_left
TOGGLE spawnflag set
|
Copying the door
|
Select the door brush and press CTRL-E (Select Complete entity). The entire func_rotatingdoor entity will be selected, origin brush and all. Press the spacebar to copy it.
|

While your new door is selected, press the Y Flip button (or X Flip button, depending on the orientation). The door should spin 180 degrees and face the other direction now. The texture may be backwards too, so press the Tx Flip button to flip it.
Re-set the angle key/value pair on the new door so that the angle arrow is pointing away from the hinge.
|
Make sure ONLY the following key/value and spawnflags are set on the new door :
TOGGLE
(this should already be set)
sound_locked: player/blade/locked.wav
(this should already be set)
sound_move: misc/null.wav
sound_stop: misc/null.wav
targetname: door_right
You may be wondering why we set the move and stop sounds of door_right as a null and eliminated the message key/value. Since the doors are touching, they automatically link and operate as a unit, thus only one door needs to make sound.
Move door_right to its final position: adjacent to door_left, and resize the doorway to accomodate TWO doors. If you set the origin of your door with the origin key/value pair instead of an origin brush, you will have to re-calculate the new origin coordinates for door_right.
That's all there is to making a pair of double doors. As long as the two doors are within 4 units of each other (or touching), they will synchronize their operation. Since the angle is pointing away from the hinge on both doors, they will both open away from the player. Go ahead and compile the map and test it, just to make sure it works.
|
7. Scripting Locked Doors
|
Eventually, there comes a time when only so much can be done with an entities key/value pairs and spawnflags. This is where the power of scripting comes in. The following exercise uses a relatively simple script that performs nearly the same task as setting the key key/value, except it plays a sound rather than display a message. The exercise here is to familiarize you with scripting, so that you can be prepared for bigger and better things... like func_scriptdoor's which absolutely require scripting to operate.
|
ScriptFile
|
Let's dive right into the scripting process. Make sure you have saved your map, then press the Open SCR button. This will launch the default text editor (notepad) or the text editor your project file points to (UltraEdit or other) and automatically open a new file by the same name as your map, with an SCR extension. Type these simple script commands:
thread lock_door //call the thread that starts at the label lock_door:
waitforplayer //makes sure the doors are locked before the player has spawned in the map
end
lock_door: //start of thread
$door_left lock //lock the left door
$door_right lock //lock the right door
end
got_it: //this thread will be called when the player picks up the blue card
$door_left unlock //unlock left door
$door_right unlock //unlock right door
end //end of thread
* NOTE-
The targetnames of the door entities (door_left, door_right) are case-sensitive, so it is important to enter them accurately. The text in green preceded by // are comments and are not absolutely necessary. They just provide information about what each line does.
It isn't necessary to lock both doors if you know which door is the master. But since it's such a pain to determine which door is the master, it's much easier to lock both doors and not worry about the master-slave thing.
|
Inventory_BlueCard
|
If you are using the same map from Part I, you should still have an Inventory_BlueCard in there. Add the following key/value pair to the Inventory_BlueCard entity:
thread: got_it
Yeah, but what does this key/value pair do?... Just look at the definition:
thread: the name of the thread to call when the item is picked up by the player.
So in this case, picking up the Blue Card will execute the thread starting at the "got_it" label and unlock the doors. Simple as that.
|
Let's go for a spin
|

Save the script file, compile the map and lets go for a test drive. Don't pick up the Blue Card yet and try opening the door. Blade says: "Damn, a security door!", but doesn't tell you what you need to unlock it. Now it's up to the player to find the object that might unlock the door.
Pick up the BlueCard and try it again; this time the doors open and the message "Security Zone- 5" is displayed on the screen. Notice that the doors always open away from the player. Congratulations, you just scripted a locked door.
|
8. Irregular shaped doors
|
One of the most interesting ways to render a door involves multiple brushes and shaping. Using multiple brushes to flesh-out complex door shapes can greatly enhance the look and feel of a map. The only difficult part of this procedure is planning where to divide the door into multiple brushes.
If you've made it this far into the tutorial, you're nearly a pro. So we are going to jump right into a complex door shape without hesitation.
Selecting the right texture
|
First, we need to find a texture that would warrant an irregular shape. It is important to find or create a texture that has a noticable seam between two doors, since that is what we are going to use as a guideline to separate them. It also enhances the look of the door.
|
I used waterbase/wl_basedoor, because the doors make a cool angled shape when separated.
Waterbase/wl_basedoor is a 128 x 112 sized texture, which nearly fits into our 128 x 128 doorway. By reducing the height of the current doors and doorway, the texture will fit the door surfaces without stretching or shifting. If the doors are positioned "just right" on the grid, the texture will align perfectly on the brush surface. Getting the texture to align without a lot of fiddling will make the process of shaping the door a little easier.
Reduce the height of the doorway and doors to 112 units.
|
Slicing and Dicing
|
Apply the waterbase/wl_basedoor texture to door_left and door_right. Now comes the FUN part: figuring out where to divide the door into multiple brushes.
|
We're going to use our imagination and mentally draw a dividing line between the two doors, like the image on the right. I rendered the dividing line in red, but more importantly, note the points in green. Those green points tell us where to divide the door brush into 7 individual brushes (actually you could do it with only 4 brushes: Eutectic's 2¢'s).
|
Let's start with door_right. Using any method you feel comfortable with, divide the door into seven brushes. I usually draw or copy each brush, and stack them on top of each other like blocks. All the blocks should have the same width and length, but the heights will vary. Use the imaginary green points as a guide to determine each block height.
(Personally, I favor clipping the original door brush into smaller pieces for this kind of operation: Eutectic's 2¢'s more).
|
Merge all the extra brushes to the door_right func_rotatingdoor (if you created your pieces by clipping the original door brush, this won't be necesssary BTW because the pieces remain part of the original entity: another Eutectic's 2¢'s more - getting rich here ;) ). The origin brush is a good candidate as the primary selection for the merge operation since we haven't altered it in any way.
|
Shaping the door
|
We now have a func_rotatingdoor consisting of multiple brushes in a rectangular formation. Not very impressive, not yet anyway. Molding the door into shape is quick and easy.
Start by resizing the 5 brushes that will maintain their rectangular shape. They can be resized individually just like ordinary world brushes.
The 2 remaining brushes require a sharp angle. The angle isn't difficult to create, it just requires a different technique. These brushes can be shaped directly in the Camera View but doing this in the 2D Views is more reliable.
|
Start by selecting one of the two brushes that will be molded into a sharp angle. Press E to change the editing mode to "Edge manipulation" (Drag Edges), and the brush becomes highlighted with blue nodes.
|
The blue nodes are actually "handles". By dragging the handles, you are actually dragging an edge of the brush. At first, all those nodes may seem a little confusing. Try moving a few nodes so you can see the relationship between the node and edge. If you need to, adjust the grid setting to snap the nodes exactly where you want them.
|
Shape the brush into the desired angles by dragging the appropriate nodes, one at a time.
Shape the other sharp-angled brush and door_right is finished.
|
The left side door, door_left is a slightly different shape than door_right, but you can follow the same procedure and have two doors that mesh together seamlessly. As a matter of fact, you won't even be able to see the seam until the door opens.
The two doors are finished, all we have to do is add the finishing touches. Edit the following key/value pairs for door_left and door_right:
* door_left
targetname: door_left
sound_locked: player/blade/locked.wav
sound_move: environment/doors/airlock/lock2.wav
sound_stop: environment/doors/metal/lrgmtldr.wav
time: 2
* door_right
targetname: door_right
sound_locked: player/blade/locked.wav
sound_move: misc/null.wav
sound_stop: misc/null.wav
time: 2
|
Since we still have the script and Blue Card, this door will basically work the same way as in the previous exercise. All we did was a little "cosmetic surgery".
Make sure the angle key/values are set properly, compile your map and test it. This particular texture would also look very good on a regular func_door.
|
9. Destruction - shattering glass doors
|
While we are on the subject of creating interesting doors, we can make one specifically for demolition. There are many ways to implement this idea, or a variation of it. Hopefully, this will inspire some of you to create some cool and clever destructible objects, not just doors (I love destruction!).
We are going to create a glass door, similar to those you would find in a grocery store. In this example I am going to use two doors that operate independently, but you can use a single door if you want.
In order to proceed quickly through this exercise, I'll assume you know the basic procedures pretty well. Let's get right down to business by making a metal door frame to hold the glass.
|
The metal door frame
|
Create three very thin brushes for the left, top and right side of each door frame. The bottom edge of the door will be a kickplate, about 24 units high.
Reduce the overall door thickness to 4 units.
Apply any texture; I used generic/wall_metal1/wl_pipe1 and generic/wall_metal1/wl_steel to give it a dirty metal look.
Assuming each door is already a func_rotatingdoor entity, change the following key/value pairs and spawnflags:
|
DOOR_DONT_LINK (set this spawnflag on BOTH doors)
* left
targetname: glassdoor_left
sound_move: environment/doors/pneu/smlpnu2.wav
sound_stop: misc/null.wav
time: 1
* right
targetname: glassdoor_right
sound_move: environment/doors/pneu/smlpnu2.wav
sound_stop: misc/null.wav
time: 1
|
The doors are done. You might be thinking: "But what about the GLASS?". Well, the glass will be made with a func_scriptobject entity bound to the func_rotatingdoor using a script. We can't use a transparent func_glass or func_shatter entity, because they aren't meant to rotate or be bound to door entities. They basically malfunction when the door is opened.
* NOTE-
You may be wondering why we set the DOOR_DONT_LINK spawnflag on both doors. The reason is that for the glass panes to be bound and rotate each with their respective door, the 2 doors must be able to operate independently. Now you remember what I mentionned earlier: "Doors that touch are automatically linked and operate as a unit". In fact, the game links them so well that it sees both doors as the same once compiled (in a way). The net result is that both glass panes end up being bound to and rotate along with the master door. The only way to remedy this is to make the doors operate seperately by setting this spawnflag on BOTH doors. However, if you want double doors with "breakable glass" that operate together, you COULD place a trigger_use or trigger_multiple around it. The trigger could have its thread key set to a script thread that contains 2 open commands (one for each door). You would also have to set both doors' TARGETED spawnflag for this. But this is a bit beyond the scope of this tutorial.
|
Creating the "false glass".
|
Draw two brushes about 2 units thick. They are going to be the glass, so fit them snugly into the door frames.
Press S to bring up the Surf Inspector. The Surf Inspector is a little large, so you may have to move it around to perform the following actions. Apply the generic/wall_organic/wl_glass texture to both brushes and set the following surface attributes:
|
The Surf Inspector displays the attributes for the generic/wall_organic/wl_glass texture. Make this texture transparent by setting the transluscent content flag.
|
The value of Translucent should be a little more than 0.5 (50% opacity). 0.0 is opaque (solid) and 1.0 is nearly invisible. You can see the transparent effect in the Camera View by turning on the Show Translucent filter (press ALT 3 to toggle).
|
Make each glass brush into a separate func_scriptobject and edit their targetname key to the following values:
* glass for glassdoor_left
targetname: glass_left
* glass for glassdoor_right
targetname: glass_right
* NOTE-
It's important to pair the door_left with the glassdoor_left
and the door_right with the glassdoor_right, otherwise you'll get
some pretty weird effects when you bind the glass panes to the doors.
|
Writing the script
|
Ok, you have the metal doorframe and the glass. If you compiled the map now, the glass would not move with the door, it would just suspend in mid-air. Also, the func_scriptobject entity doesn't shatter without the aid of a script. So here are the "guts" that make this whole thing work (If your previous script pops up, delete it or rename it):
thread glassdoor_leftside //start this thread
thread glassdoor_rightside //start this thread
waitforplayer //makes sure the doors are ready before the player has spawned in the map
end
//*******************SHATTER GLASS_LEFT******************************
glassdoor_leftside: //start of thread
$glass_left bind $glassdoor_left //glass_left is now a attached to glassdoor_left
check_glassleft_damage:
$glass_left ondamage shatter_glass_left //thread jumps to label if glass_left is damaged
pause //suspend execution and wait for glass_left to receive damage
shatter_glass_left:
$glass_left phssound impact/glass/lrggls1.wav 1 2 1 //glass plays a shattering glass sound
$glass_left shatter "90 90 0" 40 40 50 100 0 //make "glass_left" throw fragments of itself
$glass_left remove //removes glass_left from the game
end //end of thread
//*******************SHATTER GLASS_RIGHT*****************************
glassdoor_rightside: //start of thread
$glass_right bind $glassdoor_right //glass_right is now attached to glassdoor_right
check_glassright_damage:
$glass_right ondamage shatter_glass_right //thread jumps to label if glass_right is damaged
pause //suspend execution and wait for glass_right to receive damage
shatter_glass_right:
$glass_right phssound impact/glass/lrggls2.wav 1 2 1 //glass plays a shattering glass sound
$glass_right shatter "90 90 0" 40 40 50 100 0 //make "glass_right" throw fragments of itself
$glass_right remove //removes glass_left from the game
end //end of thread
Wrapping it all up
|
Save the script, compile the map and test it. Open the doors first to ensure everything works properly. Since the DOOR_DONT_LINK flag is set, each door opens seperately unless you walk right up between them and use them both at the same time.
Blast the glass with your magnum. Smash the door with your fists. No need to open the doors any more if we can just break the glass and jump through the door frame (watch out tho, the glass can hurt you!).
|
By now, you should have a pretty good understanding of normal and rotating doors plus basic understanding of the scripting required for additional goodies like breaking glass. Remember, these tutorials are used ONLY to introduce concepts. The real power of these entities and scripting relies on YOUR continuous efforts to experiment and explore their possibilities.
|
Part I
|
| | | |
|
|