Reducing Draw Calls Using a Simple Texture Packer

When we started making Academia, we didn’t really plan out how are we going to manage our sprites. We just did the quickest way which was to make them individually and load them in the game. All of our game world sprites are stored in StreamingAssets. We load them dynamically when the game is run. This is different from the normal way using an imported texture. We did it this way in preparation for modding support. I envision that modders could then add their own folders and provide them the mechanism to override the base game images.

Ideally, all game sprites should be in a single big texture. This will allow you to use a common material throughout your game objects so that dynamic batching can indeed batch. Now that the game got bigger, it’s harder to put all of our sprites in one single atlas. Our artist wouldn’t agree to this as it’s a lot of work. Additionally, we no longer have the time. We’re releasing our Early Access this September 8.

SomeObjects
A few samples of our sprites. We have folders of these.

While coming up with solutions, I thought what if I could pack the images dynamically instead and use the generated atlas. It should be simple enough to recompute the UVs of the packed sprites. I scrounged the internet on algorithms on how to optimally pack rectangles in a bigger one. Turns out that this is an interesting problem. There are numerous papers about this. It also turned out that I no longer have to roll up my own packer. Unity already made one.

It needs some help, though. I needed something that keeps track of the packed textures. I needed a way to get the same sprite out of the packed one. Time to code!

Here’s the class that abstracts an “entry” of a packed texture:

    public class PackedTextureEntry {

        private readonly Texture2D atlas;
        private readonly Rect uvRect;
        private readonly Rect spriteRect;
        private readonly Sprite sprite;

        public PackedTextureEntry(Texture2D atlas, Rect uvRect) {
            this.atlas = atlas;
            this.uvRect = uvRect;
            this.spriteRect = new Rect(this.uvRect.x * this.atlas.width, this.uvRect.y * this.atlas.height,
                this.uvRect.width * this.atlas.width, this.uvRect.height * this.atlas.height);
            this.sprite = Sprite.Create(this.atlas, this.spriteRect, new Vector2(0.5f, 0.5f), 768);
        }

        public Texture2D Atlas {
            get {
                return atlas;
            }
        }

        public Rect UvRect {
            get {
                return uvRect;
            }
        }

        public Rect SpriteRect {
            get {
                return spriteRect;
            }
        }

        public Sprite Sprite {
            get {
                return sprite;
            }
        }

        public Sprite CreateSprite(Vector2 pivot) {
            return Sprite.Create(this.atlas, this.spriteRect, pivot, 768);
        }

    }

Basically, it’s just a container of the generated atlas and the UV coordinates of a particular sprite entry. The Rect passed in the constructor is a normalized UV (values are zero to one). Sprites, however, are created using pixels. So we need a new Rect for this which is just the UV rect multiplied by the dimensions of the atlas. This class also has a pre-generated Sprite pivoted at the center.

The following class is the texture packer itself:

    public class TexturePacker {

        // Contains the associated names of the added texture so we can easily query its entry after packing
        private List<string> names = new List<string>();

        // This contains the textures to pack
        // Used a list here so we could easily convert to array during packing
        private List<Texture2D> textures = new List<Texture2D>();

        // Keeps track of the packed entries
        private Dictionary<string, PackedTextureEntry> entriesMap = new Dictionary<string, PackedTextureEntry>();

        private Texture2D atlas;

        public TexturePacker() {
        }

        public void Add(string key, Texture2D texture) {
            this.names.Add(key);
            this.textures.Add(texture);
        }

        public void Pack() {
            this.atlas = new Texture2D(2, 2, TextureFormat.ARGB32, false); // Will expand on packing
            Rect[] rects = this.atlas.PackTextures(this.textures.ToArray(), 0, 8192, true);

            // Populate entries
            this.entriesMap.Clear();
            Assertion.Assert(this.names.Count == this.textures.Count);
            for(int i = 0; i < this.names.Count; ++i) {
                this.entriesMap[this.names[i]] = new PackedTextureEntry(this.atlas, rects[i]);
            }

            // Clear to save memory
            // These textures may also be released
            this.textures.Clear();
        }

        public PackedTextureEntry GetEntry(string key) {
            return this.entriesMap[key];
        }

    }

Usage is self explanatory. Create an instance of the packer. Add the textures that you want to pack, each associated with a string key. Call Pack(). Use GetEntry() to get an instance of PackedTextureEntry associated with the sprite. Use PackedTextureEntry.Sprite property to have access of the sprite that is from the packed texture.

TexturePacker packer = new TexturePacker();

// Let's just say you have a library of textures associated by name
foreach(TextureEntry entry in entries) {
    packer.Add(entry.Name, entry.Texture);
}

packer.Pack();

// Get a packed entry and use its sprite
PackedTextureEntry packedEntry = packer.Get("Grass");
spriteRenderer.sprite = packedEntry.Sprite;

And that’s it! It’s really simple but this thing helped in batching by a lot.

YuugeBatch
Yuuuge batch! This is from the frame debugger.

 

Advertisements

Some Utility Code

Every now and then, I’d like to share some of my small utility classes that I’ve made a long time ago but are still being used until now. The following are what I call some of my mini utilities. They can be used in your own Unity projects.

(Note that I’ve removed method description comments so that the code part looks more compact.)

Comment Component

A lot of times have come up where I have an object or prefab that contains a lot of components. Sometimes, I want to be able to note about the values I’ve entered for some component or state why the component is there. Sadly, Unity doesn’t have a feature where you can comment on components (let me know if you know one). However, it’s quite easy enough to roll your own “comment” component.

public class Comment : MonoBehaviour {

    [SerializeField]
    private string text;

    void Awake() {
        DestroyImmediate(this); // auto destroy to save memory
    }

    public string Text {
        get {
	    return text;
	}

        set {
	    this.text = value;
        }
    }

}

// This is the editor script. Place it under an Editor folder.
[CustomEditor(typeof(Comment))]
public class CommentEditor : Editor {

    private Comment targetComponent;

    void OnEnable() {
        this.targetComponent = (Comment)this.target;
    }

    public override void OnInspectorGUI() {
        if(targetComponent.Text == null) {
            // use an empty string so that it won't throw null pointer exception
            targetComponent.Text = "";
        }

        targetComponent.Text = GUILayout.TextArea(targetComponent.Text, GUILayout.Height(100), GUILayout.MinWidth(200));
    }

}

To use, just add the Comment component and type your comment text in the textbox.

CommentComponent

UI Image Sprite Animation

I tried to use the animation timeline for animating sprites in a UI Image. But it didn’t work or maybe I was using it wrong. I got impatient, so I turned to code.

public class ImageAnimation : MonoBehaviour {

    [SerializeField]
    private Image image;

    [SerializeField]
    private float framesPerSecond = 15;

    [SerializeField]
    private Sprite[] sprites; // The sequence of sprites comprising the animation

    private float polledTime;

    private int currentFrameIndex;

    private void Awake() {
        Assertion.AssertNotNull(this.image);
        Assertion.Assert(this.framesPerSecond > 0);

        this.polledTime = 0;

        // Reset
        this.currentFrameIndex = 0;
        SetSprite(this.currentFrameIndex);
    }

    private void Update() {
        this.polledTime += UnityEngine.Time.deltaTime;

        // We didn't cache this so we can see the effect of framesPerSecond on the fly like tweaking it in editor
        float timePerFrame = 1.0f / this.framesPerSecond;

        while(this.polledTime > timePerFrame) {
            this.polledTime -= timePerFrame;

            // Show next frame
            this.currentFrameIndex = (this.currentFrameIndex + 1) % this.sprites.Length;
            SetSprite(this.currentFrameIndex);
        }
    }

    private void SetSprite(int index) {
        this.image.sprite = this.sprites[index];
    }

}

This is very straightforward to use. Just specify an Image target, a frame rate, and the sequence of sprites to show. That’s it. It will play on loop. Nothing fancy. I’ve use this on the game over screen of Academia.

GameOver

SelectionSequence

Political Animals had a common UI where you select an item from a finite set of items using next and previous buttons. I’ve noticed that I’m repeating the same code spread across different UI screens. So I made a generic class for this.

public class SelectionSequence<T> {

    private readonly List<T> items = new List<T>();

    private int currentIndex = 0;

    public SelectionSequence(T[] array) {
        this.items.AddRange(array);
        Reset();
    }

    public void Reset() {
        this.currentIndex = 0;
    }

    public void MoveToNext() {
        this.currentIndex = (currentIndex + 1) % this.items.Count;
    }

    public void MoveToPrevious() {
        int decremented = this.currentIndex - 1;
        this.currentIndex = decremented < 0 ? this.items.Count - 1 : decremented;
    }

    public T Current {
        get {
            return this.items[this.currentIndex];
        }
    }

    public void Select(int index) {
        this.currentIndex = index;
    }

    public void Select(T item) {
        for(int i = 0; i < this.items.Count; ++i) {
            if(this.items[i].Equals(item)) {
                Select(i);
                return;
            }
        }

        throw new Exception("Can't find the item to select: " + item.ToString());
    }

    public int Count {
        get {
            return this.items.Count;
        }
    }

    public T GetAt(int index) {
        return this.items[index];
    }

}

// Usage
// Say you have an array of LanguageOption
LanguageOption[] languages = ...;
SelectionSequence<LanguageOption> langSequence = new SelectionSequence<LanguageOption>(languages);

// When next button is clicked
langSequence.MoveToNext();

// When previous button is clicked
langSequence.MoveToPrevious();

// Do something with current selection
ChangeLanguage(langSequence.Current);

This kind of selection have come up in Academia, too.

SelectionExample

These are just some of my cute little classes. They’re small but they have been very useful. Here’s some programming tip, think of making classes like making a new invention or a device. That way, you will think in terms of narrowing functionality as much as possible and expose only appropriate public methods. This will also make your classes more modular. Over time, you will develop a library of robust and reusable code.

Reflection Series – Part 2: Harnessing Properties

One of my favorite features in reflection is the ability to know about the properties of a certain instance at runtime. This has helped a lot in making tools to aid game development.

Basics

The following code is the simplest way to iterate through the properties of an instance:

// Let's say there's some instance named myInstance
Type type = typeof(myInstance);

// Get the properties that are public and not static
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

foreach(PropertyInfo property in properties) {
    // Do something with property...
}

PropertyInfo contains the information about a property. You can get its getter and setter methods. You can invoke them. You can get its return type. You can query the attributes attached to the property. You will know a lot (just head over to its API reference).

Example Usage

One utility class that I’ve made is a generic XML writer and loader that can accept any instance and write the instance’s public properties to an XML. This way, I don’t have to make a custom XML writer code for each type of data. I can just use this class and tell it to write the instance.

I don’t write all public properties. I only select those with public getter and setter. The following is a utility method if such property is meant to be written:

public static bool IsVariableProperty(PropertyInfo property) {
    // should be writable and readable
    if(!(property.CanRead && property.CanWrite)) {
        return false;
    }

    // methods should be public
    MethodInfo getMethod = property.GetGetMethod(false);
    if(getMethod == null) {
        return false;
    }

    MethodInfo setMethod = property.GetSetMethod(false);
    if(setMethod == null) {
        return false;
    }

    return true;
}

This is how I built the writer class (showing only the important parts):

class InstanceWriter {

    private readonly Type type;
    private PropertyInfo[] properties;

    // A common delegate for writing a property of each type
    private delegate void PropertyWriter(XmlWriter writer, PropertyInfo property, object instance);
    private Dictionary<Type, PropertyWriter> attributeWriterMap = new Dictionary<Type, PropertyWriter>();

    // Constructor
    public InstanceWriter(Type type) {
        this.type = type;
        this.properties = this.type.GetProperties(BindingFlags.Public | BindingFlags.Instance); // Cached

        // Populate map of writers
        this.attributeWriterMap[typeof(string)] = WriteAsAttribute;
        this.attributeWriterMap[typeof(int)] = WriteAsAttribute;
        this.attributeWriterMap[typeof(float)] = WriteAsAttribute;
        this.attributeWriterMap[typeof(bool)] = WriteAsAttribute;
        // ... More types can be added here if needed
    }

    // Writes the specified instance to the writer
    public void Write(XmlWriter writer, object instance) {
        writer.WriteStartElement(this.type.Name);

        // Traverse properties
        foreach (PropertyInfo property in this.properties) {
            if (!TypeUtils.IsVariableProperty(property)) {
                // Not a candidate to be written
                continue;
            }

            // Must have a writer
            PropertyWriter propWriter = null;
            if (this.attributeWriterMap.TryGetValue(property.PropertyType, out propWriter)) {
                // Invokes the property writer
                propWriter(writer, property, instance);
            }
        }

        writer.WriteEndElement();
    }

    // Writer methods. There are more of these to support the types you need to support.
    private static void WriteAsAttribute(XmlWriter writer, PropertyInfo property, object instance) {
        // Invoking the getter using reflection
        object value = property.GetGetMethod().Invoke(instance, null);
        if (value != null) {
            writer.WriteAttributeString(property.Name, value.ToString());
        }
    }

}

This class just maintains a dictionary of writer methods mapped by Type. During traversal of properties, it checks if a property needs to be written and that it has a mapped writer method. PropertyInfo.PropertyType was used to get the type of the property. It then proceeds to invoke that writer method which writes the value to the XmlWriter.

The actual class is more elaborate than this. There’s a separate map for property writers that needs child elements. For example, Vector3 needs a child element and its xyz values are stored as attributes. We also took it further by checking if the property implements a custom interface of ours named IXmlSerializable and invokes the writer method of that property.

This is then how it is used:

InstanceWriter writer = new InstanceWriter(typeof(MyClass));
writer.Write(xmlWriter, myClassInstance); // Writes a whole element representing MyClass

My XmlLoader was made using the same concept only that the mapped methods now invoke the setter of the property. This is one of the loader methods:

private static void LoadString(SimpleXmlNode node, PropertyInfo property, object instance) {
    if(!node.HasAttribute(property.Name)) {
        // No value from XML. Set a default value.
        SetDefault(property, instance);
        return;
    }

    string value = node.GetAttribute(property.Name);
    property.GetSetMethod().Invoke(instance, new object[] { value });
}

Other Uses

I’ve made a generic data Unity editor that can support any type of data class as long as they expose the editable variables in the form of properties. As you can see in my other posts, most of our data editors have the same look. That’s because we are using the same base code for the editor and provide the custom rendering when necessary.

CharacterSpritesEditor
One of our generic data editors

In part 1 of this series where I talked about loading instances from mere string, we are also using properties manipulation to expose the variables of such instances in the editor. The following is a screenshot from our GOAP data editor.

GoapVariables

All of these are possible because of reflection.

Render Two Sprites in One Shader

Most of the character faces in Academia are generated in a procedural way. Each character has its own combination of face and head. Heads can either be hair or construction hat if they are workers. Both faces and heads are contained in a single texture so Unity may apply batching.

FaceHair
Part of the faces and heads texture

To generate a character, you simply render a face and render its head on top of it.

Characters

This can be done with two sprites. However, there are disadvantages. First, more draw calls. Second, it looks weird when the characters are overlapping because of z fighting. I needed a way to render both the face and head in only one sprite. This reduces draw calls and prevents weird z fighting. Fortunately, this is easy enough to do using a custom shader.

While working on our custom quad mesh where the characters would be displayed on, I realized that vertices can have more than one UV. This means that I can use the first UV for the face and use a second UV to draw the head. These two renders can be done within a single shader.

Shader "Common/TwoUvsLayeredTexture"
{
	Properties
	{
		_Texture("Main Texture", 2D) = "white" {}
	}

	SubShader
	{
		Tags{ "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
		ZWrite Off Lighting Off Cull Off Fog{ Mode Off } Blend SrcAlpha OneMinusSrcAlpha
		LOD 110

		Pass
		{
			CGPROGRAM
			#pragma vertex vert_vct
			#pragma fragment frag_mult 
			#pragma fragmentoption ARB_precision_hint_fastest
			#include "UnityCG.cginc"

			sampler2D _Texture;

			struct vin_vct
			{
				float4 vertex : POSITION;
				float4 color : COLOR;
				float2 texcoord0 : TEXCOORD0;
				float2 texcoord1 : TEXCOORD1;
			};

			struct v2f_vct
			{
				float4 vertex : SV_POSITION;
				fixed4 color : COLOR;
				float2 texcoord0 : TEXCOORD0;
				float2 texcoord1 : TEXCOORD1;
			};

			v2f_vct vert_vct(vin_vct v)
			{
				v2f_vct o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				o.color = v.color;
				o.texcoord0 = v.texcoord0;
				o.texcoord1 = v.texcoord1;
				return o;
			}

			fixed4 frag_mult(v2f_vct i) : SV_Target
			{
				fixed4 col1 = tex2D(_Texture, i.texcoord0) * i.color;
				fixed4 col2 = tex2D(_Texture, i.texcoord1) * i.color;

				fixed4 output;
				output.rgb = (col1.rgb * (1.0f - col2.a)) + (col2.rgb * col2.a);
				output.a = min(col1.a + col2.a, 1.0f);
				return output;
			}

			ENDCG
		}
	}
}

The magic is in frag_mult(v2f_vct i). Actually, I’m not too sure if that’s the proper way to blend two images. I’m not very good with shaders. I arrived with that using trial and error. Let me know if there’s a better way.

[Edit] Some people pointed out that it’s generally not ok to have conditionals in shader code. Thus, I’ve updated the code with a better one. This is why I blog.

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!