Categories
Game Development

Just Adobe Being Adobe

This week I dealt with an issue that made me want to write about the times software has proven me wrong in some way, either to make me realize it was better than I thought, or (perhaps more often) the other way around. Sometimes, this broken trust isn’t the fault of the developer. Big corporations buying things usually spells dystopian doom for a good product, and I’ve come to expect that. When companies like Microsoft or Adobe get involved, maybe it’s just time to leave. And on the other side of the table, sometimes it’s worth giving smaller players you never considered before a chance. They may truly impress you. (And then, of course, you just hope they don’t become yet another company absorbed by one of the tech giants)

In this post, I’m going to write about the issue in question, but in future posts I’d like to write about some other (hopefully more positive) stories.

Substance

A few years back, I found a piece of software called Substance Painter, which I eagerly included into my 3D workflow, because it truly is an excellent texture painting program. I also picked up the companion program Substance Designer at the time, and I purchased both of them via Steam. I appreciated the fact that you could sign up on their website, link your steam account, and download a standalone copy of their software, with a perpetual license for that version. As nice as it can be to install software via Steam, sometimes it’s nice to be able to minimize distractions, and there’s also the notorious issue that steam will only allow you to have any game or software open on one computer at a time (even free software, strangely). As such, this standalone version was pretty important to me.

And as great of software as Substance made, I’m here to explain how Adobe instantly erased all of the good will that the developers of Substance built over the years.

If you know anything about Adobe, you probably wonder why I even made the mistake of trusting any of their software to remain reasonably priced. And the answer to that is that I, unfortunately, gave them the benefit of the doubt. I’ve since learned my lesson.

2021 Edition Lies

This year, Adobe released the 2021 edition of Substance, and I actually pre-ordered it because I had owned both the 2019 and 2020 editions previously, and I really liked it. I figured 2021 would bring only good things for Substance. A bit wary of Adobe, I double checked the community posts to confirm that the 2021 edition would still offer a standalone version. Everybody said it would.

January 28th rolled around and as before, I tried to sync my Steam account to update my standalone perpetual licenses to include 2021. Only this time their site threw errors and wouldn’t let me update the license. No big deal, release days often have bugs like this. I’d check back later on to update my license.

A few days into March I decided to check back, and the site was still giving the same unhelpful error, telling me I had no products to sync. This time I emailed Substance, and here’s the conversation:

Hi, I purchased the 2021 editions of both Substance Painter and Designer on Steam at the end of last year, and they’ve been working great through steam, however I haven’t been able to get the standalone licenses working. Looking in my account (under same email as I’m sending from) when I attempt to sync the licenses from Steam, I get a message saying “Could not find any product to sync”, and the end-of-maintenance date does not update on the licenses I have. My profile is set to public, and I do have both of those in my library. Is there anything you guys can do from your end to help me get this working? Thanks for the help, and let me know if you need any additional information.

Also FYI, I’m emailing directly instead of using the contact form on the site because it looks like I can’t submit the form after filling it out. It scrolls part way up the page as if it was indicating a form validation error, but there’s no error messages visible, and all of the fields are filled out, so I’m not really sure what that issue is about either. I’m using the latest stable version of Firefox if that helps. Thanks again!

Me

Hi Alex, Thanks for reaching out but unfortunately since the end of February you are no longer able to sync your Steam license to your Substance profile. The page will remain up for a few weeks while our web team disables the backend feature. I apologize for the confusion.

Jeffrey – Customer Success Manager

First of all, it seems like their web team is woefully understaffed. Not only has their contact form been broken for months, but they couldn’t even coordinate the removal of the button to sync your steam account? And the explanation is a little odd. Why can’t a frontend button be removed before a backend feature? And given the error, it would appear the backend feature has been disabled for at least a couple of months now. If I was willing to dig in deeper I might call shenanigans and say that the presence of that button has been a deliberate attempt to deceive existing users into thinking their licenses would continue being supported. But I digress.

At this point I was a bit concerned. But since I purchased the software in December, I figured, like every other software company I’d ever dealt with before, they’d be willing to honor the license at the time of purchase. So that’s what I asked for.

Okay, no worries. Is there not another way to get a standalone license for this product without purchasing a second license on the website? I purchased this via steam under the expectation that I would be able to do that as I have in the past. If that isn’t the case, I would have purchased the stand alone license instead of the steam one. Also to note, I did purchase this in December and attempted to sync in January, so if this is a recent change, I feel I ought to still be eligible for the license as it was at the time I purchased it. Thanks again for the help!

Me

Hi Alex, The only way to obtain a perpetual license is via Steam. I apologize but since the end of February we can no longer manually sync them internally either as that system has not been disabled.

Jeffrey – Customer Success Manager

Getting some repetitive answers by now, and it’s also become abundantly obvious that they have no intention of helping me at all. Since their website has literally been broken for months though, I figure, perhaps they just need to catch up before bringing the functionality back in the future.

Is there any chance of seeing perpetual licenses (standalone, not via steam) return in the future, or does this mean Substance is moving to subscription-only going forward?

Me

Alex, Substance will be subscription based moving forward.

Jeffrey – Customer Success Manager

Ah, good old Adobe. I should have known what to expect. I gave them the benefit of the doubt, and I regret that so much. So what I’ve got so far is: (1) Adobe will not honor the license I had at the time of purchase, and instead they are revoking access to software that I paid for, and (2) the software cannot be purchased ever again, as it will only be available on a rental basis. Again, this is expected behavior from Adobe. This is how all of their software works. What I didn’t expect from Adobe, however, was the blatant willingness to spit in their customers faces shortly after releasing a new version. I expected to have my 2021 license honored, and the 2022 edition would introduce a new licensing scheme. But in the end Adobe had more contempt for their customers than I had ever expected.

And so at this point I gave up. I figured I’d let the guy know how I felt, and try to move on.

Fair enough. I’m not really surprised, I just never expected this to happen in such a way as to leave us high and dry immediately after preordering an upgrade. It’s super disappointing. What can I do to get a refund for my purchase?

Me

Alexander, In this case since the purchase was made on Steam you will need to contact them immediately to advise if a refund is eligible for the product. Cheers,

Jeffrey – Customer Success Manager

Of course. By all means don’t go out of your way to help me. Just tell me to deal with another company. Well despite the fact that I literally have a combined total of 4 minutes of time on both of these apps in steam, when I pleaded by case and requested a refund, they of course rejected it, because it’s been more than two weeks since I purchased it. I’m 100% certain that they could grant my refund themselves, as I’ve seen other developers do. But unlike most indie developers, Adobe represents all of the worst parts of the software industry, and you can damn well count on them to do nothing to help you out when it comes down to it.

And that will, without a doubt, be the last time I make the mistake of trusting Adobe.

Moving On

Oh well, if I can’t get a refund, at least I’ve got some decent texture painting software to use in the future, right?

Well of course, the problem with that comes down to standardization. In making assets for games, I like the files generated by my workflow to be files that I can continue to rely on in the future, and continue to iterate with. But seeing as I know I will never own another edition of Substance in the future, it makes little sense to make anything with it now, knowing that I will just be using other tools down the line.

And for those wanting some recommendations to replace Substance in your own workflows (and probably for the same reason), there’s some amazing software out there.

For one, Blender has decent texture painting built in. If you want to upgrade the texture painting functionality to be on par with Substance (and all without having to leave your modeling software!) you can purchase BPainter for a fraction of the cost of Substance. And you’ll be supporting a developer who actually stands behind their product. And, if you prefer a standalone tool, you can buy ArmorPaint, once again for a fraction of the cost, and you’ll be supporting open source software.

TL;DR

For me personally, the Blender/BPainter combo will be my main workflow going forward. You should do whatever works for you, all I ask is that you please, please stop supporting companies like Adobe. Substance is a good piece of software, but I wouldn’t recommend any individual developers rely on it anymore. It now exists more in the space of rental-only corporate-targeted software that exists as an industry standard solely because larger studios have bottomless pockets and prefer to throw money at problems than find creative solutions. And that type of software is precisely what Adobe does best.

Categories
Game Development Game Jams Software Development

Ludum Dare 41 Postmortem

Ludum Dare 41 came to an end just a little more than a week ago. I believe I’ve finally recovered from the mayhem and come through the better in the end.

Project Status

Unfortunately, my Ludum Dare 41 project, a “turn-based AI battle-royale game” called “Turn-by-Turn Deathmatch” was not finished when the 72-hour deadline hit. This was, unfortunately, my expectation for this one. I may continue working on the game in the future, but I am particularly averse to the theme and not satisfied with the idea that I was able to come up with.

The Theme

My struggle for this Ludum Dare originated at the outset of the theme announcement. The theme for 41 was “combine two incompatible genres.” I know a lot of people had differing opinions on this, but it is mine that it is the worst theme yet. For those of us that are relatively new to the game-design scene, the theme is asking us to throw out any convention of design that we may have relied upon to make a game that is not a complete flop. For those more experienced, I will grant that it does provide a unique challenge, and for that reason I was almost somewhat conflicted. That said, it is my opinion that Ludum Dare should be welcoming to new developers, as Ludum Dare is often some of the first exposures to the indie game dev scene that they will get. It should be a good impression. I believe this theme, by significantly raising the difficulty of creating a workable concept, likely created a bad first experience for them.

I created a very large, very time-costly spreadsheet of an incomplete list of game genres in an adjacency-martrix-like spreadsheet, which ranked the combination of any two genres as “good,” “neutral,” or “bad” based on how well it fit the theme, which is to say that a “good” combination was two genres that one would not expect to see combined. Even with this chart, I found myself struggling to come up with a theme, because as it turned out, many of the “good” combinations sounded like awful games, or they required some complexity that I knew would take longer than the deadline. For example, I really wanted to do a “first-person platformer,” which would be a 3D game where you basically see something like Super Mario from Mario’s eyes. It’s been done before, and I recall positive reception. Unfortunately, my 3D-focused game engine did not have a completed rendering engine, and with 3D being somewhat new territory for me, I scrapped that idea. Eventually I came up with combining “turn-based” and “shooter” and then eventually I realized that “shooter” could be replaced with “battle-royale” if the enemies are roughly equally equipped and trying to kill each other as well. I thought it was a neat concept, but I also knew that I had minimal experience with AI, and no experience with turn-based games. I was also looking at just over 24 hours left on the deadline when I had the theme nailed down, so I knew it would be a big challenge.

The Technical Struggle

I wanted to go into this jam using my custom 2D game engine, ArcticWolf. I saw this as an opportunity to, as I did last time, use the game jam as a sort of excuse and motivation to further my progress on the mundane core subsystems of my game engines. I had intended to work out a simple painterly blitting rendering engine, because I thought it would be the most straightforward design where the least issues could crop up. What I did not realize, however, was that rendering things in the correct order being mandatory in a painterly renderer, would actually prove to be a nightmare of a challenge.

In C++ there is no STL container that allows indexed access and automatically organizes them based on a comparison. There is a container called std::priority_queue, but that will only provide access to the front element in the queue. This could work if we rebuilt the entire rendering queue every frame, but I knew that would be a huge detriment to the performance of the game, since elements would not be frequently (probably never) changing their z-indices, and therefore what I really wanted was a std::priority_vector.

Well, I hate to break this to you all, but no such thing as std::priority_vector exists. So I made one. In the ArcticWolf code, there is a class called aw::PriorityVector, which does everything that I needed. It took a lot of work, but it is one of the pieces of my creation during this game jam that I am the most proud of. It’s very simple, and all it does is store elements into the vector in the correct order based on a comparison type, the same that you might pass to std::sort. If the attributes defining the comparable values of the contained objects change, there’s a method that allows reordering the entire container, which is expensive, but useful if z-indices change. Another couple of useful helper classes were created to be used with this: aw::PointerGreater and aw::PointerLess. They are equivalent to std::greater and std::less, but they dereference each element being compared. This was necessary because the aw::Renderable class was polymorphic and referenced via pointer only, which meant the usual comparisons would have actually compared their addresses, not their values.

The main technical struggle, however, was in the complexity of my ambitious rendering engine. I decided it to render a scene which contains polymorphic layer objects, each of which can contain a polymorphic aw::Renderer and a bunch of objects that contain aw::Renderables, getting passed as pointers back to the aw::Renderer when the layer is rendered. With this system, I could have a base that is a tile map, representing the game world (which I also intended to allow multiple layers with different parallax values to make it a 2.5D engine if I wanted), and then a sprite layer on top of that into which the player and other entities would be placed, and then a UI layer on top of that which uses a rendering pattern similar to HTML5 DOM.

I spent a lot of time on this rendering code, and I am glad to say that I made quite a bit of progress in the process, but it also means that I didn’t have enough time as the idea that I had would have required. I was able to get the preloading and menu completed, but not much beyond that. I actually ended up trying to temporarily bail on the new rendering engine and write raw rendering code within the last 12 hours of the jam as a last-ditch effort to get a workable game by the deadline, but by that point I had not slept in a long while and I didn’t have the focus to keep myself awake.

At some point, I decided that this jam and its very undesirable theme was not worth the suffering. I remember waking up with my face down on my laptop, completely disoriented, with the morning sun shining brightly on me, and at that point I decided to close the project on the ldjam website and go to sleep. I woke up at about 11PM that evening. So much for my sleep schedule.

Future Plans

I was quite disappointed in the theme for this Ludum Dare, and I can’t help but wonder if the reason we’ve started getting worse and worse themes over the last few years might be directly correlating to the improvements being made to the ldjam website and making voting easier. They received well in excess of three thousand votes for the theme, but based on past trends, I think we can expect approximately a third of those people to submit games. Thus, it seems likely that many of the people voting on the theme have little to no intention of making a game for it, and therefore they have no interest in making sure the theme is workable, reasonable, and doesn’t cut out new developers.

My suggestion of improvement would be to allow theme voting only for users with submitted, completed, and rated jam or compo submissions to vote. In my opinion this would drastically improve the themes. That said, I don’t think that is likely to happen. Given that, next Ludum Dare, I may or may not participate depending on the theme. If the trend of worsening themes continues, I will not be participating in Ludum Dare 42. Nonetheless, I do intend to do a game jam in August. That is not a question, what is a question is whether or not I will be following the chosen theme or the standard deadline. I’ve been toying with the idea of running my own week-long jam with a theme chosen by either myself or by taking one of the losing themes from a previous Ludum Dare. Obviously I’d lose out on some of the community features of Ludum Dare, but it would still provide more or less the same training effect, and I think in the course of a full week, I’d be able to create a more-than-trivial game design.

Categories
Game Development Software Architecture Software Development

The Scourge of Global Mutable State

I’m not a perfect programmer, but I like to think that I’m getting better every day. I’ll admit that one of the things that I’ve had the most difficult time adapting to was what I perceived as other programmers being somewhat pedantic about avoiding what they called “global state” or sometimes more specifically “global mutable state.”

In the past, I held onto some conviction that while this is something to be avoided where possible, there are actually cases where it is appropriate. Recently I’ve decided to not only better conform to this well-known best-practice, but also to understand why it is that this is an issue, and what can be done to achieve what I, in the past, mistakenly perceived as the reason that global mutable state made sense, without violating this fundamental rule.

To learn something, you must learn the rules, but to become an expert, you must learn when to break these same rules. Programming is no different. However, over several years of programming I’ve come to find that the “when” of “when to break the rules” is an exception, not the rule. If you’re breaking the rule more often than not, then you’re probably misunderstanding something or being lazy. It pays to heed the words of the experts superior to you in experience even when you don’t fully understand why, because eventually, with more experience, you will understand why, and if you failed to heed their advice, you’ll find yourself chastising your past self for it.

Let me start by explaining why it is that I’ve so often resisted this best practice and gone ahead with global mutable state anyway. When writing larger code bases, which most projects inevitably aspire to be, you will find that some of your code will be, as a matter of necessity, used by a large proportion of the code base. Using dependency injection as your only distribution technique, as an example, might drastically increase the verbosity of your code. Who wants to pass an instance a “Log” class to every single instance of a “GameState” object? I’ll admit that it’s not really THAT much extra work, but it has a distinct feeling of not being “DRY,” since I’m essentially repeating myself on every object instantiation. There are some subsystems which, by nature, the entire system will need to access, and would be better off being organized.

A pet peeve not only of myself, but of many people, is that some people seem to think that using the singleton pattern is a solution to this problem. Singletons are global state, and moreover, it appears to me that by now, the singleton is obsolete. Any problem solvable with a singleton can be solved with a static class with mutable private fields, and frankly these static classes don’t lie about their true nature. But both are global state, and both ought to be avoided anyway. But if you’re going to ignore the advice anyway, you might as well just use the static class instead, so users can recognize the warning signs of nondeterministic behavior before it’s too late.

I’ve learned, however, that all of my arguments came from a lack of understanding of the actual problem, and the actual solutions.

  • Global mutable state generally excludes communication channels that allow decoupled non-global objects to communicate (such as Observers or Service Locators). This is a significant solution to this problem.
  • Global mutable state implies that the global items in question contain actual values that change between subsequent calls. Global classes are still perfectly valid for resolving the problem of subsystems that need to be accessed from across the system as whole, so long as they incorporate the solutions from the previous bullet.

I’ll document one solution that I solved with my TimberWolf game engine.

In the past, I’ve had a static class named Log, which was declared as follows:

#ifndef H_CLASS_LOG
#define H_CLASS_LOG

#include <iostream>
#include <string>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <ctime>
#include <mutex>
#include <exception>
#include <cstdlib>
#include <atomic>
#include "../../enum/LogLevel/LogLevel.hpp"

class Log {

public:

    Log () = delete; // static only
    ~Log () = delete;

    Log (Log&&) = delete;
    Log& operator = (Log&&) = delete;

    Log (const Log&) = delete;
    Log& operator = (const Log&) = delete;

    static bool cliOutputEnabled ();
    static void enableCliOutput ();
    static void disableCliOutput ();

    static LogLevel getCliFilterLevel ();
    static void setCliFilterLevel (LogLevel);

    static bool fileOpen ();
    static bool openFile (const std::string&);
    static void closeFile ();

    static bool fileOutputEnabled ();
    static void enableFileOutput ();
    static void disableFileOutput ();

    static LogLevel getFileFilterLevel ();
    static void setFileFilterLevel (LogLevel);

    template<typename ...T>
    static void log (LogLevel messageType, T&&... message) {

        std::string out;

        if (
            (m_cliOutputEnabled || m_fileOutputEnabled) &&
            (
                messageType == LogLevel::UNDEFINED ||
                messageType >= m_cliFilterLevel ||
                messageType >= m_fileFilterLevel
            )
        ) {
            out = Log::formatMessage(messageType, Log::concatMessage(std::forward<T>(message)...));
        } else {
            return;
        }

        if (m_cliOutputEnabled) {
            if (messageType == LogLevel::UNDEFINED || messageType >= m_cliFilterLevel) {

                if (messageType == LogLevel::ERROR) {
                    std::unique_lock<std::mutex> lock_stderr(Log::mutex_stderr);
                    std::cerr << out << std::endl;
                } else {
                    std::unique_lock<std::mutex> lock_stdout(Log::mutex_stdout);
                    std::cout << out << std::endl;
                }

            }
        }

        if (m_fileOutputEnabled && fileOpen()) {
            if (messageType == LogLevel::UNDEFINED || messageType >= m_fileFilterLevel) {

                std::unique_lock<std::mutex> lock_file(Log::mutex_file);
                m_file << out << std::endl;

            }
        }

    }

    template<typename ...T>
    static void verbose (T&&... message) {
        Log::log(LogLevel::VERBOSE, std::forward<T>(message)...);
    }

    template<typename ...T>
    static void notice (T&&... message) {
        Log::log(LogLevel::NOTICE, std::forward<T>(message)...);
    }

    template<typename ...T>
    static void warning (T&&... message) {
        Log::log(LogLevel::WARNING, std::forward<T>(message)...);
    }

    template<typename ...T>
    static void error (T&&... message) {
        Log::log(LogLevel::ERROR, std::forward<T>(message)...);
    }

    static void bindUnhandledException ();
    static void unhandledException ();

private:

    template<typename ...T>
    static std::string concatMessage (T&&... message) {

        std::ostringstream oss;
        (oss << ... << std::forward<T>(message));
        return oss.str();

    }

    static std::string formatMessage (LogLevel, const std::string&);

    static std::atomic<bool> m_cliOutputEnabled;
    static std::atomic<bool> m_fileOutputEnabled;

    static std::atomic<LogLevel> m_cliFilterLevel;
    static std::atomic<LogLevel> m_fileFilterLevel;

    static std::ofstream m_file;

    static std::mutex mutex_stdout;
    static std::mutex mutex_stderr;
    static std::mutex mutex_file;

};

#endif

As you can see, everything is static, and the class cannot be instantiated. This makes sense, because the Log class is something that needs to be accessed from all over the code base, and we generally want all log messages to funnel through a single location so none of them accidentally go astray before they are sent or stored. This was all fine and dandy when it was originally written, because it only had one purpose then, and that was to write error messages to std::cout or std::cerr. It interacted with a global I/O, but it didn’t store any kind of state.

After a while, though, I decided that a few features would be nice to have in the logging subsystem. First of all, I wanted to be able to filter log messages by their LogLevel enum. This was simple enough. Add a static field to store the minimum log level, and then compare the log level of each message before writing it to I/O. But now the log class contains the global state for the current log level. Anybody could change that, and it might make it hard to debug stuff because its behavior becomes nondeterministic. The behavior of the Log class depends on the order of modifications. If some code somewhere deep in the code base calls Log::setFilterLevel(LogLevel::ERROR) because the developer was getting inundated with verbose calls and wanted to temporarily silence them, and then forgot to remove it, all LogLevel::WARNING messages sent after that piece of code gets invoked will fail to display. The developer might mistakenly think that no errors are occurring. And there’s no way to receive those messages elsewhere unless the filter level is dropped again.

This hasn’t actually been a problem for me yet, but I was slightly aware of the possibility, but I hadn’t yet done anything to resolve it. I added additional features, such as allowing Log to write to a log file, and allowing a separate filter level for the log file. I noticed some additional potential problems after adding these features, however. First of all, I can only write to a single log file. What if I decide later on that I want to write only warnings and errors to one file, and all messages to another file. Or what if I want a console window in my game somewhere to display the error messages, without requiring the end-user to look at their terminal or open up a log file?

The Log subsystem is a problem that can be solved by the observer pattern. I can leave the API for sending log messages alone, because it implements everything the observables need (but I am going to add a context string that can be used for additional filtering, in case we want the log messages pertaining only to the sound system to go somewhere, for example). There will need to be a separate API for RECEIVING the log messages, however. One of these will be used to receive messages and route them to the command line. Another one will be used to put messages into a file. But more can be created and removed as needed.

If those changes are implemented, The Log class, which is the global portion of the subsystem, will no longer contain any global state at all (the vectors of observers are not technically “state” in this context, as they are not going to have any effect on one another, the log subsystem, or the game as a whole). Any code in the code base can send messages to it, but the output, which is the real tangible effect of the log subsystem, will only be controlled by non-global objects. Code far across the code base will not be able to create nondeterministic behavior in the logs of an unrelated system.

This is, for all intents and purposes, the best of both worlds. I can expose a global API without needing to define behavior globally. I can create a log subsystem that doesn’t require dependency injection, but also doesn’t expose any state to the global scope, while still supporting stateful behavior at one end of the pipeline.

With that explanation out of the way now, allow me to show you the finished solution. Note that between the old version and this current one, the namespace “tw” was added to everything.

Here’s the new Log class declaration:

#ifndef H_CLASS_LOG
#define H_CLASS_LOG

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <sstream>
#include <mutex>
#include <functional>
#include "../LogObserver/LogObserver.hpp"
#include "../../enum/LogLevel/LogLevel.hpp"

namespace tw {
class Log {

public:

    Log () = delete; // static only
    ~Log () = delete;

    Log (Log&&) = delete;
    Log& operator = (Log&&) = delete;

    Log (const Log&) = delete;
    Log& operator = (const Log&) = delete;

    template <typename ...T>
    static void log (LogLevel messageType, const std::string& context, T&&... message) {

        std::unique_lock<std::mutex> lock(m_mutex);

        for (unsigned int i = 0; i < m_observers.size(); ++i) {

            m_observers[i]->notify(messageType, context, concatMessage(std::forward<T>(message)...));

        }

    }

    template <typename ...T>
    static void verbose (const std::string& context, T&&... message) {
        Log::log(LogLevel::VERBOSE, context, std::forward<T>(message)...);
    }

    template <typename ...T>
    static void notice (const std::string& context, T&&... message) {
        Log::log(LogLevel::NOTICE, context, std::forward<T>(message)...);
    }

    template <typename ...T>
    static void warning (const std::string& context, T&&... message) {
        Log::log(LogLevel::WARNING, context, std::forward<T>(message)...);
    }

    template <typename ...T>
    static void error (const std::string& context, T&&... message) {
        Log::log(LogLevel::ERROR, context, std::forward<T>(message)...);
    }

    static void bindUnhandledException ();

    template <typename T, typename ...Targ>
    static LogObserver* makeObserver (Targ&&... args) {

        auto observer = std::make_unique<T>(std::forward<Targ>(args)...);
        auto observerPtr = observer.get();

        m_observers.push_back(std::move(observer));

        return observerPtr;

    }
    static void registerObserver (std::unique_ptr<LogObserver>&&);
    static void registerObserver (LogObserver*);

private:

    template <typename ...T>
    static std::string concatMessage (T&&... message) {

        std::ostringstream oss;
        (oss << ... << std::forward<T>(message));
        return oss.str();

    }

    static std::mutex m_mutex;
    static std::vector<std::unique_ptr<LogObserver>> m_observers;

};
}

#endif

As you can see, the Log class is now nothing more than a shell, a notification system for a collection of observers (and also a system for creating and managing said observers). It presents a new class known as LogObserver, which is a virtual class.

#ifndef H_CLASS_LOGOBSERVER
#define H_CLASS_LOGOBSERVER

#include <string>
#include <set>
#include "../../enum/LogLevel/LogLevel.hpp"
namespace tw{ class Log; }

namespace tw {
class LogObserver {

// WARNING: Never call Log::* functions from inside subclasses of this.
// It will cause infinite recursion and you will be sad.

public:

    static inline constexpr unsigned int ALLOW_UNDEFINED = 0b00001;
    static inline constexpr unsigned int ALLOW_VERBOSE =   0b00010;
    static inline constexpr unsigned int ALLOW_NOTICE =    0b00100;
    static inline constexpr unsigned int ALLOW_WARNING =   0b01000;
    static inline constexpr unsigned int ALLOW_ERROR =     0b10000;

    LogObserver () = default;
    explicit LogObserver (unsigned int);
    explicit LogObserver (const std::set<std::string>&);
    explicit LogObserver (const std::string&...);
    LogObserver (unsigned int, const std::set<std::string>&);
    LogObserver (unsigned int, const std::string&...);
    virtual ~LogObserver () = default;

    LogObserver (LogObserver&&) = default;
    LogObserver& operator = (LogObserver&&) = default;

    LogObserver (const LogObserver&) = default;
    LogObserver& operator = (const LogObserver&) = default;

    void notify (LogLevel, const std::string&, const std::string&);

    bool undefinedAllowed () const;
    void allowUndefined ();
    void blockUndefined ();
    bool verboseAllowed () const;
    void allowVerbose ();
    void blockVerbose ();
    bool noticeAllowed () const;
    void allowNotice ();
    void blockNotice ();
    bool warningAllowed () const;
    void allowWarning ();
    void blockWarning ();
    bool errorAllowed () const;
    void allowError ();
    void blockError ();

    bool contextAllowed (const std::string&...) const;
    void allowContext (const std::string&...);
    void blockContext (const std::string&...);
    bool allContextAllowed () const;
    void allowAllContext ();
    void restrictContext ();

protected:

    virtual void notifyCallback (LogLevel, const std::string&, const std::string&) = 0;

    unsigned int m_allowedLevelFlags {
        ALLOW_UNDEFINED |
        ALLOW_VERBOSE |
        ALLOW_NOTICE |
        ALLOW_WARNING |
        ALLOW_ERROR
    };

    std::set<std::string> m_allowedContexts;
    bool m_allContextsAllowed {true};

};
}

#endif

LogObserver classes contain non-overrideable fields and methods for storing and manipulating data about the log levels and contexts that it’s interested in. The notify method is markedly non-virtual because it implements the code for determining if the LogObserver even cares about the message that’s being sent to notify. As such, notify is the method that is initially invoked whenever a log message is being processed. If all of the checks pass, then it is finally forwarded on to the protected virtual method notifyCallback, which is to be overridden to the tastes and needs of the implementor.

Let’s look at some examples of implementations, the few that I’ve already made, perhaps all that I’ll ever need:

#ifndef H_CLASS_LOGOBSERVER
#define H_CLASS_LOGOBSERVER

#include <string>
#include <set>
#include "../../enum/LogLevel/LogLevel.hpp"
namespace tw{ class Log; }

namespace tw {
class LogObserver {

// WARNING: Never call Log::* functions from inside subclasses of this.
// It will cause infinite recursion and you will be sad.

public:

    static inline constexpr unsigned int ALLOW_UNDEFINED = 0b00001;
    static inline constexpr unsigned int ALLOW_VERBOSE =   0b00010;
    static inline constexpr unsigned int ALLOW_NOTICE =    0b00100;
    static inline constexpr unsigned int ALLOW_WARNING =   0b01000;
    static inline constexpr unsigned int ALLOW_ERROR =     0b10000;

    LogObserver () = default;
    explicit LogObserver (unsigned int);
    explicit LogObserver (const std::set<std::string>&);
    explicit LogObserver (const std::string&...);
    LogObserver (unsigned int, const std::set<std::string>&);
    LogObserver (unsigned int, const std::string&...);
    virtual ~LogObserver () = default;

    LogObserver (LogObserver&&) = default;
    LogObserver& operator = (LogObserver&&) = default;

    LogObserver (const LogObserver&) = default;
    LogObserver& operator = (const LogObserver&) = default;

    void notify (LogLevel, const std::string&, const std::string&);

    bool undefinedAllowed () const;
    void allowUndefined ();
    void blockUndefined ();
    bool verboseAllowed () const;
    void allowVerbose ();
    void blockVerbose ();
    bool noticeAllowed () const;
    void allowNotice ();
    void blockNotice ();
    bool warningAllowed () const;
    void allowWarning ();
    void blockWarning ();
    bool errorAllowed () const;
    void allowError ();
    void blockError ();

    bool contextAllowed (const std::string&...) const;
    void allowContext (const std::string&...);
    void blockContext (const std::string&...);
    bool allContextAllowed () const;
    void allowAllContext ();
    void restrictContext ();

protected:

    virtual void notifyCallback (LogLevel, const std::string&, const std::string&) = 0;

    unsigned int m_allowedLevelFlags {
        ALLOW_UNDEFINED |
        ALLOW_VERBOSE |
        ALLOW_NOTICE |
        ALLOW_WARNING |
        ALLOW_ERROR
    };

    std::set<std::string> m_allowedContexts;
    bool m_allContextsAllowed {true};

};
}

#endif

ConsoleLogObserver is a specialization that sends the messages to the console.

#ifndef H_CLASS_FILELOGOBSERVER
#define H_CLASS_FILELOGOBSERVER

#include <string>
#include <set>
#include <ctime>
#include <iomanip>
#include "../LogObserver/LogObserver.hpp"
#include "../File/File.hpp"

namespace tw {
class FileLogObserver : public LogObserver {

public:

    FileLogObserver () = default;
    explicit FileLogObserver (const std::string&);
    FileLogObserver (const std::string&, unsigned int);
    FileLogObserver (const std::string&, const std::set<std::string>&);
    FileLogObserver (const std::string&, const std::string&...);
    FileLogObserver (const std::string&, unsigned int, const std::set<std::string>&);
    FileLogObserver (const std::string&, unsigned int, const std::string&...);
    ~FileLogObserver () override = default;

    FileLogObserver (FileLogObserver&&) = default;
    FileLogObserver& operator = (FileLogObserver&&) = default;

    FileLogObserver (const FileLogObserver&) = default;
    FileLogObserver& operator = (const FileLogObserver&) = default;

    std::string getFilePath () const;
    bool setFilePath (const std::string&);

protected:

    virtual void notifyCallback (LogLevel, const std::string&, const std::string&) override;

    virtual std::string formatMessage (LogLevel, const std::string&, const std::string&);

    File m_file {"./log.txt", File::ENABLE_WRITE | File::ENABLE_APPEND};

};
}

#endif

FileLogObserver is a specialization that stores a file handle and writes log messages to that file.

And finally, FunctionLogObserver is a specialization that stores and manages a collection of function objects that receive the message (so in a way, it can do anything).

#ifndef H_CLASS_FUNCTIONLOGOBSERVER
#define H_CLASS_FUNCTIONLOGOBSERVER

#include <string>
#include <set>
#include <vector>
#include <functional>
#include <mutex>
#include "../LogObserver/LogObserver.hpp"

namespace tw {
class FunctionLogObserver : public LogObserver {

public:

    typedef std::function<void(LogLevel, const std::string&, const std::string&)> Callback;

    FunctionLogObserver () = default;
    explicit FunctionLogObserver (const std::vector<Callback>&);
    explicit FunctionLogObserver (const Callback&...);
    FunctionLogObserver (const std::vector<Callback>&, unsigned int);
    FunctionLogObserver (const Callback&, unsigned int);
    FunctionLogObserver (const std::vector<Callback>&, const std::set<std::string>&);
    FunctionLogObserver (const Callback&, const std::set<std::string>&);
    FunctionLogObserver (const std::vector<Callback>&, const std::string&...);
    FunctionLogObserver (const Callback&, const std::string&...);
    FunctionLogObserver (const std::vector<Callback>&, unsigned int, const std::set<std::string>&);
    FunctionLogObserver (const Callback&, unsigned int, const std::set<std::string>&);
    FunctionLogObserver (const std::vector<Callback>&, unsigned int, const std::string&...);
    FunctionLogObserver (const Callback&, unsigned int, const std::string&...);
    ~FunctionLogObserver () = default;

    FunctionLogObserver (FunctionLogObserver&&) = default;
    FunctionLogObserver& operator = (FunctionLogObserver&&) = default;

    FunctionLogObserver (const FunctionLogObserver&) = default;
    FunctionLogObserver& operator = (const FunctionLogObserver&) = default;

    const std::vector<Callback>& getCallbacks () const;
    void setCallbacks (const std::vector<Callback>&);
    void addCallback (const Callback&...);
    void clearCallbacks ();

protected:

    virtual void notifyCallback (LogLevel, const std::string&, const std::string&);

    std::mutex m_mutex;
    std::vector<Callback> m_callbacks;

};
}

#endif

If you’d like to read the implementation .cpp files, those are available in the git repo. They didn’t seem to be necessary to illustrate the design pattern. Also, this example code isn’t perfect, and I’m sure in short order I’ll find reason to modify it. At the time of writing, TimberWolf doesn’t have a stable API, so please don’t use this as documentation for it.

Categories
Game Development Game Jams Software Development

Ludum Dare 38-40 Postmortem

It’s been nearly a year since I participated in Ludum Dare 38, which was my first ever game jam. I finished the game, and received decent ratings (I mean, not great over all, but given it was my first one), placing me between the 500th and 700th place in the rankings. I also participated in Ludum Dare 39, which unfortunately never made it to the playable stage by the 72 hour deadline. Ludum Dare 40 I likewise did not finish, however it was much more finished than 39, and was more technically advanced than either of the previous attempts.

Ludum Dare 41 is coming up in less than a month, and as I try to prepare my game engine implementations, now seems like as good a time as any to do a postmortem on each of my experiences.

Ludum Dare 38 – Asteroid Field

Let’s start with a simple explanation of my concept for Ludum Dare 38. The theme was “A Small World.” My game was called “Asteroid Field,” and was a game wherein you play as the planet earth, firing missiles into space to blow up large asteroids that are careening randomly toward your tiny planet. As time progresses, so does the average size, number, and speed of asteroids. The more points you score, the better your missiles get.

It was written in JavaScript using ES6 and transpiled and bundled with Webpack and Babel. The game engine was Phaser CE 2.

The majority of my time was spent learning the Phaser engine, which I had never used before. I had especially debilitating delays caused by issues with collision physics and particle emitters as implemented in Phaser then. I ended up implementing the physics collision math myself instead of going through the game engine, as the built-in arcade physics that I had initially intended to use gave strange behavior, as everything was expected to be a square, whereas my assets were generally better represented as circles. Unfortunately, my reliance on circles for hitboxes resulted in performance-killing trigonometry in the inner loop, which makes it difficult to play on older computers. I also made the mistake of starting the project without using the pushdown game state system, which resulted in having to significantly refactor my code when I decided I needed a game over screen.

My next biggest use of time was late-phase debugging, which I think is largely due to the fact that I went into this challenge knowing very little about the library I was using. I spent very little time on creating assets, game design, and play testing/balancing.

Overall, I would say I did a great job of limiting the scope of the project to make it realistic for my experience level with the tools, format, and process. Right from the start, my strategy was to create a game design with minimum complexity of mechanics, but with a lot of potential for cosmetic and very simple gameplay additions that could improve on the barebones concept. This strategy worked well for a first game.

Aside from inexperience, which can only be remedied over time, I think the primary lesson that I learned from this experience was that I needed to plan things more effectively. I think more complete mockups of the design, as well as figuring out the requirements for things like the physics engine going into the project would have helped me to avoid many roadblocks.

Ludum Dare 39 – Blackout (Incomplete, Unplayable)

A few months after Asteroid Field, I participated in Ludum Dare 39, for which the theme was “Running Out of Power.” My game was called “Blackout”, and the premise was that you would play as a dispatcher for a power company, and your screen would be a control panel showing a city and providing notices when the city’s power grid was damaged. Your job would be to dispatch your repair crews as effectively as possible to keep the power on for the greatest number of people. If you were not powering part of the city, that part would not be paying you, and if enough people are without power, the power company loses money. You lose when your company runs out of money.

This game was built on the same tech stack as Asteroid Field.

During this project, I spent the majority of my time, disappointingly, designing the digital representation of a city. This project was a lot more ambitious than my last one. From the design perspective, it didn’t seem that much more complex, certainly not enough to prevent me from completing the game on time. What I underestimated, however, was the complexity of the “street node” system, and the difficulty of implementing both the rendering and the physics of the vehicles travelling along said street nodes. I did get so far as determining that there needed to be object representations of “intersections” and “roads.” Intersections would contain references to each road that connected to them, and each road would contain references to each intersection acting as an endpoint. Additionally, roads would contain data on the amount of traffic, the speed of the road, and sometimes control points, through which a Catmull-Rom spline would travel to create curved or windy roads. The vehicles would be dispatched by passing their origin, destination, and the city grid to an A* pathfinding function, which would spit out the turn-by-turn directions. An update loop callback would then be responsible for moving the vehicle along this path based on the speed and traffic values for the street upon which the vehicle is currently traveling.

That design was straightforward enough. Unfortunately, that is more or less the entirety of the implementation that I had even figured out, let alone implemented. A number of problems appeared this time around. First of all, how do I draw this city? Well what I did was literally to draw it on a sheet of paper, and then draw a grid on top of it and manually write out a big long JavaScript object containing the intersections and streets and their coordinates as they appeared on the sheet of paper that I drew up. This took an ENORMOUS amount of time. I spent over a day and a half doing just this. With this task figured out, I also needed to implement the preloading system and game menu. Fortunately, I was able to complete those things, and I do have a menu complete with the ability to name your power company. After that, I needed to render the city. I had initially determined that a Catmull-Rom spline would be the best way to implement curves in the roads, since I could simply give it coordinates that fell on the path instead of off-path control points which Bezier curves need. Unfortunately, after completing the map, I found that the game engine I was using had already implemented Bezier curves, but not Catmull-Rom, which were more complex. At this point, I realized that I needed to implement the curve algorithms myself. This was about where I was when 72 hours came to an end. The extent of playability is that you can load the game, name your company, and start the disappointingly blank screen that is the “playing” state, even though somewhere in your browser’s memory is an invisible and very complex street map that I painstakingly wrote out by hand.

I think that the biggest lesson here, is, once again, to learn how to better plan ahead of time, better consider the technical limitations of the game engine at the time of the contest, and to better assess the difficulty and depth of the gameplay elements. I think this might have also gone better if I had been able to adapt the design as issues appeared, which might have been possible if I had more often benchmarked my progress according to a schedule.

Ludum Dare 40 – Speed Fishing (Incomplete, Kind Of Playable, Pretty Neat)

Okay, Ludum Dare 40 was the best of times, and the worst of times for me. I had hyped myself up for this challenge, for which the theme was “The More You Have, The Worse It Is.” I’m not going to lie, I hated the theme for this one. But that’s how Ludum Dare goes sometimes, and that’s what’s so cool about it. You show your adaptability by taking the theme and working with it, regardless of your feelings towards it. And so I did.

My game was called “Speed Fishing,” and its premise was that you are a fisherman in a fishing boat. You must collect as many fish as you can in a specific time limit. Before the timer is up, you must return to the starting place. You drive your motorized boat out into the water, avoiding obstacles such as land, sharks, and battleships, and collect the fish as you go. The more fish your boat contains, the more difficult it is to accelerate, brake, and turn, thus making travel more perilous.

This time, I broke from the web stack and decided to implement this game in C++ using SFML. I had already been working on the engine for a Minecraft-like game called Territory, and I ported much of the code from that, refactoring it to use SFML instead of GLFW. I chose SFML over GLFW because of its relative simplicity for 2D games, whereas GLFW was chosen for Territory because of its more do-it-yourself nature, and its support for Vulkan.

I spent a good part of my time working on real game features, believe it or not. That said, a few things took longer than expected because I had come from the web stack and expected functionality to be present that simply wasn’t, even in as high-level a framework as SFML. Namely, I had to figure out how to place elements on the screen correctly using direct coordinates (which I did in the previous games, but there seemed to be more helper functions available there to do things like center an asset), and I had to handle click events myself to determine if clickable elements had in fact been clicked. This click event handling was the biggest shock of all to me, although I was able to figure it out, and fortunately determining if a click lands inside a rectangle is fairly straightforward math. I also expected SFML to implement game states and associated functionality, but that was also left to me. Fortunately, I had already done most of that in the Territory project. As in the last Ludum Dare, however, the complexity of the game map implementation was one of the biggest consumers of time. In this case, I wanted the world to be mostly ocean with some land, and I wanted everything to be generated procedurally using perlin noise. I also needed to guarantee that the boat would spawn in water. Additionally, I needed to use the flyweight pattern for the rendering of the map itself, as I didn’t want to have millions of instances of multi-byte objects eating up large swaths of fragmented memory and slowing down the rendering because of CPU prefetch misses. I also needed to be able to move the player entity around through the world, all while keeping the camera centered on it. Because the world was larger than the screen, I also didn’t want to have to render the tiles that were outside of the visible screen area.

All of these features, I was able to implement, and just in time for the deadline no less. Unfortunately, things like the menu, score-keeping, fish entities, enemy entities, land collision, timer, and boat movement physics did not get completed within the time limit.

This time around, I will say that I had more distractions than before, and therefore I could have put more focus into this project than I did. Despite that, however, this project has more lines of hand-written code than both previous projects, and despite being incomplete, it is more technically complex than the completed Asteroid Field game, and I am truly proud of it. In all 3 Ludum Dare experiences, this one is the one of which I am the most proud. So that’s the good. The bad, however, is that the last day is one of which I did not sleep one minute. In fact, when the 72-hour mark was met, I had been awake for 30-something hours. I felt awful and tired. There were many moments between the 24-hour mark and the end that I was absolutely suffering. But I was determined to finish something, to be proud of something. I succeeded at that, and I am grateful.

I think one of the things that I learned with Speed Fishing was that I loved C++ more than I loved JavaScript. I also learned that code reuse is a beautiful and helpful thing, and I need to be doing more of that. This project, Speed Fishing, is the origin of a game engine I created called “ArcticWolf,” which comes after “TimberWolf,” which is the one that I used in Territory. Each of them is a specialization of a very deliberately similar API. I will be able to use these engines in not only future Ludum Dare projects, but also in free and commercial games that I will be releasing via my game company “Danger Zone Games.” So in a way, Ludum Dare 40 was the beginning of Danger Zone Games, it was the beginning of me making modular and reusable game engines, and created the ArcticWolf game engine (which was originally borrowed code from TimberWolf, which was borrowed code from Territory).

The Future!

I am very excited to participate in future Ludum Dare jams, and I am very excited to be creating games at long last. It’s been a dream of mine for a very long time, and it’s been a wild ride getting to where I am now. Even though I’m not making money on my games yet, I still love it. For me, game development isn’t a job or an industry. Game development is an amazing art. It incorporates technical skill through programming, graphic art and musical talent through game assets, and psychology through gameplay design. When you’re on your own, you have to be a skilled artist and technician in each of these fields. When you work with others, you may only need to specialize in one, but as a team your talents must stay in sync. I am proud to be a part of this community, and I am excited to see what we can all achieve in the coming years.