Wizard Jam 5 Post-Mortem

Two weeks?!

It has been a while since I participated in a game jam.  All of the game jams I have been a part of are generally 24-48 hour jams where the result is little more than a hacked together demo of mechanics.  Normally, this ends in a poorly polished game and is more so an exercise in conveying your "general idea" rather than producing anything of quality.  For that reason, I generally stray from participating because I have a tough time leaving a concept unrealized if there is potential.

Wizard Jam is a little different in that regard. The prompt is simple: take an Idle Thumbs podcast title and spin it into a game. With a two week time limit and an idea of the prompt well in advance, it caters to my slow and deliberate approach to making games.  The week before the jam started, James and I brainstormed what kind of game we wanted to make.  We ended up picking the most straightforward title and see what we could do to expand past the obvious game from that prompt.  We decided on Kill the Last Alien, a game where instead of killing aliens in the traditional manner, a set of 4 friends try to kill the last alien mochi at dinner.

Overall, this was one of the most powerful and formative experiences I've had in my game dev career.  The community was extremely supportive and the breadth of games created from the list of podcasts was refreshing.  In addition to our game, some others you may check out include Nick Breckons: 1, Mackle: more and The Wizard. For the rest of this post, I'd like to dig into a section of our game code I was fond of and will end up reusing in the future: A simple Event Manager class that uses Unity's Action/Event system to keep dependencies low in the scene.

Event Manager's Purpose

The Event Manager itself is a class that is responsible for storing any events that an object may trigger, and then invoking it when the time comes for it to fire. This class is important because it organizes the various UnityActions/Events that can exist in the scene.  It also allows multiple UnityActions to associate with the same event.  This means, when one event is invoked, it can trigger all corresponding Actions across various objects at the same time.

The main logic is comprised of three main methods: StartListening(), StopListening(), and TriggerEvent(). It also contains some initialization logic to create dictionaries where the lists of different events will be stored during runtime.  Below, I'll dive into each of the three main methods and explain their purpose.

StartListening()

public static void StartListening(string eventName, UnityAction listener) {
        UnityEvent thisEvent = null;
        if(instance.eventDictionary.TryGetValue(eventName, out thisEvent)) {
            thisEvent.AddListener(listener);
        } else {
            thisEvent = new UnityEvent();
            thisEvent.AddListener(listener);
            instance.eventDictionary.Add(eventName, thisEvent);
        }
}

The first method StartListening() is called by a game object during its OnEnable method.  This means every time a game object becomes enabled, it will call this function for each event it's interested in.  The two parameters that are entered are a string corresponding to what this particular event is called game-wide. For example, "RoundReset" is an event that is called at the beginning of each round in Kill the Last Alien.  Since the player needs to know when the round is reset, it *starts listening* to it when the game starts.

The second parameter is a UnityAction, or what is known as a delegate.  This is a lot like a variable, however instead of some number or value being stored, it's refers to a function that is called.  Some more information on delegates can be found here if additional context is necessary. Using the player as an example once more, when it starts listening to "RoundReset", it passes a reference to the function ResetRound(), where it then handles all the logic necessary to prepare for the next round.  

As you can see in the code above, the EventManager class checks its dictionary of existing events to see if the string passed through is already a key. If so, it attaches the reference to that delegate to the event.  If not, it creates a new Event, attaches the delegate to it and adds it to the dictionary instead. This will be important information later, when we invoke the event so remember this!

Altogether, this call in OnEnable() for the player looks like this:

void OnEnable() {
    EventManager.StartListening("RoundReset", ResetRound);
}
void ResetRound() {
        hasMochi = false;
        ResetPlayerVariables();
}
void ResetPlayerVariables() {
        reachedMochi = false;
        selection = null;
}

With this event set up, anytime the "RoundReset" event is triggered, the player object will call the ResetRound() function. Pretty neat!

StopListening()

public static void StopListening(string eventName, UnityAction listener) {
        if (eventManager == null) return;

        UnityEvent thisEvent = null;
        if(instance.eventDictionary.TryGetValue(eventName, out thisEvent)) {
            thisEvent.RemoveListener(listener);
        }
}

The StopListening() function is essentially a way to clean up the EventManager class when objects are disabled.  In the corresponding object's OnDisable() function, a call to StopListening() with the same parameters as its StartListening() counterpart is made to ensure there's nothing listening that doesn't need to be.  Not much else left to say about this one, which leads us to number three....

TriggerEvent()

public static void TriggerEvent(string eventName) {
        UnityEvent thisEvent = null;
        if(instance.eventDictionary.TryGetValue(eventName, out thisEvent)) {
            thisEvent.Invoke();
        }
}

This last function is what actually calls these events we've been listening to in our other objects.  Remember the listeners we were adding back in StartListening()?  Now's their time to shine.

Sticking with the example from above, when the game logic reaches the end of a round, the TriggerEvent() function is called by the GameManager class with the "RoundReset" passed in as the parameter.  The EventManager then looks in the eventDictionary for the corresponding UnityEvent and "invokes" it.  Invoke is what Unity uses to look for all of those delegates we added to begin with and call their corresponding logic in the object that owns them.  

The end result of this invoke call for our "RoundReset" is that for each Player object in our scene that is currently enabled and listening will run the logic we've written in our ResetRound() function.  The benefit to this approach is now whenever there's a change to what happens at the end of a round, the logic in the Player's ResetRound() is the only thing that needs to change.  This also allows for changes to a specific object's reaction to a round ending without interfering with other objects that may want to keep their behavior the same.

Additional features

So far, this class can only handle events that don't accept any additional parameters.  The final version of my EventManager class expands on the basic use case and adds functionality for delegates that accept parameters as well such as player ids.  Since that is beyond the scope of what I wanted to touch on with this blog post, I'd encourage readers to take a look at the whole source code here.  I'd also be willing to explain in more detail if you shoot me a message on any of my social media platforms found on my site here.

Unity allows for UnityActions/Delegates with a number of parameters of any basic variable type and this EventManager can be written to address any and all combinations of necessary delegate structures.  For our jam game, I only included the delegate types I needed and actually didn't use the bool set of delgates after all as we refactored over the course of two weeks.  

Wrapping up

Wizard Jam 5 was a blast.  I think a jam of this nature was one of THE best ways to gain some chops and get a tremendous confidence boost in my skills as a developer.  I would absolutely encourage all developers to try this one out even if you're normally adverse to jams.  The extended time period for development and supportive community truly give it a different jam vibe.  The Wizard Jam happens every six months, so expect to see the next one sometime towards the end of 2017.  If you'd like to check out our jam game or any of my other stuff you can find em on my itch.io page and the code for everything on github.  Thanks for reading!