11 May

Refactoring AGameMode Part 2 – Splitting the class

Part 1 of this article series is here.

Now before I actually start showing how I’ve split the class, let’s talk about design goals.

My goals:

  • Remove the requirement that a game must use the Epic state machine hardcoded into AGameMode
  • Remove coupling between engine classes and AGameMode, by replacing those explicit couplings with delegates

The Split

So, the split itself is very simple. We want to make a new base class. I called it ABaseGameMode. That may not fit Epic naming convention, so it may have to change. But this allows AGameMode to retain its original name and interface, through the magic of inheritance.

The way that I’ve done this refactor is move everything specific to AGameMode to that class, and all the base functionality (that all game modes will need) to ABaseGameMode. That makes AGameMode quite lean. Here’s the class declaration:

If you look at the overridden methods at the end, they are methods that are also in ABaseGameMode, but the AGameMode implementation has some state machine things that it does as well. For instance, StartNewPlayer():

As you can see, the heavy lifting is done in ABaseGameMode, with the state machine specific code contained entirely in AGameMode. This is the pattern for the overridden methods.

Now, we’re not done yet! There’s lots of places throughout the code where the engine calls World->GetAuthGameMode(). Now, that’s a templated function, but without any template arguments it used to return AGameMode. Now, it returns ABaseGameMode:

And there’s two types of code that want that, as you would expect. The most innocent is the kind that wants the functionality of ABaseGameMode, and the more nefarious ones that wants to poke into gameplay code. The first are simple enough to address. Here’s one of those in Controller.cpp

Very simple change, just fix the type being returned. But the more nefarious ones are like this:

You can see the bad behavior there – it’s reaching into the gameplay FSM to tell the match to abort, rather than just firing a delegate. As of right now, this code works just fine. But it will ONLY work with AGameMode, so we need to fix that.

Fixing coupling using delegates

In the case shown above, the engine is telling a bunch of interested parties about the net connection being disconnected. It’s assuming that we have a game mode, and that the game mode is interested. This is a prime candidate for a delegate, but unfortunately since it’s a static function, it’s unclear where the delegate would go. The best I could come up with was on UWorld. Or rather, on FWorldDelegates, which is a static class that appears to have all of the delegates for the UWorld(s). So, I added this:

With this change, the code above becomes much simpler:

And AGameMode subscribes to it like so:

Now, if you’ve gotten this far in the article, you’ve got a ABaseGameMode that doesn’t have the hardcoded FSM. This actually works! But, you can’t just plug and play. AGameMode does a lot of things that are required at certain points in the state machine, such as spawning players, etc.

In the next two articles, we’ll look at how to replace all that functionality, and how it worked in the first place.

5 thoughts on “Refactoring AGameMode Part 2 – Splitting the class

  1. Awesome thing you’re working on there! We have stumbled across that very issue just the other day, and I was very happy to find your blog about it. Curious thing that you started working on that basically the very same day as we did :). I’ll shelve my work on it until yours is finished, and I hope they’ll accept that change into the engine!
    Networking and Multiplayer needs a little more love from Epic.

Leave a Reply

Your email address will not be published. Required fields are marked *