Replace String in Scriptable Object,,Replacing text in Scriptable Object

I’m setting up a dialog system for my game, and I’d like to make it so that when there’s a string with “!player” in it, it replaces that with a name the player has entered. The way I have it set up is with scriptable objects feeding into a UI canvas. Currently my Scriptable Object looks like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[CreateAssetMenu(fileName = "New Dialog", menuName = "Dialog Boxes")]
public class Dialog : ScriptableObject
{
    public string playerName;
    public string playerPronouns;
    public string talkeeName;
    public string talkeePronouns;
    public string[] dialogEntries;


    public Sprite[] playerPortraits;
    public Sprite[] talkeePortraits;

    private void Awake()
    {
        dialogEntries[0] = dialogEntries[0].Replace("!player", "you");
    }
}

And my UI canvas script looks like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class DialogDisplay : MonoBehaviour
{
    public Dialog dialog;

    public TextMeshProUGUI playerName;
    public TextMeshProUGUI talkeeName;
    public TextMeshProUGUI playerPronouns;
    public TextMeshProUGUI talkeePronouns;

    public Image playerImage;
    public Image talkeeImage;

    public TextMeshProUGUI dialogText;
    public int dialogLength;

    // Start is called before the first frame update
    void Start()
    {
        playerName.text = dialog.playerName;
        talkeeName.text = dialog.talkeeName;
        playerPronouns.text = dialog.playerPronouns;
        talkeePronouns.text = dialog.talkeePronouns;

        playerImage.sprite = dialog.playerPortraits[0];
        talkeeImage.sprite = dialog.talkeePortraits[0];
        dialogText.text = dialog.dialogEntries[0];
        dialogLength = -1;

    }

    // Update is called once per frame
    void Update()
    {
        if(Input.GetKeyDown(KeyCode.E))
        {
            dialogLength++;
            {
                if(dialogLength < dialog.dialogEntries.Length)
                {
                    dialogText.text = dialog.dialogEntries[dialogLength];
                }

                else if(dialogLength >= dialog.dialogEntries.Length)
                {
                    dialogLength = -1;
                    gameObject.SetActive(false);
                }
            }
            
        }
    }
}

Right now I’m just trying to have it where it replaces “!player” with “you” in the scriptable object script on the first string, but it won’t even do that. It’s not returning any errors but it’s also simply not doing anything to the string. Does anyone have a good method for doing this?

Well, you have several smaller issues here. First of all since your ScriptableObject is an asset, you shouldn’t have it modify itself as those changes would persist when you exit play mode in the editor. Second in your code you replace “!player” with “you” only for the first dialog entry. However in your description you said you want to replace it with the name the user has entered. Therefore either your code does not represent your actual code, or your description is wrong. Either way keep in mind that Awake is called right at the game start. So there’s no time for the user to enter his name.

I would generally avoid overwriting the original dialog text. It’s usually better to replace such markers on the fly when you need the dialog.

Some other minor things that I would call horrible design are:

  • you called your variable “dialogLength” but it seems to indicate the current index and not any length.
  • The fact that this index is always off by 1 leads to some strange condition and reset values. Especially it leaves the value in an invalid state at the beginning (as -1 is not a valid index). It would be much better to adjust the code so the index will actually represent the current index.

So I would recommend to remove the Awake method from your Dialog class. You could add a method like this:

public string GetDialogText(int aIndex)
{
    if (aIndex < 0 || aIndex >= dialogEntries.Length)
        return "";
    return dialogEntries[aIndex].Replace("!player", playerName).Replace("!talkee", talkeeName);
}

This allows your DialogDisplay class to simply request a “fixed” dialog entry where the player and talkee names have been replaced. With the aforementioned changes it would look something like this

public class DialogDisplay : MonoBehaviour
{
public Dialog dialog;

public TextMeshProUGUI playerName;
public TextMeshProUGUI talkeeName;
public TextMeshProUGUI playerPronouns;
public TextMeshProUGUI talkeePronouns;

public Image playerImage;
public Image talkeeImage;

public TextMeshProUGUI dialogText;
public int dialogIndex;

void Start()
{
    playerName.text = dialog.playerName;
    talkeeName.text = dialog.talkeeName;
    playerPronouns.text = dialog.playerPronouns;
    talkeePronouns.text = dialog.talkeePronouns;

    playerImage.sprite = dialog.playerPortraits[0];
    talkeeImage.sprite = dialog.talkeePortraits[0];
    dialogIndex = 0;
    dialogText.text = dialog.GetDialogText(dialogIndex);
}

void Update()
{
    if(Input.GetKeyDown(KeyCode.E))
    {
        dialogIndex++;
        if(dialogIndex < dialog.dialogEntries.Length)
        {
            dialogText.text = dialog.GetDialogText(dialogIndex);
        }
        else
        {
            dialogLength = 0;
            dialogText.text = dialog.GetDialogText(dialogIndex);
            gameObject.SetActive(false);
        }
    }
}

}