Larger Doors and Doors that can be shot through

Some of my doorways were 2 grids wide so I refactored and extended the Door class with a LargeDoor class that overrides the start method to set both grids under it as interactable.

I also set the colliders to be disabled when the doors are open so that they can be shot through, as suggested in another post.

Since this code is using the IInteractable Interface I thought it would be better to post it under this lesson instead of the last one to reduce confusion.

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

public class LargeDoor : Door
{
protected override void Start()
{
gridPositionList = new();
GridPosition leftDoorGridPosition = LevelGrid.Instance.GetGridPosition(transform.position + Vector3.right * 0.01f);
GridPosition rightDoorGridPosition = LevelGrid.Instance.GetGridPosition(transform.position + Vector3.left * 0.01f);

    colliderArray = GetComponentsInChildren<Collider>();

    gridPositionList.Add(leftDoorGridPosition);
    gridPositionList.Add(rightDoorGridPosition);

    LevelGrid.Instance.SetInteractableAtGridPosition(leftDoorGridPosition, this);
    LevelGrid.Instance.SetInteractableAtGridPosition(rightDoorGridPosition, this);

    if (isOpen)
    {
        OpenDoor();
    }
    else
    {
        CloseDoor();
    }
}

}

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

public class Door : MonoBehaviour, IInteractable
{
[SerializeField] protected bool isOpen;

protected List<GridPosition> gridPositionList;
protected Collider[] colliderArray;
bool isActive;
float timer;
Animator animator;
Action onInteractComplete;

void Awake()
{
    animator = GetComponent<Animator>();
}

protected virtual void Start()
{
    gridPositionList = new();
    GridPosition gridPosition = LevelGrid.Instance.GetGridPosition(transform.position);
    gridPositionList.Add(gridPosition);

    colliderArray = GetComponentsInChildren<Collider>();

    LevelGrid.Instance.SetInteractableAtGridPosition(gridPosition, this);
    Pathfinding.Instance.SetIsWalkableGridPosition(gridPosition, true);

    if (isOpen)
    {
        CloseDoor();
    }
    else
    {
        OpenDoor();
    }
}
void Update()
{
    if (!isActive)
    {
        return;
    }


    timer -= Time.deltaTime;

    if (timer <= 0f)
    {
        isActive = false;
        onInteractComplete?.Invoke();
    }
}
public void Interact(Action onInteractComplete)
{       
    this.onInteractComplete = onInteractComplete;

    isActive = true;
    timer = 0.7f;
    if (isOpen)
    {
        CloseDoor();
    }
    else
    {
        OpenDoor();
    }
}

protected void OpenDoor()
{
    isOpen = true;
    
    animator.SetBool("IsOpen", isOpen);
    foreach (GridPosition gridPosition in gridPositionList)
    {
        Pathfinding.Instance.SetIsWalkableGridPosition(gridPosition, true);
    }
    foreach (Collider collider in colliderArray)
    {
        collider.enabled = false;
    }
}

protected void CloseDoor()
{
    isOpen = false;
    animator.SetBool("IsOpen", isOpen);
    foreach (GridPosition gridPosition in gridPositionList)
    {
        Pathfinding.Instance.SetIsWalkableGridPosition(gridPosition, false);
    }
    foreach (Collider collider in colliderArray)
    {
        collider.enabled = true;
    }
}

}

Very nice.

Some suggestions:

I could be wrong, but I think these doors cannot be rotated.

GridPosition leftDoorGridPosition = LevelGrid.Instance.GetGridPosition(transform.position + Vector3.right * 0.01f);
GridPosition rightDoorGridPosition = LevelGrid.Instance.GetGridPosition(transform.position + Vector3.left * 0.01f);

This is setting the grid positions to the left and right of the door pivot because it’s using the world space left and right. Using transfform.right should allow you to change the orientation.

GridPosition leftDoorGridPosition = LevelGrid.Instance.GetGridPosition(transform.position + transform.right * 0.01f);
GridPosition rightDoorGridPosition = LevelGrid.Instance.GetGridPosition(transform.position + -transform.right * 0.01f);

Your OpenDoor and CloseDoor code is exactly the same. You could use a single method instead

protected void ToggleDoor(bool open)
{
    isOpen = open;
    
    animator.SetBool("IsOpen", isOpen);

    foreach (GridPosition gridPosition in gridPositionList)
    {
        Pathfinding.Instance.SetIsWalkableGridPosition(gridPosition, isOpen);
    }
    foreach (Collider collider in colliderArray)
    {
        collider.enabled = !isOpen;
    }
}
2 Likes

Great suggestions!

Good catch on the world space vs local space issue, I had only tested on doors that were not rotated. :sweat_smile:

Thanks for helping to keep my code more DRY as well, I modified your ToggleDoor slightly:

protected void ToggleDoor()
    {
        animator.SetBool("IsOpen", isOpen);

        foreach (GridPosition gridPosition in gridPositionList)
        {
            Pathfinding.Instance.SetIsWalkableGridPosition(gridPosition, isOpen);
        }
        foreach (Collider collider in colliderArray)
        {
            collider.enabled = !isOpen;
        }

        isOpen = !isOpen;
    }
1 Like

This modification is a little volatile.

First, you are setting all the door states to the current value of isOpen and once you are done, you change the state. If you were to look at the state of a door at any point in the game, it will be open (for example), but the isOpen state would be false. And when all the doors are closed, the isOpen state will be true.

If you want to do a pure ‘toggle’ (like you did), set the isOpen value first, then set the states

protected void ToggleDoor()
{
    isOpen = !isOpen;

    animator.SetBool("IsOpen", isOpen);

    foreach (GridPosition gridPosition in gridPositionList)
    {
        Pathfinding.Instance.SetIsWalkableGridPosition(gridPosition, isOpen);
    }
    foreach (Collider collider in colliderArray)
    {
        collider.enabled = !isOpen;
    }
}

Then, I personally don’t like ‘pure’ toggles like this. It removes control from the consumer. If, for any reason, the method gets called twice with the intention of having the doors open, it will leave the doors closed instead. It also means I would have to check the state of the door (which currently does not reflect the actual state of the door because of my first point) before I can decide if I want to ‘open’ the door, or just leave it as it is.

If you really want this toggle, I would keep what was there and just add another method that I can use whenever the state of the door doesn’t matter

protected void ToggleDoor(bool open)
{
    // ... snip ... - Original ToggleDoor code here
}

protected void ToggleDoor()
{
    ToggleDoor(!isOpen);
}
2 Likes

I realized my mistake not long after posting and went to edit my post, saw your response and removed my edits to reduce confusion.

This is what I came up with after realizing the state of the door was backwards.

I separated ToggleDoor into ToggleDoor and UpdateDoor so that I could Update the Door on start without toggling its state. Then I make sure to toggle the door’s state before I update the visuals and pathfinding.

   protected void ToggleDoor()
    {        
        isOpen = !isOpen;
        UpdateDoor();
    }

    protected void UpdateDoor()
    {
        animator.SetBool("IsOpen", isOpen);

        foreach (GridPosition gridPosition in gridPositionList)
        {
            Pathfinding.Instance.SetIsWalkableGridPosition(gridPosition, isOpen);
        }
        foreach (Collider collider in colliderArray)
        {
            collider.enabled = !isOpen;
        }
    }

This is a really good point, I added an override method as you suggested so that I won’t need to check the state on a door. Now with this new method I could iterate over a list of all doors toggle all the doors to be open without currently checking if the door is already open.
Edit: Or I can use this override method to ensure the door was closed at the start instead of being worried about toggling the state when I don’t want to… just as you were saying :stuck_out_tongue:

    protected void ToggleDoor(bool open)
    {
        isOpen = open;
        isOpen = !isOpen;
        UpdateDoor();
    }

Thank you for helping me to better understand how to keep my code clean.

1 Like

This is negating what you pass in. If I say

ToggleDoor(true);

I expect the door to be open. But the above will close it.

// Assume: open = true, isOpen = false
protected void ToggleDoor(bool open)
{
    // open = true, isOpen becomes true
    isOpen = open;
    // isOpen is true, but !isOpen makes it 'not true', so false
    isOpen = !isOpen;
    // Update the door with isOpen = false
    UpdateDoor();
}
2 Likes

Ah okay for some reason I was reading it as ToggleDoor(bool currentStateOfDoor) where we are setting which state we want the door to be in before we toggle it, but you’re saying it as ToggleDoor(bool stateWeWantDoorToBeAfterToggle), which makes a lot more sense!

edit:

This is negating what you pass in

:man_facepalming: I see where I did that.
fixed!

    protected void ToggleDoor(bool open)
    {
        isOpen = open;
        UpdateDoor();
    }

:+1:

1 Like

I use this code to find the grid positions of the door. It is more versatile, the doors can be of any length, Turned horizontally or vertically. DOES NOT WORK with diagonal doors.

 private List<GridPosition> GetDoorGridPositionList() 
    {
      

        float offsetFromEdgeGrid = 0.01f; 

        GridPosition childreGridPositionLeft = LevelGrid.Instance.GetGridPosition(_transformChildrenDoorArray[1].position + _transformChildrenDoorArray[1].right * offsetFromEdgeGrid); 
        GridPosition childreGridPositionRight = LevelGrid.Instance.GetGridPosition(_transformChildrenDoorArray[2].position - _transformChildrenDoorArray[2].right * offsetFromEdgeGrid);
    

         // ПЕРЕНДИКУЛЯРНАЯ ДВЕРЬ
        if (childreGridPositionLeft.x == childreGridPositionRight.x)
        {
            if (childreGridPositionLeft.z <= childreGridPositionRight.z)
            {
                for (int z = childreGridPositionLeft.z; z <= childreGridPositionRight.z; z++) 
                {
                    GridPosition testGridPosition = new GridPosition(childreGridPositionLeft.x, z); 

                    _doorGridPositionList.Add(testGridPosition);  
                }
            }

            if (childreGridPositionRight.z <= childreGridPositionLeft.z) 
            {
                for (int z = childreGridPositionRight.z; z <= childreGridPositionLeft.z; z++)  
                {
                    GridPosition testGridPosition = new GridPosition(childreGridPositionLeft.x, z); 

                    _doorGridPositionList.Add(testGridPosition);    
                }
            }
        }

        // ГОРИЗОНТАЛЬНАЯ ДВЕРЬ
        if (childreGridPositionLeft.z == childreGridPositionRight.z)
        {
            if (childreGridPositionLeft.x <= childreGridPositionRight.x) 
            {
                for (int x = childreGridPositionLeft.x; x <= childreGridPositionRight.x; x++)  
                {
                    GridPosition testGridPosition = new GridPosition(x, childreGridPositionLeft.z); 

                    _doorGridPositionList.Add(testGridPosition);  
                }
            }

            if (childreGridPositionLeft.x >= childreGridPositionRight.x)
            {
                for (int x = childreGridPositionRight.x; x <= childreGridPositionLeft.x; x++)  
                {
                    GridPosition testGridPosition = new GridPosition(x, childreGridPositionLeft.z); 

                    _doorGridPositionList.Add(testGridPosition);  
                }
            }
        }    

        return _doorGridPositionList;
    }

To check the door for penetration, I added a Door layer. And in the Shoot Action in the inspector settings of the unit, I checked the box for 2 layers of Obstacles and Door

Privacy & Terms