Page 1 of 1

Adding nodes to conversation at runtime

Posted: Thu Apr 14, 2016 6:05 am
by Ders
I've been trying to create a small dynamic system for a game I'm working on where conversations can be altered at runtime. At the moment I'm trying to figure out why something's going wrong when adding a new node to a conversation. For some reason the link doesn't seem to connect properly.
The first indication is when I go talk to the NPC with the altered conversation he doesn't have the new option for the player to select.

The second thing I noticed can be seen in this screenshot:
Image

You can see in this screenshot that the last link isn't connecting properly (although I checked the Link object at least ten times and the originalConversationID and such are set up correctly. Also the Editor draws the new Entry just fine with the connection arrow between the new one and the previous one).

Image

So, to give this some more information, this is the code I use to add a new node to a conversation:

Code: Select all

List<Field> dialogueEntryFields = new List<Field>();
dialogueEntryFields.Add(new Field("Title", string.Empty, FieldType.Text));
dialogueEntryFields.Add(new Field("Pictures", "[]", FieldType.Files));
dialogueEntryFields.Add(new Field("Description", string.Empty, FieldType.Text));
dialogueEntryFields.Add(new Field("Actor", string.Empty, FieldType.Actor));
dialogueEntryFields.Add(new Field("Conversant", string.Empty, FieldType.Actor));
dialogueEntryFields.Add(new Field("Menu Text", string.Empty, FieldType.Text));
dialogueEntryFields.Add(new Field("Dialogue Text", string.Empty, FieldType.Text));
dialogueEntryFields.Add(new Field("Parenthetical", string.Empty, FieldType.Text));
dialogueEntryFields.Add(new Field("Audio Files", "[]", FieldType.Files));
dialogueEntryFields.Add(new Field("Video File", string.Empty, FieldType.Text));
dialogueEntryFields.Add(new Field("Sequence", string.Empty, FieldType.Text));

public static void AddNewExternalConversation(ref Conversation conversation, string externalConversationTitle, int previousNode, bool isPlayerChoice, string dialogueText, string condition, string script) {
        var nodes = conversation.dialogueEntries;
        Conversation externalConversation = DialogueManager.DatabaseManager.DefaultDatabase.conversations.Find(x => x.Title == externalConversationTitle);

        DialogueEntry newEntry = new DialogueEntry();
        newEntry.fields = CreateFields(dialogueEntryFields);
        newEntry.conversationID = conversation.id;
        newEntry.Title = "Empty";
        newEntry.id = nodes.Count;
        newEntry.DialogueText = dialogueText;
        newEntry.conditionsString = condition;
        newEntry.userScript = script;
        newEntry.ActorID = isPlayerChoice ? 1 : 2;
        newEntry.ConversantID = isPlayerChoice ? 2 : 1;
        nodes.Add(newEntry);

        nodes[newEntry.id].outgoingLinks.Add(new Link(conversation.id, newEntry.id, externalConversation.id, 1));
        nodes[previousNode].outgoingLinks.Add(new Link(conversation.id, previousNode, conversation.id, newEntry.id));
        DialogueManager.Instance.ResetDatabase(DatabaseResetOptions.KeepAllLoaded);
    }
As you can see I create a new DialogueEntry, set up its data using the predefined fields and then create two links, one for the outgoing link for the new entry (this method creates a node that links to an external conversation) and a second link that links the previous entry to the new one I just created.

The weird thing is that this actually works if you run this function in the Start() method of one of the Monobehaviors in the game. It starts failing when you're doing it outside of this method, just somewhere in the game when I need it to change. So, I'm not really sure what is going wrong.

Is there something I should do to generate this link properly that I'm not doing right now? Am I missing some piece of code from the database or something?
Thanks in advance.

Re: Adding nodes to conversation at runtime

Posted: Thu Apr 14, 2016 10:48 am
by Tony Li
Hi,

Nothing jumps out at me from your code.

The missing link button in the Inspector view means that the editor's call to DialogueDatabase.GetDialogueEntry(Link) failed. This method:
  1. Looks up the conversation whose ID matches the Link's destinationConversationID. (Returns null if not found.)
  2. In that conversation, looks up the dialogue entry whose ID matches the Link's destinationDialogueID. (null if not found.)
So it's either not finding the conversation or dialogue entry, or it's finding the wrong conversation or dialogue entry if there happen to be multiple conversations with the same ID or multiple dialogue entries with the same ID in a single conversation.

Here are some notes that might help. The first two are somewhat unrelated to the issue, but I didn't want to forget to mention them.

1. DatabaseManager.ResetDatabase() also resets the Lua environment to correspond to the newly-reset database state. This means you'll lose the current values of all Lua variables. To retain the current values, use PersistentDataManager.Record() to record them before resetting and PersistentDataManager.Apply() after resetting. If you're only changing dialogue entries, you don't have to call ResetDatabase() unless you use SimStatus.

2. Don't link to another conversation's START node. This is a restriction imposed by the Chat Mapper data standard, which the Dialogue System adheres to. In practice, it shouldn't matter. The Dialogue System should happily follow the link to START anyway. You just won't be able to export the dialogue database to Chat Mapper should you want to in the future.

3. Make sure the dialogue entry ID is unique. Also consider using the Template class to create an initialized DialogueEntry object as a convenience to make sure all the fields get set up. Here's an example:

Code: Select all

using PixelCrushers.DialogueSystem;
using System.Linq; // Convenience for finding a unique dialogue entry ID.
...
// Get a conversation from a database:
var conversation = someDatabase.GetConversation("My Conversation");

// Get a unique dialogue entry ID:
var id = 1 + conversation.dialogueEntries.Max(existingEntry => existingEntry.id);

// Create a dialogue entry from the template:
var template = Template.FromDefault(); // Cache this if you're going to create lots of entries.
var newEntry = template.CreateDialogueEntry(id, conversation.id, "My Title");
conversation.dialogueEntries.Add(newEntry);

// Link from another entry to this new one:
var link = new Link(conversation.id, someParentEntry.id, conversation.id, newEntry.id);
someParentEntry.outgoingLinks.Add(link); 
4. You can examine the raw dialogue database. To do this, close the Dialogue Editor. Select the database in the Project view. In the Inspector, expand Conversations. Find your conversation and keep drilling down until you get to the new dialogue entry's outgoingLinks. Then spot check it to make sure the IDs look correct.

If none of that helps, please feel free to send a reproduction project to tony (at) pixelcrushers.com. I'll be happy to take a look.

Re: Adding nodes to conversation at runtime

Posted: Fri Apr 15, 2016 7:26 am
by Ders
Thanks a lot, I made a couple of changes based on what you said. I added the Record() and Apply() when reloading the database and I changed my "new DialogueEntry()" line with the one you presented that uses the Template class. These things seem to have fixed it!
The only thing left to do is fix the removal of those conversations from the database. The problem is that the DatabaseManager seemed to have cleaned up its things when I still need them in the OnDestroy() of another class that handles the custom conversations.

But yeah, Thanks a lot! Quickly solved! :)

Re: Adding nodes to conversation at runtime

Posted: Fri Apr 15, 2016 12:02 pm
by Tony Li
Hi,

I'm not sure what your OnDestroy() method needs to do, but what about manually creating a new database, adding the conversation to that database, and then adding the database to the Dialogue Manager's master database? When you're done, you can remove and destroy the database. Roughly speaking, something like:

Code: Select all

// Databases are ScriptableObjects, so create them using ScriptableObject.CreateInstance:
var dynamicDatabase = ScriptableObject.CreateInstance<Database>();

// Set up your conversation:
var conversation = //your conversation creation code.
database.conversations.Add(conversation);

// Add the database to the master list:
DialogueManager.AddDatabase(dynamicDatabase);

... // use your conversation

// Remove the database from the master list:
DialogueManager.RemoveDatabase(dynamicDatabase);

// Destroy the database to release memory:
Destroy(dynamicDatabase);

Re: Adding nodes to conversation at runtime

Posted: Sat Apr 16, 2016 4:31 am
by Ders
I already fixed it by using the OnApplicationQuit() method from Unity. The problem was that the OnDestroy in the DialogueManager ran before the OnDestroy on my own objects which made the instance for the databse null. So, I found out that OnApplicationQuit runs BEFORE OnDestroy for some reason so I put my code in that method and that fixed the problem.
Thanks again for the advice