Fungus Game: Loading Characters Dynamically

I have recently started working on a homebrew game project. The project will be a RPG with lots of in-game events and texts. Naturally I needed an system to handle that. Luckily there is an amazing open source project for implementing visual novels in Unity called Fungus.

I used Fungus before in game jams. It is pretty easy to learn and makes game writers happy as they can finally do their jobs without waiting for programmer. And it makes me happy, just connect it with the rest of the game, write couple of extensions for game mechanic and your simple jam game is now full of content.

It is not perfect though. Sometimes you just have to adjust it a bit to fit your project. This is one of those adjustments.

Problem

When you define dialogs in your flowchart blocks you have option to define which character says each line. This will display name and portrait of the character in the dialog UI. It is pretty easy to setup; create a Character object in the scene, setup its properties, drag and drop the reference to Say command. Fungus even provides a handy drop down so you don’t have to search the Character object in the scene.

Problem starts when you want to dynamically load characters into the scene. Since flowchart commands only receive Character objects on the scene you cannot set prefabs as Characters.

Why would you want to dynamically load characters? It is possible that you have extended your characters with additional functionality that lets you upgrade/modify the way character works and you wish to persist this character between scenes. Or some characters are optional/swichable in your story and you want to describe characters after you set the scene.

You can solve these problems with writing custom scripts for each situation but I have created a generic method to solve these issues using proxy characters.

Proxy Characters

Proxy characters are simple standins used in flowcarts instead of actual characters. You can use them just like the normal characters. Only difference is once the actual Character object is added to the scene it will duplicate values from it.

This little hack works in two parts. You need a character with an unique identifier, and a proxy character that copies any character with that identifier. Both is achieved by extending the Character class.

public class StoredCharacter : Fungus.Character
   {
       public string Identifier;

       protected override void OnEnable()
       {
           base.OnEnable();

           if (string.IsNullOrEmpty(Identifier))
           {
               return;
           }

           foreach (ProxyCharacter result in activeCharacters.OfType<ProxyCharacter>())
           {
               if (result.ProxiedIdentifier == Identifier)
               {
                   result.Original = this;
               }
           }
       }
   }

StoredCharacter is simply character with an Identifier. It extends OnEnable function of the Character so when it is added to the scene it searches current proxied objects and pass itself as the original character.

public class ProxyCharacter:StoredCharacter
   {
       public string ProxiedIdentifier;

       private StoredCharacter _original;
       public StoredCharacter Original
       {
           get { return _original; }
           internal set
           {
               _original = value;
               CopyValuesFromOriginal();
           }
       }

       protected override void OnEnable()
       {
           if (string.IsNullOrEmpty(ProxiedIdentifier))
           {
               return;
           }

           foreach (StoredCharacter result in activeCharacters.OfType<StoredCharacter>())
           {
               if (result.Identifier == ProxiedIdentifier)
               {
                   this.Original = this;
               }
           }

           base.OnEnable();
       }

       private void CopyValuesFromOriginal()
       {
           nameText = Original.nameText;
           nameColor = Original.nameColor;
           soundEffect = Original.soundEffect;
           profileSprite = Original.profileSprite;
           portraits = Original.portraits;
           portraitsFace = Original.portraitsFace;
           state = Original.state;
           
           setSayDialog = Original.setSayDialog;
           
           description = Original.description;
       }
   }

ProxiedCharacter does the reverse. Every time it is enabled it searches for the Characters on the scene which has same identifiers and sets them as its Original object. So it doesn’t matter whichever is added to the scene first.

When an Original property is set in the proxy, it copies all the values into itself. This is not strictly in line with the Proxy pattern. A better way would be overriding accessors in Proxy to point to the original values. However C# does not let you overriding properties without accessors. So this is the only way to do it without modifying original Fungus code. (Which you may do since it is open source.)

How To Use It?

To use ProxyCharacter simply create an empty game object and add the component into it. You will see ProxiedIdentifier and Identifier fields on the component.  You can see that I created a Teacher object with the proxy component in it. To set which Character to proxy you set the ProxiedIdentifier. Identifier property is there simply because we extended StoredCharacter. This is not an error, I will come to why in a moment. But for the simple use case just leave Identifier empty.custom-inspector-fungus-character-proxyIn inspector there are original properties of Character component which we are no longer using with the proxy object. I wanted to hide these components so it wont confuse the user. Following custom inspector hides all values except ProxiedIdentifier and Identifier.

[CustomEditor(typeof(ProxyCharacter))]
   public class ProxyCharacterEditor : UnityEditor.Editor
   {
       override public void OnInspectorGUI()
       {
           ProxyCharacter myTarget = (ProxyCharacter)target;

           myTarget.ProxiedIdentifier = EditorGUILayout.TextField("Proxied Identifier", myTarget.ProxiedIdentifier);
           myTarget.Identifier = EditorGUILayout.TextField("Self Identifier", myTarget.Identifier);
       }
   }

With this proxy character you can create Flowcharts just like a normal character.  You can then create a StoredCharacter and set the Identifier value you have set in the ProxiedCharacter. When both of these objects are in the same scene ProxiedCharacter will replace its values with the original and display correctly.

fungus-character-proxy-in-action

Roles vs Characters

Remember I said ProxyCharacter extending StoredCharacter is not an error. Imagine following scenario. You are designing an generic creature encounter. These encounters events will have creatures and party members exchanging taunts in the beginning. But there can be many different creatures and your party members may change.

In Fungus, when you define a Say command you connect it to a character. But with proxies you can define it to a role instead. What you need to do is simple:
– Create ProxyCharacter with ProxyIdentifier="rolename" ( eg. “creature”)
– When scene loads add StoredCharacter. Your character will have its unique identifier Identifier="trollcaptain". This is the unique identifier in your TrollCaptain prefab.
–  Another ProxyCharacter to the scene with Identifier = "creature", ProxiedIdentifier="trollcaptain". And that will map the role with the original character.

Hope these little code snippets will be useful for you in your next Fungus masterpiece.

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