Fungus Game: Input Dialog & Writing Custom Commands

I was hanging out in the Fungus forums and stumbled into the question regarding how to receive input from players in Fungus. Fungus has an example scene called EnterName showing how this can be done without coding. Method shown in the example scene works but it is a little too much to setup. If you are going to do this more than once in your game, it is better to make something reusable. So I decided to create an InputDialog and SetVariableFromInput command for Fungus.

Before moving on to my solution it is important to point out the method in EnterName example scene is a valid method. Especially if your narrative block is going to be started from input triggers. For example if you can have an passcode locked door, player may input passcode before starting fungus block and when passcode is submitted you trigger an related fungus block based on the input. My method in contrast only works after you start a fungus block. And here is what that looks like in action.

custom-command-input-set-variable

Creating A New Input Dialog

The first step is to make an input dialog like above. There are 3 important UI elements here: label, inputfield, submit button. The arrangement and graphics of the UI elements are not important here. You can design it anyway you want as Unity UI permits and as long as it can be instantiated in the scene without a root object. What is important is the script that controls the InputDialog. Here is what that script looks like.

using UnityEngine;
using UnityEngine.UI;
using System;
using UnityEngine.EventSystems;
public class InputDialog : MonoBehaviour
{
// Currently active Input Dialog used to display Menu options
public static InputDialog activeInputDialog;
public InputField Input;
public Text Label;
public Button SubmitButton;
protected Action<string> Callback;
public static InputDialog GetInputDialog()
{
if (activeInputDialog == null)
{
// Use first Input Dialog found in the scene (if any)
InputDialog id = GameObject.FindObjectOfType<InputDialog>();
if (id != null)
{
activeInputDialog = id;
}
if (activeInputDialog == null)
{
// Auto spawn a menu dialog object from the prefab
GameObject prefab = Resources.Load<GameObject>("InputDialog");
if (prefab != null)
{
GameObject go = Instantiate(prefab) as GameObject;
go.SetActive(false);
go.name = "InputDialog";
activeInputDialog = go.GetComponent<InputDialog>();
}
}
}
return activeInputDialog;
}
public virtual void Awake()
{
if (Application.isPlaying)
{
Clear();
}
}
public virtual void OnEnable()
{
// The canvas may fail to update if the input dialog is enabled in the first game frame.
// To fix this we just need to force a canvas update when the object is enabled.
Canvas.ForceUpdateCanvases();
}
public virtual void Clear()
{
StopAllCoroutines();
if (Input != null)
{
Input.onEndEdit.RemoveAllListeners();
Input.text = "";
}
}
public virtual void InitializeInputField(string label, string placeholder, Action<string> callback )
{
// If inputfield is not set send empty input and continue
Callback = callback;
if (Input == null)
{
Callback("");
return;
}
else
{
EventSystem.current.SetSelectedGameObject(Input.gameObject);
Text placeholderText = Input.placeholder.GetComponent<Text>();
if (placeholderText!=null)
{
placeholderText.text = placeholder;
}
Input.onEndEdit.AddListener(OnEndEdit);
}
if (Label != null)
{
Label.text = label;
}
if (SubmitButton != null)
{
SubmitButton.onClick.AddListener(OnClickSubmit);
}
HideSayDialog();
}
public virtual void OnEndEdit(string inputValue)
{
if (inputValue != "")
{
Callback(inputValue);
Clear();
}
}
public virtual void OnClickSubmit()
{
OnEndEdit(Input.text);
}
public virtual void HideSayDialog()
{
SayDialog sayDialog = SayDialog.GetSayDialog();
if (sayDialog != null)
{
sayDialog.FadeOut();
}
}
}
view raw InputDialog.cs hosted with ❤ by GitHub

This class was modeled after the MenuDialog. It does a lot of things so lets break it apart. First of all you may have recognized two static elements; a method and a property. Built in Fungus dialogs feature this static method which does two things. Making sure there is a singleton dialog that can be referenced from other scripts and instantiating default dialog if there aren’t any in the scene.  In order to instantiate dialog you need to put your newly made InputDialog prefab under a Resources folder. Unity will let you load assets under Resources folder using  Resources.Load<GameObject>("path-inside-resource-folder");method.

After static methods there are Awake, OnEnable & Clear functions that manages the dialog’s life cycle. In Fungus dialogs are not destroyed after use but disabled and enabled. You can of course change this lifecycle on your own code but I decided to follow the same convention. In this regard, OnEnable forces recalculation of canvas’s positioning when it is re-enabled and Clear resets everything so when the next time Dialog is activated it will not display previously entered text or trigger an old event listener.

However the meat of the Dialog script is in InitializeInputField and OnEndEdit methods. InitializeInputField will be called from the command we will write, with an callback which will send input value back to the same command in OnEndEdit. There is also an OnClickSubmit method that simply proxies same submit action if triggered via submit button.

Last method HideSayDialog was copied from the MenuDialog class, does what it says on the name. The reason we have this method is that Fungus assumes the say dialog cover the majority of the flow as such keeps the dialog open unless there is something breaking that flow, in this case our InputDialog. So we need to manually hide it.

Creating A Fungus Command

What I love about Fungus is that it is extendable. That essentially why I am able to write this blog post. Writing a custom command can change your game in a big game. In my previous jam project with the Fungus, I written custom commands that would change the game world based on the narrative choices made by the player which in turn changed actions available. So I would recommend anyone to learn how to write custom commands to try new mechanics in their narrative. Here I wrote a custom command to open previously created InputDialog and assign value to an Variable in flowchart.

using UnityEngine;
using Fungus;
[CommandInfo("Variable",
"Set Variable From Input",
"Sets a Boolean, Integer, Float or String variable from Input Dialog")]
[AddComponentMenu("")]
public class SetVariableFromInput : Command {
[Tooltip("The variable whos value will be set")]
[VariableProperty(typeof(BooleanVariable),
typeof(IntegerVariable),
typeof(FloatVariable),
typeof(StringVariable))]
public Variable Variable;
public string Label="";
public string Placeholder="";
public override void OnEnter()
{
InputDialog inputDialog = InputDialog.GetInputDialog();
if (inputDialog == null || Variable == null)
{
Continue();
return;
}
inputDialog.gameObject.SetActive(true);
inputDialog.InitializeInputField(Label,Placeholder, OnInputSubmit);
}
private void OnInputSubmit(string value)
{
if (Variable.GetType() == typeof(BooleanVariable))
{
BooleanVariable boolVar = (Variable as BooleanVariable);
if (value.ToLower() == "true" || value == "1")
{
boolVar.value = true;
}
else
{
boolVar.value = false;
}
}
else if (Variable.GetType() == typeof(IntegerVariable))
{
IntegerVariable intVar = (Variable as IntegerVariable);
int intResult;
if (int.TryParse(value, out intResult))
{
intVar.value = intResult;
}
intVar.value = 0;
}
else if (Variable.GetType() == typeof(FloatVariable))
{
FloatVariable floatVariable = (Variable as FloatVariable);
float floatResult;
if (float.TryParse(value, out floatResult))
{
floatVariable.value = floatResult;
}
floatVariable.value = 0f;
}
else if (Variable.GetType() == typeof(StringVariable))
{
StringVariable stringVar = (Variable as StringVariable);
stringVar.value = value;
}
InputDialog inputDialog = InputDialog.GetInputDialog();
inputDialog.gameObject.SetActive(false);
Continue();
}
}

Here I send a callback to the InputDialog. As mentioned above that callback is called when user submits an input. OnInputSubmit method takes that value and parses it to store the value inside an Variable. This is what final flowchart looks like.
custom-command-input-set-variable-fungus-flowchartHere in this flow chart you can see 3 variables that are set in the SetVariableFromInput command. In built-in commands there are more options on setting custom dialogs etc. However this is enough as an example. When player submits the passcode following If block will check the sent value. If value is correct then player will be moved to Correct block, otherwise they will move to Wrong block. You can of course do whatever you want with the value you received, for instance storing it as player name. You can customize both of dialog and command to create different prompts, like infamous “Are you a boy or a girl?” from pokemon professors. Go wild.


About the author

Sercan Altun: Hello there fellow human, I am Sercan Altun: video game developer, coding aficionado & part-time weirdo. Check out my blog on game development and stuff.

B801 300D 93CC 11A9 3332 4270 A5FB C300 1020 24E7