News & Blog

UE4 Coding: Decoupling for Better Code

This ar­ti­cle is the first in a se­ries of cod­ing tu­to­ri­als for Un­re­al En­gine 4. The read­er of this ar­ti­cle is as­sumed to know ba­sic C++ and the UE4 API.

In the past year of UE4 de­vel­op­ment, I had prob­lems fig­ur­ing out how to struc­ture my game code: where to put game log­ic code to keep it read­able/main­tain­able, testable and re­us­able. This blog post sum­ma­rizes what I and my fel­low de­vel­op­ers have learnt as I fi­nal­ly feel con­fi­dent with my un­der­stand­ing.

UE4 Code Blog Header

GameMode

The doc­u­men­ta­tion de­scribes three main tasks of GameM­o­de: play­er man­age­ment, lev­el man­age­ment and game rules like win­ning con­di­tions. GameM­o­des come with two re­stric­tions: it is not pos­si­ble to swap out the GameM­o­de out at run­time with­out open­ing a dif­fer­ent lev­el and the GameM­o­de on­ly ex­ists on the serv­er (on­ly re­al­ly rel­e­vant for on­line games). Im­por­tant­ly, the lat­ter im­plies that there should be no da­ta in the GameM­o­de that needs to be trans­ferred to the client. Those should in­stead be stored in the GameS­tate.

Serv­er? Client? I’m mak­ing a sin­gle play­er game. - Our games are all, too! For lo­cal games, client and serv­er will be one and you will there­fore have a GameM­o­de.

In our games, we have ac­tu­al­ly nev­er made use of the sep­a­ra­tion in­to GameS­tate and GameM­o­de up till now. In­stead, ev­ery­thing went di­rect­ly in­to the GameM­o­de, as we nev­er need­ed to run serv­er and client sep­a­rate­ly. I see this as a mis­take which orig­i­nat­ed from my mis­un­der­stand­ing of GameM­o­de and GameS­tate. In­stead, I ad­vise you to write your code “as if” you had to split them one day.

“But, YAG­NI!”, you may cry. Though, as the ti­tle may sug­gest, my main mo­ti­va­tion for this is not be­ing able to split the code in­to client and serv­er.

No GameMode

One of the most com­mon code pat­terns you would find in my code, was the fol­low­ing:

// in SomeActor.cpp
auto gm = Cast<AMyGameMode>(GetWorld()->GetAuthGameMode());
check(gm);

gm->NotifyOfSomeEvent();

The check is guar­an­teed to suc­ceed, as we nev­er run the game with sep­a­rat­ed client and serv­er. In­stead I be­lieve this code is bad be­cause of the `gm->No­ti­fy­Of­SomeEvent()`, which makes the Some­Ac­tor class de­pend on the GameM­o­de class. We will need to `#in­clude “MyGameM­o­de.h”` just be­cause of this func­tion call. When­ev­er MyGameM­o­de.h changes, the Some­Ac­tor.cpp file will be re­com­piled.

Now sup­pose you do this in ev­ery ac­tor class. In Wastepa­perbin VR for in­stance: Pa­per, Print­er, Waste­bin, Lever, the Play­er­Pawn and more. Ev­ery time you change MyGameM­o­de.h, all these class­es are re­com­piled and I found that that hap­pens rather fre­quent­ly. Es­pe­cial­ly when you do not split off your GameS­tate prop­er­ly as you may add new prop­er­ties there of­ten.

While one so­lu­tion is to just split off ev­ery­thing in­to the GameS­tate to avoid this, there are fur­ther rea­sons to fo­cus on fol­low­ing the “as if serv­er and client are split” rule.

Game Code is Throwaway-Code

Apart from when you lit­ter­al­ly write “throw-away-code” for a game called “Wastepa­perbin”, yes, you can prob­a­bly throw it away if you can­not re­use it. Jokes aside, I rec­om­mend writ­ing your code in a re­us­able way, as this can save you time in the fu­ture! You can write your own “tech­ni­cal cap­i­tal”.

For our VR games, we write us­er in­ter­ac­tion com­po­nents, a Lever ac­tor for ex­am­ple. This is code that might as well be reused by an­oth­er game. Fol­low­ing the pat­tern above, there was code like this:

// in ALever.cpp (not actual code, condensed to "the gist")
void ALever::leverFlipped(bool up) {
    auto gm = Cast<AMyGameMode>(GetWorld()->GetAuthGameMode());
    check(gm);

    if(up) {
        gm->SetChallengeMode();
    } else {
        gm->SetLeaderboardMode();
    }
}

This code again, would not work with client-serv­er sep­a­ra­tion, of course. The re­al cul­prit here is, though, that the code is so tight­ly “cou­pled” to the GameM­o­de, that it is not pos­si­ble to use this code in an­oth­er game with­out chang­ing it. Op­ti­mal­ly, we would like to copy it in­to an­oth­er game or even use it in a plug­in shared by both games.

This can be fixed by adding a del­e­gate, which is bound from the out­side (e.g. in the GameM­o­de if need be or, bet­ter, in a Lev­elScrip­tAc­tor). That may then look like this:

/**
 * @brief Delegate executed when a lever flips into a new state.
 * @param IsUp `true` if the lever has been flipped to up state, `false` otherwise.
 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLeverStateChanged, bool, IsUp);

The above code snip­pet de­clares your new `FLever­Stat­e­Changed` del­e­gate, which can then be added as a prop­er­ty in the Lever ac­tor:

UPROPERTY(BlueprintAssignable)
FLeverStateChanged OnLeverStateChanged;

Oth­er ac­tors can then bind to the del­e­gate like this:

ALever* lever = /* ... */;
lever->OnLeverStateChanged.AddDynamic(this, &ASomeActor::onLeverStateChanged);

Where `on­Lever­Stat­e­Changed` will look some­thing like this:

/* Header: */
UFUNCTION()
void onLeverStateChanged(bool IsUp);

/* Source: */
void ASomeActor::onLeverStateChanged(bool IsUp) {
    /* Do something according to IsUp */
}

To call the del­e­gate in the lever and con­se­quent­ly ex­e­cut­ing all bound func­tions, you would sim­ply call broad­cast:

void ALever::SomethingHappenedThatFlipsUpTheLever() {
    OnLeverStateChanged.Broadcast(true);
}

In­stead, yes, you could check `if (gm)` and then on­ly do this on the serv­er. Us­ing del­e­gate will ad­di­tion­al­ly make the code re­us­able and al­low you to re­move that MyGameM­o­de.h head­er, which can avoid re­com­pi­la­tions of the source.

Hardened Mode

Us­ing the afore­men­tioned del­e­gates to de­cou­ple your code has one huge ad­van­tage: it makes au­to­mat­ed test­ing eas­i­er.

I feel like unit test­ing is one of the most over­looked tasks dur­ing game de­vel­op­ment. With Cook for the Gi­ant (VR), not writ­ing unit tests from the start end­ed up be­ing my big­gest re­gret:

For ev­ery change I made on the GameM­o­de, I would need to start the game, load a lev­el, play the lev­el to the end (which is time-con­strained, as the time to read in a giv­en lev­el is fixed) and then re­play the lev­el to check what hap­pens when you fail it, re­play the lev­el to ace it, delet­ing the save file be­fore­hand to make sure the gold medal spawn an­i­ma­tion is run cor­rect­ly and so many more com­bi­na­tions of in­ter­act­ing events. Ful­ly test­ing the game with ev­ery edge case that could po­ten­tial­ly cause the game to crash would eas­i­ly take half an hour or more.

Go fig­ure, I didn’t. As a re­sult, you can check old­er ver­sions of the game to find game-crash­ing bugs, which have been added com­pared to the ver­sion be­fore.

The game log­ic is com­plex enough that you can nev­er be sure you did not break any­thing while fix­ing some­thing else. This is a big rea­son for why I love unit test­ing, you save time en­sur­ing parts of your game still work as they did be­fore.

Back to del­e­gates and de­cou­pling: as­sume you want to test the above lever. To test whether some­thing caused the lever to flip in the orig­i­nal code, you need a valid GameM­o­de. And how would you as­sure the lever has been flipped? In this case by check­ing which mode is set in the GameM­o­de. Set­ting the mode will cause the GameM­o­de to hide some ac­tors, un­hid­ing oth­ers… oh, you need all of those, too! Just to test a lever.
The test may fail, be­cause your test lev­el is miss­ing an Ac­tor that the GameM­o­de needs when chang­ing the GameM­o­de, which is not at all what you want­ed to test in the first place!

Af­ter refac­tor­ing the code to use del­e­gates, you mere­ly need an emp­ty lev­el with your lever ac­tor in it, bind the del­e­gate from the unit test and then ex­e­cute the code to cause the lever to flip. Then check if the del­e­gate has been called. You are test­ing ex­act­ly what you need, not more, not less, al­low­ing you to fo­cus on one thing on­ly.

More Modes

In Wastepa­perbin VR we came across the prob­lem of want­ing to sup­port dif­fer­ent sets of game rules, an ar­cade-like mode and a cam­paign-like mode. If you put the game log­ic of both in­to one GameM­o­de, you will end up with code clut­ter like this in many func­tions:

if(Mode == KingOfTheHill) {
    // do one thing
} else {
    // do another thing
}

If in­stead you split in­to two GameM­o­des, you will end up hav­ing to `Open­Lev­el`, which re­sults in an­noy­ing load­ing times if you are reusing the same lev­els. We cur­rent­ly solved this by in­tro­duc­ing al­ways load­ed stream­ing lev­els whose ALevelScrip­tAc­tor sub­class­es con­tain the game log­ic. This again goes against the “as if client and serv­er are sep­a­rat­ed” rule, which makes me be­lieve there is a bet­ter way. Po­ten­tial­ly mov­ing the game rules in­to an in­de­pen­dent in­vis­i­ble ac­tor which is spawned from the GameM­o­de is a bet­ter so­lu­tion.

Conclusion

This ar­ti­cle is meant to make you think: When­ev­er you need to use the GameM­o­de class from with­in an­oth­er ac­tor, maybe you are do­ing some­thing the wrong way around. In­stead, think of the GameM­o­de as be­ing a chess play­er, that moves the pieces around and re­acts to oth­er pieces be­ing moved rather than the pieces them­selves know­ing the game and act­ing ac­cord­ing­ly. It would be a re­al­ly stress­ful game if you had all the pieces scream­ing at you all the time.

Takeaways

  • GameM­o­de con­tains your games rules
  • De­cou­pling means mak­ing code in­de­pen­dent of oth­er code
  • De­cou­pling your code can help with com­pile times
  • De­cou­pled code can be test­ed more eas­i­ly
  • De­cou­pled code can be reused more eas­i­ly
  • Avoid `Get­World()->GetAu­thGameM­o­de()` to force your­self to de­cou­ple your Ac­tors from GameM­o­de
  • Del­e­gates can be used to make your code more re­us­able
  • Write Tests to save time en­sur­ing your code keeps do­ing what it is meant to do af­ter changes

Discuss

Join the dis­cus­sion over on