Changing SETS of Portraits in a generic way

Announcements, support questions, and discussion for the Dialogue System.
eidab
Posts: 6
Joined: Fri May 09, 2025 9:58 am

Changing SETS of Portraits in a generic way

Post by eidab »

Hi!
Apologies if the subject is unclear. I'll try to explain what I'm trying to do, apologies if it gets too verbose! I'm wondering if you have any ideas or possible solutions for making it work more generically (or maybe an alternative line of thought!).

I've searched through this forum and the Discord server, and I know how to change portraits mid-conversation with markup tags and sequencer commands and even in advance, but these aren't exactly scalable solutions for our use case.

The way our character sprites are drawn and set up is that we draw full poses and expressions together, so we can't utilize layers or child visual elements for the portraits (as it is in the animated facial features example). Every expression or gesture variant is its own sprite, which we swap to on the fly.

For example, we have 'John Doe' and 'Jane Doe' as characters (ghosts specifically), both having a default sprite with a neutral expression. Changing to JohnDoe_Angry, [...]_Happy, [...]_Crying, etc., is simple enough; however, we would like to give the player an item—goggles—that allows them to see ghosts as they were alive back then, AND keep the conversations the same, but present a different set of sprites of each character to the player.

In a nutshell, how would you go about working with a similar setup:
  • JohnDoe || JohnDoeAlive (display "alive" version of the actor if the player is wearing the goggles)
  • JohnDoe_Happy || JohnDoeAlive_Happy
  • JohnDoe_Confused || JohnDoeAlive_Confused
  • and so on...
...without going crazy with conditions and/or sequences in dialogue nodes, or working with duplicated conversations?
For simplicity, the player can't equip or unequip the goggles mid-conversation. By generic, I meant that ideally, this would scale to every single NPC( and, of course, conversations associated with them).

I hope I didn't miss something obvious, and that it's not too much work either. I love the support this asset gets, already learned so much from this forum, the discord, and the documentation, so thank you for your hard work ^^
User avatar
Tony Li
Posts: 23250
Joined: Thu Jul 18, 2013 1:27 pm

Re: Changing SETS of Portraits in a generic way

Post by Tony Li »

Hi,

You could do that with a small script and a Dialogue System variable. Here's an example scene:

DS_PortraitSetExample_2025-05-09.unitypackage

This is the script it uses:

Code: Select all

using UnityEngine;
using PixelCrushers.DialogueSystem;

public class Goggles : MonoBehaviour
{
    [SerializeField] private UnityEngine.UI.Toggle gogglesToggle;

    private bool IsWearingGoggles => gogglesToggle.isOn;

    // This method runs on the Dialogue Manager when a conversation starts.
    void OnConversationStart(Transform actor)
    {
        // Set a DS variable "ActorState" to "Alive" if wearing goggles, blank otherwise:
        var actorState = IsWearingGoggles ? "Alive" : "";
        DialogueLua.SetVariable("ActorState", actorState);

        // Set the conversant's portrait, taking into account the ActorState variable:
        var conversation = DialogueManager.masterDatabase.GetConversation(DialogueManager.lastConversationStarted);
        var conversant = DialogueManager.masterDatabase.GetActor(conversation.ConversantID);
        DialogueManager.SetPortrait(conversant.Name, $"{conversant.Name}{actorState}");
    }
}
The scene has 4 portrait images (one of which is assigned to the NPC "Alan" in the database) in a Resources folder: Alan, Alan_Happy, AlanAlive, and AlanAlive_Happy.

The script sets a DS variable named "ActorState" to "Alive" if the Goggles checkbox is ticked and a blank string if not. Then it sets the conversant's portrait image to the conversant's name (e.g., "Alan") plus the ActorState (e.g., blank string if Goggles is UNticked), resulting in:

alan1.png
alan1.png (359.22 KiB) Viewed 5548 times

The last line of the conversation uses this Sequence:

Code: Select all

{{default}};
SetPortrait(Alan, Alan[var=ActorState]_Happy)
This sets the portrait to "Alan" + ActorState (e.g., blank string) + "_Happy":

alan2.png
alan2.png (379.53 KiB) Viewed 5548 times

However, if you start the conversation with the Goggles checkbox ticked, ActorState is set to "Alive", so it uses the image named "Alan" + "Alive" = "AlanAlive":

alan3.png
alan3.png (428.74 KiB) Viewed 5548 times

And the last node in the conversation's SetPortrait() command will use "Alan" + "Alive" + "_Happy" = "AlanAlive_Happy".
eidab
Posts: 6
Joined: Fri May 09, 2025 9:58 am

Re: Changing SETS of Portraits in a generic way

Post by eidab »

I appreciate your help, I managed to get it working with this solution ^^
I even extended it to the player actor's portrait (wearing glasses or not); however, I ran into a problem, and I'm unsure what I'm missing.

I'm getting the player with ActorID instead of ConversantID, and all is well with showing the non-goggled/goggled sprites plus expression variants using the sequences, EXCEPT for the very beginning of each conversation. This fixes itself as soon as the player starts speaking, even if there's no SetPortrait sequence or such in the node or the one before it.

I don't have a sprite assigned to the actor in the dialogue database, as we have player genders. This is only relevant because the very first time I start a conversation, there is no player portrait at all until the conversant subtitle plays and the player picks a response, which basically means the "None" sprite is not actually updated to what I set in the C# code until the player actor starts their subtitle, which is not ideal if the conversation begins with the NPC talking, or having to pick a response.

I'm using a Dialogue System Trigger on the player to set its portrait back to "default" (still following this method) On Conversation End, but the issue manages to slip through.

Also, sometimes the conversant NPC retains their last displayed portrait (alive+expression/ghost+expression/etc) from the previous conversation until they begin speaking their subtitle. I can't reproduce it consistently, though.

My question would be, could it be some setting that I changed, or is OnConversationStart() really not running at the exact beginning? I don't run any other C# code or sequence that would overwrite or race with it.
eidab
Posts: 6
Joined: Fri May 09, 2025 9:58 am

Re: Changing SETS of Portraits in a generic way

Post by eidab »

I appreciate your help, I managed to get it working with this solution ^^
I even extended it to the player actor's portrait (wearing glasses or not); however, I ran into a problem, and I'm unsure what I'm missing.

I'm getting the player with ActorID instead of ConversantID, and all is well with showing the non-goggled/goggled sprites plus expression variants using the sequences, EXCEPT for the very beginning of each conversation. This fixes itself as soon as the player starts speaking, even if there's no SetPortrait sequence or such in the node or the one before it.

I don't have a sprite assigned to the actor in the dialogue database, as we have player genders. This is only relevant because the very first time I start a conversation, there is no player portrait at all until the conversant subtitle plays and the player picks a response.
portraitsets.png
portraitsets.png (59.65 KiB) Viewed 5537 times
The player's portrait only fixes itself to what it should be according to the new class when the conversation flow crosses the green line on the pic. The NPC is correct from the get-go, although sometimes it can retain their last displayed portrait (alive+expression/ghost+expression/etc) from the previous conversation until they begin speaking their subtitle. I can't reproduce it consistently, though.

I'm using a Dialogue System Trigger on the player to set its portrait back to "default" (still following this method) On Conversation End, but the issue manages to slip through.

My question would be, could it be some setting that I changed, or is OnConversationStart() really not running at the exact beginning? I don't run any other C# code or sequence that would overwrite or race with it.
User avatar
Tony Li
Posts: 23250
Joined: Thu Jul 18, 2013 1:27 pm

Re: Changing SETS of Portraits in a generic way

Post by Tony Li »

Hi,

OnConversationStart() happens after the dialogue UI has opened. This means it happens after the dialogue UI has opened any subtitle panels whose Visibility dropdowns are set to Always From Start.

Since no sprite is assigned to the Player actor's Portrait Sprite, the dialogue UI deactivates the Portrait Image GameObject. Try assigning the sprite named "transparent" (included in the Dialogue System) to the Player's Portrait Sprite.

If that's not the issue, can you share your OnConversationStart() method or send a reproduction project to tony (at) pixelcrushers.com?
eidab
Posts: 6
Joined: Fri May 09, 2025 9:58 am

Re: Changing SETS of Portraits in a generic way

Post by eidab »

Hello,

I don't think unassigned portrait sprites/images are a problem, as they work correctly for the NPCs themselves, who didn't have anything assigned either. Not in the dialogue database, nor the Dialogue Actor component on their game objects. But just to make sure, I tried assigning one to the player (a regular player sprite, then the provided transparent), and it doesn't appear until the player speaks, and even when it does, it's correctly set by the custom method. Which is not ideal if the conversation starts with responses or NPC speech. I tried reproducing this in the example package you kindly provided, but there, it changes even when the player is presented with the response options. I can't get the player portrait to show up right at the start, as I'm not familiar with the VN Template UI yet, so I can't confirm whether the same issue persists there or not.

This is my OnConversationStart() method placed on the Dialogue Manager (sorry if the code is ugly)

Code: Select all

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using PixelCrushers.DialogueSystem;

public class DialoguePortraits : MonoBehaviour
{
    void OnConversationStart(Transform actor)
    {
        var gender = GameManager.Instance.IsPlayerMale ? "Male" : "Female";
        var siblingGender = GameManager.Instance.IsPlayerMale ? "Female" : "Male";
        var thiefGender = GameManager.Instance.IsPlayerMale ? "Female" : "Male";
        var goggles = GameManager.Instance.IsWearingGoggles ? "_Goggles" : "";
        var alive = GameManager.Instance.IsWearingGoggles ? "_Alive" : "";

        DialogueLua.SetVariable("PlayerGender", gender);
        DialogueLua.SetVariable("SiblingGender", siblingGender);
        DialogueLua.SetVariable("ThiefGender", thiefGender);
        DialogueLua.SetVariable("Goggles", goggles);
        DialogueLua.SetVariable("Alive", alive);

        var conversation = DialogueManager.masterDatabase.GetConversation(DialogueManager.lastConversationStarted);

        var playerActor = DialogueManager.masterDatabase.GetActor(conversation.ActorID);
        DialogueManager.SetPortrait(playerActor.Name, $"Portrait_{playerActor.Name}_{gender}{goggles}");

        var conversant = DialogueManager.masterDatabase.GetActor(conversation.ConversantID);

        if (conversant.Name == "Sibling")
        {
        DialogueManager.SetPortrait(conversant.Name, $"Portrait_Player_{siblingGender}{alive}");
        }
        else if (conversant.Name == "Thief")
        {
        DialogueManager.SetPortrait(conversant.Name, $"Portrait_{conversant.Name}_{thiefGender}{alive}");
        }
        else
        DialogueManager.SetPortrait(conversant.Name, $"Portrait_{conversant.Name}{alive}");
    }
}
Just to clarify, it's intended that the Sibling and Thief genders depend on the player's picked gender as they should be the opposite of it!

The variable names and their strings—as well as the portrait sprite names in Resources—match
An example in our case is: "Portrait_Player_Male_Goggles_Happy"

This is the default sequence I use to work with Text Animator:
Screenshot 2025-05-11 180416.png
Screenshot 2025-05-11 180416.png (13.93 KiB) Viewed 5514 times
which, as far as I'm aware, shouldn't conflict with what we're trying to accomplish

I tried tweaking the panel Visibility options as well, to no avail.

Also, I have yet to figure out why/how the conversant actors can sometimes retain their last displayed portrait from a previous conversation in a new one :(
User avatar
Tony Li
Posts: 23250
Joined: Thu Jul 18, 2013 1:27 pm

Re: Changing SETS of Portraits in a generic way

Post by Tony Li »

Is the dialogue UI's Default PC Subtitle Panel still set to Subtitle Panel 1? And is Subtitle Panel 1's Visibility set to Always From Start?

If so, then please check which actors are assigned to the conversation's Actor and Conversant dropdowns. (Menu > Conversation Properties in the Dialogue Editor.) I assume the Actor is set to Player and the Conversant is set to one of your NPCs.

If that doesn't help, can you send a reproduction project? Reproduction steps using the example I sent you would also be fine.
eidab
Posts: 6
Joined: Fri May 09, 2025 9:58 am

Re: Changing SETS of Portraits in a generic way

Post by eidab »

While trying to reproduce it in the example you provided—so effectively a vacuum—I realized the issue is not the UI or Dialogue Manager settings. I copied over the prefabs we use in our project (just had to fix missing references to sprites and UI materials, in our dialogue UI, and add Text Animator and TMP integration)

The answer to your previous questions is yes, and I double-checked the actor assignments, which ended up correct. The custom class works as expected in the example project, so I assumed the source of the problem couldn't be with the conversation or UI, nor the Dialogue Manager, but somewhere else.

I have this method in a different class, called GameManager:

Code: Select all

public void UpdateGenders(Gender playerGenderChoice)
	{
		PlayerGender = playerGenderChoice;

		DialogueLua.SetVariable("PlayerName", IsPlayerMale ? "MMC" : "FMC");
		DialogueLua.SetVariable("PlayerSheHe", IsPlayerMale ? "he" : "she");
		DialogueLua.SetVariable("PlayerHerHim", IsPlayerMale ? "him" : "her");

		DialogueLua.SetVariable("SiblingName", IsPlayerMale ? "Fibling" : "Mibling");
		DialogueLua.SetVariable("SiblingSheHe", IsPlayerMale ? "she" : "he");
		DialogueLua.SetVariable("SiblingHerHim", IsPlayerMale ? "her" : "him");

		DialogueLua.SetVariable("ThiefName", IsPlayerMale ? "Fanny Shultz" : "Danny Shultz");
		DialogueLua.SetVariable("ThiefSheHe", IsPlayerMale ? "she" : "he");
		DialogueLua.SetVariable("ThiefHerHim", IsPlayerMale ? "her" : "him");

		switch (playerGenderChoice)
		{
			case Gender.Female:
				DialogueLua.SetActorField("Player", DialogueSystemFields.DisplayName, "FMC");

				DialogueLua.SetActorField("Sibling", DialogueSystemFields.CurrentPortrait, "Portrait_Player_Male");
				DialogueLua.SetActorField("Sibling", DialogueSystemFields.DisplayName, "Mibling");

				DialogueLua.SetActorField("Thief", DialogueSystemFields.CurrentPortrait, "Portrait_Thief_Male");
				DialogueLua.SetActorField("Thief", DialogueSystemFields.DisplayName, "Thief Boy");
				break;

			case Gender.Male:
				DialogueLua.SetActorField("Player", DialogueSystemFields.DisplayName, "MMC");

				DialogueLua.SetActorField("Sibling", DialogueSystemFields.CurrentPortrait, "Portrait_Player_Female");
				DialogueLua.SetActorField("Sibling", DialogueSystemFields.DisplayName, "Fibling");

				DialogueLua.SetActorField("Thief", DialogueSystemFields.CurrentPortrait, "Portrait_Thief_Female");
				DialogueLua.SetActorField("Thief", DialogueSystemFields.DisplayName, "Thief Girl");
				break;
		}
It only runs on Start() of the GameManager game object, and when I choose the player's gender in a different scene, always before conversations. While testing previously, it was never called more than once after its Start.

If I comment out the entire switch (but keep the SetVariables above), our main problem seems to go away. What gives? The player's portrait isn't even changed with a SetActorField line. As soon as I uncomment the switch, the issue comes back. After this "fix" (hopefully), I haven't been able to reproduce the "retain their last displayed portrait from a previous conversation in a new one" bug either, but I can't rule it out yet.

Those fields exist in the dialogue database with an initial value, so they are not created at runtime like the OnConversationStart variables we use for switching portraits with sequences. I know the database is read-only, and the lua environment serves as a "virtual machine" during runtime. Still, I don't understand how this switch messes up the Player actor's portrait UNTIL it starts playing a subtitle without even touching a relevant field, and the OnConversationStart method runs after it. The Sibling and Thief conversants were more or less correct (might be the cause for the "retaining problem", so deleting this switch may just end up fixing it).

I'm confused, but it seems to work! Thank you very much for your help! If you don't mind, I would appreciate insight on this so I can avoid running into a similar issue down the line.
User avatar
Tony Li
Posts: 23250
Joined: Thu Jul 18, 2013 1:27 pm

Re: Changing SETS of Portraits in a generic way

Post by Tony Li »

Hi,

I have no idea. When it comes to the Player actor, the switch statement only sets its Display Name field. I think I'd need to see a reproduction project to be able to get to the bottom of it. As a test, what if you were to also set the Player's DialogueSystemFields.CurrentPortrait field in the switch statement?
eidab
Posts: 6
Joined: Fri May 09, 2025 9:58 am

Re: Changing SETS of Portraits in a generic way

Post by eidab »

It breaks the same way :lol:
The player's portrait becomes whatever I hardcode it to in the switch statement until their first subtitle plays, then it is fixed to what OnConversationStart told it to. I don't know why it lags behind if that switch is kept around.

I'll chalk it up to a mistake I implemented or wired elsewhere in the project, as it doesn't seem to be a bug of the Dialogue System! If I run into it again with no acceptable workaround, I'll make a separate thread and try my best to provide a reproduction project. Thank you again for the support!
Post Reply