A really, really noob question.. easy to answer, I assume. Pickups and Doors

Hey, guys…

Thank you for reading this.

I want one key to open various doors. I have my key in my scene while having this code attached:

using UnityEngine;
using System.Collections;

public class silverKey : MonoBehaviour {

void OnTriggerEnter(Collider other)
    {
        if(other.tag == "Player")
        {
            other.GetComponent<Inventory>().itemNumSilver ++;
            Destroy(gameObject);
        }
    }
}

The following code is on my player as an inventory:

using UnityEngine;
using System.Collections;

public class Inventory : MonoBehaviour {
    /* Increase total item count per pickup
    */
    public int itemNumSilver = 0;

    void update()
    {
        print(itemNumSilver);
    }
}

The following is the code on my door that I want to unlock once a player has picked up the key:

using UnityEngine;
using System.Collections;

public class DoorUsed : MonoBehaviour {

    //private variable for our animator
    Animator anim;
    private readonly bool ontrigger;

    void Start () {

        anim = GetComponent<Animator>();
    }

    void Used ()
    {
        anim.SetBool("interact", false); ;
    }
}

How, when, where to place C# code that looks to see if the key has been picked up and if true, door can be opened?

TIA

Are you wanting the door to unlock when the player picks up the key, or when the player touches or uses the door with the key in the inventory? If you’re wanting it to happen when the player touches the door, you can do it similarly to how you handled collision with the key. Just have the door check if the player has the key when the player collides with the door.

You can access scripts attached to other objects using GetComponent<>().

http://unity3d.com/learn/tutorials/modules/beginner/scripting/getcomponent

DoorUsed doesn’t seem like a clear idea for a class. I’m not really sure what it’s supposed to represent. It’s called “DoorUsed” but it’s handling animation…? Classes should usually have nouns as their names because they should represent objects (or at least hypothetical objects).

If the key and the door are both in your scene (rather than instantiated at runtime), you can easily access the scripts from each other.

For example, you could have your Door class (make a Door class that represents the door itself) and your SilverKey class.

Give a member variable to the SilverKey class like this:

public Door doorToOpen;

where Door is the name of your Door class, and doorToOpen would be a variable that holds a reference to the Door object.

Now click on one of your keys in your scene hierarchy, and in the inspector you should see under the SilverKey script component, there is a Door To Open field. You can just drag the Door component from your door object that you want to open into this slot. You can also click the little selector thing and a list of objects in the scene will appear, and you can choose the object with the attached script you want to reference there.

Then you’ll have access to that specific script with your doorToOpen variable.

So if, for example, your Door class had a method like “Unlock()” that opened the door, you could easily access it from within your SilverKey class/script like this:

doorToOpen.Unlock();

If you need to instantiate the keys at runtime (rather than having them in the scene from the start), there are a variety of ways you could initialize the doorToOpen variable. For example, you could have an method in your SilverKey class like this:

IdentifyDoor(Door d)
{
    doorToOpen = d;
}

then you could just call it from a variety of places. If for some reason, you want the door itself to instantiate the key, you could have the Door instantiate the key and then just call the IdentifyDoor method immediately afterward, passing “this” as the parameter. Or, you could have some kind of key generator object in your scene that instantiates keys and then calls the method that has the door it ‘injects’ into the keys set in the inspector like described before.

You can also tag your door with a ‘Door’ tag and then have the SilverKey class find it with FindObjectWithTag(“Door”).GetComponent(). Note that in this example, the first “Door” is the string identifying the tag, and the second Door is the name of the class/script component on the object that will be found.

@Thaao 's answer’s good.

Here’s some example code from the game we’re making, where a door is opened if the player walks into a collider next to it while carrying a key of the correct type. So any bronze/silver/gold key can be used with any bronze/silver/gold door respectively.

//On the door:

public Animator doorAnim; //Animator has an "Open" animation that moves the door.

void OnTriggerEnter(Collider other) {
    KeyInventory inventory = other.GetComponentInChildren<KeyInventory>();
    if(inventory == null)
        return; //The thing that walked into the door didn't have a key inventory
   
    if(inventory.HasKey(myKeyType)) { //myKeyType is an enum.
        inventory.UseKey(myKeyType); //removes the key from the inventory
        doorAnim.SetTrigger("Open");
    }
}

This is somewhat simplified, but it should be enough to get you going if that’s the kind of keys you’re working with.

Just getting back in the thread. Sorry for the delay. I’ll be sure to learn as I implement. Thank you all very much. Will update soon!

@Thaao
I think I created a door class… however, I am so new to this, I may have goofed somewhere along the lines. I also created a public class in my silverKey, and dragged my door from my hierarchy onto the newly available space. Here is the door class:

using UnityEngine;
using System.Collections;

public class Door : MonoBehaviour
{
    public GameObject hinge;
    private silverKey silverKey;

    Animator anim;


    void Start()
    {
        anim = hinge.GetComponent<Animator>();
    }

    void OnTriggerEnter(Collider entity)
    {
        if (entity.tag == "Player")
        {
            print("The Player has entered the trigger.");
        }
    }

    void OnTriggerStay(Collider entity)
    {
        if (entity.tag == "Player")
        {
            if (Input.GetKeyUp(KeyCode.E))
            {
                anim.SetBool("interact", true);
                print("The Player has interacted with the door.");
            }
        }
    }

    void OnTriggerExit(Collider entity)
    {
        if (entity.tag == "Player")
        {
            print("The Player has left the trigger.");
        }
    }
}

What I noticed is that the door still opens regardless of me having the key or not… I know that I’m asking a lot. I watched the video on GetComponent but for some reason I think that I’m making this harder than necessary.

    void OnTriggerStay(Collider entity)
    {
        if (entity.tag == "Player")
        {
            if (Input.GetKeyUp(KeyCode.E))
            {
                anim.SetBool("interact", true);
                print("The Player has interacted with the door.");
            }
        }
    }

If this is where you want to unlock the door, at this point, you need to check if the player has the key.

Since you know you have the player at this point, you can use GetComponent to gain access to your player’s Inventory script.

for example:

entity.GetComponent<Inventory>()

will return the Inventory object attached to entity, if it has one.

You can store it in a variable if you need to use it more than once:

Inventory i = entity.GetComponent<Inventory>();
if (i.itemNumSilver > 0)
{
  // open the door
}

Or if you only need to use it once, you can do it all in one line:

if (entity.GetComponent<Inventory>().itemNumSilver > 0)
{
  // open the door
}

Now that I notice this, you’ve already done something quite similar when adding to itemNumSilver in your SilverKey class.

You can always use GetComponent to access the scripts and any component on any other GameObject this way.

Note that this is just an example to make the door open if the player has at least one Silver Key, using what you already have. Also, I’m not sure how all your stuff is set up, so you might need to use something like GetComponentInChildren depending on what exactly is colliding.

It doesn’t take into account that GetComponent<> can return null, and it doesn’t change the amount of keys the player has after using one.

Note that exposing variables like itemNumSilver to other classes is generally not a good thing – if you look in @Baste 's example, you can see rather than directly working with variables, other classes outside the KeyInventory class interact with it by calling methods inside it. This way they can’t accidentally mess up the data of the KeyInventory class, plus if you need to change something (say, for example, later, you change how keys work, you’d have to go and change everywhere you interacted with itemNumSilver – so you’d have to go into your SilverKey class and change that, then go into your Door class and change that… but if they all just called functions like “AddKey()” or “CheckForKey()” or something, you only have to change the insides of those methods within the Inventory class.

I know that’s a bit outside the scope of your question, but encapsulation can save you a lot of frustration later. Even a fairly simple game can get way out of hand and be very difficult to maintain and change when there is a lot of exposed information being edited from all over the place.

I’ve been working on this scene for one week. This is the first roadblock I’ve had to face. I’m still not able to make this work. In fact, I’ve frozen unity twice… I may have to step away from my computer before I become even more aggravated.

I freeze Unity a lot myself, too. If you’re feeling too frustrated, definitely take a break. It’s so much harder to think properly if you’re aggravated. You might actually have an epiphany while you’re away from the computer :slight_smile:

Played some Payday 2. Alright… now that I feel a wee bit better. I think that the only solution is to start anew. However, I think that I need to keep my Door script as is. Also, I think an inventory.cs and a key(type).cs are needed as well. How should I approach?

From the below, place Inventory.cs on player… key.cs on Key… and Door.cs on all my doors. (One key to rule them all).


Inventory.cs

using UnityEngine;
using System.Collections;

public class Inventory : MonoBehaviour
{
     public int hasKey = 0;
}

Key.cs

using UnityEngine;
using System.Collections;

public class Key : Monobehaviour
{
     public Door doorToOpen;
     void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
}
}

Door.cs

using UnityEngine;
using System.Collections;

public class Door : MonoBehaviour
{
    public GameObject hinge;
    private silverKey silverKey;

    Animator anim;


    void Start()
    {
        anim = hinge.GetComponent<Animator>();
    }

    void OnTriggerEnter(Collider entity)
    {
        if (entity.tag == "Player")
        {
            print("The Player has entered the trigger.");
        }
    }

    void OnTriggerStay(Collider entity)
    {
        if (entity.tag == "Player")
        {
            if (Input.GetKeyUp(KeyCode.E))
            {
                anim.SetBool("interact", true);
                print("The Player has interacted with the door.");
//from your earlier suggestion
                 entity.GetComponent<Inventory>();               
                {

                }
            }
        }
    }

    void OnTriggerExit(Collider entity)
    {
        if (entity.tag == "Player")
        {
            print("The Player has left the trigger.");
        }
    }
}

Where would I add your suggestions from earlier? I tried them all in various spots. I’m only using the above scripts now.

Basically, in my scene I have one key that needs to open roughly 9 doors. (There are more keys in my scene and I believe if I’m able to pass this area I can replicate for the others.) The other keys (2 more) are to be used on an elevator. IF player has both goldKeyOne && goldKeyTwo GWC has been met… /end level.

actually… in Inventory.cs

public class Inventory : MonoBehaviour
{
public bool hasKey = false;
}

how to pick a key up and change this bool to true and if true door will open?

I’m here all night… and tomorrow.

Update:

Here are my current scripts… and I’ll show you the error message (only one)


Inventory.cs

using UnityEngine;
using System.Collections;

public class Inventory : MonoBehaviour
{

    public bool hasMasterKey = false;

  
}

Door.cs
//have done majority of work in this one

using UnityEngine;
using System.Collections;

public class Door : MonoBehaviour
{
    public GameObject hinge;
    private masterKey masterKey;

    Animator anim;


    void Start()
    {
        anim = hinge.GetComponent<Animator>();
    }

    void OnTriggerEnter(Collider entity)
    {
        if (entity.tag == "Player")
        {
            print("Player has entered door range");
        }
        Inventory i = entity.GetComponent<Inventory>();
    
    }

    void OnTriggerStay(Collider entity)
    {
        if (entity.tag == "Player")
        {
            print("Ok.. you've made contact with the door");
            if (Input.GetKeyUp(KeyCode.E))
            {
                if (GetComponent<Inventory>().hasMasterKey = true) ;
                {
                    anim.SetBool("interact", true);
                    print("Success");
                }
               

            }
        }
    }

    void OnTriggerExit(Collider entity)
    {
        if (entity.tag == "Player")
        {
            print("The Player has left the trigger.");
        }
    }
}

masterKey.cs

using UnityEngine;
using System.Collections;

public class masterKey : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        Destroy(gameObject);
         
    }
    void OnTriggerUpdate(Collider other)
    {
        GetComponent<Inventory>().hasMasterKey = true;
    }
}

Error Message in Unity
I can approach the door. The print to console simple repeats itself until I leave the collider.
Upon pressing E, the game will freeze and state,
"NullReferenceException: Object reference not set to an instance of an object Door.OnTriggerStay (UnityEngine.Collider entity) (at assetts etc etc / Door.cs:35)

Thoughts…?

In Door.cs:

In the OnTriggerEnter method, you get the component Inventory from entity and assign its reference to variable i. But then the method is over, so it goes out of scope and is lost. Essentially, this line does nothing.

In the OnTriggerStay method, in this line:

if (GetComponent<Inventory>().hasMasterKey = true) ;

if you just use GetCompnent, you’re going to be trying to find the component on the GameObject running the script it’s called from. That means that here, GetComponent is trying to find an Inventory script that is attached to your Door object, which is not what you want. You want to see if there is one attached to the object that collided with the door, so you want to use entity.GetComponent here.

Also, whenever you get a null reference exception, check the line it’s on and see why you would be trying to work with something null there. In this case, because you are calling GetComponent on your door game object, and your door does not have its own Inventory script, it is returning null. But then you are trying to access the hasMasterKey bool of a script that does not exist (is null), so you get the Null Reference Exception.

So you want to change this line to

if (entity.GetComponent<Inventory>().hasMasterKey = true) ;

Also, note that if the player continues to press and release the E key while the collider is inside, it will repeatedly run this part of the script. If that’s not desired, you may want to create a bool in Door.cs that lets the door know it’s been unlocked so it will only do it the first time.

Null reference exceptions will almost always crash your game, so when you’re looking for an object from somewhere else, it’s good to ‘safeguard’ yourself with something like this:

if (entity.tag == "Player")
        {
            print("Ok.. you've made contact with the door");
            if (Input.GetKeyUp(KeyCode.E))
            {
                Inventory i = entity.GetComponent<Inventory>();
                if (i != null && !unlocked i.hasMasterKey = true) ;
                {
                    unlocked = true;
                    anim.SetBool("interact", true);
                    print("Success");
                }
              

            }
        }

This adds a check to see if GetComponent didn’t return null, and a check to see if the door is already unlocked (so it won’t try to unlock it if it’s already unlocked). Note you’ll have to initialize this variable with class scope, of course, if you want to use something like that.

Thank you, Thaao! I forgot to mention that I was truly appreciative of your efforts.