Search Unity

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

Discussion in 'Scripting' started by bTaY225, Oct 10, 2015.

  1. bTaY225

    bTaY225

    Joined:
    Oct 24, 2013
    Posts:
    12
    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:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class silverKey : MonoBehaviour {
    6.  
    7. void OnTriggerEnter(Collider other)
    8.     {
    9.         if(other.tag == "Player")
    10.         {
    11.             other.GetComponent<Inventory>().itemNumSilver ++;
    12.             Destroy(gameObject);
    13.         }
    14.     }
    15. }
    16.  
    The following code is on my player as an inventory:

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class Inventory : MonoBehaviour {
    6.     /* Increase total item count per pickup
    7.     */
    8.     public int itemNumSilver = 0;
    9.  
    10.     void update()
    11.     {
    12.         print(itemNumSilver);
    13.     }
    14. }
    15.  
    The following is the code on my door that I want to unlock once a player has picked up the key:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class DoorUsed : MonoBehaviour {
    5.  
    6.     //private variable for our animator
    7.     Animator anim;
    8.     private readonly bool ontrigger;
    9.  
    10.     void Start () {
    11.  
    12.         anim = GetComponent<Animator>();
    13.     }
    14.  
    15.     void Used ()
    16.     {
    17.         anim.SetBool("interact", false); ;
    18.     }
    19. }
    20.  
    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
     
  2. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    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:

    Code (CSharp):
    1. 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:

    Code (CSharp):
    1. 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:

    Code (CSharp):
    1. IdentifyDoor(Door d)
    2. {
    3.     doorToOpen = d;
    4. }
    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<Door>(). 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.
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    @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.

    Code (CSharp):
    1. //On the door:
    2.  
    3. public Animator doorAnim; //Animator has an "Open" animation that moves the door.
    4.  
    5. void OnTriggerEnter(Collider other) {
    6.     KeyInventory inventory = other.GetComponentInChildren<KeyInventory>();
    7.     if(inventory == null)
    8.         return; //The thing that walked into the door didn't have a key inventory
    9.    
    10.     if(inventory.HasKey(myKeyType)) { //myKeyType is an enum.
    11.         inventory.UseKey(myKeyType); //removes the key from the inventory
    12.         doorAnim.SetTrigger("Open");
    13.     }
    14. }
    This is somewhat simplified, but it should be enough to get you going if that's the kind of keys you're working with.
     
  4. bTaY225

    bTaY225

    Joined:
    Oct 24, 2013
    Posts:
    12
    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!
     
  5. bTaY225

    bTaY225

    Joined:
    Oct 24, 2013
    Posts:
    12
    @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:
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Door : MonoBehaviour
    5. {
    6.     public GameObject hinge;
    7.     private silverKey silverKey;
    8.  
    9.     Animator anim;
    10.  
    11.  
    12.     void Start()
    13.     {
    14.         anim = hinge.GetComponent<Animator>();
    15.     }
    16.  
    17.     void OnTriggerEnter(Collider entity)
    18.     {
    19.         if (entity.tag == "Player")
    20.         {
    21.             print("The Player has entered the trigger.");
    22.         }
    23.     }
    24.  
    25.     void OnTriggerStay(Collider entity)
    26.     {
    27.         if (entity.tag == "Player")
    28.         {
    29.             if (Input.GetKeyUp(KeyCode.E))
    30.             {
    31.                 anim.SetBool("interact", true);
    32.                 print("The Player has interacted with the door.");
    33.             }
    34.         }
    35.     }
    36.  
    37.     void OnTriggerExit(Collider entity)
    38.     {
    39.         if (entity.tag == "Player")
    40.         {
    41.             print("The Player has left the trigger.");
    42.         }
    43.     }
    44. }
    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.
     
  6. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    Code (CSharp):
    1.     void OnTriggerStay(Collider entity)
    2.     {
    3.         if (entity.tag == "Player")
    4.         {
    5.             if (Input.GetKeyUp(KeyCode.E))
    6.             {
    7.                 anim.SetBool("interact", true);
    8.                 print("The Player has interacted with the door.");
    9.             }
    10.         }
    11.     }
    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:

    Code (CSharp):
    1. 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:

    Code (CSharp):
    1. Inventory i = entity.GetComponent<Inventory>();
    2. if (i.itemNumSilver > 0)
    3. {
    4.   // open the door
    5. }
    Or if you only need to use it once, you can do it all in one line:

    Code (CSharp):
    1. if (entity.GetComponent<Inventory>().itemNumSilver > 0)
    2. {
    3.   // open the door
    4. }
    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.
     
  7. bTaY225

    bTaY225

    Joined:
    Oct 24, 2013
    Posts:
    12
    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.
     
  8. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    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 :)
     
  9. bTaY225

    bTaY225

    Joined:
    Oct 24, 2013
    Posts:
    12
    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

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Inventory : MonoBehaviour
    5. {
    6.      public int hasKey = 0;
    7. }
    ___________________________________
    Key.cs

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Key : Monobehaviour
    5. {
    6.      public Door doorToOpen;
    7.      void OnTriggerEnter(Collider other)
    8. {
    9. Destroy(gameObject);
    10. }
    11. }
    ___________________________________
    Door.cs

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Door : MonoBehaviour
    5. {
    6.     public GameObject hinge;
    7.     private silverKey silverKey;
    8.  
    9.     Animator anim;
    10.  
    11.  
    12.     void Start()
    13.     {
    14.         anim = hinge.GetComponent<Animator>();
    15.     }
    16.  
    17.     void OnTriggerEnter(Collider entity)
    18.     {
    19.         if (entity.tag == "Player")
    20.         {
    21.             print("The Player has entered the trigger.");
    22.         }
    23.     }
    24.  
    25.     void OnTriggerStay(Collider entity)
    26.     {
    27.         if (entity.tag == "Player")
    28.         {
    29.             if (Input.GetKeyUp(KeyCode.E))
    30.             {
    31.                 anim.SetBool("interact", true);
    32.                 print("The Player has interacted with the door.");
    33. //from your earlier suggestion
    34.                  entity.GetComponent<Inventory>();              
    35.                 {
    36.  
    37.                 }
    38.             }
    39.         }
    40.     }
    41.  
    42.     void OnTriggerExit(Collider entity)
    43.     {
    44.         if (entity.tag == "Player")
    45.         {
    46.             print("The Player has left the trigger.");
    47.         }
    48.     }
    49. }
    ___________________________________________
    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.
     
  10. bTaY225

    bTaY225

    Joined:
    Oct 24, 2013
    Posts:
    12
    actually.. in Inventory.cs

    Code (CSharp):
    1. public class Inventory : MonoBehaviour
    2. {
    3. public bool hasKey = false;
    4. }
    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.
     
  11. bTaY225

    bTaY225

    Joined:
    Oct 24, 2013
    Posts:
    12
    Update:

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

    __________________________________________________________
    Inventory.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Inventory : MonoBehaviour
    5. {
    6.  
    7.     public bool hasMasterKey = false;
    8.  
    9.  
    10. }
    ___________________________________________________________
    Door.cs
    //have done majority of work in this one
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Door : MonoBehaviour
    5. {
    6.     public GameObject hinge;
    7.     private masterKey masterKey;
    8.  
    9.     Animator anim;
    10.  
    11.  
    12.     void Start()
    13.     {
    14.         anim = hinge.GetComponent<Animator>();
    15.     }
    16.  
    17.     void OnTriggerEnter(Collider entity)
    18.     {
    19.         if (entity.tag == "Player")
    20.         {
    21.             print("Player has entered door range");
    22.         }
    23.         Inventory i = entity.GetComponent<Inventory>();
    24.    
    25.     }
    26.  
    27.     void OnTriggerStay(Collider entity)
    28.     {
    29.         if (entity.tag == "Player")
    30.         {
    31.             print("Ok.. you've made contact with the door");
    32.             if (Input.GetKeyUp(KeyCode.E))
    33.             {
    34.                 if (GetComponent<Inventory>().hasMasterKey = true) ;
    35.                 {
    36.                     anim.SetBool("interact", true);
    37.                     print("Success");
    38.                 }
    39.                
    40.  
    41.             }
    42.         }
    43.     }
    44.  
    45.     void OnTriggerExit(Collider entity)
    46.     {
    47.         if (entity.tag == "Player")
    48.         {
    49.             print("The Player has left the trigger.");
    50.         }
    51.     }
    52. }
    ______________________________________________________________
    masterKey.cs

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class masterKey : MonoBehaviour
    5. {
    6.     void OnTriggerEnter(Collider other)
    7.     {
    8.         Destroy(gameObject);
    9.          
    10.     }
    11.     void OnTriggerUpdate(Collider other)
    12.     {
    13.         GetComponent<Inventory>().hasMasterKey = true;
    14.     }
    15. }
    16.    
    ___________________________________________________________
    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...?
     
  12. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    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:

    Code (CSharp):
    1. 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

    Code (CSharp):
    1. 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.
     
  13. Thaao

    Thaao

    Joined:
    Jun 9, 2014
    Posts:
    54
    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:

    Code (CSharp):
    1. if (entity.tag == "Player")
    2.         {
    3.             print("Ok.. you've made contact with the door");
    4.             if (Input.GetKeyUp(KeyCode.E))
    5.             {
    6.                 Inventory i = entity.GetComponent<Inventory>();
    7.                 if (i != null && !unlocked i.hasMasterKey = true) ;
    8.                 {
    9.                     unlocked = true;
    10.                     anim.SetBool("interact", true);
    11.                     print("Success");
    12.                 }
    13.              
    14.  
    15.             }
    16.         }
    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.
     
  14. bTaY225

    bTaY225

    Joined:
    Oct 24, 2013
    Posts:
    12
    Thank you, Thaao! I forgot to mention that I was truly appreciative of your efforts.