GOAP Extensions

We use GOAP for our AI in Academia. It’s working well for us so far. New behaviour can be easily added and AI glitches can be easily fixed. But I’ve had problems with it as well. One of them which needed fixing is duplicate actions.

We use GOAP for our AI in Academia. It’s working well for us so far. New behaviour can be easily added and AI glitches can be easily fixed. But I’ve had problems with it as well. One of them which needed fixing is duplicate actions.

We have different classes of characters in the game. We have students, teachers, workers, cooks, nurses, and janitors (more are coming). Each of them have different set of actions but most of the time, they also have a common set of actions. For example, eating. If I fix the eating behavior in students, that means that I have to also apply the same fix to the other classes. This is maintenance nightmare. A character class could become broken if I just happen to forget a certain fix. Applying a fix to each of the classes is tedious, too.

GOAP Actions Refactoring

I needed a way to refactor GOAP actions in such that I could just edit one set of actions and it will be applied to all the character classes. Thus, I introduced “extensions” to our GOAP framework.

GoapExtensions

An extension is basically a reference to another GOAP data set. During parsing of this data, the system adds all the actions found in the extension. An extension can also have a set of preconditions. These preconditions are added to all the actions in the extension. For example, from the image above, the NeedsBehaviour extension will only be executed if HasRequestedTreatment = false.

The refactored actions that pertains to “needs” are now placed in its separate GOAP data set:

NeedsBehaviour

The specific GOAP data for each character class can simply reference this “needs” data to be able to execute such actions. I only need to fix this “needs” GOAP data if I have to fix a “needs” related behavior. No need to apply separate fixes to each character class.

This feature turned out to be very useful. Every time there’s a new set of behaviour that could be reused, I put them in a separate GOAP data. The character class that requires it could just add this new data as an extension. For example, for now, only students can use computers. So I made a separate GOAP data called “ComputerLabBehaviour” and added it as extension to the student’s GOAP data. Later on, if we decide that teachers could also use computers, I can simply add the “ComputerLabBehaviour” data as extension to the teacher’s GOAP data.

Behaviours
Our current set of behaviours

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);
        }

    }

New Stuff and a Tip on Starting Coding Momentum

Development of Party Animals has been hanging around for over a year already (or years) but the truth is that the game’s mechanics are not yet zoned in. We’re still looking for the fun part. We’re at the stage when we are adding new mechanics and testing them out as soon as we can then decide whether to keep them, change, or discard. If we’re not having fun with it, our target players most probably won’t. While we unanimously agree that what we have is fun enough, something is still missing. This post will be enumerating what we’ve done so far. As a bonus, if you stay a while, you’ll get a tip on how I start my coding momentum.

Command Points

As context, Party Animals is a game about winning an election. The main mechanic unit of the game are the Staff which the player can move around in different districts and make them execute actions. The game is basically an attrition with an opponent through Staff actions.

We introduced the concept of Command Points (CP) to control the movement of Staff such that players are forced to strategize on which district to move next as usage of such units now have cost. CP cost is simply defined as the shortest district distance of a Staff from its Candidate (a Candidate is just another Staff with special abilities). If a Staff is in the same district as its Candidate, then CP cost for the Staff is zero. CP resource is assigned to each candidate at the start of the turn. It is increased when certain campaigning days are reached. In real life, we’d like to think of this as the cost of planning with your Staff that is in another city or province.

During playtesting, Command Points had a direct effect on Staff movement. During early game, players only move their Staff along one adjacent district distance from their Candidate.

Pay CP first before you can use your Staff
Pay CP first before you can use your Staff

New Meaning of Reputation

Reputation is one of the most important metric in our game. It directly tells whether or not people will vote for a candidate in a certain district. It used to be a percentage of the population in the district that is willing to vote for you. It’s a value between 0 – 100. Not anymore. It’s now the actual count of people that will vote for a candidate. This implies that districts with more population is now harder to own (get at least 50% of voters).

Due to this change, we introduced the concept of Reputation Decay. A percentage of a candidate’s reputation in a district is deducted if the Candidate has no Staff stationed in that district. The message is that voters are fickle. They won’t vote for someone unless they constantly stick around, or the candidates give them money. 🙂

Raise Funds

We finally have this Staff action. It’s in the game design papers ever since. We designed it such that the more Reputation a candidate has in a district, the more money it can raise. That’s how it is in real life. People are willing to give you money if they like you.

Now players can use the people they've convinced to give them money
Now players can use the people they’ve convinced to give them money

Patrons

I had a direct hand in introducing this mechanic and I’m happy with it. Before this change, each district has a Kapitan which Candidates can have a relationship with (in a friendly way). The only effect it had was during elections. If you’re closer to the Kapitan, then people will vote for you in that district.

As a strategy gamer, I feel that the game doesn’t have much avenue in acquiring ‘things’ that generates resources, which I really like in such games. Mechanics like building an expansion in Starcraft to exploit new resources, building that Bank to generate gold in Civilization, etc. I suggested what if there are patrons or sponsors that players can court. They could be an influential family in the district, a business man, celebrity, or another politician. You gain some effects if you can get them to support you like giving you funds per turn, or slowing Reputation Decay.

Our game designer, Tristan, came up with Patrons. Kapitans were discarded. There are now 3 Patrons in each district. Each Patron has a distinct effect depending on a player’s relationship with such. One Patron had an effect when Raising Funds, another one for CP cost (reduced or increased), and last one had an effect when doing Sortie (increases or decreases the amount of reputation gained). A player can decide to make a good relationship with one or more of these Patrons using the Gift action.

Patronage politics at its best!
Patronage politics at its best!

Scandals

Everybody loves scandals… as long as you’re not implicated. Our political climate is rife with it, even during elections. I’ve heard in an interview with a local election campaign consultant that there’s this one politician who didn’t win but spent so much. He was running for Senator. When he was asked to compute his expenses divided by the votes he got, he spent around Php5000 per voter. The election campaign consultant then said “Imagine if he bought votes instead. That’s only Php500 per voter. He would have gotten ten times more votes.” Vote buying is a big scandal, but if you’re the one running the election, it could be very tempting.

Given such fact, the concept of Scandals is a vital mechanic in Party Animals. Of course the representation is rather simplified. Scandals in our game is just a number of scandalous acts that a candidate has done in a district. Actions like Bribe and Smear Campaign increases the Scandal Count. Other neutral actions like Campaign, Sortie, Gift can become scandalous (increases the Scandal Count) if a bigger amount of money was used to carry out such actions. If you didn’t know, COMELEC has a prescribed amount of election money to be spent. It differs per location and position. You can get sued if you’re found spending more.

Red means "scandalous"
Red means “scandalous”

At the start of the next turn, each “unchecked” Scandal are then “checked” if it is revealed or not. We are running a formula to this. If it gets revealed, the game imposes harsh penalties for the candidate on that district. The penalties are in the form of reduction of Patron relationship, Reputation and increased CP cost on the district. The higher the Scandal Count, the higher the probability of it getting revealed.

As simple as it looks, implementing Scandals gave us a lot of headache. There were a lot of design issues that we’ve encountered; there were some cases that we needed to consider. As of writing, I’m still working on the final touches of the mechanic.

The Tip

As promised, the tip I’m sharing is about how do I start my coding momentum. It may not work for everybody but it certainly worked for me. Programmers are peculiar creatures. They need to be in a certain state so that they can work productively. But reaching that state is hard because programmers must be working to reach it. It’s kind of like an “almost” chicken-egg problem. Reach that state to get work done but work to reach that state.

As I ponder upon this, I thought about working out. People hate it. It’s tiring. The time used for it could have been used for something else. To make things worse, the main benefit of working out is kind of an abstract bullshit: health. Then I thought of myself. I go to a martial arts gym but how could I do it? I know I need to do it but what really gets me to go there and tire myself? The answer is really quite simple. “I packed up my gym stuff.” Packing up gym stuff is easy. It doesn’t take a lot of will. But it starts there. Next, I go out and commute to the gym. While I’m at a bus, it’s already hard for me turn back and change my mind.

Back to programming work, I can ask the same analogous question, “What’s the least and simplest thing that I can do to start coding?” My answer is “write one line of code”. It works for me like sorcery. It might be different for you, so go find yours.

Smooth and Shiny Weekend

People might have already forgotten, but the One Year Game Challenge program is still ongoing. It’s an IGDA program that I handle in which developers pledged to complete their games within one year starting July 2013. The catch is they would pay a certain amount not less than P1000 to get into the program. If they release their game within the year, IGDA would return their money. Otherwise, IGDA would consider it as donation.

There are already lots of game jams and hackathons but there are only a handful of projects from these events that get revived, polished, and released. I think it’s time that we have an event for completing existing projects. I organized this experimental event called “Smooth and Shiny Weekend” that happened last Saturday, March 29 at Globe Labs. It’s a 24-hour event where developers gather in one roof and together will make polishes to their game projects. It’s kind of like a game jam but for polishing an existing game project. That’s why it’s called “Smooth and Shiny”. For now, I only invited the participants of One Year Game Challenge as it is still experimental.

20140329_222507
The first Smooth and Shiny Weekend evaaar!
Cool poster eh
Cool poster eh

A big big thanks to Globe Labs for letting us use their place. They were really nice to us. Only 5 out of 16 of us came up on Saturday (I am a participant of 1YGC as well). Two more guys caught up on Sunday. The turn out was, well, disappointing. I shout out to those who didn’t attend. I miss you guys! But the reviews I’ve got were great. Someone mentioned that this kind of party is needed as it is hard to work on something when you have the option to procrastinate. The participants are willing to do it again even if there was a fee like maybe for food and stuff. But personally, I don’t want any form of fees. It’s just extra work for me. I like the way it is.

It was such a relaxed event. There was not much pressure, no free food to prepare, no money sponsor to satisfy. When it’s time to eat, we ordered pizza and ate together. There were lots of junk food and juice drinks. Questions were asked and answered. Some needed help and got it right away. It was a good experience, overall. I hope the participants made good progress to their projects. I certainly did. I was able to fix some bugs and prepare a presentable build for my Mind Museum exhibit for the next day. So yeah, I didn’t really stay for 24 hours. I had to head home and prepare for my exhibit (like taking a bath). Sorry guys =) I’ll make it up next time. I would like to thank Gwen for leading the event during Sunday.

So who’s up for the next Shiny Weekend? The next one would certainly be open for all.

Mar 17-22 Updates

New Tutorial Sequence

I’ve shown the game to a lot of people by now. When I’m showing it, I don’t want to specifically point out to the player what he/she should do. I’ve always wanted my games to show itself how to play it. In this aspect, my evaluation so far is not so good. I’ve spent the beginning of this week changing the initial tutorial.

3-22-2014a
Now it says where to tap
Tap the dangerous button when you're ready
Tap the dangerous button when you’re ready

The tutorial sequence looks better to me now. But it means nothing unless actual players verify it.

Tiles that shouldn’t be walked upon

Now that I have the actual tile assets with houses, rivers, and other stuff, it’s also the time to update the tilemap data that some objects are no longer ‘passable’. It’s kind of like setting collision boxes to these tiles but instead of boxes, a number is just assigned to each of these tiles which signifies that units can’t pass through here. Here’s a screenshot from Tiled:

Black means 'no'
Black means ‘no’

For those of you who doesn’t know, Tiled is a free software that is used mainly for tile based games or applications. I’m using a different layer with solid colored tiles that acts as a data file for the game to determine which parts are passable or not. Now that the map has blocked areas like houses and trees, I changed the solid tiles to transparent ones so that I can see through it. All I have to do then is set this layer as the topmost and paint those blocked areas with a special tile (the black one). This special tile is read as ‘unpassable’ in the game.

Unit Upgrade Items

The Upgrade screen is far from complete. For me, it’s actually the most boring thing to do. Maybe because it’s all just UI and data. It’s like working with a web application all over again with MVC patterns and shit. It’s so unexciting but it has to be done. Right now, the ‘unit upgrades’ is work in progress.

Unit upgrades using dummy icons
Unit upgrades using dummy icons

Mind Museum

I also have great news. I was invited to have a game exhibit in Mind Museum! I will showcase Warrior Defense, of course. It’s all happening on March 30. Other Filipino game developers will be there as well.

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.