Blending player object from gameplay to cutscene and back again

Hi guys

What is the correct approach to blend from gameplay seamlessly into a cut-scene using the timeline ?

My question is mainly to the devs as i am finding some ambiguity about this after going through unity conference videos and the online docs (preview doc, official doc, playable apis, forum, unity unite on youtube)

I am checking playables and understood that they allow me to create custom tracks to animate extra things im interested in like lights without creating default animation tracks with clips for them. ok i understood that and downloaded the asset and wizard from the store.

Now supposing i want to blend my script controlled player car/box (the script controls the world location either direction or with a animator state machine) into a cutscene seamlessly, how would i go about doing that ?

I want to smoothly overide what the ‘gamplay’ animation of the play box including any script and animator state machines it has running into the cut-scene clip.

Im not talking here about cameras, im checked cinemachine it will blend nicely the camera i tried that, i am talking about the player object itself, of type generic not humanoid in the import settings.

I tried updating to the latest beta, set extrapolation to none, enable/disable root motion and still cant get a smooth transition from gameplay to cutscene. it just snaps. i did find a reference to a bug on this in another thread. Issue with mixing Animator and Timeline animations - Unity Engine - Unity Discussions

Im currently considering testing and scripting a custom playable that exposes a transform reference and “cinematic” bool to decide how how to blend clips based on whether the bool is cinematic or not. Still thinking about it and i have no idea if it will even resolve my problem when tested.

So my question is the the unity devs, again, very specific, not about cinemachine or other stuff…

What are the steps you yourself designed and will use we you need to take to blend a player controlled object like a car/box with its own animator state machine (generic) into a cutscene anition smoothly on the timeline ?

Just point us in the right direction

Big heart & thanks

ps: do excuse any typo errors in the first post

What do you mean by ‘it just snaps’?

Can you give more context of your car/box example and what you want to sequence in the cinematic?

Hi Andy,

While we are working on finding a workflow for this, meanwhile I thought i’d try my luck in trying to explain what Roy was asking earlier. Perhaps you can help us with certain tips and guidelines.

Basically imagine we have a flying ship with generic animation and a state machine setup that drives all the hierarchy movements on this ship. The ship is in gameplay mode and the player has control over it - but at a certain point it needs to perform a a pre animated action for a cut-scene. But it needs to blend seamlessly from the gameplay mode into the cut-scene mode and then when the cut scene ends it blends back to gameplay mode again… This also needs to obviously blend not just the root of the object but the entire hierarchy as well, for instance the wings movements etc… (Basically blending with the state machine).

If you follow this link I’ve uploaded a simple pre-animated sequence to try to explain what we want to achieve in game.

How can we achieve this via the timeline?

1 Like

Hi,
I was struggling with the same challenge and I wrote two simple scripts, that will automatically blend character’s animation from the one that is set in Timeline (last frame) and current played animation (from the Animator graph).

The first script should be attached to the same game object as the PlayableDirector. It checks when the director stops playing and starts AutoBlend() functions on the characters.

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

//Attach this script to the game object containg the PlayableDirector component

public class PlayableDirectorAutoBlendOut : MonoBehaviour {

    //a reference to the playable director
    public PlayableDirector director;

    //if set to true, a "Reset" trigger will be called on all animated characters
    public bool resetCharacters = true;
    public bool unparentCharacters = true;
    //blend out duration
    public float blendCharactersTime = 0.5f;

    //a reference to all characters (AutoBlendOut components) that need the blending
    public AutoBlendOut[] autoBlends;
    public PlayableDirector[] directorsToStop;
    //normalized director playback time
    float time;

    private void Awake()
    {
        if (director == null)
        {
            director = GetComponent<PlayableDirector>();
        }
        if (director != null)
        {
            //set the extraplotaion mode to Hold - we will still stop the director, but it ensures the last frame of the animation
            //stays long enough to be captured by the AutoBlendOut scripts
            director.extrapolationMode = DirectorWrapMode.Hold;
        }
    }

    bool _directorsStopped = false;

    private void Update()
    {
        //check if the director is currently playing
        if (director != null && director.state == PlayState.Playing)
        {
            //if so - check the normalized time of the director
            time = (float)(director.time / director.duration);

            //stops any additional directors at the start of the Timeline

            if (time >= 0.1f && !_directorsStopped)
            {
                for (int i = 0; i < directorsToStop.Length; i++)
                {
                    directorsToStop[i].Stop();
                }
                _directorsStopped = true;
            }
            if (time >= 1f)
            {
                //if the director is done playing, start the autoblend scripts
                for (int i = 0; i < autoBlends.Length; i++)
                {
                    autoBlends[i].AutoBlend(resetCharacters, blendCharactersTime);
                   
                    //automatically unparents characters at the end of the timeline
                    if (unparentCharacters)
                    {
                        autoBlends[i].transform.parent = null;
                    }
                   
                }
                //and finally stop the director
                director.Stop();
            }
        }
    } 
}

The second script is attached to all characters that need this auto blending feature. You need to pass them to the autoBlends[ ] array of the PlayableDirectorAutoBlendOut component.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
//A simple class to store the local position and local rotation of a bone (transform) and the reference to that bone
public class BlendedTransform
{
    public Transform targetTransform;
    public Vector3 position;
    public Quaternion rotation;
}

public class AutoBlendOut : MonoBehaviour {

    //the reference to the rig game object (the bones should be children of that transform)
    public Transform myRig;

    //a list containing all the child game objects of the myRig transform
    List<BlendedTransform> allmyBones = new List<BlendedTransform>();

    //a reference to the animator component
    Animator anim;

    //the blend weight. When 0 the Animator animation is being played, when 1 the last frame set by the AutoBlend function is played
    float weight = 0f;
   
    //blend duration
    float _blendOutTime = 0.5f;

    //update mode set for the Animator component (we need to override it to "Normal" for the blend)
    AnimatorUpdateMode _animUpdateMode;
    private void Awake()
    {
        //getting the reference to the Animator
        anim = GetComponent<Animator>();
       
        if (myRig == null)
        {
            myRig = transform;
        }
    }

    //call this function to start the blending process - it takes saves the current bones' positions and rotations (a reference frame)
    public void AutoBlend (bool reset, float blendTime) {
        _animUpdateMode = anim.updateMode;
       
        //override animation update mode to Normal (only then the blend looks good)
        anim.updateMode = AnimatorUpdateMode.Normal;

        Transform[] myBones = myRig.GetComponentsInChildren<Transform>();
        _blendOutTime = blendTime;
        for (int i = 0; i < myBones.Length; i++)
        {
            if (myBones[i] != myRig)
            {
                BlendedTransform bt = new BlendedTransform();
                bt.targetTransform = myBones[i];
                bt.position = myBones[i].localPosition;
                bt.rotation = myBones[i].localRotation;
                allmyBones.Add(bt);
            }
        }

        //if you pass a reset bool, the "Reset" trigger will be additionally called on the animator

        if (reset)
        {
            anim.SetTrigger("Reset");
        }

        //here we set the weight to 1f to start with the saved reference frame

        weight = 1f;
        _blend = true;
    }

    public void SetAnimatorUpdate(AnimatorUpdateMode animUpdate)
    {
        anim.updateMode = animUpdate;
    }
    bool _blend = false;
    private void LateUpdate()
    {
        //we blend bones in the late update to override the Animator component
        BlendBones();
    }

    void BlendBones()
    {
        //we stop blending bones when the _blend parameter is false
        if (!_blend)
        {
            return;
        }
        if (_blendOutTime > 0f && weight > 0f)
        {
            //continue to blend bones untill the weight reaches 0f
            weight -= Time.deltaTime / _blendOutTime;
            for (int i = 0; i < allmyBones.Count; i++)
            {
                allmyBones[i].targetTransform.localRotation = Quaternion.Lerp(allmyBones[i].targetTransform.localRotation, allmyBones[i].rotation, Mathf.Max(0f, weight));
                allmyBones[i].targetTransform.localPosition = Vector3.Lerp(allmyBones[i].targetTransform.localPosition, allmyBones[i].position, Mathf.Max(0f, weight));
            }
        }
        else
        {
            //stop blending bones
            _blend = false;

            //reset the update mode of the Animator
            anim.updateMode = _animUpdateMode;
        }
    }
}

Hope it helps!