top of page

I programmed an over engineered door in Unity using Playables API

So, in my previous blog post I talked about my level design process. In this post I want to show you something I concocted while I was working on that.

If you want to check that post out you can read it here : https://www.3dbynoobs.com/post/how-to-design-a-game-level

This post is not going to follow any structure. Beware.


Problem

Now this level needed doors that can be triggered open or close from a C# API. Now what a sane person does is think of his options.

Option 1: Use something like DOTween to tween the rotation value

Option 2: Use inbuilt lerp to compute the interpolation values and set the rotation values

Option 3: Animate each door and use Animation Controller to control the doors


Now I am somewhat of a sane person but I went through all these, but each one had some weird issue, e.g., in the first option the problem was that I'm an idiot and I get confused with unity's euler quaternion conversions.

In the second option, well I chucked it out as soon as I thought about it cos the end output is the same as the first option, only the method calls would be slightly bigger.

Now I really liked the third option because all I needed to do was animate the doors manually and use a bool as a condition for state transition. Straight forward. Right?

Nopes.

It was probably an issue with Unity, or the way I was running it but the speed wasnt right sometimes or I had animated at the wrong speed and making changes in the animator again and again wasn't feasible. So my little monkey brain decided to take things right into my own little hands.

The next big thought that came to my mind, as cursed as it sounds looked like a big bright golden apple to my monkey brain. Playables API.

This thing is absolutely brilliant and capable of crazy things; you can represent complex animation systems on this thing, and what did I want to do with it?

Open doors. Cos why the hell not.

Absolute madness, but also fine grain control.


The code:


using UnityEngine;

using UnityEngine.Playables;

using UnityEngine.Animations;


public class DoorController : MonoBehaviour

{

[Header("Left door")]

public GameObject L_Door;

public AnimationClip L_OpenClip;

public Animator L_Animator;


[Header("Right door")]

public GameObject R_Door;

public AnimationClip R_OpenClip;

public Animator R_Animator;


[Header("Settings")]

public float duration = 1f;


private PlayableGraph graphLeft;

private PlayableGraph graphRight;


private AnimationClipPlayable playableLeft;

private AnimationClipPlayable playableRight;


public bool isOpen=false;

public bool isInProgress { get; private set; }


[Space]

public OcclusionPortal portal; // this is because I'm using occlusion culling in the scene, makes sense to cull stuff if behind a door


private void Start()

{

L_Animator.enabled = false;

R_Animator.enabled = false;

SetupClips();

if (portal != null)

portal.open = isOpen;

}


private void OnDestroy()

{

graphLeft.Destroy();

graphRight.Destroy();

}


private void SetupClips()

{

// LEFT DOOR

graphLeft = PlayableGraph.Create("LeftDoorGraph");

var outputLeft = AnimationPlayableOutput.Create(graphLeft, "LeftDoor", L_Door.GetComponent<Animator>());

playableLeft = AnimationClipPlayable.Create(graphLeft, L_OpenClip);

playableLeft.SetDuration(L_OpenClip.length);

playableLeft.SetSpeed(0);

outputLeft.SetSourcePlayable(playableLeft);


// RIGHT DOOR

graphRight = PlayableGraph.Create("RightDoorGraph");

var outputRight = AnimationPlayableOutput.Create(graphRight, "RightDoor", R_Door.GetComponent<Animator>());

playableRight = AnimationClipPlayable.Create(graphRight, R_OpenClip);

playableRight.SetDuration(R_OpenClip.length);

playableRight.SetSpeed(0);

outputRight.SetSourcePlayable(playableRight);

}


public void PlayOpenAnimation()

{

if (isInProgress) return;


isInProgress = true;

L_Animator.enabled = true;

R_Animator.enabled = true;


playableLeft.SetTime(0);

playableLeft.SetSpeed(L_OpenClip.length / duration);


playableRight.SetTime(0);

playableRight.SetSpeed(R_OpenClip.length / duration);


graphLeft.Play();

graphRight.Play();


if (portal != null)

portal.open = isOpen;


Invoke(nameof(FinishPlaying), duration);

}


public void PlayCloseAnimation()

{

if (isInProgress) return;


isInProgress = true;

L_Animator.enabled= true;

R_Animator.enabled = true;


playableLeft.SetTime(L_OpenClip.length);

playableLeft.SetSpeed(-L_OpenClip.length / duration);


playableRight.SetTime(R_OpenClip.length);

playableRight.SetSpeed(-R_OpenClip.length / duration);


graphLeft.Play();

graphRight.Play();


Invoke(nameof(FinishPlaying), duration);

}


private void FinishPlaying()

{

isInProgress = false;

graphLeft.Stop();

graphRight.Stop();

L_Animator.enabled = false;

R_Animator.enabled = false;


isOpen = playableLeft.GetSpeed() > 0;


EditorOnlyDebug.Log("Door speed: "+playableLeft.GetSpeed());


if (portal != null)

portal.open = isOpen;

}

}


As complex and unnecessarily overcomplicated as it looks, this is in fact fairly simple too (well atleast compared to some of the dumbest overcomplicated things I have written).


Now let us look at it in one open close cycle.


1. Start()

Disables animators, builds the PlayableGraphs through SetupClips().

2. SetupClips()

Creates left and right graphs and clip playables, connects them to their animators, sets speed = 0 (it means its paused).

3. PlayOpenAnimation()

Resets clip time to 0, sets positive speed so it finishes in duration, plays both the graphs, schedules FinishPlaying().

4. FinishPlaying() (after open)

Stops both the graphs, disables animators, marks door as open, updates portal.

5. PlayCloseAnimation()

Sets clip time to end, gives negative speed for reverse playback, plays the graphs, schedules FinishPlaying() again.

6. FinishPlaying() (after close)

Stops graphs, disables animators, marks door as closed, updates occlusion portal.


Takeaways

As over engineered as it is, this works perfectly fine. I have full control over the speed of the animation, can toggle the doors with a simple method call.

If it's stupid and ridiculous but works, it's not stupid and ridiculous. (I spent roughly 2 hours on this script I think, could be more, could be less)


Resources

Recent Posts

See All

Comments


Subscribe for Updates

© 2020–2025 3DbyNoobs by Abhishek Jathan.

All rights reserved.

bottom of page