How to use a cloned database

Announcements, support questions, and discussion for the Dialogue System.
lord_loej
Posts: 8
Joined: Wed Jun 13, 2018 4:34 pm

How to use a cloned database

Post by lord_loej »

sorry if this has been asked before, I searched the forum and couldnt find a solution to my problem.

to clarify what I am trying to do, all of my npcs are generated at runtime, and I want to clone the main dialogue database, that way I can change varibled and actors without it overwriting the original, this works fine. the issue I have is when I try to trigger the dialogue, whenever the dialogue is triggered it will open up the vanilla main dialogue database, and not the cloned one with edited information. I believe the issue is with the unique id system of the dialogue databases, I tried using the dialoguemerger but it's not what i need, the final goal is to have anywhere from 20 to 50 npcs in one scene, so I need that many cloned instances of the main dialogue database.
User avatar
Tony Li
Posts: 22058
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use a cloned database

Post by Tony Li »

Hi,

A dialogue database is an object that contains a list of actor objects, variable objects, conversation objects, etc. Each of these sub-objects (actor, variable, etc.) has a unique ID number within its list.

At runtime, the Dialogue System maintains a master database in memory. It contains all of the sub-objects from all of the databases that you've loaded. You can load a database by assigning it to the Dialogue Manager's Initial Database field, or using the Extra Databases component, or calling DialogueManager.AddDatabase() in a script. However, as you've seen, it assumes that the IDs are unique across all databases. In addition, all names should be unique. So you can't have two variables named "Foo".

Instead of cloning your main database, which would require assigning unique IDs to its actors, variables, etc., you can create a new database with unique IDs and add it. Also, if you're associating data with each actor, it's probably cleaner to put that data in actor fields rather than variables.

Here's an example that adds 50 NPCs, each with a custom field named 'Has Met Player':

Code: Select all

using PixelCrushers.DialogueSystem;
...
// Create a temporary database:
var database = ScriptableObject.CreateInstance<DialogueDatabase>();

// Create a template, which provides helper methods for creating database content:
var template = Template.FromDefault();

// Create actors:
// Use whatever method you prefer to guarantee unique actor IDs. In the code below,
// we assume actors in the main database have IDs under 1000.
// The code below adds 50 actors with IDs 1000 to 1049:
for (int actorID = 1000; actorID < 1050; actorID++)
{
    var actor = template.CreateActor(actorID, "NPC" + actorID, false)
    
    // We'll also add a custom Boolean field 'Has Met Player':
    actor.fields.Add(new Field("Has Met Player", "false", FieldType.Boolean));
    
    // Add add this actor to the temporary database:
    database.actors.Add(actor);
}

// Finally, add it to the Dialogue System's runtime master database:
DialogueManager.AddDatabase(database);
lord_loej
Posts: 8
Joined: Wed Jun 13, 2018 4:34 pm

Re: How to use a cloned database

Post by lord_loej »

Thanks for the fast reply,

the method you suggested might not work for me, the way im doing it is my main database has a bunch of conversations in them, each one represents a profession that an npc could have, then each conversation will branch out based on the assigned personality of the npc. additionally not all of my npcs are generated at the same time (you start with about 5 or so, and that number increases over the course of the game).

Also, how do you change the actor of nodes on the fly (in the conversation nodes where you assign the actor, I need to be able to change this based on which npc the player is talking to) I would also need to be able to check the active actors variables (there profession and personality) to branch out the conversations

furthermore is it possible to assign an actor to an npc, so it changes the active actor of the conversations (i would assume assigning this in onconversationstart)


-Thanks
User avatar
Tony Li
Posts: 22058
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use a cloned database

Post by Tony Li »

Hi,

Thanks for clarifying.

Runtime-Generated Actors:
You can add a new database for each actor. These databases only need to contain the new actor. Here are general-purpose methods to add and remove new actors at runtime:

Code: Select all

Dictionary<string, DialogueDatabase> actorDatabases = new Dictionary<string, DialogueDatabase>();

public void AddActor(string actorName, string personality)
{
    // Determine a unique actor ID:
    int highestExistingID = 1;
    foreach (var existingActor in DialogueManager.masterDatabase.actors) 
    {
        highestExistingID = Mathf.Max(highestExistingID, existingActor.id);
    }
    int actorID = highestExistingID + 1;
    
    // Create a database:
    var database = ScriptableObject.CreateInstance<DialogueDatabase>();
    
    // Keep track of the database so we can remove it later:
    actorDatabases.Add(actorName, database);

    // Create actor:
    var template = Template.FromDefault();
    var actor = template.CreateActor(actorID, actorName, false);
   
    // Add custom fields:
    actor.fields.Add(new Field("Personality", personality, FieldType.Text));
    
    // Add this actor to the temporary database:
    database.actors.Add(actor);

    // Add our new database to the master database:
    DialogueManager.AddDatabase(database);    
}


public void RemoveActor(string actorName)
{
    if (actorDatabases.ContainsKey(actorName))
    {
        var database = actorDatabases[actorName];
        DialogueManager.RemoveDatabase(database);
        Destroy(database);
        actorDatabases.Remove(actorName);
    }
}
(I typed this straight into the reply. Please forgive any typos.)


Conversations:
You can write general-purpose conversations and assign the primary NPC at runtime.

I'll usually keep the default starting actor "NPC" in my dialogue database to use for these general-purpose conversations:

Image

Let's say we have two profession conversations, "Baker" and "Hitman". Assign the generic NPC actor as the primary conversant in both conversations:

Image

Image

And let's say you have an NPC named "Adam", represented by a GameObject. Add a Dialogue Actor component so you can specify which actor this GameObject is associated with.

Image

If you create the NPC at runtime, assign DialogueActor.actor:

Code: Select all

var dialogueActor = newNPC.gameObject.AddComponent<DialogueActor>();
dialogueActor.actor = "Adam";
When starting a conversation, you can tell the Dialogue System to use this GameObject (and its associated actor) as the primary conversant. You can do this using a Dialogue System Trigger component:

Image

Or using DialogueManager.StartConversation in script:

Code: Select all

DialogueManager.StartConversation("Baker", player.transform, adam.transform);

Accessing Actor-Specific Fields In Conversations:
Everything in the master database is also in the Dialogue System's runtime Lua environment. For every actor, there's a corresponding Lua table element, such as Actor["Adam"].

Say you've added an actor field named "Personality", as in the example code above. You can check if Adam's personality is "grumpy" in a dialogue entry node by setting its Conditions field to:

Code: Select all

Actor["Adam"].Personality == "grumpy"
Image

This works if the conversation's actor will always be Adam. But you want to allow any actor to use any conversation. In this case, you can't hard-code "Adam" in the Conditions field.

Fortunately, when a conversation starts, the Dialogue System automatically defines some useful variables. The one you'll need is Variable["ConversantIndex"]. When you start the conversation with Adam assigned as the primary conversant, Variable["ConversantIndex"] will be "Adam". So you can set the Conditions to:

Code: Select all

Actor[Variable["ConversantIndex"]].Personality == "grumpy"
Image


Sorry for the long-winded War & Peace. If you have any questions, just let me know!
lord_loej
Posts: 8
Joined: Wed Jun 13, 2018 4:34 pm

Re: How to use a cloned database

Post by lord_loej »

Hey Tony, a massive thank you for taking the time to help me with this, and for how detailed you are in your response, this is amazing. the suggestions you provided work perfectly except for one minor thing, I'm trying to get the dialogue to go down different branches based on the npc personality and you had suggested using

Code: Select all

Actor[Variable["ConversantIndex"]].Personality == "grumpy"
I cant seem to get this working, the variable gets assigned correctly, and it becomes the value it should, however it doesnt work. the branch goes down the path as if the variable was null

I have even tried switching up the actors and conversant fields in the dialogue box, as well as the condition box.
User avatar
Tony Li
Posts: 22058
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use a cloned database

Post by Tony Li »

I put together a little example scene to make sure everything works as described:

DS2_RuntimeNPCExamples_2018-06-14.unitypackage

There are some input fields where you can specify an actor's name, profession, and personality.

The "Add Actor" button adds the actor.

The "Start Conversation" button starts a conversation named "Starter Conversation" that immediately branches to a baker conversation or a hitman conversation based on the actor's profession. The baker conversation branches based on the actor's personality.

Here's the code in case you don't want to download the example:

RuntimeNPCs.cs
Spoiler

Code: Select all

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

public class RuntimeNPCs : MonoBehaviour
{

    private Dictionary<string, DialogueDatabase> actorDatabases = new Dictionary<string, DialogueDatabase>();
    private Template template = Template.FromDefault();

    public void AddActor(string actorName, string profession, string personality)
    {
        // Determine a unique actor ID:
        int highestExistingID = 1;
        foreach (var existingActor in DialogueManager.masterDatabase.actors)
        {
            if (string.Equals(existingActor.Name, actorName))
            {
                Debug.LogError("Actor '" + actorName + "' already exists.");
                return;
            }
            highestExistingID = Mathf.Max(highestExistingID, existingActor.id);
        }
        int actorID = highestExistingID + 1;

        // Create a database:
        var database = ScriptableObject.CreateInstance<DialogueDatabase>();

        // Keep track of the database so we can remove it later:
        actorDatabases.Add(actorName, database);

        // Create actor:
        var actor = template.CreateActor(actorID, actorName, false);
    
        // Add custom fields Profession & Personality:
        actor.fields.Add(new Field("Profession", profession, FieldType.Text));
        actor.fields.Add(new Field("Personality", personality, FieldType.Text));

        // Add this actor to the temporary database:
        database.actors.Add(actor);

        // Add our new database to the master database:
        DialogueManager.AddDatabase(database);
    }


    public void RemoveActor(string actorName)
    {
        if (actorDatabases.ContainsKey(actorName))
        {
            var database = actorDatabases[actorName];
            DialogueManager.RemoveDatabase(database);
            Destroy(database);
            actorDatabases.Remove(actorName);
        }
    }
}
TestRuntimeNPCs.cs
Spoiler

Code: Select all

using UnityEngine;
using PixelCrushers.DialogueSystem;

[RequireComponent(typeof(RuntimeNPCs))]
public class TestRuntimeNPCs : MonoBehaviour
{

    public UnityEngine.UI.InputField actorNameInputField;
    public UnityEngine.UI.Dropdown professionDropdown;
    public UnityEngine.UI.Dropdown personalityDropdown;

    public void AddNewActor()
    {
        // Get the UI values:
        var actorName = actorNameInputField.text;
        if (string.IsNullOrEmpty(actorName)) return;
        if (DialogueManager.masterDatabase.GetActor(actorName) != null)
        {
            Debug.LogError("Actor '" + actorName + "' already exists.");
            return;
        }
        var profession = professionDropdown.options[professionDropdown.value].text;
        var personality = personalityDropdown.options[personalityDropdown.value].text;

        // Add actor:
        var runtimeNPCs = GetComponent<RuntimeNPCs>();
        runtimeNPCs.AddActor(actorName, profession, personality);

        // Create GameObject:
        var actorGameObject = new GameObject(actorName);
        var dialogueActor = actorGameObject.AddComponent<DialogueActor>();
        dialogueActor.actor = actorName;
    }

    public void StartConversation()
    {
        var actorName = actorNameInputField.text;
        var conversant = GameObject.Find(actorName);
        if (conversant == null)
        {
            Debug.LogError("Can't find actor GameObject named '" + actorName + "' to start conversation.");
        }
        else
        {
            DialogueManager.StartConversation("Starter Conversation", null, conversant.transform);
        }
    }
}
lord_loej
Posts: 8
Joined: Wed Jun 13, 2018 4:34 pm

Re: How to use a cloned database

Post by lord_loej »

I cant download that project you sent me, it just takes me to a webpage filled with text.

also, that original method you suggested for adding the actors works, when I add the actor it gets added correctly, and the fields are correct. Also the new actors name shows up in the conversation windows, the issue im having is with the conversations, its always treating the Personality value as being null, even though the value is set to what I want it.

I know that the field is being assigned because I will do a debug.log for all the field values and names, and they all show up. additionally the npc is assigned to the conversation correctly.
User avatar
Tony Li
Posts: 22058
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use a cloned database

Post by Tony Li »

Right-click that download link and save it as a file. Then you can import that file into a project.

Can you post the code you're using to add the Personality field, or email it to tony (at) pixelcrushers.com?
lord_loej
Posts: 8
Joined: Wed Jun 13, 2018 4:34 pm

Re: How to use a cloned database

Post by lord_loej »

Here is the code I am using.
http://prntscr.com/jv1p85
Here is the result from the DEBUG.LOG
http://prntscr.com/jv1pnf
Here is my dialogue node
http://prntscr.com/jv1pza
Here is the system trigger
http://prntscr.com/jv1q4m
User avatar
Tony Li
Posts: 22058
Joined: Thu Jul 18, 2013 1:27 pm

Re: How to use a cloned database

Post by Tony Li »

Hi,

Is the Dialogue System Trigger's Conversation Conversant pointing to the prefab asset or the instance in the scene for Leif Olavsson?

It may help to add a DialogueActor component to the instance, and set its actor property to "Leif Olavsson" as in the example I posted above. Were you able to download the example by right-clicking on it and saving it as a file?
Post Reply