Forum rules - please read before posting.

Change player character's expression during an npc's dialogue line?

edited March 2021 in Technical Q&A

Hi everyone.

I'm making a game with a heavy emphasis on a Visual Novel-style dialogue. I'm using the Gabriel Knight conversation template available from the Downloads section, but I'm having trouble figuring out how to change the portrait expression of a non-speaking character during multiple lines of an NPC character. I've searched the forums, and read through a fair bit of the manual, but to no avail. I've provided an image with an example of what I'm trying to do. The boy in the image below walks into a pawn shop, hoping he'll get rich by selling an item - his expression while the woman says "Sure" is a happy one, with it changing to a sad one WHILE the woman is telling him how much money he'll actually get. What I'm trying to accomplish here, is have the player character have a sad face WHILE the NPC is telling him the bad news, and not have him standing there with a silly, smiling face while his plans of getting rich are ruined. I'm struggling with trying to make this work in a single Dialogue: Play Speech ActionList (with speaker set to an NPC).

https://ibb.co/0JhDMx2

Comments

  • edited March 2021

    Welcome to the community, @newrunegames.

    Provided you've defined expressions in your Character's Inspector, you can use the [expression:Name] token in your speech to change their expression as they speak - see this tutorial.

    However, this token only affects the character who is speaking the actual line.

    To instead affect a different character - in this case, the Player, while the NPC is speaking - you'll need to rely on a custom speech token that you can define in a custom script.

    You can learn more about this feature in the Manual's "Speech event tokens" chapter.

    Try this. Paste the code below into a C# file named CustomPlayerExpressions.cs, and place it in your scene. It'll then allow use of a custom [PlayerExpression:Name] token in your speech text, which should force the expression of the Player, regardless of who is speaking the text.

    using UnityEngine;
    using AC;
    
    public class CustomPlayerExpressions : MonoBehaviour
    {
    
        private const string tokenName = "PlayerExpression";
    
        private void OnEnable ()
        {
            KickStarter.dialog.SpeechEventTokenKeys = new string[1] { tokenName };
            EventManager.OnSpeechToken += OnSpeechToken;
        }
    
        private void OnDisable () { EventManager.OnSpeechToken -= OnSpeechToken; }
    
        private void OnSpeechToken (AC.Char speakingCharacter, int lineID, string tokenKey, string tokenValue)
        {
            if (tokenKey == tokenName)
            {
                int expressionID = KickStarter.player.GetExpressionID (tokenValue);
                KickStarter.player.SetExpression (expressionID);
            }
        }
    
    }
    

    For example:

    Sure. [PlayerExpression:Sad] I can give you $5 for it.
    
  • It works like a charm! Thank you so much Chris!

  • Just a follow-up question concerning this because I'm not coding savvy at all - how would the code above need to be changed to accomplish a vice-versa effect i.e. change an NPC's face while the player speaks?

  • You could make a new token for each NPC in the same way, but it wouldn't be too convenient. Alternatively, you could have a more general "NPCExpression" token that instead affects the last NPC to speak.

    This script, CustomExpressions.cs, combines both the previous PlayerExpression token with a new NPCExpression token:

    using UnityEngine;
    using AC;
    
    public class CustomExpressions : MonoBehaviour
    {
    
        private const string playerToken = "PlayerExpression";
        private const string npcToken = "NPCExpression";
        private NPC npc;
    
        private void OnEnable ()
        {
            KickStarter.dialog.SpeechEventTokenKeys = new string[2] { playerToken, npcToken };
            EventManager.OnSpeechToken += OnSpeechToken;
            EventManager.OnStartSpeech += OnStartSpeech;
        }
    
        private void OnDisable ()
        {
            EventManager.OnSpeechToken -= OnSpeechToken;
            EventManager.OnStartSpeech -= OnStartSpeech;
        }
    
        private void OnSpeechToken (AC.Char speakingCharacter, int lineID, string tokenKey, string tokenValue)
        {
            if (tokenKey == playerToken)
            {
                int expressionID = KickStarter.player.GetExpressionID (tokenValue);
                KickStarter.player.SetExpression (expressionID);
            }
            else if (tokenKey == npcToken && npc)
            {
                int expressionID = npc.GetExpressionID (tokenValue);
                npc.SetExpression (expressionID);
            }
        }
    
        private void OnStartSpeech (Char speakingCharacter, string speechText, int lineID)
        {
            if (speakingCharacter is NPC)
            {
                npc = speakingCharacter as NPC;
            }
        }
    
    }
    

    Use this new token in the same way. The only thing to keep in mind, however, is that it'll only affect the last NPC to speak. So, you'll need your NPC to say something before they'll be affected by the token.

  • Many thanks Chris!

  • Update: For some reason the NPCExpression isn't changing the NPC's expression. The PlayerExpression works though. When I delete the CustomExpressions.cs from the scene the [NPCExpression:sample_expression] appears in the Subtitles menu as text, as it should I guess, since the script that defines it is missing. But, when I attach it back to the scene, the [NPCExpression:sample_expression] doesn't appear as a text in the Subtitles anymore, but it doesn't change the expression either. I made it a point to test it after an NPC spoke , since you stressed that, but it just seems like it's not registering the NPC at all?

  • Where have you placed the CustomExpressions component? Try placing it on a new empty GameObject, so that it's not enabled/disabled by other means.

    Here's an alternative that will print messages in the Console whenever a character speaks, or a token is processed:

    https://pasteall.org/emGM

    What messages show when testing?

  • Yes, I've been doing it just like that, creating an empty GameObject in the scene and attaching the script to it. I just made a new empty GameObject (just in case) and renamed the new script you provided to ConsoleCustomExpressions (I renamed it in the .cs too) and this is what it gave me:

    https://ibb.co/7rfzQkz

    [var:2] is the Player character, and I've attached the ActionList in question too. I'm trying to have the NPC change expression on Player's second line of speech, but it somehow prints that the NPCExpression pertains to the Player character, if I'm getting this right?

  • edited March 2021

    The Console messages will appear in white as "info" statements. You'll need to enable them in the top-right of the Console to see them.

    The warning you're getting, though, is to do with the expression name (likely "katie_neutral") not being present on the NPC. Is this definitely the case?

    It might be worth making your expression names more generic, e.g. "neutral", "happy" etc - that way, you can use the same names regardless of which NPC is speaking. At the moment, you're setting a character-specific expression (katie_neutral) to a non-specific NPC (the last NPC to have spoken).

    Alternatively, the original script could be adapted to instead affect specific characters, for example:

    using UnityEngine;
    using AC;
    
    public class CustomExpressions : MonoBehaviour
    {
    
        public NPC katie;
        private const string katieToken = "KatieExpression";
        private const string playerToken = "PlayerExpression";
    
        private void OnEnable ()
        {
            KickStarter.dialog.SpeechEventTokenKeys = new string[2] { playerToken, katieToken };
            EventManager.OnSpeechToken += OnSpeechToken;
        }
    
        private void OnDisable ()
        {
            EventManager.OnSpeechToken -= OnSpeechToken;
        }
    
        private void OnSpeechToken (AC.Char speakingCharacter, int lineID, string tokenKey, string tokenValue)
        {
            if (tokenKey == playerToken)
            {
                int expressionID = KickStarter.player.GetExpressionID (tokenValue);
                KickStarter.player.SetExpression (expressionID);
            }
            else if (tokenKey == katieToken && katie)
            {
                int expressionID = katie.GetExpressionID (tokenValue);
                katie.SetExpression (expressionID);
            }
        }
    
    }
    
  • edited March 2021

    Yes, thank you Chris, I've managed to make it work with this script, so I'll be using this instead as it completely covers what I need right now. And thank you for the advice regarding expression naming, you're right, it is easier. I've switched over from Ren'Py so I just copied all my sprites, and they had to be named like that there. A valuable piece of advice for sure.

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.