Mute Tracks or Add Animations to Instance of Playable from Script

I am looking to see if it is possible to get all of the instances of playables within a playableDirector component.

I am somewhat of a beginner c# programmer, so I wasent able to see a way via the API documentation, and havent seen any examples yet. I dug through all the examples posted on this thread. (Which have been helpful in writing the other C# snippets/actions).

I want to be able to mute tracks for example, in a running timeline, or add animations at X time, etc.

Any example scripts available?

You might be interested in something like this:

    public PlayableDirector collectedTimeline;
    public string playerAnimationTrackName;

    void BindPlayerToAnimationTrack(){

        //Loop through timeline tracks and find the desired track to bind the player to
        foreach (var playableAssetOutput in collectedTimeline.playableAsset.outputs) {
            if (playableAssetOutput.streamName == playerAnimationTrackName) {
                collectedTimeline.SetGenericBinding (playableAssetOutput.sourceObject, player);
            }
        }

    }

It's from the project linked in the comments of this video.

During playback, the playable graph is available through the playableGraph property on the playable director. You can grab the outputs from the graph - each track is represented by an output, or you can parse the graph from the root (playableGraph.GetRootPlayable(0)).

Most tracks have a mixer that represents the track. This would be the input to the root. Then the inputs to the mixer are the playables representing the clip.

Animation is more complex, but the leafs nodes are AnimationClipPlayables.

Hope that helps.

Thank you both. I will give that a try and see what I can do. :)

Anything more concrete from this? Ex: how to actually mute a track at runtime?

Same thing question here. I do get the whole playableGraph.GetRootPlayable(0), but what next once we have that? What's the missing link that could get us to 'animationTrack.muted = true' ?
I've read about many complicated ways. Including some AnimationPlayableOutput thingy, but that aren't recognized in Unity 2018.2.4f1.

I've also tried some AnimationTrack a = playableDirector.GetGenericBinding(animator) as AnimationTrack;
Debug.Log(a);
but nothing happens, the AnimationTrack isn't recognized

The goal is always the same: how to simply 'mute' a playable track from script ?

While the timeline is playing

animationTrack.muted = true;
playableDirector.RebuildGraph();

should work (rebuilding can be expensive though).

or, alternately you can strip the target animator on the output. e.g.

        public void RuntimeMute(AnimationTrack track)
        {
            for (int i=0; i< playableDirector.playableGraph.GetOutputCountByType<AnimationPlayableOutput>(); i++)
            {
                var output = (AnimationPlayableOutput) playableDirector.playableGraph.GetOutputByType<AnimationPlayableOutput>(i);
                if (output.GetReferenceObject() == track)
                    output.SetTarget(null);
            }
        }
1 Like

Ok, so playableDirector.playableGraph.GetOutputByType(i) is an other way to get the AnimationTrack. But, as I said, there's a weird behaviour in Unity 2018.2.4.f1, AnimationPlayableOutput isn't recognized. (everything else is, using Timeline and all..). Am I missing something or could that be an issue in this version ?

Include UnityEngine.Animations. The animation-specific playable classes lives there.

Oh. Yeah. of course ^^. Thanks.
As I'm still working on the topic:
I'm in the TextSwitcher default playable,and I would like to know if It's possible to get the current track the clip is on?
So that I can test if the track is mute, and if so disable its functionality.
I suppose I should get access at least to the director or such to do so ? But didn't found so far.

There are a couple of ways to get the director.

CreatePlayable() passes a gameObject. For timeline, that is the game object the playable director belongs to.

Or, you can ask a playable for it's PlayableGraph. From there you can call PlayableGraph.GetResolver() -> it's an IExposedPropertyTable, but that's actually the playable director as well.

Or, the FrameData passed into Prepare/ProcessFrame contains the PlayableOutput currently being processed.
GetReferenceObject() on the playable output should give you the track that created it.

Everything felt a bit cryptical ^^. Thanks for clarification :)

^^ Got a new problem. Weirder this time.
Here's my example:
On entering new clips with timeline's reading head, I'm checking if they're are of type TextSwitcher. If so, in their mixerBehaviour, I've added some function that asks to mute an AudioTrack, below in the same timeline.

Editor mode, everything works, Playmode: everything slowly falls apart. i've got hard time to understand how it slowly degradates.

For example, if the director does one entire play till the end, (in which the audio gets mute/unmute regularly depending on the TextSwitcher track above), once the head goes back to the start of the timeline, audio track, even unmute, displays correctly the change on the timeline, but does not give any sound anymore. It's super weird :/ (or same thing If I manually move the reading head a few times accross the timeline)

I felt like maybe I should do some 'RebuildGraph' after muting/unmuting a track. But when I do so, I get warnings telling me that I can't do it while the graph is being evaluated. And it seems to be evaluated all the time.

So yeah... bit lost again, can you help?
(using 2018.2.5f1)

Flag the graph to be rebuilt, and rebuild it from a monobehaviour (e.g. in an update method). Rebuilding the graph from Prepare or Process frame doesn't work because it's in the middle of execution.

Actually, I was already doing that part of doing the rebuild within a monobehaviour, in an update method ^^'
But maybe I'm missing the flagging part. Not sure what you meant by 'flag the graph to be rebuild'..

Have a static boolean act as "flag". Set it to true when you want the graph to be rebuilt, then when your monobehaviour (which is checking that same bool) fires, set the boolean back to false.

1 Like

Thanks for this code, it works fine but after some testing I found two limitations:

  • Only AnimationPlayableOutput has the SetTarget method, this means that I cannot mute tracks of other types right? Is there a generic method for IPlayableOutput?

  • I have a timeline with some Override Tracks on my Animation Track. When I mute the parent track in editor the children are still played, unless I also mute them. Using the runtime method provided however the children are muted too and that's not what I want. Is there another way to achieve the same result?

Sorry for bumping this thread but it's very hard to find how to mute an Override track, say I have the AnimationPlayable X has an override track Y, how do I access it and mute it?

at the moment I'm using the TimelineAsset to mute it indirectly and rebuild the graph

track = Timeline.playableAsset.outputs.Where(x => x.streamName == "MainAnimTrack").First().sourceObject;
AnimationTrack animTrack = track as AnimationTrack;
if (animTrack != null)
{
       foreach (TrackAsset childTrack in animTrack.GetChildTracks())
        {
               Debug.Log(childTrack.name);
               childTrack.muted = muteOverride;
        }
        Timeline.RebuildGraph();
}

But wish I could do it in the graph directly

So I'll answer my own question, found how to remove tracks sadly weighting doesn't work as it's get reset the next frame for whatever reason

for (int i = 0; i < director.playableGraph.GetRootPlayableCount(); i++)
                Process(director.playableGraph.GetRootPlayable(i));
void Process(Playable playable, int depth = 0)
    {
        if (playable.GetPlayableType() == typeof(AnimationLayerMixerPlayable))
        {
            AnimationLayerMixerPlayable mixer = (AnimationLayerMixerPlayable)playable;
            Debug.Log("Found Mixer!" + mixer.GetInputCount());
            Debug.Log(mixer.CanSetWeights());
!!!  //mixer.SetInputWeight(1, 0f);  <<<----- this doesn't work, it works only if you pause the graph, but if you resume it gets reset
            mixer.DisconnectInput(1);
            return;
        }
        Debug.Log(new string('>', depth + 1) + playable.GetPlayableType().ToString());
        for (int i = 0; i < playable.GetInputCount(); i++)
        {
            Process(playable.GetInput(i), depth + 1);
        }
    }

So seems like I found how to weight down the layer mixer, but wish an official will state why exactly this is the behavior and if it's the intended one.

So to disable a branch of a mixer, you need to disable the inputs of the children sub mixers
9007168--1241464--upload_2023-5-11_17-31-21.png

this is the minimal actions that needs to be done to set all branch to 0 and to make Animation Layer Mixer not get back to 1.0.

//mixer.SetInputWeight(1, 0f); <--- 2nd AnimationLayerMixer branch, gets reset next frame
//mixer.GetInput(1).SetInputWeight(0, 0f); <---- Animation Offset, doesn't do anything
mixer.GetInput(1).GetInput(0).SetInputWeight(0, 0f); <----- Animation Mixer, this one does it!
//mixer.GetInput(1).GetInput(0).GetInput(0).SetInputWeight(0, 0f); <---- Animation Clip, doesn't change anything
//mixer.DisconnectInput(1); <--- If you don't want to play with weights, do a hard cut instead