• Register

Currently developing GearBlocks. I guess you could say I'm an industry veteran (in other words, I'm old ;)). After having worked as a programmer in "AAA" games for around 15 years, I decided to try an experiment, could I develop and release my own game? Jury's still out on that, but things are going pretty well so far.

RSS My Blogs

Well, it's been a while, so time for a progress update I think! The material tool is now done, and I'll show it in action very soon, so watch out for that. Most of my time however has been occupied with a massive code re-architecture effort, and that's what I'm going to go over in this update.

From a high level perspective the GearBlocks game code is quite well laid out in terms of separating various subsystems (e.g audio, graphics, player, UI, etc.) via namespaces and so on. However, there was still a lot of code coupling (i.e. direct dependencies) between areas of the game code that should really be completely independent. This made it impossible to reuse or test parts of the code independently, and it was only going to get worse as development progressed.

ScriptableObjects to the Rescue

I'd been using ScriptableObjects in Unity for a long time, but only in a select few cases as data containers, I certainly hadn't been using them to their full potential.

I watched these two excellent presentations a while back:-

Ever since, I'd been wanting to adapt the ideas presented in these talks to the game to improve the code architecture, and so I finally decided to take the plunge. This was a huge endeavour, but well worth it I think.

ScriptableObject Events

Previously I was using Unity's ExecuteEvents system as the basis for events in the game. This was helpful for code decoupling, however it still had some disadvantages:-

  • In order to add a new event, a new interface has to be written (derived from IEventSystemHandler), and then implemented in all the MonoBehaviours that need to receive the event.
  • It's necessary to explicitly call ExecuteEvents.Execute() on every GameObject with MonoBehaviours that need to receive the event. To me, this makes ExecuteEvents more like messages than true events, but perhaps that's just semantics.
  • Only MonoBehaviours on GameObjects can receive these events, ScriptableObjects can not.

So I replaced these with a new system, where each event is now a ScriptableObject asset. Here's a simplified version of the code:-

public class EventAsset : ScriptableObject
{
    public delegate void EventHandler();
    public event EventHandler Handler = null;

    public void Raise()
    {
        if( Handler != null )
        {
            Handler();
        }
    }
}

The real implementation is slightly more complex, but follows the same principle. It's implemented using C# generics to allow for different event argument types, and has support for logging and listing the current event subscribers. This is used by a custom editor I wrote to display this info while the game is running in the Unity editor, here's an example of it in action:-

To use an event it can simply be assigned to a variable in the Unity inspector, then to receive it, just subscribe to Handler:-

public class Receiver : MonoBehaviour
{
    [SerializeField] EventAsset somethingHappened;

    EventAsset.EventHandler onSomethingHappened;

    void OnEnable()
    {
        onSomethingHappened = () => { Debug.Log( "I hear that something happened!" ); };
        somethingHappened.Handler += onSomethingHappened;
    }

    void OnDisable()
    {
        somethingHappened.Handler -= onSomethingHappened;
    }
}

Or to raise the event, just call Raise() on the event:-

public class Sender : MonoBehaviour
{
    [SerializeField] EventAsset somethingHappened;

    void SomethingHappened()
    {
        Debug.Log( "Something happened, telling everyone!" );
        somethingHappened.Raise();
    }
}

This setup has some useful advantages over the old ExecuteEvents system:-

  • No need to write any code to add a new event, just create a new event asset and assigned it in the inspector where needed.
  • No need to explicitly refer to specific GameObjects to send the event.
    Don't even need to be using GameObjects, these events can be used by ScriptableObjects as well as MonoBehaviours.
  • The events are more easily debuggable via the custom editor.

ScriptableObject Variables

Events aren't always the most appropriate pattern for sharing data between subsystems, for example sometimes it's necessary to store a value somewhere and allow it to be read a later point, perhaps continuously polling it to watch as it changes.

Previously I was doing this by having my subsystems be singletons, and then directly reading / writing properties in them where needed, thereby tightly coupling different areas of the code together, not good! To solve this I made a new "variable" system, where each variable is a ScriptableObject asset. Whereas events can be thought of as radio broadcasts, the variable system is conceptually more like a noticeboard (with each variable being a notice pinned to the board).

Here's a simplified version of the code, it's implemented as a generic class to allow for different variable types:-

public abstract class VariableAssetBase<T> : ScriptableObject
{
    [SerializeField] T value;

    public T Value { set { this.value = value; } }

    public static implicit operator T( VariableAssetBase<T> variableAsset )
    {
        return variableAsset.value;
    }
}

For example, a bool variable type:-

public class BoolVariableAsset : VariableAssetBase<bool>
{
}

Again, the real code has a bit more going on. It has an event delegate that code can subscribe to, in order to be notified when the variable value is assigned to (this saves having to use a separate event for this). It also has support for serialisation so that I can use these variables for things like game settings (e.g. controls, gameplay, video) and allow the player to save / load them. Plus I made a custom editor that allows variable values to be viewed or even modified while the game is running in the Unity editor. At some point I might implement a debug console that would allow this to be done even in standalone builds, which would be super cool!

To use a variable it can be assigned in the inspector, then written to / read from. Notice that Assigner and Watcher in this example are completely independent of one another:-

public class Assigner : MonoBehaviour
{
    [SerializeField] BoolVariableAsset isThingTrueVar;

    void ThingBecomesTrue()
    {
        isThingTrueVar.Value = true;
    }
}

public class Watcher : MonoBehaviour
{
    [SerializeField] BoolVariableAsset isThingTrueVar;

    void Update()
    {
        PollThingTruthiness();
    }
	
    void PollThingTruthiness()
    {
        Debug.Log( "Thing is currently " + isThingTrueVar );
    }
}

I replaced data in my subsystems that needed to be shared with these new ScriptableObject variables. This allowed me to remove a lot of code dependencies, and eliminate the need for singleton references in most cases.

One example being the UI overlay that displays the player's speed, acceleration, and altitude. It now just reads variables for these values and displays them, completely independently of the player code that updates them.

ScriptableObject Dictionaries

There's one slight wrinkle with the ScriptableObject variable system, in that there is only one global instance of each variable. For example, sometimes I need one instance of a variable per player (in multi-player games). To solve this I implemented a simple ScriptableObject dictionary, here's the implementation pretty much in full:-

public abstract class DictionaryAssetBase<TKey, TValue> : ScriptableObject
{
    Dictionary<TKey, TValue> dictionary = null;

    void OnDisable()
    {
        if( dictionary != null )
        {
            dictionary.Clear();
        }
    }

    public TValue this[TKey key]
    {
        get
        {
            if( dictionary != null )
            {
                TValue value;
                if( dictionary.TryGetValue( key, out value ) )
                {
                    return value;
                }
            }

            return default(TValue);
        }

        set
        {
            if( dictionary == null )
            {
                dictionary = new Dictionary<TKey, TValue>();
            }

            dictionary[key] = value;
        }
    }
}

Then for example, a dictionary with byte keys and bool values:-

public class ByteBoolDictionaryAsset : DictionaryAssetBase<byte, bool>
{
}

The only part I left out here is some code for listing the entries currently in the dictionary, used by another custom editor I added for debugging while the game is running in the Unity editor.

A dictionary is used in much the same way as a ScriptableObject variable:-

public class Assigner : MonoBehaviour
{
    [SerializeField] byte thisPlayersID;
    [SerializeField] ByteBoolDictionaryAsset isThingAboutPlayerTrueVar;

    void PlayerThingBecomesTrue()
    {
        isThingAboutPlayerTrueVar[thisPlayersID] = true;
    }
}

public class Watcher : MonoBehaviour
{
    [SerializeField] byte thisPlayersID;
    [SerializeField] ByteBoolDictionaryAsset isThingAboutPlayerTrueVar;

    void Update()
    {
        PollPlayerThingTruthiness();
    }

    void PollPlayerThingTruthiness()
    {
        Debug.Log( "Thing is currently " + isThingAboutPlayerTrueVar[thisPlayersID] + ", about player with ID: " + thisPlayersID );
    }
}

Replacing Singletons

The game has many self contained code modules providing utilities and functionality used by other parts of the code. Previously these were either static classes or singleton MonoBehaviours, both having their disadvantages:-

  • Static classes can't have variables serialized by Unity or edited in the inspector.
  • Singleton MonoBehaviours need to live on a GameObject somewhere in the scene (or at least in a prefab).

So now I've re-implemented most of these as ScriptableObjects which have neither of these downsides. They work well with the new ScriptableObject events too, these modules being able subscribe to or raise events, which helps with code decoupling.

Other Uses of ScriptableObjects

I found many more places to use ScriptableObjects, far too many to go over in detail now, but here's a brief summary of a few of them:-

  • Added ScriptableObject "delegate objects", making use of the strategy pattern where different variations on a theme implement a common interface. For example I use this for the procedural generation code for the various different re-sizable parts in the game.
  • Replaced some enums with ScriptableObject assets.
  • Implemented ScriptableObject data assets with built in functionality for better separation of concerns. For example, I implemented a "sound asset" ScriptableObject that handles random AudioClip selection and playback, and then created a whole bunch of these assets for all the sounds in the game.
Damage is done

Damage is done

dangersam Blog
Start a group Groups
Unity Games

Unity Games

1,794 members Hobbies & Interests

For all Unity developers and developers-to-be, both beginners and professionals!

Indie Devs

Indie Devs

1,644 members Hobbies & Interests

A group dedicated to indie and standalone game development.

Blenderheads

Blenderheads

572 members Arts & Literature

For us folks who like to stay Free and use the Blender 3d program over every other costly options!

SmashHammer Games

SmashHammer Games

1 member Developer & Publisher

Indie developer of GearBlocks - a game about creative building, interactive physics based machines, and gears - lots of gears.

Comments
clashka43
clashka43

I really would like to add something to the game. For example, new part) Just how to do it, I don't know(

Reply Good karma Bad karma+1 vote
clashka43
clashka43

Hi sam! I very like you game)

Reply Good karma Bad karma+1 vote
Post a comment
Sign in or join with:

Only registered members can share their thoughts. So come on! Join the community today (totally free - or sign in with your social account on the right) and join in the conversation.

Level
Avatar
Avatar
Last Online
Country
Canada 🇨🇦
Gender
Male
Friends
Become friends
Member watch
Follow
Statistics
Rank
7,105 of 681,607
Visitors
5,572 (2 today)
Time Online
2 days
Activity Points
472
Watchers
2 members
Comments
110
Tags
3
Site visits
2,468
Contact
Contact
Send Message
Twitter

Latest tweets from @dangersamn

Material tool test in #GearBlocks Youtu.be #gamedev #indiedev #madewithunity

Feb 13 2019

ScriptableObjects For Fun and Profit - Well, it’s been a while, so time for a progress update I think!... Tmblr.co

Feb 12 2019

New material tool, and a new year! - Hey all, hope everyone has had a good holiday break.  I thought I’d... Tmblr.co

Dec 31 2018

📹 Damage is done Well, it took me long enough, but finally the damage system is complete!  Most of the... Tmblr.co

Dec 6 2018

#GearBlocks: Rapid Unscheduled Disassemblies Youtu.be #gamedev #indiedev #madewithunity

Dec 5 2018

Yet more optimisations - OK, it seems I spoke too soon when I said in the last blog post that I was done... Tmblr.co

Nov 19 2018

Networking and optimisations - Sorry for the lack of updates for the past couple of months, I was away... Tmblr.co

Oct 22 2018

Arrived at long last. Awesome to see your name on the cover, @kenpex, congrats! #realtimerendering T.co

Sep 19 2018

Friends
uberblah Online