How to acess a script in a public variable

I’m attempting to make an item and interaction system that allows you to open certain doors with keys, use crowbars on nails, etc. Unfortunately, unique action and item script names must be used, and I’m not sure how to access their Interact function using my Raycast without the script name. I tried attaching a simple script to ALL the interactables and accessing the unique script with a public variable, but it didn’t work. Any ideas on how to do this :face_with_spiral_eyes:?

Raycast() can be configured to return you a RaycastHit object.

That RaycastHit object contains the Collider instance that you may have hit.

You can use GetComponent() on that Collider to get at other scripts on the thing you hit.

Relatedly this is a potential place to implement an interface, such as something like IInteractible, which you would implement in any scripts that you could interact with, which would save you from having to hard-code every type of thing you can interact with.

Every Interactable object in my game has it’s own unique interaction script with an Interact() function in it. If I use Collider.GetComponent, I can’t get at the Interact() function because it thinks I am accessing the Component class. Also, I’m not seeing how that would remove the need of hard-coding every script name. My ItemSystem script is kinda like this: How to Pick Up Items in Unity? - Patryk Galach.

This is the beauty of interfaces in Unity. Look up some tutorials. It’s seriously awesome.

This interface:

public interface IOpenable { void Open(); }

could be implemented in any Monobehavior you want:

public class Door : MonoBehaviour, IOpenable {}
public class Window : MonoBehaviour, IOpenable {}
public class JarOfPeanutButter : MonoBehaviour, IOpenable {}
public class JackInTheBox : MonoBehaviour, IOpenable {}

etc.

You would GetComponent<IOpenable>() and if you got something, you could confidently call its .Open() method, where each type of thing would implement its own “what it means for me to open” mechanism.

OK, Thanks. I have never used Interfaces before, I kinda overlooked them. They seem to be exactly what I need though. It was only returning an error because I wasn’t getting my script from the Interface LOL.

I did what said and implemented an interface in my code, as well as taking a tutorial. Unfortunately, the hit (my RayCastHit).collider.GetComponent code returns a NullReferenceError when I interact with my door. What am I doing wrong?

You are doing too many steps at once. See the documentation for RaycastHit.collider:

This means that if you assume that your raycast will always hit an object and blindly try to access the component from it, you get a NullReferenceError in cases when your raycast did not hit an object, because the property collider is null while you are trying to access a component of that.

You should add checks to ensure that everything works as intended, as the example in Physics.Raycast illustrate:

RaycastHit hit;
if (Physics.Raycast(transform.position, transform.TransformDirection(Vector3.forward), out hit, Mathf.Infinity, layerMask))
{
    IOpenable openable = hit.collider.GetComponent<IOpenable>();
    if(openable != null)
    {
         openable.Open();
    }
}

If you do this and you still get an error/no object is being hit, make sure that the object with the IOpenable component has the correct LayerMask and that you use the correct layers in the raycast itself. Also look at the distance of your raycast and make sure that it is long enough.

Gizmos.DrawRay can help to identify problems with the raycast, although you might need to use Gizmos.DrawLine for raycasts with a limited distance, since a ray is infinite, judging by Ray.

Exactly everything that @Ardenian says above… it’s always a good idea to do one step per line, such as:

  • do the raycast
  • check if you hit anything
  • if you did, ask for your IOpenable
  • if you get one, call .Open()

Unravelling this all to one step per line lets you reason about it when you do get a null reference and you can go “Oh, I need to check if THAT part is null…”

I covered breaking it apart in this short little blog:

http://plbm.com/?p=248

Actually, never mind. My code is fine, I just accidentally removed the door script while modifying the collider. I’ll remember that advice if it happens again though!

Okay, last question: What if the interactable is a Prefab? In this case, I have my Locked_Door Prefab with Lock and Door GameObjects. Both objects have colliders, so I guess I have to acess the script in the main Locked_Door Prefab, right? However, the RaycastHit.collider returns the child object’s collider, not the parent. I also need to know whether I hit the lock or the door for my IInteractable to work. On TOP of all that, not all IInteractables are Prefabs.

You don’t interact with prefabs. Prefabs just exist on disk.

You must Instantiate() prefabs first, then they are in the scene and indistinguishable from anything else in the scene.

As Kurt-Dekker already said, you don’t interact with prefabs, but with copies of that prefab that you create in one way or another. A Prefab is used in the editor to define the composition of an object, creating a template.

If you instantiate such an object into the scene, either by dragging it into the scene hierarchy in the editor or through code at runtime, you interact with these copies, not with the original prefab.

What do you think about making the lock and the door two separate GameObjects? If the player tries to open the door, check in your script of the door whether the lock is open or not and make a decision based on that.

OK, I made them separate GameObjects, but what I really wanted to know was how to access the isLocked variable in my lock IInteractable from the door IInteractable. When I use GetComponentInChildren, it just errors out.

Never mind. It was just bad syntax on my part. Here’s my finished code: Door

public class Door : MonoBehaviour, IInteractable
{
    public void Interact(GameObject item)
    {
        if (GetComponentInChildren<Lock>().unlocked)
            Debug.Log("Opened Door");
    }
}

Lock

public class Lock : MonoBehaviour, IInteractable
{
    public bool unlocked = false;
   
    public void Interact(GameObject item)
    {
        if (item && item.name == "Key")
            unlocked = true;
    }
}

Thanks for all the help!