Two different objects, same tag, same script, wrong function being called

Salutations,

I'm trying to create a script in C# that can allow the player to open and close doors and drawers. To save myself any extra work, I've attempted to kill two birds with one stone by deciding to tag both doors and drawers as "Door", as well as using the same script on them. Since both share the same script, I decided to identify them through the use of a private integer, configurable in the Unity inspector, in order to differentiate between the two. I'm trying to make the functionality different depending on the integer ID, and if the door/drawer is open or not. I don't want doors to behave like drawers, and vice versa, so I thought providing an integer value would be beneficial if I'm going to use the same script on both objects.

I made sure both objects have this script and are both tagged as "Door". Both have their own integer values set in the inspector at the "Door ID" field. I named my door object "Office Door" with a value of 0 (for door objects), and named my drawer object as "Drawer" with a value of 1 (for drawer objects).

My problem, however, is that while this script works for my door named "Office Door" object, it's not working for my drawer object named "Drawer". For one, my drawer object thinks it's named "Office Door", despite having named it "Drawer" in the inspector. Additionally, while checking Debug.Log() at playtime, I can see that the OpenDoor()/CloseDoor() functions are being called on the drawer and not OpenDrawer()/CloseDrawer(), so it still thinks it's a door.

Apologies for my messy code,

Door.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Door : MonoBehaviour
{
    // Door Identifier
    // 0 is swing door (regular)
    // 1 is pull-out drawer
    [SerializeField] int _doorID = 0;

    private float _drawerSpeed = 1f; // Currently unused

    [SerializeField] private string doorName = "Door";
    private PlayerInteractiveBubble _playerBubble;

    // Boolean variables
    private bool _iAmOpen = false;


    void Start() {
        _playerBubble = GameObject.Find("Player/InteractiveBubble").GetComponent<PlayerInteractiveBubble>();
    }

    void Update() {
        
    }

    public void ManageDoor() {
        if (_iAmOpen) {
            if (_doorID == 0) {
                CloseDoor();
            } else if (_doorID == 1) {
                CloseDrawer();
            }
        } else if (!_iAmOpen) {
            if (_doorID == 0) {
                OpenDoor();
            } else if (_doorID == 1) {
                OpenDrawer();
            }
        }
    }

    private void OpenDoor() {
        if (_playerBubble.doorIsInRange) {
            _iAmOpen = true;
            Debug.Log("Door: " + doorName.ToString() + " is open.");
        }
    }

    private void CloseDoor() {
        if (_playerBubble.doorIsInRange) {
            _iAmOpen = false;
            Debug.Log("Door: " + doorName.ToString() + " is now closed.");
        }
    }

    private void OpenDrawer() {
        if (_playerBubble.doorIsInRange) {
            // Todo: add new Vector3, plus 0.26f on Z-axis
            _iAmOpen = true;
            Debug.Log("Drawer: " + doorName.ToString() + " is open.");
        }
    }

    private void CloseDrawer() {
        if (_playerBubble.doorIsInRange) {
            // Todo: add new Vector3, minus 0.26f on Z-axis
            _iAmOpen = false;
            Debug.Log("Drawer: " + doorName.ToString() + " is closed.");
        }
    }
}

My function gets called inside the Update() function of PlayerController.cs:

        if (Input.GetButtonDown("Open")) {
            _door.ManageDoor();
        }

Here is where I'm checking my collision, PlayerInteractiveBubble.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerInteractiveBubble : MonoBehaviour
{
    public bool doorIsInRange = false;

    // Currently Unused
    private PlayerController _playerController;
    private UIManager _uiManager;

    void Start() {
        _playerController = GameObject.Find("Player").GetComponent<PlayerController>(); // Currently Unused
    }

    void Update() {

    }

    void OnTriggerEnter(Collider other) {
        if (other.gameObject.CompareTag("Door")) {
            doorIsInRange = true;
        }
    }
    void OnTriggerExit(Collider other) {
        if (other.gameObject.CompareTag("Door")) {
            doorIsInRange = false;
        }
    }
}

Thank you for taking the time to look at my problem and my code. I appreciate it.


Solution 1:

You seem to only check if any door is in range and then open/close whatever is currently assigned to _door. And that simply seems to not be what you expect it to be.


You should not only check

if(_playerBubble.doorIsInRange)

but actually rather store the door that is in range

public Door doorInRange;

void OnTriggerEnter(Collider other)  
{
    // There is no need for a Tag at all! 
    // Rather simply check if the object contains a Door (or derived) component
    if (other.TryGetComponent<Door>(out var door)) 
    {
        doorInRange = door;
    }
}

void OnTriggerExit(Collider other) 
{
    if (other.gameObject.TryGetCompoment<Door>(out var _)) 
    {
        doorInRange = null;
    }
}

and then only in the PlayerController script rather do

if (Input.GetButtonDown("Open")) 
{
    // In this case this is rather the implicit bool conversion 
    // and basically equals a check for != null
    if(_playerBubble.doorInRange)
    {
        _playerBubble.doorInRange.ManageDoor();
    }
} 

The doors themselves don't need to double check whether they are in range.


And then personally I'd not use that int to differ between door types. Rather use inheritance and have your base class Door

public class Door : MonoBehaviour
{
    private bool _iAmOpen = false;

    public void ManageDoor ()
    {
        // Invert the flag
        _iAmOpen = !_iAmOpen;

        if(_iAmOpen)
        {
            // simply log the name of this door object, no need for an additional field
            // by also including a context you can simply click once 
            // on the logged message in the console and this object will be highlighted
            Debug.Log($"{name} is opened", this);
            Open();
        }
        else
        {
            Debug.Log($"{name} is closed", this);
            Close();
        }
    }

    protected virtual void Open()
    {
        // Whatever a default door does
    }

    protected virtual void Close()
    {
        // Whatever a default door does
    }
}  

and then rather extend and modify the behavior slightly

public class Drawer : Door
{
    public float drawerSpeed = 1f;

    protected override void Open()
    {
        // Do different things for a drawer
        // if you want to also include the default stuff additionally include a call to
        //base.Open();
    }

    protected override void Close()
    {
        // Do different things for a drawer         
        // if you want to also do the default stuff additionally include a call to
        //base.Close();
    }
}