Page 1 of 2

Enable modding capabilities on database

Posted: Sat Jul 01, 2023 5:14 pm
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!

Re: Enable modding capabilities on database

Posted: Sat Jul 01, 2023 5:32 pm
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.

Re: Enable modding capabilities on database

Posted: Mon Jul 03, 2023 12:28 pm
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"
}

Re: Enable modding capabilities on database

Posted: Mon Jul 03, 2023 10:10 pm
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.

Re: Enable modding capabilities on database

Posted: Sun Jul 09, 2023 5:48 pm
by BrightC
It took me some time, but I got it to work eventually. With quests even. Thank you!

Re: Enable modding capabilities on database

Posted: Sun Jul 09, 2023 9:11 pm
by Tony Li
Awesome, great job!

Re: Enable modding capabilities on database

Posted: Wed Jul 12, 2023 6:48 pm
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.

Re: Enable modding capabilities on database

Posted: Wed Jul 12, 2023 8:36 pm
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?

Re: Enable modding capabilities on database

Posted: Sat Jul 15, 2023 6:04 am
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 428 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).

Re: Enable modding capabilities on database

Posted: Sat Jul 15, 2023 9:54 am
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