Smooth open/close door script coroutine issue

I’ve just watched this video and grabbed the script from it. Here’s an enhanced C# version of it. It works fine.

However, I decided that I want to make a Coroutine to open/close the door:

void Update()
{
	if (Input.GetKeyDown(KeyCode.E) && inSight) {
		open = !open;
		StartCoroutine(DoorCoroutine(open));
	}
}

IEnumerator DoorCoroutine(bool open)
{
	Vector3 target = open ? openRot : defaultRot;
	float t = 0;
	while (t < 1) {
		t += Time.deltaTime / duration;
		cachedTransform.eulerAngles = Vector3.Slerp(cachedTransform.eulerAngles,
													target,
													t);
		yield return null;
	}
	cachedTransform.eulerAngles = target;
}

It’s doing the job well if I wait for it to finish, but the problem is, if I try to close/open the door while it’s opening/closing, it doesn’t act right. I understand that the problem is related to the fact, that when I call the closing coroutine while the opening one hasn’t finished yet, they will conflict with each other and give wrong results.

I tried to stop the coroutine before calling it, didn’t work. I also tried making the ‘t’ variable a member field, not local, so that if the door hasn’t finished rotating yet, and I tried to open/close it, ‘t’ would resume and continue from where it left of. But it didn’t quite work…

So, how can I write that coroutine properly? such that, if I open/close the door while it’s closing/opening, it would react properly.

Thanks for any help.

EDIT:

Here’s a video showing the door opening with and without the coroutine.

Here’s one way, stopping the Coroutine and restarting it:

void Update()
{
    if (Input.GetKeyDown(KeyCode.E) && inSight)
        ToggleDoor();
}

void ToggleDoor()
{
    open = !open;
    StopCoroutine("DoorCoroutine");
    StartCoroutine("DoorCoroutine", open);
}

Here is one without Coroutines, using Animation instead:

void Update()
{
    if (Input.GetKeyDown(KeyCode.E) && inSight) 
        ToggleDoor();
}

void ToggleDoor()
{
    open = !open;

    if (open)
        animation.Play("Open");
    else
        animation.Play("Close");
}

Here’s the really long way of achieving the same thing without having to use strings. I haven’t tested it, but the code is so small so I’ll take my chances of public embarrasment if it turns out to have bugs. :slight_smile: If anything is wrong, I would suspect you may have to change the Current value to return null when you try to access it if it has been aborted, but I doubt it.

AbortableEnumerator doorCoroutine;

void ToggleDoor()
{
	open = !open;

	StopDoorCoroutine ();
	StartDoorCoroutine ();
}

void StopDoorCoroutine ()
{
	if (doorCoroutine != null)
		doorCoroutine.Abort ();
}

void StartDoorCoroutine ()
{
	doorCoroutine = new AbortableEnumerator (DoorCoroutine (open));
	StartCoroutine (doorCoroutine);
}

And the code that enables this new behaviour:

public class AbortableEnumerator : IEnumerator
{
	IEnumerator enumerator;
	bool isAborted;

	public AbortableEnumerator(IEnumerator enumerator)
	{
		this.enumerator = enumerator;
	}

	public void Abort()
	{
		isAborted = true;
	}

	bool IEnumerator.MoveNext ()
	{
		if (isAborted)
			return false;
		else
			return enumerator.MoveNext ();
	}

	void IEnumerator.Reset ()
	{
		isAborted = false;
		enumerator.Reset ();
	}

	object IEnumerator.Current 
	{
		get { return enumerator.Current; }
	}
}

What I really think your approach should have been if you were to do it purely with code, but this is my preference and not a rule. Take it for a spin. I believe it behaves pretty nicely, and you can adjust the rotational angles and how fast it should smooth/move. If your world expects that the original rotation should be taken into account, you’d have to fix that.

  1. Create a blank GO, attach this script to it.
  2. Add a cube child to it, offset: 0.5, 0.5, 0.0, rotation: 0, 0, 0, scale: 1, 2, 0.1.
  3. Press “E”
  4. Tweak values in inspector to your hearts content

And the code:

using UnityEngine;
using System.Collections;

public class SmallDoorExample : MonoBehaviour {

	public float closedAngle = 0;
	public float openedAngle = 90;
	public float doorSwingSmoothingTime = 0.5f;
	public float doorSwingMaxSpeed = 90;

	private float targetAngle;
	private float currentAngle;
	private float currentAngularVelocity;

	void Update () 
	{
		if (DoorWasInteractedWith ())
			ToggleAngle ();

		UpdateAngle ();
		UpdateRotation ();
	}

	static bool DoorWasInteractedWith ()
	{
		return Input.GetKeyDown (KeyCode.E);
	}

	void ToggleAngle ()
	{
		if (targetAngle == openedAngle)
			targetAngle = closedAngle;
		else
			targetAngle = openedAngle;
	}

	void UpdateAngle ()
	{
		currentAngle = Mathf.SmoothDamp (currentAngle, 
		                                 targetAngle, 
		                                 ref currentAngularVelocity, 
		                                 doorSwingSmoothingTime, 
		                                 doorSwingMaxSpeed);
	}

	void UpdateRotation ()
	{
		transform.localRotation = Quaternion.AngleAxis (currentAngle, Vector3.up);
	}
}