Page 1 of 2

How to use Spine Actors as Portraits?

Posted: Mon Jun 08, 2020 2:55 am
by Ducky
Hey,

I've been reading through the documents but I'm struggling to understand how I can use my Spine models like you would with the default portraits that the database provides.

Ideally, I want the Actor to pop up like normal and call animations based on the Dialogue Node plus inside the text (may need a custom LUA script). I tried looking at the demo scene but it appears to only involve actors that are already in the scene instead of being Instantiated then used.

I have the add-on for Spine integration downloaded and installed with my project.

If anyone can make things clearer for me, I will greatly appreciate it!

EDIT: I've also cloned the SpineSequencerReferences script (to SpineSequencerReferencesUI) in order to use SkeletonGraphic instead of SkeletionAnimation. I made it so I can use SpineAnimationUI() for the sequencers.

Re: How to use Spine Actors as Portraits?

Posted: Mon Jun 08, 2020 8:52 am
by Tony Li
Hi,

When using Spine, don't use the dialogue UI's / dialogue database's portrait images. Instead, set it up like on the Spine Integration page:
  • Replace the dialogue UI's regular StandardUISubtitlePanel scripts with SpineSubtitlePanel.
  • Remove the portrait image GameObjects.
  • Add your own UI image GameObject for each actor. It can be in the dialogue UI or separate. For example, if you're using multiple scenes with different characters in each scene, you'll want to have the UI images in the scene instead of the Dialogue Manager hierarchy.
  • Add a SpineDialogueActor component to a GameObject for each character. It can be the same UI image GameObject or a separate GameObject (e.g., empty GameObject).
See the Additional Setup Options to automatically show and hide the spine character when involved in a conversation, and to play additional Spine animations.

Re: How to use Spine Actors as Portraits?

Posted: Mon Jun 08, 2020 10:50 am
by Ducky
Thanks for the reply but I'm not sure if this method works if I'm going to have 50+ characters.

Because the project will be on mobile, it might be too much idle information. This is why I want to swap actors like you can with Portraits. (Even if I Destroy and Instantiate another one)

I also got the Spine Actors working with animations and can make them into prefabs. I just want to know if it's possible to change the Portrait from a sprite to a gameObject? If not, I can try to work around it.

Re: How to use Spine Actors as Portraits?

Posted: Mon Jun 08, 2020 11:54 am
by Tony Li
You can still instantiate and destroy Spine characters. They don't have to be part of the dialogue UI.

Portraits must be sprites or textures. They can't be GameObjects. This is to maintain compatibility with various import/export formats.

Re: How to use Spine Actors as Portraits?

Posted: Tue Jun 09, 2020 11:48 am
by Ducky
Yeah I was wondering if I can edit the code or something. But thanks for information. I'm adding a seperate Database which will hold all the Spine GameObjects and can access them with a string (actorName).

Right now, I'm trying to add SpineAnimation() to the typewriter to play talking animation on the onCharacter event.

I'm also trying to access the Subtitle Panels in my code for my SequencerCommandSpineShowActorUI() in order to place the Spine Actor in the right location.

Thanks again Tony. I hope I can get this to work. :D

Re: How to use Spine Actors as Portraits?

Posted: Tue Jun 09, 2020 12:08 pm
by Tony Li
Hi,

You can put SpineAnimation() in the dialogue entry node's Sequence field, or in the Dialogue Manager's Default Sequence. If a node's Sequence field is blank, it will use the Dialogue Manager's Default Sequence. Then you can listen for the typewriter to send its "Typed" sequencer message:

Code: Select all

SpineAnimation(talk);
required SpineAnimation(idle)@Message(Typed)
The sequence above will tell the subject to play the "talk" Spine animation as soon as the node starts. When the typewriter is done, it will change to the "idle" animation. The "required" keyword ensures that it always plays "idle" even if the player skips ahead before the typewriter has finished.

In your custom sequencer command, you can do something like this:

Code: Select all

var currentSubtitle = DialogueManager.currentConversationState.subtitle;
DialogueActor dialogueActor;
var ui = DialogueManager.dialogueUI as StandardDialogueUI;
var panel = ui.conversationUIElements.standardSubtitleControls.GetPanel(currentSubtitle, out dialogueActor);

Re: How to use Spine Actors as Portraits?

Posted: Fri Jun 12, 2020 4:37 am
by Ducky
Thanks for the extra help! It helped me with the following code below. The only issue is that when I try to play an animation it cannot find the speaker. I was wondering do I need set the speaker through code?

SpineShowActorUI

Code: Select all

using UnityEngine;
using PixelCrushers.DialogueSystem.SpineSupport;

namespace PixelCrushers.DialogueSystem.SequencerCommands
{
    /// <summary>
    /// Call an actor's name in the sequencer and they will appear. Use the Subtitle Panels to position the actor.
    /// Position selects which Subtitle Panel should the Actor appear on. 
    /// </summary>
    public class SequencerCommandSpineShowActorUI : SequencerCommand
    {
        public void Start()
        {
            // Parameters
            var actorName = GetParameter(0);
            var position = GetParameterAsInt(1);

            // SetPanel is needed in order to move the actor
            var currentSubtitle = DialogueManager.currentConversationState.subtitle;

            // Find if actor exists on any of the panels, if so, use that GameObject. Else, create a new actor from the database.
            GameObject actor = null;
            DialogueActor dialogueActor = null;

            StandardDialogueUI ui = DialogueManager.dialogueUI as StandardDialogueUI;
            bool actorExists = false;
            int actorIsOnSubtitlePanel = 0;

            // Checking each subtitle panel, to see if the actor already exists.
            for (int i = 0; i < ui.conversationUIElements.subtitlePanels.Length; i++)
            {
                StandardUISubtitlePanel currentPanel = ui.conversationUIElements.subtitlePanels[i];
                if (currentPanel.GetComponentInChildren<DialogueActor>())
                {
                    // If the parameter name matches the existing actor's name, use that actor instead of creating one.
                    if (actorName == currentPanel.GetComponentInChildren<DialogueActor>().GetName())
                    {
                        //Debug.Log("Found " + actorName + " in Subtitle Panel " + i);

                        dialogueActor = currentPanel.GetComponentInChildren<DialogueActor>();
                        actor = currentPanel.GetComponentInChildren<DialogueActor>().gameObject;
                        actorExists = true;
                        actorIsOnSubtitlePanel = i;

                        // If the actor is already in position, stop this function.
                        if (position == i)
                        {
                            Debug.LogWarning(actorName + " already exists on Subtitle Panel " + i);
                            return;
                        }

                        break;
                    }
                }

            }

            // If the actor was not located in the Subtitle Panels, make a new actor and place them in the desired Panel.
            if (!actorExists)
            {
                actor = Instantiate(GamePrefabReferences.Instance.dialogueActorReferences.GetActorByName(actorName).prefab);
                dialogueActor = actor.GetComponent<DialogueActor>();
                actor.name = actorName;
                Debug.Log(actorName + " has been created.");
            }

            // Selects the desired panel from the parameter and actives it.
            var panel = ui.conversationUIElements.subtitlePanels[position];
            panel.SetOpen(true);

            // Move actor to the right Panel
            actor.transform.SetParent(panel.transform);
            actor.transform.position = panel.transform.position;

            // End the Command Sequencer
            Stop();
        }
    }
}

SpinePlayAnimationUI

Code: Select all

using UnityEngine;
using PixelCrushers.DialogueSystem.SpineSupport;

namespace PixelCrushers.DialogueSystem.SequencerCommands
{
    public class SequencerCommandSpinePlayAnimationUI : SequencerCommand
    {
        public void Start()
        {
            // Parameters
            var animationName = GetParameter(0);
            var subject = GetSubject(1, speaker);
            var trackIndex = GetParameterAsInt(2);
            var loop = GetParameterAsBool(3, true);

            var references = (subject != null) ? subject.GetComponentInChildren<SpineSequencerReferencesUI>() : null;
            var animation = (references != null) ? references.animationReferenceAssets.Find(x => x.name == animationName) : null;
            var state = (references != null && references.skeletonGraphic != null) ? references.skeletonGraphic.AnimationState : null;

            if (subject == null)
            {
                if (DialogueDebug.logWarnings) Debug.LogWarning("Dialogue System: Sequencer: SpinePlayAnimationUI(" + GetParameters() + ") can't find the subject.");
            }
            else if (references == null)
            {
                if (DialogueDebug.logWarnings) Debug.LogWarning("Dialogue System: Sequencer: SpinePlayAnimationUI(" + GetParameters() + ") subject " + subject + " needs a SpineSequencerReferencesUI component.", subject);
            }
            else if (animation == null)
            {
                if (DialogueDebug.logWarnings) Debug.LogWarning("Dialogue System: Sequencer: SpinePlayAnimationUI(" + GetParameters() + ") SpineSequencerReferencesUI on " + subject + " doesn't have an AnimationReferenceAsset named '" + animationName + "'.", subject);
            }
            else if (state == null)
            {
                if (DialogueDebug.logWarnings) Debug.LogWarning("Dialogue System: Sequencer: SpinePlayAnimationUI(" + GetParameters() + ") SkeletonAnimation referenced by SpineSequencerReferencesUI on " + subject + " doesn't have an AnimationState.", subject);
            }
            else
            {
                if (DialogueDebug.logInfo) Debug.Log("Dialogue System: Sequencer: SpinePlayAnimationUI(" + GetParameters() + ")", subject);
                state.SetAnimation(trackIndex, animation, loop);
            }

            Stop();
        }

    }
}

Re: How to use Spine Actors as Portraits?

Posted: Fri Jun 12, 2020 7:06 am
by Tony Li
You don't need to set the speaker through code.

Temporarily set the Dialogue Manager's Other Settings > Debug Level to Info. When a conversation starts, it will log a line like this in the Console:

Dialogue System: Starting conversation 'AAA' with actor=XXX and conversant=YYY

Make sure XXX and YYY are the GameObjects that you intend. If not, or if the dialogue entry is assigned to a character that isn't the conversation's primary actor or conversant, see: Character GameObject Assignments

Re: How to use Spine Actors as Portraits?

Posted: Sun Jun 14, 2020 11:42 pm
by Ducky
I tried the debug and it shows no name for both fields. I've check the dialogue and I've set it to who I want to speak but still doesn't work. Is there a way to set the field manually?

Re: How to use Spine Actors as Portraits?

Posted: Mon Jun 15, 2020 12:01 am
by Ducky
I've also realized in StartConversation() I only added the title and not the actor and conversant, this could be the issue of the fields being null.

I can't attach anything to the fields because I don't have any actors in the scene yet. I wanted the dialogue system to show the Spine Actors and set them as the speaker.