0
\$\begingroup\$

I'm making a 2D platformer with Unity. Recently, I implemented a pressure pad system. Basically, when the player triggers the pad, a door near there should be opened. The system works properly but the only problem is when the player stays on the pad, the OpenWhenOn animation (an idle animation that is for the door opening) is not playing. I want to make this keep playing as long as the player is on the pad and if it exits the trigger, the DoorClosing animation should be played. I wrote this code mainly for playing the animation but it doesn't work and I get Null Reference error all the time.

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

public class PressurePad : MonoBehaviour
{
    private Animator anim;
    public UnityEvent OnActivate;
    public UnityEvent OnDeactivate;
    public GameObject door;

    public float deactivationDelay = 2f;

    int objectsInContact;
    Coroutine waitingToDeactivate;


    IEnumerator Timer()
    {
        yield return new WaitForSeconds(deactivationDelay);

        // Tell our listeners we're switched OFF.
        if (OnDeactivate != null)
            OnDeactivate.Invoke();

        // Return to ready-to-activate state:
        waitingToDeactivate = null;
    }

    void OnTriggerEnter2D(Collider2D other)
    {
       
        if (other.CompareTag("Player"))
         
        {
            anim.Play("OpenWhenOn");

        }

        door.GetComponent<Animator>().Play("OpenWhenOn");
       

        objectsInContact++;

        // Pressure pad pressed.
        if (objectsInContact == 1)
        {

            if (waitingToDeactivate != null)
            {
                // Cancel deactivation timer. 
                StopCoroutine(waitingToDeactivate);
                waitingToDeactivate = null;

            }
            else if (OnActivate != null)
            {
                // Tell our listeners we're switched ON.
                OnActivate.Invoke();
            }

        }
    }

    void OnTriggerExit2D(Collider2D other)
    {
        objectsInContact--;

        // Pressure pad released.
        if (objectsInContact == 0)
        {
            // Start a delay before telling anyone.
            waitingToDeactivate = StartCoroutine(Timer());
        }
    }
}

enter image description here This is my Animator Controller. HasExitTime is disabled for all the transitions. The condition for the transition between OpenWhenOn and DoorClosing is set to false.

\$\endgroup\$
0
\$\begingroup\$

I'd recommend you do something like this:

  1. Set up your Animator control graph with 4 animation states:

    • Opening

    • Open (single frame with the door stationary in its open position)

    • Closing

    • Closed (single frame with door stationary in its closed position)

  2. Add two "Trigger" parameters to your graph:

    • "StartOpening"

    • "StartClosing"

  3. Add transitions:

    • from Opening to Open with "HasExitTime" so it transitions to the Open state when the Opening animation is finished, holding the door open

    • from Closing to Closed with "HasExitTime" so it transitions to the Closed state when the Closing animation is finished, holding the door shut

    • from Open/Opening to Closing with a condition on "StartClosing"

    • from Closed/Closing to Opening with a condition on "StartOpening"

  4. Wire up the UnityEvents on your PressureSwitch:

    • OnActivate should call your door's Animator.SetTrigger() method, with the argument "StartOpening"

    Unity Event example

    • OnDeactivate should call your door's Animator.SetTrigger() method, with the argument "StartClosing"

This lets you wire up the door open-close behaviour without hard coupling between the pressure switch and the door, and you don't even need a custom door script!

Of course, you could also write a DoorControl script to slightly simplify the Inspector setup by letting the door manage its own animator state:

[RequireComponent(typeof(Animator))]
public class DoorControl {

    [SerializeField, HideInInspector]
    Animator _animator;

    [SerializeField]
    bool _isOpen;

    void OnValidate() {
        _animator = GetComponent<Animator>();
        SetOpen(_isOpen);
    }

    void Start() {
        SetOpen(_isOpen);
    }

    public void Open() {
        SetOpen(true);
    }

    public void Close() {
        SetOpen(false);
    }

    public void SetOpen(bool open) {
        _isOpen = open;
        _animator.SetBool("Open", _isOpen);
    }
}

Now you can use a single boolean instead of two triggers to control your door (the SetBool method takes two arguments, so we can't wire that one up via the Inspector). And hooking up your events is a little easier - you can just call DoorControl.Open() or DoorControl.Close() without needing to type the exact name of the animation parameter - that knowledge is delegated to the DoorControl, keeping your setup less coupled.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much! I set up my Animator control graph and now the animations are working! \$\endgroup\$ – Ali Dec 26 '20 at 4:19
0
\$\begingroup\$

I think you could simply

this.gameObject.GetComponent<Animator>().Play("DoorOpening");

So it will just play and loop.

If you prefer using the animator itself you can also duplicate the open door animation and have a new trigger for it, so when you trigger it will stay in that forever, or it will change based on your animator

For example, if you have the pressure pad in an OnTriggerEnter() you need to access the Animator of the door and then use my script:

public Animator anim;


   void OnTriggerEnter(Collision other)
{
  if(other.compareTag("Player")
anim.Play("DoorOpening");
}

This is just an example, but you can use it anywhere

\$\endgroup\$
6
  • \$\begingroup\$ Thanks for your answer. I added my code to the question. Where should I put this line of code? \$\endgroup\$ – Ali Dec 23 '20 at 0:03
  • \$\begingroup\$ I have edited with a new example, @Ali \$\endgroup\$ – Leoverload Dec 23 '20 at 6:51
  • \$\begingroup\$ If you have a reference for door, it's not a "this". You should just door.GetComponent :) @Ali \$\endgroup\$ – Leoverload Dec 23 '20 at 19:41
  • \$\begingroup\$ I added that but still not working. Before this, at least it could play the other animations but now even the pressure pad is not opening the door, it's just like a normal object in the scene. Still, I get NullReference when the player collides with the pad. The IDE shows anim.Play("OpenWhenOn"); as the source of the error. That's so weird. I don't know what the problem is from. \$\endgroup\$ – Ali Dec 24 '20 at 0:16
  • \$\begingroup\$ If you have the access to the door you need to do door.getcomponent<Animator>() but if you have the access to the animator you have to do anim.Play(). You are telling me 2 different things, pay attention. I suggest to use a public GameObject door; then drag the door inside it in the inspector and then start the animation with door.GetComponent<Animator>().Play("OpenWhenOn"); @Ali \$\endgroup\$ – Leoverload Dec 24 '20 at 8:06
0
\$\begingroup\$

I can't see anywhere where you're setting the condition to true to move between the DoorOpening and OpenWhenOn animations. Try removing the condition on the transition between DoorOpening and OpenWhenOn, if there's no condition then OpenWhenOn should play automatically after the DoorOpening animation plays.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your answer. I removed that condition but still the animation doesn't play. I think this problem is mostly related to the code, because Unity doesn't know when to play the animation. \$\endgroup\$ – Ali Dec 23 '20 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.