How to use Spine Actors as Portraits?

Announcements, support questions, and discussion for the Dialogue System.
Ducky
Posts: 10
Joined: Fri Apr 24, 2020 2:37 am

How to use Spine Actors as Portraits?

Post 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.
User avatar
Tony Li
Posts: 22054
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use Spine Actors as Portraits?

Post 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.
Ducky
Posts: 10
Joined: Fri Apr 24, 2020 2:37 am

Re: How to use Spine Actors as Portraits?

Post 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.
User avatar
Tony Li
Posts: 22054
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use Spine Actors as Portraits?

Post 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.
Ducky
Posts: 10
Joined: Fri Apr 24, 2020 2:37 am

Re: How to use Spine Actors as Portraits?

Post 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
User avatar
Tony Li
Posts: 22054
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use Spine Actors as Portraits?

Post 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);
Ducky
Posts: 10
Joined: Fri Apr 24, 2020 2:37 am

Re: How to use Spine Actors as Portraits?

Post 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();
        }

    }
}
User avatar
Tony Li
Posts: 22054
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use Spine Actors as Portraits?

Post 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
Ducky
Posts: 10
Joined: Fri Apr 24, 2020 2:37 am

Re: How to use Spine Actors as Portraits?

Post 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?
Ducky
Posts: 10
Joined: Fri Apr 24, 2020 2:37 am

Re: How to use Spine Actors as Portraits?

Post 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.
Post Reply