Creating a Multi-Floor Elevator
In this tutorial I lay out the essentials of creating an elevator that generally functions in a manner similar to a real elevator. In other words:
A. The Parameters of the Elevator
First, let's lay down the parameters of this specific construct. It is a 4-storey building with an elevator that can stop at each floor. This is a true 3D structure, and the entrance to the elevator on each floor is above/below the others. The bottom floor is at 0 height, and each successive floor is 160 units above the previous one. The map starts with the elevator on Floor 3, and can be called down or up from relevant floors using an "outside" switch on each floor (which is common on all floors, i.e., it's the same linedef that you activate on all the floors). The doors are really a single polyobject, which means they open on all floors simultaneously. While you're inside the elevator you have access to a wall switch on each floor (named the "inside" switch), and you can use the same switch to determine whether to ride the elevator up or down. Again, this switch is common on all floors, i.e., it's the same linedef that you activate on all the floors. I have set up the script on the outside switch to check the height of the player and compare it to the floor at which the elevator is. The elevator moves up or down until it gets to the player's floor, before opening the doors. For the inside switch I wanted to be able to control the script to ride up or down an elevator using a single switch. In other words, if I want to ride the elevator up I would press the top part of the switch, and if I want to ride down I'd press the bottom part.
The parameters of this elevator you can change with relative ease are the number of floors, the floor at which the elevator is at map start, and the height differential between respective floors (making sure that the height differential between floors is the same).
To follow this tutorial, previous knowledge of the following topics is required:
The two pictures below respectively show the elevator with its doors shut and open. This gives you a sense for the overall geometry of the area. What you see in these two pictures is virtually exactly the same as what you'll see if you were operating the elevator from a lower or higher floor (with the exception of any wall/floor/ceiling texture variations you may introduce).
The picture below shows the elevator area in a map editor. I have marked, in yellow text, some of the relevant aspects of the constructs that are controlled by the scripts. Please note that the polyobject and its anchors are not shown in the picture (as they are drawn in another part of the map), but must be included in your map. Line_SetID = 2 represents the "outside" switch and Line_SetID = 3 represents the "inside" switch. I have also highlighted the sector with Tag = 83, as this plays a role in controlling the movement of the elevator's 3d control sectors. You can create a link between two or more sectors so that they will move together. This allows for the creation of complex lifts comprised of many sectors that will hold together if their movement gets blocked by the player or another actor. The sector tagged 83 serves this linking function (more on it in the discussion of scripts below).
D. The Mechanics of Motion & Control
Now let's look at the scripting that went into operating the elevator from the outside:
// Script 1
script 1 OPEN
An OPEN script is executed upon map start, and is therefore used to establish any specials that must be activated independent of the player's actions. In this OPEN script, the three sectors that make up the elevator (tags 80, 81, and 82) are linked to a single sector (tagged 83) using Sector_SetLink. In addition, the OPEN script sets up the "outside" (Line_SetIdentification = 2) and the "inside" (Line_SetIdentification = 3) switches to activate Scripts 3 and 4 respectively. These scripts are discussed below.
// Script 3: Raises/lowers elevator from outside; opens door to elevator
int LiftFloor = 3;
script 3 (void)
int PlayerFloor = GetActorZ(PlayerNumber())/10485760+1;
One of the common functions of a real elevator is that when you're outside it, and you press a button to call it, the elevator knows at which floor it needs to stop. This, in turn, is based on some value assigned to the button you press. Therefore, before the script for your in-game elevator can move the elevator to your floor, it needs to know which floor it's on. This is achieved by the following line of code:
If the elevator is above the player, it checks to make sure that that this condition continues to be met while executing the following part of the script, namely making the relevant sectors (in this case, sector with tag = 83) move down by an appropriate amount. Note that the script is set up to move in increments of 160 units, which corresponds to the height difference between each floor. In other words, the elevator will move down with a speed of 64, continuously checking if the elevator is still above the player's floor, and stop when it reaches the proper floor. The switch texture needs to be defined based on whether the elevator is moving down or up. In this case, as it's moving down, the texture changes from the default one to the one showing the elevator is moving down, as shown in the images to the left.
A couple of important points. First, as with a real elevator, you need to make sure that the elevator doesn't begin moving until the doors have shut completely from any previous elevator use. This is achieved through the line of code "PolyWait (6);". Via the line of code "TagWait(83);", the script waits until the relevant sector (i.e., with with tag = 83) has stopped moving before proceeding to the next part of the script, namely setting the texture back to the default, as shown in the images to the left. Meanwhile, the script has recalculated the value of the floor at which the elevator has stopped, via the line of code "LiftFloor--;".
Finally, the script opens the door by sliding the relevant polyobject sideways, and prevents the script from being activated further until the polyobject has completed its range of movement (i.e., the doors have shut again after opening). In addition to emulating the behavior of a real elevator, there's another reason to ensure that the elevator does not move until the doors have shut - the player can fall/jump into the elevator shaft and get stuck in there. Now let's look at the second segment of the script:
[Continued from script, see above]
else if (LiftFloor < PlayerFloor)
The first segment of the continued script above is virtually identical to that of the script previously discussed, except that the player is now calling the elevator from a floor that is above that of the elevator. As such, the direction of the elevator's travel is reversed so that it's now moving upwards. In addition, the switch texture is changed from the default one to the one showing the elevator is moving up. Once the elevator reaches the desired floor the texture switches back to the default one. These are illustrated in the images to the left. As before, the script has recalculated the value of the floor at which the elevator has stopped, via the line of code "LiftFloor++;", only this time it has incremented the values instead of decrementing them.
The second segment of the continued script above is for the instance when the elevator is already at the floor from which the player is calling it. In this case, the script simply opens the doors, pauses, and shuts the doors. As before, the script checks to ensure that the elevator doors do not open before they are completely shut from a previous elevator operation, and then sets it up so that any futher elevator operation is prevented until the doors are completely shut again. The third segment simply allows the doors to be repeatedly opened from the floor where the elevator has stopped.
One thing to keep in mind is that if you have a set of stairs nearby, you can call the elevator when it's 2 or more floors above/below, run up/down, and by the time the elevator reaches your original floor and the doors open, you are on another floor; when the doors open you can jump on top of the elevator or into the pit below. I found that by:
Finally, let's take a look at the operation of the elevator from the inside:
// Script 4: Raises/lowers elevator from inside; opens door to elevator
script 4 (void)
if (GetActorPitch (0) > 0)
This entire script hinges on the direction the player is looking when activating the switch. Looking/pointing at the upper part of the switch will tell the script to move the elevator up, and vice-versa. If the player is pointing at exactly the middle of the switch, the script will merely open the doors without moving the elevator. [However, by trial-and-error I've discovered that it's virtually impossible to press the switch at the exact middle when using free-look.] The script relies on the GetActorPitch special to determine which way the player is pointing. Note that looking up produces a negative value and looking down produces a positive value; 0 is looking straight ahead. Everything else is controlled in the same way as the script for operating the elevator from the outside.
One thing to keep in mind is that for the script to work properly, the switch needs to be placed at exactly the correct height. For example, at the bottom floor the in-game 3D sector that represents the switch has a floor height of 48 and a ceiling height of 64; the floor of the elevator is at a height of 0. Because the player's height is 56, which is the mid-point between 48 and 64, this floor/ceiling height combination works well.
So there you have it. Go forth and conquer!