BGE: Basic Door & Key Logic
A door key question deserves a dorky answer, so here goes...

I set up a Player and a Non-Player Character (NPC) which could just as easily serve as Player 2 in a 2-player game. Here I used it just for testing, although in a game it could serve as a scripted enemy or ally, or whatever. The point is that it can acquire keys just like the Player can.

Character Properties



These Boolean values go from 0 to 1 (False to True) when a key is acquired by the character.

(NPC has the same Properties as Player, except that it has the Property npc instead of player.)

Each character has its movement controls and an interaction control which can be used to (among other things) open doors when it is near them, and has the required key. Interaction from each character sends a unique message (either player_interact or npc_interact).

In order to add multi-key support, each character has a Sensor for each key, which all get fed into a Python Script Controller (keys.py).

keys.py

import bge
cont = bge.logic.getCurrentController()
own = cont.owner

key_alpha_status = cont.sensors["GetKeyAlpha"]
if key_alpha_status.positive:
    own['has_key_alpha'] = True
    print("Alpha Key acquired!")

key_beta_status = cont.sensors["GetKeyBeta"]
if key_beta_status.positive:
    own['has_key_beta'] = True
    print("Beta Key acquired!")

key_gamma_status = cont.sensors["GetKeyGamma"]
if key_gamma_status.positive:
    own['has_key_gamma'] = True
    print("Gamma Key acquired!")

key_delta_status = cont.sensors["GetKeyDelta"]
if key_delta_status.positive:
    own['has_key_delta'] = True
    print("Delta Key acquired!")

key_epsilon_status = cont.sensors["GetKeyEpsilon"]
if key_epsilon_status.positive:
    own['has_key_epsilon'] = True
    print("Epsilon Key acquired!")

key_zeta_status = cont.sensors["GetKeyZeta"]
if key_zeta_status.positive:
    own['has_key_zeta'] = True
    print("Zeta Key acquired!")


Character Logic



The key objects themselves don't play a very active role in the logic. If touched by something having the property player or the property npc the given key will disappear.

Key Logic



Each door object animates through various States, the important thing being that animation is initiated upon receiving a Message unique to that door (open_door01, open_door02, and so on).

Each door has a corresponding sensor object that is an Invisible physics Ghost. It's just an area within which collision can be detected, as one condition that needs to be met for a door to be opened. 

Door Sensor Object - Physics Settings



We don't want it to be possible for a player to open a door from across the room - unless that door is intended to be opened by a remote switch, in which case the sensor can be moved to the location of the switch instead of surrounding the door.

Each door sensor object's game logic does two things: 

(1) Checks if a character is colliding with the sensor object (using two Collision Sensors that listen for player and npc Game Properties respectively), and (2) Checks if the character is attempting interaction (by listening for either a player_interact or npc_interact Message). This input gets routed to a Python Script Controller for that specific door type (door_alpha.py, door_beta.py, and so on).

Door Sensor Object Logic



Someone with better programming skills than myself could surely find a way to eliminate the need for individual scripts for each door/key type, but at least this works. It just requires preparing one .py script of each door/key type by doing a Find/Replace in a text editor to change require_key_beta and has_key_beta to require_key_gamma and has_key_gamma... if you follow what I mean. I've only prepared Alpha and Beta types for this demo, since there are only two keys. How many key types do you realistically need? (6 should be plenty?)

door_alpha.py

import bge
cont = bge.logic.getCurrentController()
own = cont.owner

p_collision = cont.sensors["PlayerColl"]
p_interaction = cont.sensors["PlayerInteract"]

npc_collision = cont.sensors["NPCColl"]
npc_interaction = cont.sensors["NPCInteract"]

open_door = cont.actuators["OpenDoor"]


### PLAYER ###

#If this door requires a key, and player is colliding with it, and colliding player has the key, and a player interaction message is received:
if own['require_key_alpha'] == True and p_collision.positive and p_collision.hitObject['has_key_alpha'] == True and p_interaction.positive:
    print("Door unlocked")
    cont.activate(open_door)

#If this door requires a key, and player is colliding with it, and colliding player does NOT have the key, and a player interaction message is received:
if own['require_key_alpha'] == True and p_collision.positive and p_collision.hitObject['has_key_alpha'] == False and p_interaction.positive:
    print("Can't unlock this door without a key")


### NON-PLAYER CHARACTER ###

#If this door requires a key, and non-player is colliding with it, and colliding non-player has the key, and a non-player interaction message is received:
if own['require_key_alpha'] == True and npc_collision.positive and npc_collision.hitObject['has_key_alpha'] == True and npc_interaction.positive:
    print("Door unlocked")
    cont.activate(open_door)

#If this door requires a key, and non-player is colliding with it, and colliding non-player does NOT have the key, and a non-player interaction message is received:
if own['require_key_alpha'] == True and npc_collision.positive and npc_collision.hitObject['has_key_alpha'] == False and npc_interaction.positive:
    print("Can't unlock this door without a key")


The resulting output of the Python script (example:door_alpha.py) from the given door sensor object will activate a Message Actuator, which will send a Message with a subject such as open_door01, which I mentioned earlier. 

Here are the parts of that code to pay attention to:

open_door = cont.actuators["OpenDoor"]


and within the IF statements:

cont.activate(open_door)


You know how the rest goes - corresponding door object receives the message, the animation of the door opening is triggered. Our player progresses and presumably has fun.

As for the animation of the doors, there are several ways to do it. Most will involve switching States. On my doors there are four States: Initial, Opening, Open, Closing. Closing loops back to Initial, in other words Initial is the same as Closed. 

States can't be labeled directly, but labeling the State Actuator's destination makes it easier to keep track of what State should come next (In mine, the State Actuator of Initial is labeled "To Opening", for example).

Door Object Logic: Animating Through States



Door01 is basic, using Delay Sensors and Motion Actuators. For Door02 I wanted more control in animating individual parts of the door, so it makes use of Property Sensors (that evaluate if the Property frame is equal to a given frame, then change the State accordingly) and Action Actuators that play specific frame ranges of the animated Action and set the value of frame. Because Door02's animation is done using Actions, it is actually an Armature Object, with Mesh Objects parented to its animated Bones.

I think that covers everything. Enjoy playing with the .blend. If anyone has questions, comments, or ideas for improvement, I'm all ears.



(Made with Blender 2.79b)

Demo file screenshot: