Enable modding capabilities on database

Announcements, support questions, and discussion for the Dialogue System.
BrightC
Posts: 7
Joined: Sat Jul 01, 2023 4:46 pm

Enable modding capabilities on database

Post by BrightC »

Hi all! I am currently developing a game that puts heavy emphasis on being moddable, i.e. featuring user generated content. I would like to use the Dialogue System For Unity with it, but am still looking for ideas how to make this work. The players who wish to mod the game should be able to add new characters and conversations on the fly. However, since the Dialogue Database can only really be edited from the Editor, the modders would not be able to make changes to dialogues without downloading Unity, setting up a dummy project, downloading the Dialogue System, making the changes to the database there, probably dealing with missing references, then copy the edited database back into the original project, which is a lot more effort than I would like to put onto the end users. This is why I am looking for a way to allow users to modify the database without downloading Unity. I identified the following as a possible solution:

1. Use an external dialogue editor, like ChatMapper.
2. Include the raw ChatMapper file in my game files.
3. Import the ChatMapper file when the game starts up.

Modders could then edit texts in the external Editor and the database would be updated on startup.

Firstly, is this idea sensible? I imagine it is an obstacle to import the file (as an addressable) at runtime, and could find no detailed information on whether this is supported. Secondly, if it is possible: which file type(s) would you recommend for this, if I have no prior knowledge to any of them and the goal is allowing the players to modify as much of the dialogue as possible using free software? And thirdly, are there perhaps alternative, better solutions to this problem as a whole? Thanks!
User avatar
Tony Li
Posts: 21681
Joined: Thu Jul 18, 2013 1:27 pm

Re: Enable modding capabilities on database

Post by Tony Li »

Hi,

The Dialogue System can import several formats at runtime. Of the free ones, the most approachable would probably be to use Twine import. Briefly: Each conversation would be its own Twine "story" (file), which the modder will save in JSON format using a Twine extension called Twison. You'd create a conversation for each one like this:

Code: Select all

var twineImporter = new TwineImporter();
...
var json = File.ReadAllText(jsonFilename);
var story = JsonUtility.FromJson<TwineStory>(json);
twineImporter.ConvertStoryToConversation(database, template, story, storyInfo.actorID, storyInfo.conversantID, storyInfo.splitPipes, prefs.useTwineNodePositions);
This should be in a new database that you'd add to the Dialogue System at runtime using DialogueManager.AddDatabase().

Another option would be to take the modder's input and create the new database on the fly at runtime. See How To: Create Dialogue Database At Runtime. You could define your own text format that modders could use, or you could provide an in-game runtime editor for dialogue.
BrightC
Posts: 7
Joined: Sat Jul 01, 2023 4:46 pm

Re: Enable modding capabilities on database

Post by BrightC »

That sounds like an excellent approach, thank you! I went with the option of using Twine and got most of it to work, but ran into an issue. When importing the Twine file at runtime, for some reason it cuts off the options that branch to the right, i.e. always the first option is picked and it is interpreted as a node, rather than a choice. The rest of the nodes do not get imported. Notably, this only happens when doing the import at runtime. When I run the Twine import from within the editor, it works fine. What could cause this? Here is a minimal example:

LoadDialoguesFromAddressables.cs

Code: Select all

using PixelCrushers.DialogueSystem;
using PixelCrushers.DialogueSystem.Twine;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

public class LoadDialoguesFromAddressables : MonoBehaviour
{
    TwineImporter twineImporter;
    DialogueDatabase dialogueDatabase;
    Template template;
    void Start()
    {
        twineImporter = new TwineImporter();
        template = Template.FromDefault();
        dialogueDatabase = ScriptableObject.CreateInstance<DialogueDatabase>();
        DoLoad();
    }


    protected void DoLoad()
    {

        AsyncOperationHandle twineHandle = Addressables.LoadAssetAsync<TextAsset>("Assets/test.json");
        twineHandle.Completed += (x) =>
        {
            var json = x.Result as TextAsset;
            var story = JsonUtility.FromJson<TwineStory>(json.text);
            twineImporter.ConvertStoryToConversation(dialogueDatabase, template, story, 0, 1, true);
        };
        twineHandle.WaitForCompletion();

        DialogueManager.AddDatabase(dialogueDatabase);
        DialogueManager.StartConversation("Test Story");
    }
}
test.json

Code: Select all

{
  "passages": [
    {
      "text": "Test\n[[Option 1]]\n[[Option 2]]",
      "links": [
        {
          "name": "Option 1",
          "link": "Option 1",
          "pid": "3"
        },
        {
          "name": "Option 2",
          "link": "Option 2",
          "pid": "4"
        }
      ],
      "name": "Default Conversation",
      "pid": "1",
      "position": {
        "x": "700",
        "y": "250"
      }
    },
    {
      "text": "Did you get an option? Neither did I!",
      "name": "It does not work",
      "pid": "2",
      "position": {
        "x": "700",
        "y": "575"
      }
    },
    {
      "text": "You picked Option 1.\n[[It does not work]]",
      "links": [
        {
          "name": "It does not work",
          "link": "It does not work",
          "pid": "2"
        }
      ],
      "name": "Option 1",
      "pid": "3",
      "position": {
        "x": "825",
        "y": "500"
      }
    },
    {
      "text": "You picked Option 2\n[[It does not work]]",
      "links": [
        {
          "name": "It does not work",
          "link": "It does not work",
          "pid": "2"
        }
      ],
      "name": "Option 2",
      "pid": "4",
      "position": {
        "x": "575",
        "y": "500"
      }
    }
  ],
  "name": "Test Story",
  "startnode": "1",
  "creator": "Twine",
  "creator-version": "2.6.2",
  "ifid": "880AE81B-D434-4643-80DD-4B1CEC0A12B2"
}
User avatar
Tony Li
Posts: 21681
Joined: Thu Jul 18, 2013 1:27 pm

Re: Enable modding capabilities on database

Post by Tony Li »

Hi,

After this line:

Code: Select all

dialogueDatabase = ScriptableObject.CreateInstance<DialogueDatabase>();
create a Player actor and an NPC actor like in the "// Create actors:" section of code in this how-to guide.

Assuming your Player actor's ID is 1 and the NPC's ID is 2, change your twineImporter line of code to:

Code: Select all

twineImporter.ConvertStoryToConversation(dialogueDatabase, template, story, 1, 2, true);
Otherwise it will assign all nodes to an NPC actor. You want the choices to be assigned to the Player actor. In the Dialogue Editor, nodes assigned to the Player actor will be blue, and nodes assigned to a non-player actor will be gray (unless you've set custom node colors). When a node links to one or more NPC nodes, the conversation will use the first NPC node whose Conditions are true or blank. The conversation will only show a response menu if the only currently-valid nodes are assigned to a Player actor.
BrightC
Posts: 7
Joined: Sat Jul 01, 2023 4:46 pm

Re: Enable modding capabilities on database

Post by BrightC »

It took me some time, but I got it to work eventually. With quests even. Thank you!
User avatar
Tony Li
Posts: 21681
Joined: Thu Jul 18, 2013 1:27 pm

Re: Enable modding capabilities on database

Post by Tony Li »

Awesome, great job!
BrightC
Posts: 7
Joined: Sat Jul 01, 2023 4:46 pm

Re: Enable modding capabilities on database

Post by BrightC »

Actually, one more thing did come up. When working with Twine, I do not exactly have the option to start or end the conversation with a choice, since Twine only allows for a single connection from the start node, and requires text on its end node. A solution would be simply ignoring empty nodes from the Twine file, but I am unsure how I would best implement this. Or, if there might be a better, intended solution to it. Perhaps that's even be a feature to be added to the asset, since it is probably easiest to intercept this on import.
User avatar
Tony Li
Posts: 21681
Joined: Thu Jul 18, 2013 1:27 pm

Re: Enable modding capabilities on database

Post by Tony Li »

Hi,

You can end the conversation with a choice by putting "Player:" at the front of the last node's text. This will assign the node to the Player actor.

If you've configured the Dialogue Manager's Input Settings to skip response menus if there's only one player choice (see How To: Bypass Response Menu When Player Has One Choice), you can force this node to be shown in a menu by including "[f]" in the text.

If that doesn't cover your needs, would it be sufficient for the Twine importer to ignore final nodes if they don't contain any text?
BrightC
Posts: 7
Joined: Sat Jul 01, 2023 4:46 pm

Re: Enable modding capabilities on database

Post by BrightC »

Ah, putting just "Player:" on the final node indeed solves the issue of the conversation ending "too late", thank you!
However, at the beginning of the conversation, it is still an issue if I want the player to have the first line. Essentially, I would like a start node branching into three possible player lines. However, Twine only allows for the start node to have a single child, so the best I can do is something like this.
twinequests.png
twinequests.png (20.43 KiB) Viewed 420 times
However, that does not work, and the first node having no text, no matter which actor, even deadlocks the conversation (no continue Button is available, but no choice is, either, but I did not get to check what is going on under the hood there, yet).
User avatar
Tony Li
Posts: 21681
Joined: Thu Jul 18, 2013 1:27 pm

Re: Enable modding capabilities on database

Post by Tony Li »

Hi,

Try putting this at the end of the text in the first node:

Code: Select all

Sequence:
Continue()
More info: Sequences in Twine Imports
Post Reply