In case you haven’t seen it already, the first video dev log for Electric Noir is up on my YouTube channel. It covers how Electric Noir got started, all the prototypes I worked through and the currents state of the game. Give it a watch and let me know what you think in the comments.

I plan on creating video dev logs regularly, though I haven’t decided on how often that will be. Written blog posts like this one will be used to show off more achievements that are more code oriented, or more deeply cover game mechanics.

Grappling and Throwing

Since the dev log video I’ve added grappling and throws to Electric Noir. The player can grab an enemy by pressing RB and once they are grapppled, the player can press B to throw them.

The throws follow a set of bezier curves that are stored at edit time and evaluated during play time. The throws can be configured, including changing the throw distance, the duration, and the number of bounces right in the unity editor.

Combo Chaining

The next big addition to the game is combo chaining. The player and enemies can now execute attack combos - like a three punch combo or a heavy attack followed by a ground smash. Combos are basically a very simple sort of tree that gets evaluated at play-time.

The next move the player or AI executes depends on what their current attack is and which various ComboCondition objects are met. A ComboCondition is a ScriptableObject that is created within unity and can be a check for almost anything - have you unlocked a specific move, what input are you pressing, what level are you, are you near a dead enemy, etc.

I’m happy with this sytem and I think it will allow me to create a lot of different combos with the minimum of effort.

Deferred Logging

One bug that I had to overcome was in the initialization part of the game - the stuff that spins up so that the rest of the game can work: Config, Logging etc. This is all in the GameContext object and the problem was that I made some changes along the way that made most objects throw an exception when retrieving a configuration setting or creating a logging instance. This basically meant that nothing worked when the game loaded :(

This forced me to rethink how logging objects are initialized within the game objects themselves. Before there was a tight coupling between the gameobjects and the GameContext, which is why the exceptions were happening if the GameContext couldn’t load (or wasn’t ready in time).

So I crafted a DeferredNoirLogger class that acts as a pass-through for logging calls. Each game object now instantiates this deferred logger and makes logging calls as before. Under the hood, the deferred logger launches a coroutine if it detects that the GameContext is not ready. While it is waiting for a valid context it captures all the logging calls made to it and stores them in a queue. Once the GameContext is loaded and ready, a internal logger instance is created and the queue is flushed to it. From that point on logging calls flow through to the internal logger instance.

I’ve included an example of the code below.

public class DeferredNoirLogger<T> : INoirLogger {
    private readonly NoirBehaviour<T> _context;

    private INoirLogger _internalLogger;

    private readonly List<Action<INoirLogger>> _internalLogCallQueue = new List<Action<INoirLogger>>();

    public DeferredNoirLogger(NoirBehaviour<T> context) {
        _context = context;
    }

    public bool Enabled { get; set; }

    public LogLevel Level { get; set; }

    public IEnumerator CoWaitForGameInitialization() {
        var context = GameObject.FindObjectOfType<NoirGameContext>();
        var wait = 1f;
        while(context == null) {
            yield return new WaitForSeconds(wait);
            wait += 0.1f;
            context = GameObject.FindObjectOfType<NoirGameContext>();
        }
        _internalLogger = context.CreateLogger(typeof(T));

        foreach(var call in _internalLogCallQueue) {
            call(_internalLogger);
            yield return null;
        }
        _internalLogCallQueue.Clear();
    }

    public void Debug(string message) {
        if (!_context.LoggingEnabled) return;
        if ((int)_context.LoggingLevel < (int)LogLevel.Debug) return;
        if (_internalLogger == null) {
            _internalLogCallQueue.Add((l) => l.Debug(message));
            return;
        }
        _internalLogger.Debug(message);
    }

    // ... more calls like Debug() for all interface methods
}

This immediately fixed my issues with exceptions on game startup. Getting game configuration within gameobject Awake() methods is still an issue however. As a workaround I’ve moved all the code that depends on configuration into Start() for the gameobjects that need it. This should be good enough for a while and I really hope I won’t have to craft some kind of deferred configuration system.

So that’s a quick-ish update on all the things that I’ve been working on. I’m working on another dev log video, which will cover the first alpha that I created, including the feedback recieved and changes I made coming out of it. If you haven’t already follow the game twitter account, subscribe to the youtube channel and check back here for more updates on Electric Noir!