Page 1 of 2

Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Fri Nov 12, 2021 6:13 am
by Nightkin
Hello!

I'm trying to set up my dialogue database with several conversations that I try to keep as generic as possible. By "generic" I mean the actors should not be named at design-time: I have "A", "B", "C", "D" and "E" defined in the Actors table of the dialogue database for the time being and that's it. The important takeaway is that none of these actors is to be defined as a player at design-time because the player may change character at any time (they can detach the camera from a character, fly to another character then take control of it).

To identify which "generic actor" is talking (A, B etc) in the conversation editor, I assigned a different color to all five of them, but of course none of the nodes is blue since none of the actors is defined as being a player in advance.

Here is an example of such a generic conversation. This is a test with two actors, "A" (dark green) and "B" (yellowish green), and tokens like "[@a.nn]" and "[@b.nn]" are to be replaced at runtime by A's nickname and B's nickname respectively. The condition in the "Welcome to the company" node is 'IdentFaction("@b.uid", "@a.uid", "office worker") >= 0.5' which means "does actor B identify actor A as an office worker with at least 50% confidence?" (knowing that actor A may or may not be dressed like an employee, may or may not be an actual employee, which may or may not be known by B, so it goes a little further than just "is A wearing the appropriate outfit?"). A or B may be a player, or neither of them.
2021-11-12_120126.jpg
2021-11-12_120126.jpg (102.51 KiB) Viewed 943 times
When it comes to starting an actual conversation during play, the game assigns the actors by calling

Code: Select all

CharacterInfo.RegisterActorTransform (alias, transform)
(*), then

Code: Select all

DialogueManager.StartConversation (conversationName)
I don't even bother setting the Actor and Conversant in that call, so I assume the first two registered actors become them respectively. Problem is, I don't see a way to tell Dialogue System that one of them is supposed to be a player so the game plays the conversations without ever giving the player a choice.

Please note that I make extensive use of conditions and scripts in conversations (I have an internal dictionary linking an active conversation to a set of data so I can get data in real time from my own characters, such as their name or their disguises or anything you can think of, without ever needing Lua), so a conversation has a lot of branches that still make sense whether one of the actors is a player or not.

An example of this would be a situation like in Hitman where a character wants to walk into a restricted area. If the character is wearing the proper outfit or is properly searched by the guard, the guard will say "go ahead" whether the character is a player or not. If not, the guard will deny passage or even become hostile. It is the same conversation, it doesn't matter who's actually controlling any of the characters, a player or an AI.

I can't set "isPlayer" to an actor in the database at design-time because as I pointed out above, any of these actors may be a player. I can't set it at runtime either because I could have two conversations running at the same time, both using the actors A and B and in one of the conversations A is a player but not in the other conversation.

So I'm at a loss about how to do this. How to tell DS at runtime that an actor is a player?

(*) This leads to a second question actually. If I have more than one conversation running at the same time, I call

Code: Select all

CharacterInfo.RegisterActorTransform ("A", transform1)
then a little later

Code: Select all

CharacterInfo.RegisterActorTransform ("A", transform2)
(while the first conversation is still running), and suddenly the first conversation's actor "A" will be different. I know that conversations hold their own Actor and Conversant but I don't see a way to have more than two participants proper to the conversation. If I want more than two, the third one is global if I'm not mistaken. I know someone managed to do that but they never explained how and I can't find the post. Is there a way to make those actors A, B, C etc be local to the conversation so I can have two different characters be labeled "A" (for example) in two different conversations without one stepping on the other's toes?

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Fri Nov 12, 2021 9:26 am
by Tony Li
Hi,

Given your very particular needs -- multiple simultaneous conversations + dynamically-assigned actors + any of which could be the player -- it may be easiest to use dynamically-created instances of conversations. Any two of those needs could be handled as-is, but all three together will require a little extra work.

I'll outline the idea here. If it's something you'd like to pursue and you'd like to see some example code, I can put something together.

You might approach the Hitman-style example like this:
  • Define two "template" actors named Guard and Person. (I'm using Guard and Person instead of A, B, C, etc., just for logical readability when writing the conversation.)
  • Write your "template" conversation (e.g., "Security Check") between Guard and Person.
  • When a character interacts with a guard character:
    • Duplicate the Guard and Person actors, assigning them unique IDs. If one of the characters is the player, set the corresponding actor's Is Player value to true.
    • Duplicate the Security Check conversation. Assign the new actors' unique IDs in place of the original Guard and Person IDs.
    • Start your newly-duplicated conversation.
  • When the conversation is done, remove the duplicated actors and conversations. (Alternatively, as an optimization you could pool them. But I wouldn't bother with optimizations until you settle on the basic approach that you like.)

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Fri Nov 12, 2021 10:20 am
by Nightkin
Thank you very much for your fast answer. I'm still new with Dialogue System so forgive me if my questions seem naive at times. I just haven't wrapped my head around the core design of the asset yet so there must be obvious details that I'm missing. To me, each Conversation should be a single object that contains its own data (actors and variables) and I avoid using DialogueLua.SetVariable() because the DS variables seem global to me. I need to be able to start an arbitrary number of conversations at the same time with an arbitrary number of actors per conversation (zero or more of which may be players) without any conversation stepping on another conversation's toes or overwriting its data.
Define two "template" actors named Guard and Person.
Do you mean in the list of Actors in the database? Right now I have A, B, C, D and E (or Guard, Person, C, D E if you prefer) because I'm limiting myself to five actors per conversation.
Duplicate the Guard and Person actors
I assume once again you mean the ones in the database... In that case, does this mean I have to instantiate a copy of the database first? If so I would need to do this once per active conversation (without forgetting to store their references somewhere), and this means no debugging the database at runtime in the editor anymore. If I understand you right, I would have to create two (or more) Actor objects and add them to the "actors" list of the temporarily instantiated DialogueDatabase, correct?
assigning them unique IDs
Do I calculate those IDs myself or is there a function in DS to do it for me? Looking at the Actor and Asset classes, I see IDs but I don't see where they are calculated. How do I do that?
set the corresponding actor's Is Player value to true
That one seems easy if I follow the above reasoning, that would be newActor.IsPlayer = true.
Duplicate the Security Check conversation
I assume you mean the Conversation object taken directly from the "conversations" list of the DialogueDatabase class (the one I will probably have to instantiate).
Assign the new actors' unique IDs in place of the original Guard and Person IDs
In other words, in the list of Actors of the duplicated database (not the duplicated conversation), I would replace the ID of each Actor with a new unique ID (unique in the sense of "not present in any other instantiated database"), right?
Start your newly-duplicated conversation
The only way I know to start a conversation is DialogueManager.StartConversation (conversationName)... which is a static call. How do I start a conversation from an instantiated conversation template?

Sorry, it seems your answers to my initial questions brought only more questions... I very much want to pursue the prospect of being able to have an arbitrary number of simultaneous conversations with an arbitrary number of actors (with aliases shared among conversations), an arbitrary number of which might be players, and I am not afraid of coding (that's my job). But I'm having trouble wrapping my head around the design of DS, considering how complex that asset is and the number of features it includes.

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Fri Nov 12, 2021 10:43 am
by Nightkin
To be perfectly clear, the "multiple simultaneous conversations" requirement for me is the weakest of the three, weaker than "any character may be a player" and "actors must be assigned dynamically". My game is single-player for now, emphasis on "for now" because I'm designing it in a way that adding multiplayer later may be possible later. By that I mean "not totally impossible" so I avoid concepts like "the player", "the main character" etc, I always assume there may be more than one instance of anything that moves and can be controlled in my game.

So until my game becomes multiplayer (if/when it ever happens), that first requirement may go if it greatly simplifies the process you described above, since you said "any two of those needs could be handled as-is".

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Fri Nov 12, 2021 11:32 am
by Tony Li
You could work in support for multiple conversations later in that case. It's really up to you whether you want to "future proof" your code now at the cost of extra upfront implementation work.

In a dialogue database, a conversation is an object that points to specific actors in the database. The actors themselves are not contained within the conversation object. Otherwise you'd get into a whole mess of other issues trying to keep the same actor info in sync across multiple conversations.

If you don't need simultaneous conversations, you can just set the player actor's IsPlayer property true before starting the conversation.

For your single player game, if you still need simultaneous conversations in which the actors can be different, you'll instead want to think of the dialogue database's conversation as a template, kind of like a Unity prefab, that you'll need to "instantiate" to customize with the specifics for each running instance of the conversation. You can also "instantiate" new actor objects for the conversation instance, which might simplify the code. By "instantiate" I don't mean Unity's Instantiate() method; I'm just using it as a metaphor for creating a copy of the original conversation that the Dialogue System can run with specific actors. In this case (i.e., if you need simultaneous conversations), what you'll need to do is no different from supporting multiple players.

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Fri Nov 12, 2021 1:03 pm
by Nightkin
Yes for now all I want is to make a conversation (in the database) character-agnostic, i.e. use abstract actors (A, B, C etc) which are roles I assign to the relevant characters when I need to start this conversation.

From what I observed and tested, the Dialogue Database is instantiated during play time. I know because modifying the conversation during play time in the editor does not modify the lines said by the characters during that conversation until I restart the game.

For anyone who is interested in a similar solution, here is the code I use to set the "IsPlayer" bit dynamically, knowing that "alias" is the actor alias ("A", "B" etc) which is obtained a bit earlier in the code (not shown here because irrelevant for this issue), and "character.IsControlledByPlayer()" returns True when a player controls this character and False otherwise.

This piece of code sits in my GameManager.StartConversation() function, inside a loop that loops over all the alias-character couples that I provide in its parameters to call "CharacterInfo.RegisterActorTransform (alias, character.transform)".

Code: Select all

// This function iterates over a list of couples (alias, character) and registers them to DS before starting the conversation. 
// This code here registers one such couple.
PixelCrushers.DialogueSystem.CharacterInfo.RegisterActorTransform (alias, character.transform);
			
// Find the corresponding actor in the database and set its IsPlayer bit accordingly.
foreach (var databaseActor in DialogueManager.databaseManager.masterDatabase.actors) {
	if (databaseActor.Name.Equals (alias, StringComparison.CurrentCultureIgnoreCase)) {
		databaseActor.IsPlayer = character.IsControlledByPlayer ();
	}
}
It works well, however this modifies the database (or an instance of it) so yes when I'll need simultaneous conversations (and I will), this will no longer be satisfactory.


I'm unclear about copying a Conversation object. I know it is not a ScriptableObject like the database so it cannot be "instantiated" in the Unity sense, but I don't really see what to do after copying one of the existing Conversations of the database... I assume that before starting a conversation, DS does all kinds of stuff with the Conversation object (I could go take a peek but I don't really want to) before presenting it on the screen. From what I know, Conversations do not involve more than two participants (one Actor and one Conversant) but some of my conversations will need more. By that I mean I know about RegisterActorTransform but this does not modify the conversation itself (or rather the ActiveConversationRecord object), but the database, if I'm not mistaken.

For example, I will need to create a conversation where two NPC cops talk about the player (who plays a cop at that time, pacing through the precinct) before the player comes close enough and actually intrudes in the conversation. If the two cops are actors A and B and the player is C, and at the same time two more NPC cops a bit further away discuss among themselves and are assigned to actors C and D (but in the context of the second conversation), I'm toast because suddenly C is no longer a player and is no longer even the same character.

If it's not too much work for you, I'd be very grateful for some pointers on how to code that particular feature, with an actual code example. Thanks in advance :)

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Fri Nov 12, 2021 1:34 pm
by Tony Li
Hi,

The Dialogue Manager GameObject's Other Settings > Instantiate Database checkbox lets you control whether the Dialogue System instantiates a copy of the dialogue database or directly uses the asset. If you're editing dialogue at runtime and want the changes to last, you could untick Instantiate Database.

If I don't have a chance to put together a quick example today, I'll send it to you by tomorrow.

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Fri Nov 12, 2021 2:31 pm
by Nightkin
Thank you very much for that. What I'm most interested in is what you said about copying Actors with unique IDs and setting copies of conversations. This sounds like it should solve my issue but I'm not familiar with DS enough to pull that off.

But please don't work overtime on my account!

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Sat Nov 13, 2021 12:24 pm
by Tony Li
Hi,

Here's an example (exported from Unity 2020.3):

DS_ConversationTemplateExample_2021-11-13.unitypackage

To keep the code short, it doesn't do much error checking. There are some comments in the code where ideally you'd delete or reuse conversation "instances" after they're done, but I left that out to keep the code brief.

Re: Set "IsPlayer" at runtime, and how to have more than 2 participants in a conversation

Posted: Sat Nov 13, 2021 2:03 pm
by Nightkin
Thank you so much Tony, you're always so helpful :)

Looking at the code, this is something I could never have come up with, knowing my meager knowledge of the intricacies of DS, but by playing the scene I see that this is exactly what I needed. So I will look into your code example very attentively.

Thank you again!