Advertisement
  1. Game Development
  2. Game Design

How to Code Doors and Locks

Scroll to top
Read Time: 16 min

In games made of connected rooms like The Legend of Zelda, the more recent The Binding of Isaac, or any type of Roguelike or even Metroidvania-like, doors play an essential part in the navigation and progress of the player.

Doors allow the player to travel from one room or level to another, and so have an important place in the navigation and the connection of the different rooms one to another, and in defining the map as an open world or a dungeon floor. They may also act as temporary roadblocks that the player will need to unlock through a specific mechanic (such as obtaining a key, or activating a switch).

In this tutorial, I'll demonstrate several lock mechanics, and propose ways to implement them in your games. These are in no way meant to be the only or best implementations; they are practical examples.

The interactive demos in this tutorial were made with the HTML5 game maker tool Construct 2 and should be compatible with its free version. (The CAPX files are available in the source download.) However, this tutorial should help you learn how to implement the doors and locks logic in whatever engine you fancy. Once you get the idea behind the logic, it's all down to your own knowledge of your coding tool/language and the way you want to adapt it to the game you are currently making.

Let's dive in!

The Basic Mechanic

A door is basically a block of scenery that cannot be passed, preventing the player's character to go through until it is unlocked. The door can have different states: locked or unlocked, closed or open.

There must be an obvious representation of the latter; the player must be able to tell that the door is actually a door and whether it is in its locked or unlocked state.

In the following demos, the doors are presented through two graphics :


This is a closed door.

This is an open door.

I've also used different colours to represent the different materials that the doors might be made of—but, to be honest, the graphical aspect is up to you, your game, and its universe. The most important part is that the door should be clearly identifiable as being a door, and it should be obvious whether it's going to block the player's progression or open and leading to the rest of the level or world.

When closed or locked, the door should be a block of solid state. When opened, the solid state should be disabled, allowing for characters to go through it. Be sure that whatever your collision engine is, it allows you to modify this state on the fly pretty easily.

From a programming perspective, the door object should contain or be linked to an is_locked Boolean variable. Depending on the value of this variable, you can determine what sprite to display and whether the block should be solid or not.

To unlock the door, the character should contain a has_key Boolean variable itself when the player has picked up a key: true if they have it, false if they don't.

In this basic mechanic, the key acts as a part of the inventory of the character, and one key opens all doors. Using the key on a door doesn't consume it; the key stays in the character's inventory.

To visualize it, we can simply display a picture of the key in the HUD to let the player know he "owns" a key that could open the doors once the character has picked it up (by moving the character over the key in the room).

Consider the following basic example:

Click the demo to give it focus, then control the character using the arrow keys of your keyboard and perform actions with the space bar. (In this example, the action is "open a door".)

Walls are solid blocks that don't allow the character to go through when colliding with them. Closed doors are also solid.

To open a door, the character must be within 64 pixels of the door and possess a key (that is, the has_key Boolean variable that determines whether the character has the key in its inventory must be true). 

With those conditions, when the player presses the space bar, the appropriate door's state is changed. Its Boolean variable locked is set to false, and its "solid" state is disabled.

In pseudocode, this would look something like :

1
Door.Locked = True
2
    Door.AnimationFrame = 0 
3
//The animation frame that displays the door as locked

4
    Door.Solid = Enabled 
5
//The solid state of the door is enabled

6
7
Door.Locked = False
8
    Door.AnimationFrame = 1 
9
//The animation frame that displays the door as opened

10
    Door.Solid = Disabled 
11
//The solid state of the door is disabled

12
13
Keyboard Key "Space" is pressed
14
and Distance(Character,Door) <= 64px
15
and Door.Locked = True
16
and Character.Has_Key = True 
17
//The player has a key

18
    Door.Locked = False
19
    
20
Keyboard Key "Space" is pressed
21
and Distance(Character,Door) <= 64px
22
and Door.Locked = True
23
and Character.Has_Key = False 
24
//The player does not have a key

25
    Text.text = "You don't have a key for that door"

Reminder: This code does not represent a specific language; you should be able to implement it in any language you want to.

You can also note that we do a check for when the player does not have the expected key, and display a feedback message explaining why the door wasn't unlocked. You can handle checks like those however best suits your game—just be aware that it's always nice to give feedback to your player that his action was registered, and explain the reason why it wasn't completed.

This is a very basic door and lock logic and how to implement it. In the rest of the tutorial, we'll look at other lock systems that are variants of this basic system.

Different Lock Systems

We've seen the basic system where the key is a whole part of the inventory of the character and one key opens all doors and can be reused to open up several doors. Let's build on this.

KeyStack example

In the next example, the character will have a stack of keys in its inventory. Although there are several different colours of door, the difference is strictly graphical here—the door object is logically the same as in the basic example, and one type of key can open any of them. However, this time, whenever you use a key to open a door, that key is removed from the stack.



As far as coding is concerned, this modification is mostly on the character's level. Instead of having a has_key Boolean variable, you will want to have a numeric variable that will hold the number of keys the character has "in stock".

Each time the character picks up a key, add 1 to this variable to represent the stack going up. Each time the character opens a door, subtract 1 from this variable to represent the use of a key. (In video game land, keys are destroyed as soon as they are used once.)

Another modification is to when the player presses the space bar: instead of checking that a has_key Boolean variable is true, we actually want to check that the value of KeyStack is more than zero, so that we can consume a key after opening the door.

In pseudocode, this looks something like:

1
Doors mechanics =  same as in the basic example above.
2
3
Keyboard Key "Space" is pressed
4
and Character.KeyStack > 0
5
and Distance(Character, Door) <= 64
6
and Door.Locked = True
7
    Character.KeyStack = Character.KeyStack - 1
8
    Door.Locked = False

WhichKey Example

In this new example, we will consider a scenario where different type of doors require different types of keys to be unlocked.

Here, as in the first basic example, keys will be part of the character's inventory. We will go back to using Boolean variables to determine whether the character has picked up the requisite keys. And since we will have different keys, we will also have different types of doors (black door, red door, gold door) which will also require an appropriate key to allow them to be opened (black key, red key, gold key, respectively).

The door objects will use different sprites to show their material, and will contain a numeric variable named WhichKey that will indicate the kind of key that is expected as well as the kind of graphic it should display. The different key values are contained as constant variables, for better readability.



In pseudocode:

1
CONSTANT BLACK_KEY = 0
2
CONSTANT RED_KEY = 1
3
CONSTANT GOLD_KEY = 2
4
5
Door mechanics are the same as in the basic example.
6
7
Keyboard Key "Space" is pressed
8
    //The door requires a black key but the character doesn't have one

9
    If Door.Locked = True
10
    and Door.WhichKey = BLACK_KEY
11
    and Character.Has_Black_Key = False
12
    and Distance(Door,Character) <= 64
13
        Text.text="You need a black key for this door"
14
    
15
    //The door requires a red key but the character doesn't have one

16
    Else If Door.Locked = True
17
    and Door.WhichKey = RED_KEY
18
    and Character.Has_Red_Key = False
19
    and Distance(Door,Character) <= 64
20
        Text.text="You need a red key for this door"
21
    
22
    //The door requires a gold key but the character doesn't have one

23
    Else If Door.Locked = True
24
    and Door.WhichKey = GOLD_KEY
25
    and Character.Has_Gold_Key = False
26
    and Distance(Door,Character) <= 64
27
        Text.text="You need a red key for this door"
28
    
29
    //The door requires a black key and the character has one

30
    Else If Door.Locked = True
31
    and Door.WhichKey = BLACK_KEY
32
    and Character.Has_Black_Key = True
33
    and Distance(Door,Character) <= 64
34
        Door.Locked = False
35
    
36
    //The door requires a red key and the character has one

37
    Else If Door.Locked = True
38
    and Door.WhichKey = RED_KEY
39
    and Character.Has_Red_Key = True
40
    and Distance(Door,Character) <= 64
41
        Door.Locked = False
42
    
43
    //The door requires a gold key and the character has one

44
    Else If Door.Locked = True
45
    and Door.WhichKey = GOLD_KEY
46
    and Character.Has_Gold_Key = True
47
    and Distance(Door,Character) <= 64
48
        Door.Locked = False

This is a variation on the basic example that allows several types of keys and doors and that doesn't consume keys to open doors. Once you have the key, it's part of your inventory—part of the character's "statistics".

Switch Example

This time, instead of acting directly on doors, the player has to activate a specific switch to open or close a specific door.

The doors here are essentially the same object as in the basic example. They could display different graphics, but the logic of the object is still the same. There is an addition though: we add two numeric variables DoorID and SwitchID, which we use to know which switch is tied to which door.

Switches are a new type of objects that I have chosen to make solid (but you don't have to). They contain a Boolean variable, Activated, and numeric variables DoorID and SwitchID which, as you can guess, we use to determine which switch is tied to which door.

So when a switch has Activated: True, the "linked" door is set to have Locked: False. Our action with the space bar is then going to happen when next to a switch, rather than next to a door. Note the absence of a key in this example, since the switches acts as keys:

We could just use simple code that checks for the door-switch links in the same room (since this example displays three doors and switches in the same room), but later, we will see that we may have switches acting on doors which are in another room, and so their action won't occur at the exact moment that the player activates the switch; it will occur later on, when the new room is loaded.

For this reason, we need persistence. One option for this is to use arrays to keep track of data like the state of the switches (that is, whether each switch is activated or not).

In pseudocode:

1
CONSTANT SWITCH_DOORID = 0
2
CONSTANT SWITCH_ACTIVATION = 1
3
//Those constants will allow us to keep a readable reminder of the array coordinates

4
<br>//Define some array

5
//The X coordinate of the array will correspond to the SwitchID value

6
//The Y-0 coordinate will be the DoorID

7
//The Y-1 coordinate will be the activation state

8
aSwitch(number of switches,2)
9
//2 is the height (Y) number, often 0-based.

10
<br>Run some association of the SwitchIDs with DoorIDs
11
12
Door mechanic is still the same as in the basic example.
13
14
//Displaying the correct switch graphic according to their activation state

15
Switch.Activated = True
16
    Display the animation frame Switch_ON
17
Switch.Activated = False
18
    Display the animation frame Switch_OFF
19
20
Keyboard Key "Space" is pressed
21
and Distance(Character, Switch) <= 64
22
    Switch.Toggle(Activated) 
23
    //A function that will set the value to either True or False)

24
    aSwitch(Switch.SwitchID,SWITCH_ACTIVATION) = Switch.Activated
25
    //It can depend on your coding language, but the idea is to set the value in the array where X is the SwitchID and where Y is the state of activation of the switch. The value itself is supposed to be the equivalent of the Switch.Activated boolean value.

26
27
Door.DoorID = aSwitch(Switch.SwitchID,SWITCH.DOORID)
28
//Allows us to make sure we're applying/selecting the correct door instance

29
    //Now according to the activation value, we lock or unlock the door

30
    aSwitch(Switch.SwitchID,SWITCH.ACTIVATION) = True
31
        Door.Locked = False
32
    aSwitch(Switch.SwitchID,SWITCH.ACTIVATION) = False
33
        Door.Locked = True

For this specific example, where the switches are in the same room as the doors they are linked to, using the array technique is overkill. If your game is set up in such a way that each switch acting on a door is going to be positioned in the same room, then by all means go for the simpler method, get rid of the array, and check for objects which are on screen only.

Plate-Switch Example

Plate-switches are similar to switches, in the sense that they are either activated or not, and that we can link them to doors to lock or unlock them. The difference lies in how a plate-switch is activated, which is through pressure.

In this top-down view example, the plate-switch will be activated whenever the character is overlapping it. You can press the space bar to drop a rock on the plate-switch, leaving it activated even when the character is not standing on it.

The implementation of this is similar to the previous example, with two little modifications:

  • You have to activate the plate-switch when a character or rock is on top of it.
  • You have to make the space bar drop a rock (from the inventory) on to the plate-switch.
1
//Most of the implementation is the same as the previous example replace the Switch object with PlateSwitch object

2
3
//Plate-Switch Mechanic

4
Character OR Rock is NOT overlapping PlateSwitch
5
    PlateSwitch.Activated = False
6
    aSwitch(PlateSwitch.SwitchID,SWITCH_ACTIVATION) = PlateSwitch.Activated
7
    
8
    Door.DoorID = aSwitch(PlateSwitch.SwitchID,SWITCH.DOORID)
9
    //Allows us to make sure we're applying/selecting the correct door instance

10
        Door.Locked = True
11
12
Character OR Rock is overlapping PlateSwitch
13
    PlateSwitch.Activated = True
14
    aSwitch(PlateSwitch.SwitchID,SWITCH_ACTIVATION) = PlateSwitch.Activated
15
16
    Door.DoorID = aSwitch(PlateSwitch.SwitchID,SWITCH.DOORID)
17
    //Allows us to make sure we're applying/selecting the correct door instance

18
        Door.Locked = False
19
        
20
Keyboard Key "Space" is pressed
21
And Character is overlapping PlateSwitch
22
    Spawn Rock at PlateSwitch.position
23

Mobs Example

Another possible lock mechanic is to require the player to get rid of all enemies (also known as mobs) in a room or area to trigger the unlocking of the doors.

For this example, I've made a few areas in a single room; each area has a door and several mobs (although those enemies don't move and do not deal damage).
Each area has its own colour.

The space bar will make the character fire some projectiles; three projectiles will kill a mob.

This kind of mechanic is used in The Legend of Zelda and The Binding of Isaac, and revolves around a function checking the number of living enemies in the room or area. In this example, each coloured area holds a count of the living mobs, initiated when the room loads, and is tied to the door. The death of each mob subtracts 1 from this counter; once it drops to 0, the door's Locked state is changed to False.

1
//On start of the game

2
For each Area
3
    For each Mob overlapping Area
4
        Area.AliveMobs = Area.AliveMobs + 1
5
<br>Door mechanic is the same as in the basic example<br>
6
Keyboard Key "Space" is pressed
7
    Spawn a Projectile from Character's position

8


9
Projectile collides with Mob

10
    Mob.HP = Mob.HP - 1

11
    Destroy Projectile

12


13
Mob.HP <=0 //Mob is dead

14
and Mob is overlapping Area

15
    Destroy Mob

16
    Area.AliveMobs = Area.AliveMobs - 1

17


18
Area.AliveMobs <= 0

19
and Door is linked to Area //By means of an ID, a pointer or whatever

20
    Door.Locked = False

In this example, an Area is a coloured sprite with an associated numeric variable, AliveMobs, which counts the number of mobs overlapping the area. Once all mobs in an area are defeated, the corresponding door is unlocked (using the same mechanic as we've seen since the basic example).

Navigation Example

As I mentioned in the introduction, doors can act as blocking obstacles, but can also be used to allow the player's character to navigate from a room to another.

In this example, doors will be unlocked by default since we're more interested in the navigation aspect.

The mechanic depends a great deal on the game you are making, as well as the way you handle the data structure for your floors. I won't go into the details of how my implementation works here, as it's very specific to Construct 2, but you can find it in the source files if you wish.

Conclusion

Throughout this article, we've seen how doors are temporary obstacles that require keys or unlocking mechanics such as switches, plate-switches, or even mobs' death. We've also seen how they can act as "bridges", allowing navigation through different areas of the game's world.

As a quick reminder, here are some possible lock mechanics :

  • One key for all doors, as part of the inventory.
  • Consumable keys: each time you open a door, one key gets taken away from your key stack.
  • Different doors require different keys.
  • Switches, or plate-switches, where you don't act directly on the door to unlock it but through a separate, linked device.
  • Killing all the mobs of an area automatically unlocks a door.

If you mixed all those mechanics in a game, you could end up with something like this:

Here we have a nice selection of different door and lock mechanics, requiring the player to go through several rooms to unlock the various doors. For learning purposes, you might want to reproduce this in your own programming environment, using all the previous implementations we've gone through.

I hope you enjoyed this article and that it was useful for you, and I'd like to remind that you can find the source to all the demos on Github. You can open and edit them in Construct 2's free version (version r164.2 or above).

References

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Game Development tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.