Our Tile Class

The foundation of every tile based games is the structure of their tile model. I think our game Academia has come a long way in the evolution of its tile structure. It’s robust enough to be shown to the public. If you’re making a tile based game, hopefully this helps you.

I’ll show you the member variables first:

public class Tile {

    private readonly TileGrid grid; // The parent grid
    private readonly Cell cell;

    private NodeHandle handle;

    // Note here that this is a map of layer and TileItem pair
    private Dictionary<string, TileItem> itemMap = new Dictionary<string, TileItem>();

    // Note here that task items are added to a separate map which uses its BuildItem as the key
    private Dictionary<string, TileItem> taskItemMap = new Dictionary<string, TileItem>();

    private readonly IntVector2 position;

    private int itemFlags;

    // May be null if tile is not in any zone
    private Zone zone;

    ...
}

We included the parent TileGrid so we could easily find other tiles whenever we have a reference to one tile. This helps a lot when neighbor tiles are needed. cell contains the information of the world tile position, cell width and height, bottom left position, top right position, etc. NodeHandle handle acts as the node in our A* framework. position is the tile position using integer coordinates.

The dictionaries itemMap and taskItemMap are used to store TileItem instances in each layer. A TileItem contains information about a tile. For example, say a TileItem instance for a table object. This TileItem instance means that a table occupies this tile. It also contains information like if the tile is blocked or not (for example wall). A Tile can have multiple TileItem instances for cases like the tile having a floor, dirt, and an object on top of it. Each of this are in different layers. The use of dictionary also implies that there can only be one TileItem per layer. This helps in checking if a tile already contains an item in a certain layer. This is usually used for preventing players from building objects on tiles that already have existing objects.

We differentiate between normal items and task items. Normal built items are added on itemMap while task items go on taskItemMap. This differentiation is needed so that items in multiple layers can be built in the tile. For example, build floor then build a table on top of it. Tasks used to be stored in only one layer but we found this to be inadequate, thus the current implementation.

itemFlags is a bitmask containing a bunch of information like if the tile is blocked or not, does it block students or not, does it contain a “character slot”, orientation of the character if one uses the slot. We specifically used a bitmask for faster “reachable” checking during A* search.

zone is the Zone instance where the tile is located. We added this for optimization purposes. We had to query the zone in a certain tile position in the old implementation which is slow and not very ideal.

Here’s the full class (removed function comments because “<text>” like these messes WordPress’ formatting):

    public class Tile {

        private readonly TileGrid grid; // The parent grid
        private readonly Cell cell;

        private NodeHandle handle;

        // Note here that this is a map of layer and TileItem pair
        private Dictionary<string, TileItem> itemMap = new Dictionary<string, TileItem>();

        // Note here that task items are added to a separate map which uses its BuildItem as the key
        private Dictionary<string, TileItem> taskItemMap = new Dictionary<string, TileItem>();

        private readonly IntVector2 position;

        private int itemFlags;

        // May be null if tile is not in any zone
        private Zone zone;

        public Tile(TileGrid grid, Cell cell) {
            this.grid = grid;
            this.cell = cell;
            this.position = new IntVector2(cell.x, cell.y);
        }

        public void Add(TileItem tileItem) {
            if (tileItem.Data.WorkerTask) {
                Assertion.Assert(!this.taskItemMap.ContainsKey(tileItem.BuildItemData.TileLayer));
                this.taskItemMap[tileItem.BuildItemData.TileLayer] = tileItem;
            } else {
                Assertion.Assert(!this.itemMap.ContainsKey(tileItem.Layer)); // Should not contain the item yet
                this.itemMap[tileItem.Layer] = tileItem;

                // Add the flag as well
                this.itemFlags |= tileItem.Flags;
            }
        }

        public void Remove(string tileLayer, string tileItemId) {
            // Can't remove task items through this method
            // Use RemoveTask() instead
            Assertion.Assert(!TileLayers.TASKS.EqualsString(tileLayer));

            TileItem item = null;
            Assertion.Assert(this.itemMap.TryGetValue(tileLayer, out item)); // Item should exist
            
            Assertion.Assert(item.Data.ItemId.Equals(tileItemId));
            this.itemMap.Remove(tileLayer);
            Assertion.Assert(!Contains(tileLayer));

            // Recreate the flags from the existing items
            this.itemFlags = 0;
            foreach (KeyValuePair<string, TileItem> entry in this.itemMap) {
                this.itemFlags |= entry.Value.Flags;
            }
        }

        public void RemoveTask(string buildItemLayer, string tileItemId) {
            // Must be the layer of the built item
            Assertion.Assert(!TileLayers.TASKS.EqualsString(buildItemLayer));

            TileItem taskItem = this.taskItemMap.Find(buildItemLayer);
            Assertion.AssertNotNull(taskItem);

            Assertion.Assert(taskItem.Data.ItemId.Equals(tileItemId));
            this.taskItemMap.Remove(buildItemLayer);
        }

        public bool Contains(string layerName) {
            // Note that a task layer is no longer just one item
            // It's a layer of items by itself
            Assertion.Assert(!TileLayers.TASKS.Equals(layerName));
            return this.itemMap.ContainsKey(layerName);
        }

        public bool ContainsTask(string layerName) {
            // Note that a task layer is no longer just one item
            // It's a layer of items by itself
            Assertion.Assert(!TileLayers.TASKS.Equals(layerName));
            return this.taskItemMap.ContainsKey(layerName);
        }

        public TileItem GetItem(string layerName) {
            // Note that a task layer is no longer just one item
            // It's a layer of items by itself
            Assertion.Assert(!TileLayers.TASKS.Equals(layerName));

            TileItem item = null;
            this.itemMap.TryGetValue(layerName, out item);

            // This may return null
            // Client code should check for this
            return item;
        }

        public TileItem GetTaskItem(string layerName) {
            // Note that a task layer is no longer just one item
            // It's a layer of items by itself
            Assertion.Assert(!TileLayers.TASKS.Equals(layerName));
            return this.taskItemMap.Find(layerName);
        }

        public bool HasTaskItems {
            get {
                return this.taskItemMap.Count > 0;
            }
        }

        public TileItem GetTopTaskItem() {
            if(!HasTaskItems) {
                return null;
            }

            for(int i = 0; i < TileLayers.ORDERED_LAYERS.Length; ++i) {                 
                TileItem taskItem = GetTaskItem(TileLayers.ORDERED_LAYERS[i]);                 
                if(taskItem != null) {                     
                    return taskItem;                 
                }             
            }             
            return null;         
        }         

        public bool HasCharacterSlot {             
            get {                 
                return (this.itemFlags & TileItemLayout.CHARACTER_SLOT) > 0;
            }
        }

        public bool HasPhysicalBlocker {
            get {
                return (this.itemFlags & TileItemLayout.PHYSICAL_BLOCKER) > 0;
            }
        }

        public bool HasStudentBlocker {
            get {
                return (this.itemFlags & TileItemLayout.STUDENT_BLOCKER) > 0;
            }
        }

        internal NodeHandle Handle {
            get {
                return handle;
            }

            set {
                this.handle = value;
            }
        }

        public Cell Cell {
            get {
                return cell;
            }
        }

        public TileGrid Grid {
            get {
                return grid;
            }
        }

        public IntVector2 Position {
            get {
                return position;
            }
        }

        public Zone Zone {
            get {
                return zone;
            }

            set {
                zone = value;
            }
        }

        public bool Contains(Vector3 worldPosition) {
            return this.cell.Contains(worldPosition);
        }

    }

GOAP For Our New Game

I’m excited that we’re making a builder type of game in the likes of Prison Architect Banished, and Rimworld. I love playing such games. Our’s is a school management game where you can design classrooms, offices, hire teachers, design curriculum, and guide students to their educational success.

I’m excited that we’re making a builder type of game in the likes of Prison Architect Banished, and Rimworld. I love playing such games. Our’s is a school management game where you can design classrooms, offices, hire teachers, design curriculum, and guide students to their educational success.

currentgamescreenshot

For every new game, it’s always my aim to try to implement a new algorithm or system and learn something new. I’ve always been fascinated with an AI planning system called Goal Oriented Action Planning or GOAP. If you’re not familiar with it, here’s a simple tutorialI haven’t developed such system myself as the games that I’ve made so far have no use for it. I think it’s the perfect AI system for builder games. I hope I’m right.

Why GOAP

The primary reason is I’m lazy. I don’t want to wire and connect stuff like you do with Finite State Machines and Behaviour Trees. I just want to provide a new action and my agents will use it when needed. Another main reason is I’ve reckoned that there’s going to be a lot of action order combinations in the game. I don’t want to enumerate all of those combinations. I want the game agents to just discover them and surprise the player.

Another important reason is the AI system itself is an aide for development. There’s going to be lots of objects in the game that the agents may interact with. While I’m adding them one by one, I’ll just add the actions that can be done with the object and the agents will do the rest. I don’t have to reconfigure them much every time there’s a new action available. Just add the action and it’s done.

Some Tweaks

While making the system, I had some ideas that would make the generic GOAP system better. They sure have paid off.

Multiple Sequenced Actions

Per GOAP action, instead of doing only one action, our custom GOAP action contains a set of modular atomic actions. Each atomic action is executed in sequence. This is what it looks like in editor:

multipleactions

By doing it this way, I can make reusable atomic actions that can be used by any agent. A GOAP action then is just a named object that contains preconditions, effects, and a set of atomic actions.

GoapResult

I incorporated the concept of action results like how it is in Behaviour Trees. An atomic action execution returns either SUCCESS, FAILED, or RUNNING. This is what the atomic action base class looks like:

public abstract class GoapAtomAction {

    public virtual void ResetForPlanning(GoapAgent agent) {
    }

    public virtual bool CanExecute(GoapAgent agent) {
        return true;
    }

    public virtual GoapResult Start(GoapAgent agent) {
        return GoapResult.SUCCESS;
    }

    public virtual GoapResult Update(GoapAgent agent) {
        return GoapResult.SUCCESS;
    }

    public virtual void OnFail(GoapAgent agent) {
    }

}

When an atom action returns FAILED, the whole current plan fails and the agent will plan again. A RUNNING result means that the current action is still running, thus also means that the current plan is still ongoing. A SUCCESS result means that the action has done its execution and can proceed to the next atomic action. When all of the atomic actions returned SUCCESS, the whole GOAP action is a success and the next GOAP action in the plan will be executed.

This concept makes it easy for me to add failure conditions while an action is being executed. Whenever one action fails, the agent automatically replans and proceeds to execute its new set of actions.

Condition Resolver

Condition Resolvers are objects that can query current world conditions which you need during planning. I implemented this as another base class in our system. The concrete classes can then be selectable in the editor. This is what the base class looks like:

public abstract class ConditionResolver {

    private bool resolved;
    private bool conditionMet;

    public ConditionResolver() {
        Reset();
    }

    public void Reset() {
        this.resolved = false;
        this.conditionMet = false;
    }

    public bool IsMet(GoapAgent agent) {
        if(!this.resolved) {
            // Not yet resolved
            this.conditionMet = Resolve(agent);
            this.resolved = true;
        }

        return this.conditionMet;
    }

    protected abstract bool Resolve(GoapAgent agent);

}

Note here that it has logic such that Resolve() will only be invoked once. Concrete subclasses need to only override this method. Such method may execute complex calculations so we need to make sure that it’s only called once when needed during planning.

This is what it looks like in editor:

conditionresolvers

All conditions default to false unless they have a resolver which is used to query the actual state of the condition.

Usage

Once the conditions, resolvers, and actions have been set up, all that’s left to do is to add goal conditions and invoke Replan().

void Start() {
    this.agent = GetComponent();
    Assertion.AssertNotNull(this.agent);

    // Start the AI
    this.agent.ClearGoals();
    this.agent.AddGoal("StudentBehaviour", true);
    this.agent.Replan();
}

If there are new goals to satisfy, the same calls can be invoked to change the goal(s) for a new plan to be executed.

So Far So Good

Our custom GOAP system is working well for us… for now. I now have working worker agents and student agents. More will be added. Here’s hoping that we don’t need to revamp the system as we’re already so deep with it.

The Party Base Code

After implementing some of the basic features of the game, I could definitely say I’m now an official Party Animals programmer. There’s no turning back now. I’ve invested huge amounts of time, effort, and code already. Might as well make a blood oath that I’m going to code for this game until it is released.

Starting a code base is both challenging and exciting. I have to learn new stuff and unlearn some things that I used in my previous game. I have to reset my mindset that the project is still in its early stage. Lots of tools and systems are still missing. It’s unlike the code base of a built game where the systems are already in place and I have the thought process on how to change things. This time, I’m also not a lone programmer anymore. I have to take that into consideration.

In this post, I’ll show you what I’ve done so far.

Camera Settings and Resolution

I’d like my camera and 2D Toolkit settings to be consistent. I want the orthographic size of my cameras to be 1 because it feels more consistent rather then specifying some other number. It also helps with resolution independence. We’ve also decided to use 1366×768 as the base resolution. In short, I have to rip apart the current settings (sorry Julius). I’ve requested Ryan to resize the assets to make it suitable for the new resolution. 2D Toolkit sprite collections have to be regenerated. With this, fixing the positions and colliders of the districts was also inevitable.

IslandResized
Resized districts. Still used Julius’ clouds and wave simulator. They’re cool!

uFrame

Party Animals is a systems heavy game where game rules could change a lot. I’ve decided that I should incorporate some form of framework to it so we could at least have some structure to where code goes. Julius used StrangeIoC. Then I was able to buy uFrame because it was on sale. I found uFrame to be superior than Strange. So I had to rip apart the current code base (sorry again Julius).

I’ve got to say don’t buy this product if you’re not a programmer or even a newbie coder. It’s hardcore code framework rather than a utility extension like most other asset store products. I wonder how they sell this thing. Only programming nerds are excited by this.

I really like it, though. It forces me to adhere to MVVM and have some kind of structure and separation on where the appropriate code goes. It forces me to follow the rule that views should react to changes in models. Only controllers can mutate models. I had a really hard time understanding it at first. Data initialization is not that straightforward. I even asked dumb questions to their forums. One reply said “Ugh, awkward questions… but here’s what you do…” I’m starting to get used to it now. I’ve implemented the features so far using uFrame.

uFrame
Models in uFrame are code generated. They are authored like this.

Staff Movement

The Staff and Districts are the main movers of the game. Obviously, these two had to be implemented first. The first action I’ve done is movement. Things have to be implemented a little different when using uFrame. If I had done it like I always do, I just move the staff avatar then change its current district. Not anymore. In uFrame, you have to go through a controller. I have a command called ChangeDistrict() which updates the current district in the model. The avatar (view) has been bound to the current district. It is notified whenever the current district changes. When it does, that’s the only time that I could move the avatar to the new district. See the difference there? The direction of execution is always Model changed then View reacts to it, not the other way around.

Staff Campaign and Unity UI

Party Animals is so UI heavy. I haven’t played with Unity UI yet because Warrior Defense’ UI was still using 2D Toolkit. I have no escape now. Julius is using it and highly recommends it. I hate to say that I do like it after trying it out. RectTransform is freaking awesome! I hope it won’t bite us later. I’m still not sure how to go about atlasing, hi-res assets replacement, and draw calls.

Campaign is one of the basic Staff actions. This action has a lot of parameters so I decided to use a uFrame model for it. I’m glad I did because I learned a lot of the uFrame flow when I implemented it. The major one is UI catch cases. There were lots of these. Since I have to implement in uFrame mindset, working around it is also very different. I always say “update model then UI, update model then UI…”

My first complex uFrame and Unity UI combo!
My first complex uFrame and Unity UI combo!

Generally, I’m happy with the base code right now. I’ve got two basic actions implemented. I now have prior knowledge on how to implement the next ones. Can’t wait to show you minimum playable version of the game!

Getting Started with Strange IoC

I haven’t been posting for a while because I’ve been busy implementing the persistence feature using SimpleSQL and Strange. It’s not finished yet, thus this non development post of helping you getting a head start on using Strange.

I decided to use Strange because I want to explore this framework. Strange is an Inversion of Control (IoC) framework for Unity. IoC is another way of decoupling classes, and for me, any decoupling technique is worth studying. What is IoC? Read here. IoC frameworks have been a staple in enterprise and other non game software. It has matured in those areas tremendously. I’m betting on its promise that it makes games more testable and improves collaboration. I’m curious on how it can really help game development.

Using Strange is very “strange” indeed. I found it hard to set-up something like a Hello World for it. It has a how-to page but doesn’t really have a step by step guide on how to do it. This post is a step by step guide to a Strange Hello World.

Some notes:

  • Read the Strange how-to page first before heading back here for the guide on how to put them all together.
  • This guide uses Signals instead of Events (because I like Signals more than Events)
  • I won’t give a Unity project for this so that you’ll have to set it up on your own. You’ll probably learn more.
  • The Hello World sample merely gives a simple usage of injection binding, command binding, and mediation binding.

Start Signal

Start with an empty project. Download and extract the Strange framework in the Assets folder. The folder structure should look like this:

Assets
    StrangeIoC
        examples
        scripts
        StrangeIoCTemplate

Create a “Game” folder under Assets. This will the folder for our Hello World sample.

Assets
    Game
        Scenes
        Scripts

Create a script named HelloWorldSignals.cs under Scripts. This class will contain all signals used in the sample. Open up Mono. We begin coding.

using System;

using strange.extensions.signal.impl;

namespace Game {

    public class StartSignal : Signal {}

}

Signals are more like events in an observer pattern. They are implemented as named classes that extends from Signal. We’ll see later on how they are used.

Strange employs the concept of “contexts” to identify different problem domains or sub modules. In a real game project, you can have multiple contexts like game logic, asset sources, persistence, analytics, social features, etc. We only have one context in this guide for simplicity.

A pre-made context that is available in Strange is class called MVCSContext. However, MVCSContext uses events by default. We create another base context class that uses signals. This class can be reused in other contexts.

Create a script named SignalContext.cs under Scripts.

using System;

using UnityEngine;

using strange.extensions.context.impl;
using strange.extensions.command.api;
using strange.extensions.command.impl;
using strange.extensions.signal.impl;

namespace Game {
    public class SignalContext : MVCSContext {

        /**
         * Constructor
         */
        public SignalContext (MonoBehaviour contextView) : base(contextView) {
        }

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

            // bind signal command binder
            injectionBinder.Unbind<ICommandBinder>();
            injectionBinder.Bind<ICommandBinder>().To<SignalCommandBinder>().ToSingleton();
        }

        public override void Launch() {
            base.Launch();
            Signal startSignal = injectionBinder.GetInstance<StartSignal>();
            startSignal.Dispatch();
        }

    }
}

Create a new folder named “Controller” under “Scripts”. It’s pretty clear that we are heading towards an MVC pattern. The author of Strange suggests that we implement controllers as Command classes. This folder will contain all Command classes. For now, we create a command that will be executed when StartSignal is dispatched. Create a class named HelloWorldStartCommand  under Controller.

using System;

using UnityEngine;

using strange.extensions.context.api;
using strange.extensions.command.impl;

namespace Game {
    public class HelloWorldStartCommand : Command {

        public override void Execute() {
            // perform all game start setup here
            Debug.Log("Hello World");
        }

    }
}

Now we create the custom context class for the Hello World sample. Create a class named HelloWorldContext under Scripts.

using System;

using UnityEngine;

using strange.extensions.context.impl;

namespace Game {
    public class HelloWorldContext : SignalContext {

        /**
         * Constructor
         */
        public HelloWorldContext(MonoBehaviour contextView) : base(contextView) {
        }

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

            // we bind a command to StartSignal since it is invoked by SignalContext (the parent class) on Launch()
            commandBinder.Bind<StartSignal>().To<HelloWorldStartCommand>().Once();
        }

    }
}

In here, we bind StartSignal to HelloWorldStartCommand which means that HelloWorldStartCommand gets instantiated and executed whenever an instance of StartSignal is dispatched. Note that in our sample, StartSignal is dispatched in SignalContext.Launch().

The final step is to create the MonoBehaviour that will host the context. Create a class named HelloWorldBootstrap.cs under Scripts.

using System;

using UnityEngine;

using strange.extensions.context.impl;

namespace Game {
    public class HelloWorldBootstrap : ContextView {

        void Awake() {
            this.context = new HelloWorldContext(this);
        }

    }
}

Components that hosts a Strange context is usually called a “Bootstrap”. It’s just a suggestion but it can be anything. The only thing to note here is that it extends ContextView which is a MonoBehaviour. In Awake(), it assigns an instance of the custom context that we have implemented to a special inherited variable “context”.

Create a new empty scene named “HelloStrange” under Scenes folder. Create a new GameObject named “Bootstrap”. Add the HelloWorldBootstrap to it. Hit play. “Hello World” should be printed on the console.

Strange Hello World
Strange Hello World

Injection in Mediator

So much code, we only logged “Hello World”, what’s the fuzz with Strange then? Let me say that what we have done until now is pretty cool! We now have a working context. From here on, we can now add views and their corresponding mediators. We can also use the injection binder to map an instance to some interface which can be injected into controllers/commands and mediators without them knowing where the instance came from. Follow through the rest of the guide to see some magic.

When we make games, we usually use singleton managers like EnemyManager, AsteroidManager, CombatManager, etc. There are many ways to resolve an instance to any one manager. We may use GameObject.Find() or add a GetInstance() static method. Let’s create a hypothetical manager and see how instance resolution works in Strange. Create an interface named ISomeManager.cs under Scripts folder.

namespace Game {
    public interface ISomeManager {

        /**
         * Perform some management
         */
        void DoManagement();

    }
}

This will be the interface for our sample manager. The author of Strange suggests to always use an interface and map it to an actual implementation using the injectionBinder, although the mapping would still work without using one. Let’s create a concrete implementation. Create a class named ManagerAsNormalClass.cs under Scripts.

using System;

using UnityEngine;

namespace Game {
    public class ManagerAsNormalClass : ISomeManager {

        public ManagerAsNormalClass() {
        }

        #region ISomeManager implementation
        public void DoManagement() {
            Debug.Log("Manager implemented as a normal class");
        }
        #endregion

    }
}

This is a non MonoBehaviour version of the manager. I’ll show you later how to bind a MonoBehaviour one.

Let’s create a simple UI where there’s a button that when clicked, it invokes ISomeManager.DoManagement() without using a reference to ISomeManager. In an MVC pattern, this is ideal. Views should only say, “Hey I’m clicked/used” and it should not know what happens next.

Create a folder named View under Scripts. This folder will contain the View and Mediator classes.

Game
    Controller
    View

Create a file named HelloWorldView.cs under View folder.

using System;

using UnityEngine;

using strange.extensions.mediation.impl;
using strange.extensions.signal.impl;

namespace Game {
    public class HelloWorldView : View {

        public Signal buttonClicked = new Signal();

        private Rect buttonRect = new Rect(0, 0, 200, 50);

        public void OnGUI() {
            if(GUI.Button(buttonRect, "Manage")) {
                buttonClicked.Dispatch();
            }
        }

    }
}

View is a MonoBehaviour class that is included in Strange. All views that should work within a Strange context should extend this class. HelloWorldView just renders a “Manage” button that dispatches a generic signal when clicked.

The author of Strange recommends that each view should have a corresponding mediator. A mediator is a thin layer that lets a view communicate with the rest of the application. The mediation binder is used to map a view to its corresponding mediator. Let’s create the mediator to HelloWorldView. Create a file named HelloWorldMediator.cs under View folder.

using System;

using UnityEngine;

using strange.extensions.mediation.impl;

namespace Game {
    public class HelloWorldMediator : Mediator {

        [Inject]
        public HelloWorldView view {get; set;}

        [Inject]
        public ISomeManager manager {get; set;}

        public override void OnRegister() {
            view.buttonClicked.AddListener(delegate() {
                manager.DoManagement();
            });
        }

    }
}

Now we see the magical Inject attribute here. This attribute only works with properties. When a property has this attribute, it means that Strange will automatically assign/inject an instance to it based on the mappings in the context. In the perspective of the class above, it knows that it can get an instance for its properties view and manager without knowing where it came from.

OnRegister() is a method that can be overridden to mark that the injection of instances has been done and they are ready for use. What is done here is mostly initialization or preparation routines. In the class above, we add a listener to the HellowWorldView.buttonClicked signal using a closure that invokes ISomeManager.DoManagement().

To put them all together, we will map the binding in the Strange context. Open HelloWorldContext and set the following lines inside mapBindings() method:

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

            // we bind a command to StartSignal since it is invoked by SignalContext (the parent class) during on Launch()
            commandBinder.Bind<StartSignal>().To<HelloWorldStartCommand>().Once();

            // bind our view to its mediator
            mediationBinder.Bind<HelloWorldView>().To<HelloWorldMediator>();

            // bind our interface to a concrete implementation
            injectionBinder.Bind<ISomeManager>().To<ManagerAsNormalClass>().ToSingleton();
        }

In HelloStrange scene, create a new GameObject named “View” and add the HelloWorldView script to it. Play the scene. You should be able to see a Manage button that prints “Manager implemented as a normal class” when clicked.

ISomeManager in action
Strange in action

Note also that Strange automatically adds the mediator component when the scene is played. Remember that we didn’t add the HelloWorldMediator to the View scene object.

Where did the mediator came from?
Where did the mediator came from?

MonoBehaviour Manager

Almost always, we want our managers to be implemented as a MonoBehaviour so we can inherit its advantages like coroutines, and serialization. How do we bind a MonoBehaviour manager instance in Strange?

Let’s create our MonoBehaviour manager. Create a file named ManagerAsMonoBehaviour.cs under Scripts folder.

using System;

using UnityEngine;

namespace Game {
    public class ManagerAsMonoBehaviour : MonoBehaviour, ISomeManager {

        #region ISomeManager implementation
        public void DoManagement() {
            Debug.Log("Manager implemented as MonoBehaviour");
        }
        #endregion

    }
}

In HelloStrange scene, create a new scene object named “Manager” then add the ManagerAsMonoBehaviour script to it.

Edit mapBindings() method of HelloWorldContext to the following:

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

            // we bind a command to StartSignal since it is invoked by SignalContext (the parent class) during on Launch()
            commandBinder.Bind<StartSignal>().To<HelloWorldStartCommand>().Once();

            // bind our view to its mediator
            mediationBinder.Bind<HelloWorldView>().To<HelloWorldMediator>();

            // REMOVED!!!
            //injectionBinder.Bind<ISomeManager>().To<ManagerAsNormalClass>().ToSingleton();

            // bind the manager implemented as a MonoBehaviour
            ManagerAsMonoBehaviour manager = GameObject.Find("Manager").GetComponent<ManagerAsMonoBehaviour>();
            injectionBinder.Bind<ISomeManager>().ToValue(manager);
        }

Instead of mapping ISomeManager to a type, we map it with an instance value. Play the scene again and click Manage, you should see something like this:

Manager is now a MonoBehaviour
Manager is now a MonoBehaviour

Injection in Command

What we did so far is we injected an ISomeManager instance in HelloWorldMediator and use that directly. That is actually not ideal. A mediator should only be a thin layer between its view and controllers. As much as it can be, it should not know about which part of the code performs the complex processing like managers. It can, however, know about signals that is mapped to commands.

Edit HelloWorldSignals.cs and add a DoManagementSignal:

using System;

using strange.extensions.signal.impl;

namespace Game {

    public class StartSignal : Signal {}

    public class DoManagementSignal : Signal {} // A new signal!

}

Let’s create the command that is mapped to that signal. Add a file named DoManagementCommand.cs under Controller folder.

using System;

using UnityEngine;

using strange.extensions.context.api;
using strange.extensions.command.impl;

namespace Game {
    public class DoManagementCommand : Command {

        [Inject]
        public ISomeManager manager {get; set;}

        public override void Execute() {
            manager.DoManagement();
        }

    }
}

In here, we inject ISomeManager to the command class and invoke ISomeManager.DoManagement() in its Execute() method.

Edit HelloWorldMediator to the following:

using System;

using UnityEngine;

using strange.extensions.mediation.impl;

namespace Game {
    public class HelloWorldMediator : Mediator {

        [Inject]
        public HelloWorldView view {get; set;}

        [Inject]
        public DoManagementSignal doManagement {get; set;}

        public override void OnRegister() {
            view.buttonClicked.AddListener(doManagement.Dispatch);
        }

    }
}

We now removed any references to ISomeManager in the mediator. We get an instance of DoManagementSignal instead and dispatch it whenever the button is clicked. In a way, HelloWorldMediator now doesn’t know about any managers. It just knows signals.

Finally, we add a signal-command mapping in HelloWorldContext.mapBindings():

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

            // we bind a command to StartSignal since it is invoked by SignalContext (the parent class) during on Launch()
            commandBinder.Bind<StartSignal>().To<HelloWorldStartCommand>().Once();
            commandBinder.Bind<DoManagementSignal>().To<DoManagementCommand>().Pooled(); // THIS IS THE NEW MAPPING!!!

            // bind our view to its mediator
            mediationBinder.Bind<HelloWorldView>().To<HelloWorldMediator>();

            // bind the manager implemented as a MonoBehaviour
            ManagerAsMonoBehaviour manager = GameObject.Find("Manager").GetComponent<ManagerAsMonoBehaviour>();
            injectionBinder.Bind<ISomeManager>().ToValue(manager);
        }

Play the scene. It should still work like the previous one.

Last Words

I always mention “the author of Strange recommends/suggests…”. That’s because his suggestions are optional. For example, you can inject instances directly in your views. You may not use a mediator at all. What I’m trying to say is, you can choose another pattern of using Strange depending on your needs. I just chose to follow his recommendations because it works for me.

If you made it this far, then you are probably strange, too. You might be asking “why should I add this complexity and extra layer of indirection to my project?” I’m currently looking for the answer to that question by applying it to my current game. All I can say right now is Strange forces me to keep things separate. Like for now, I have this separate scene that handles persistence using SimpleSQL within a Strange context. Database transactions are executed in commands. The existing game logic, which is non Strange, communicates with this module through signals.

We are also using it in another RPG project. I hope that it does help with respect to code collaboration as it promised. I can’t say how good it is yet as we are still starting the project but so far it is working for us. We are working on multiple contexts. Each context is owned by only one programmer. We communicate with other contexts using signals.

Finite State Machine vs Behaviour Tree, A True Story

I needed behaviour trees because I wanted to manage the complexity of the units AI in Warrior Defense which originally used finite state machines (FSM). FSMs are great because they are so simple and intuitive. When they get big, however, they become so complicated. It got to a point where I’m afraid to change the configuration of the FSM because the working AI easily breaks. In other words, it’s brittle. I don’t like that. I still have lots of features to add later on that deal with unit interactions.

A part of Defender unit FSM. Looks like spaghetti, yum!
Just a part of Defender unit FSM. It looks like spaghetti, yum!

I decided to make my own behaviour tree tool because I can’t find a decent one in the Asset Store. I’m one of those lazy guys that buy a lot of tools. I’ve tried RAIN, which is free. I hated it. The changes to the tree did not reflect in the game right away. It just frustrated me. I’ve bought React while it was on sale but I didn’t like how it worked. Adding actions to it was clunky. It uses functions and runs it in a coroutine. It doesn’t support parameters, as well.

Thus, the journey to developing my own behaviour tree tool began. I called it Banana Tree. I completed the core behaviour tree framework (nodes, sequence, decorators) some months ago. But this is pure code. There’s no visual editor. I’ve made AI behaviours that are hardcoded in classes. Production wise, this is not very efficient. AI behaviours changes a lot. Something that changes a lot should not be code, they should be data. I thought that I should probably make a data driven behaviour tree tool. Looking at the FSM above, I think I’ll be making a lot of code anyway using the current framework.

So I did make a data driven behaviour tree with its own visual editor. I just finished the bare essentials that could create a working behaviour out of data. I’ve successfully ported the complex behaviour above to its Banana Tree equivalent. The high level behaviour looks like this:

High level behaviour nodes
High level behaviour nodes. It looks very neat.

Don’t be fooled, though, because it looks like this underneath those parent nodes:

Worms under the stone
Worms under the stone

What I’d like to point out is that it’s still complicated, but it’s now more manageable. I can complete one branch of the behaviour and forget about it once it works and begin working on another one. My brain is not strained so much to the amount of visual objects because I can just fold the node and stop thinking about the things beneath it. It’s so unlike FSMs where you can’t help but see the states and their wiring.

Using behaviour trees feels more like programming. Each node feels like a function. Working with an FSM feels like wiring. It is static as “wire one event to one transition state”. I now understand what articles about behaviour trees mean when they say FSMs are constricting (I didn’t get it before). Behaviour trees don’t have wires. They execute on how the tree is structured. What to do next is not explicitly stated in the behaviour tree. It can select a lot of possible action branches to execute. In FSMs, you have to explicitly specify what state it should go next. This could be limiting because there may be a lot of possible states to go to. It’s possible to make it work like a behaviour tree but you’ll need more work and maintenance to the FSM. You’ll most probably end up with an FSM like the one I showed.

I can’t say that behaviour trees are better, either. They also have a major disadvantage: steep learning curve. You have to think like a compiler when working with BTs. You have to know what its elements mean like Sequence, Selector, Parallel, Decorator, etc. Like every skilled programmers say, “Know your tools. Know where it’s best used.” Behaviour trees has been proven to work on complex behaviours, but I probably can’t give this to a non programmer and let him/her figure it out on his/her own. FSMs are much more easier to understand. They are far more intuitive, too. FSMs are probably the way to go if you only need to model simple behaviours.

Banana Tree Success!

I think Banana Tree is complete, for now. Software projects can never be completed. Little UI elements and responses are still missing. No copy-paste yet, no undo-redo. But for its purpose and intended use, it does it very well. I was able to replicate the behaviour of ground and flying crawlers using the tool. By doing this, I also uncovered and fixed some major implementation holes.

Ground enemy behaviour using Banana Tree
Ground enemy behaviour using Banana Tree

The biggest stumbling block for me is when I found out that Unity can’t serialize a nested field that has depth of seven or more. It’s so disappointing. My node tree data was serialized like this:

[Serializable]
public class NodeData {

    [SerializeField]
    private string label;

    ... // more data

    // this is a nested serialized field
    // Unity can't handle this if it's too deep
    [SerializeField]
    private List<NodeData> children;

}

public class BananaTreeBehaviour : MonoBehaviour {

    // I thought I'll never have problems with this
    [SerializeField]
    private NodeData rootNodeData;

    ...

}

I went to the forums and found out that others are complaining about it, too. I found this out last Monday night. I was so excited that I could finally see Banana Tree in action in an actual game, then this happened. Tuesday morning was spent on fixing it. It’s not an easy fix, either. Fortunately, my habits worked for me. Layers of indirection made refactoring easier.

My fix is to have a master list of all node data. I added an extra property named masterId which works as a primary key for the node. Child nodes are then kept as a list of integers where each entry represents the masterId to the actual node instance. I made a custom container class to manage the list of all nodes.

[Serializable]
public class NodeData {

    [SerializeField]
    private int masterId;

    [SerializeField]
    private string label;

    ... // more data

    // no longer nested to circumvent the limitation
    [SerializeField]
    private List<int> children;

}

[Serializable]
public class NodeDataMaster {

    // all nodes of the behaviour tree are collected here
    [SerializeField]
    private List<NodeData> nodeList;

    /**
     * Searches for the node with the specified master id
     */
    public NodeData FindByMasterId(int masterId) {
        for(int i = 0; i < nodeList.Count; ++i) {
            if(nodeList[i].MasterId == masterId) {
                return nodeList[i];
            }
        }

        Assertion.Assert(false, "Can't find node with master id = " + masterId);
        return null;
    }

    public NodeData AddNode() {
        ...
    }

    public void RemoveNode() {
        ...
    }

    ...
}

public class BananaTreeBehaviour : MonoBehaviour {

    // all node manipulation now goes through here
    [SerializeField]
    private NodeDataMaster dataMaster;

    ...

}

I designed the tool to be flexible and extensible without editing any of its source code. This is done through reflection. Custom actions should be implemented as classes that inherits a certain class. By doing so, the editor identifies these classes and adds UI controls for it. Thus, replicating the existing behaviour means porting a lot of function calls to their action class equivalents. My action browser looks like this:

Custom actions
Custom actions

Custom action classes look like this:

    [ActionGroup("Game.Crawler")]
    public class CrawlerHasBlocker : CrawlerAction {

        #region implemented abstract members of BntNodeAdapter
        public override BntResult Start() {
            return Evaluate();
        }

        public override BntResult Update() {
            return Evaluate();
        }

        public override BntResult FixedUpdate() {
            return Evaluate();
        }
        #endregion

        private BntResult Evaluate() {
            if(GetCrawler().HasBlocker()) {
                return BntResult.SUCCESS;
            }

            return BntResult.FAILED;
        }

    }

    [ActionGroup("Game.Path")]
    public class GetTargetPathPosition : BntCachedComponentAction<PathCrawler> {

        [UseVariable]
        public BntVector3 Result {get; set;}

        #region implemented abstract members of BntNodeAdapter
        public override BntResult Start() {
            Result.Value = GetCachedComponent().GetTargetPathPosition();
            return BntResult.SUCCESS;
        }

        public override BntResult Update() {
            return BntResult.SUCCESS;
        }

        public override BntResult FixedUpdate() {
            return BntResult.SUCCESS;
        }
        #endregion

    }

It sounds silly that a single function call need to be promoted to a class to be used in an editor. But I like this better. I like the idea that you can extend the functionality of an editor by just adding classes. This adheres to the Open-Close principle of software engineering.

Working Behaviour Tree From Editor

A lot has happened to Banana Tree and I’m quite happy with its current state. More variable types are now supported in the node inspector. I prioritized the variables that are used in existing actions so I could test them as soon as possible.

While thinking about how action nodes should pass variable values to other nodes, I realized that it’s not effective for nodes to have references of other nodes. Playmaker has this concept of “FSM level” variables. These variables can be referenced by any states in the FSM. Though initially not planned, I think I needed this type of feature in Banana Tree as well for cross node variable communication. I need to have some kind of behaviour-level variables that can be referenced by any action nodes in the behaviour. I’m surprised that the implementation is really not that hard.

Node level and behaviour level variables
I have this action class “TestVariableAction” which is used to test all supported variables. On the right are the behaviour level variables which can be referenced by action level variables.

I use custom classes to add variables to an action node. The editor identifies these properties and renders the appropriate UI controls. Here’s the implementation of TestVariableAction:

    [ActionGroup("Tests")]
    public class TestVariableAction : BntAction {

        /**
         * Constructor
         */
        public TestVariableAction(string name) : base(name) {
        }

        public BntString StringVariable {get; set;}

        public BntString StringTest2 {get; set;}

        public BntGameObject GameObjectTest2 {get; set;}

        public BntFloat FloatVar {get; set;}

        public BntFloat FloatVar2 {get; set;}

        public BntVector3 PositionVar {get; set;}

        public BntVector3 PositionVar2 {get; set;}

        public BntTransform TargetVar {get; set;}

        public BntTransform TargetVar2 {get; set;}

        public BntBool BoolVar {get; set;}

        public BntBool BoolVar2 {get; set;}

        #region implemented abstract members of BntNodeAdapter
        public override BntResult Start() {
            return BntResult.SUCCESS;
        }

        public override BntResult Update() {
            return BntResult.SUCCESS;
        }

        public override BntResult FixedUpdate() {
            return BntResult.SUCCESS;
        }
        #endregion

    }

If you want to do something like this, I suggest you use custom classes instead of directly using string, float, bool, GameObject, etc. Custom class variables adds indirection so you can add some intermediary features to your variables like referencing another variable when its value is requested or add some more information to the variable. It’s also much easier to assign values to it on game runtime since the class variables can be implemented as serialized and you’ll just have to assign it directly to the action node instance properties through reflection.

With this in place, I was able to instantiate a minimal behaviour tree at runtime from serialized data. What it does is it moves an actor back and forth in two positions, Home and Destination.

Minimal working behaviour... Yey!
Minimal working behaviour… Yey!

The editor is almost complete. When I say complete, it means it must be verified which further means that it should be used in a working game. Tomorrow, I’ll replicate the existing enemy behaviour in Warrior Defense using this editor. It’s not that easy because I’ll have to implement lots of action classes. I’m sure there will be some hiccups but I’ll find a way.