Howdy, Stranger!

It looks like you're new here. Sign in or register to get started.

If you have any questions, reports, suggestions, or requests about Live2D, please send them to this forum.
※We cannot guarantee statements or answers from Live2D staff. Thank you for your understanding in advance.
 
Live2D Cubism
Cubism Products and Downloads
Cubism product manuals and tutorials
Cubism Editor Manual    Cubism Editor Tutorial    Cubism SDK Manual    Cubism SDK Tutorial

[Unity] Problems About Motion Priority & Fade

edited September 2024 in Help
I must admit, Live2D SDKs is difficult to learn. No API documentation and so.
Now I'm stuck on very basic things, Playing Animation.

MotionController.PlayAnimation() consist of:
void CubismMotionController.PlayAnimation(AnimationClip clip, [int layerIndex = 0], [int priority = 2], [bool isLoop = true], [float speed = 1])
with **priority** parameter (default: **2**):
CubismMotionPriority.cs

public class CubismMotionPriority
{
    public const int PriorityNone = 0;
    public const int PriorityIdle = 1;
    public const int PriorityNormal = 2;
    public const int PriorityForce = 3;
}

So, I play a character login/intro motion:
CharacterViewer.cs
motionController.PlayAnimation(animations["login"], isLoop: false);

From the documentation, you need to subscribe to `AnimationEndHandler` event to make the next motion transition faded.

By using a callback that is called when motion playback ends, which exists in the CubismMotionController, the same motion can be played again from the callback to generate a fade and loop the motion.

https://docs.live2d.com/en/cubism-sdk-tutorials/loop-playback-with-fade-cubism/

I follow the tutorial and make a method to subscribe to:
CharacterViewer.cs
//...
motionController.AnimationEndHandler += OnLoginComplete;
//...

//...
private void OnLoginComplete(float instanceId)
{
    // This will play `idle` animation continously
    motionController.PlayAnimation(animations["idle"], priority: 1);
    motionController.AnimationEndHandler -= OnLoginComplete;
}
//...
Above codes work as expected. From `login` -> `idle` motion with fade transition.

On another hand, i perform a Raycast to the character. When I click a part with `Touch` component, it will play a specified animation. (NB: There's 3 parts with `Touch` component)
Touch.cs
//...
if (hitCount > 0 && results[0].Drawable.TryGetComponent(out Touch _)) // If the part found...
{
    // This will play Touch animation with janky transition, no fade
    CharacterViewer.MotionController.PlayAnimation(
        CharacterViewer.Animations["touch_body"],
        priority: 3,
        isLoop: false
    );
    CharacterViewer.MotionController.AnimationEndHandler += OnTouchComplete; // It doesn't subscribed to the handler whatsoever :v
}
//...

//...
void OnTouchComplete(float instanceId)
{
    Debug.Log("Back to Idle...");
    CharacterViewer.MotionController.PlayAnimation(
        CharacterViewer.Animations["idle"],
        priority: 3,
        isLoop: true
    );
    CharacterViewer.MotionController.AnimationEndHandler -= OnTouchComplete;
}
//...
The `Touch` animation will play because I used `Force` priority. Using `Normal` priority also work, but it's give me a log message: `can't start motion.`. There's no fade transition because the Idle animation is looped (AnimationEndHandler practically will not be called because the animation will not ended. CMIIW).
The method (`OnTouchComplete()`) I registered to the event isn't called. So, the character can't go back to the Idle animation. According to the docs:

CubismMotionController.AnimationEndHandler callback will not be called if isLoop in CubismMotionController.PlayAnimation() is passed true.

https://docs.live2d.com/en/cubism-sdk-tutorials/loop-playback-with-fade-cubism/

It's clear that I don't play `Touch` animation looped. So why, `OnTouchComplete()` isn't called?

Alternatively, if I wait until the current animation is done, also not working.
Touch.cs
//...
CharacterViewer.MotionController.PlayAnimation(
    CharacterViewer.Animations["touch_body"],
    priority: 2,
    isLoop: false
);
await UniTask.WaitUntil( // Equivalent to Task.WaitUntil in System.Threading.Tasks
    () => !CharacterViewer.MotionController.IsPlayingAnimation()
);
OnTouchComplete(0); // Execute with dummy argument
//...
Bruh, it's drives me nuts... If anyone can help me is much appreciated :smile:

Best Answer

  • Answer ✓
    @kiraio
    If you want to play another animation while the idling motion is playing, it is recommended to play the motion on the same layer.
    private void Start()
    {
        MotionController.PlayAnimation(LoginAnimation, 0, CubismMotionPriority.PriorityIdle, false);
        MotionController.AnimationEndHandler += AnimationEnded;
    }
    
    
    private void Interact(InputAction.CallbackContext ctx)
    {
        ...
        MotionController.PlayAnimation(TouchAnimation, 0, CubismMotionPriority.PriorityNormal, false);
        ...
    }
    
    
    private void AnimationEnded(int instanceId)
    {
        _motionController.PlayAnimation(LoopAnimation, 0, CubismMotionPriority.PriorityIdle, false);
    }
    See the sample scenes in the /Assets/Live2D/Cubism/Samples/OriginalWorkflow/Demo for the main usage.

    Best regards.

Answers

  • edited September 2024
    Hi @kiraio

    Thank you for using our products.

    We apologize for any inconvenience caused by the lack of documentation.
    We will be enhancing the documentation in the future.

    Could you tell us what kind of behavior you would like to achieve?

    Best regards.
  • Thank you for the reply, @yasu.o@live2d .

    I want to control the motion fade from one to another either manually or already defined in Fade Motion Data without depending on MotionController.AnimationEndHandler.

    Also a better way to sequence the motion and overriding the current motion without confused about the motion priority.
  • @kiraio

    CubismMotionController.PlayAnimation() can be called at any time.
    AnimationEndHandler is only a callback delegate that is called when the animation has finished playing.
    If there is no processing to be performed when the animation ends, there is no need to use it.

    For example, the following is a snippet of a case where a motion is played when the screen is clicked.
    private void Update()
    {
        if (!Input.GetMouseButtonDown(0))
        {
            return;
        }
    
        _motionController.PlayAnimation(clip: animationClip, priority: CubismMotionPriority.PriorityForce);
    }


    The third argument of CubismMotionController.PlayAnimation(), priority, is the priority of the animation to be played.
    If it is less than or equal to the priority of the currently playing animation, it will not be played.
    https://github.com/Live2D/CubismUnityComponents/blob/304d0d0c827b7bcb86c6f5d9b0a6398eaccda556/Assets/Live2D/Cubism/Framework/Motion/CubismMotionPriority.cs#L14
    public class CubismMotionPriority
    {
        public const int PriorityNone = 0;
        public const int PriorityIdle = 1;
        public const int PriorityNormal = 2;
        public const int PriorityForce = 3;
    }

    In the sample, when the torso is clicked while the Idle animation is playing, the animation is interrupted to play, and during the playback of that animation, it is not interrupted even if the torso is clicked again.

    However, if PriorityForce is passed, the animation is forced to play regardless of the current priority.
    Use this method if you want to handle animations without considering priority.
    https://github.com/Live2D/CubismUnityComponents/blob/304d0d0c827b7bcb86c6f5d9b0a6398eaccda556/Assets/Live2D/Cubism/Framework/Motion/CubismMotionController.cs#L107

    Best regards.
  • How about the motion fade @yasu.o@live2d?
    The only way you can do is subscribe to MotionController.AnimationEndHandler, CMIIW.
  • @kiraio

    Fades are handled automatically by CubismFadeController when motion is played.
    When using CubismMotionController, CubismFadeController._fadeStates is a reference to CubismMotionLayer.
    From there, information about the motion played by CubismMotionController.PlayAnimation() can be retrieved, and the fade is calculated.

    However, if each component is added at runtime, for example, there may be no reference to CubismMotionLayer, and calling CubismMotionController.PlayAnimation() will not start the fade process.
    In that case, call CubismFadeController.Refresh() after adding each component.

    Best regards.
  • Thanks for the reply! I'll test them out.
  • Bumping this thread again.I still didn't quite understand how to properly use the motion priority.Example, I played an
    intro animation once:
    MotionController.PlayAnimation("login", 0, 1, false);
    then i register a function to play looped idle animation on the end:
     MotionController.AnimationEndHandler += OnLoginComplete;
    
    public void OnLoginComplete(float instanceId)
    {
        MotionController.PlayAnimation("login", 0, 1, true);
        MotionController.AnimationEndHandler -= OnLoginComplete;
    }</
    
    code >
    From here it's still normal. Now I'm adding a function to play animation when the character torso is clicked:
     private void Interact(InputAction.CallbackContext ctx)
    {
        if (!ctx.performed) return;
        if (Camera.main != null)
        {
            var ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
            var results = new CubismRaycastHit[1];
            var hitCount = (byte)CharacterViewer.Raycaster.Raycast(ray, results);
    
            if (hitCount <= 0 || !results[0].Drawable.TryGetComponent(out Touch touch)) return;
            if (touch != null && touch == this)
            {
                MotionController.PlayAnimation("touch", 1, 1, false);
            }
        }
        else
        {
            Debug.LogWarning("Main Camera is null. Can'\t process Touch interaction.");
        }
    }</
    
    code >
    Notice that, I'm playing the motion on layer index 1. After it finish, I'm expecting the idle motion will be
    resumed because it's play on another layer independently.
    Is there something I miss here? I wanted the character to resumed the idle motion again and repeat the touch
    interaction if clicked.
  • Thanks! Not only it's fixed the problem, but also fix another issue where some character parts aren't rendered because I'm playing the touch animation on another layer.
Sign In or Register to comment.