Sunday, July 27, 2025

Developer Insights: From Menu to Match

 One critical component of The Laser Games which was worked on recently is a series of functions that loads up games. Sounds simple? Actually, we designed it internally to provide dynamic functionality, a component of The Laser Game's core customization design. Read on to learn what goes on underneath the loading screen!


 

 If you're new here, The Laser Games is a casual shooter game based on real life laser tag. The Laser Games uses Unreal Engine 5. Play the demo now!

During the prototype phase of The Laser Games, it became evident early on how many parameters can be tweaked for any game- everything from the match length, to what type of game is being played, number of bots, number of teams, how many points each sensor rewards, even the sounds to play. All of these parameters can dictate the experience of each individual game, so by design we want The Laser Games to support tweaking these parameters as needed. These parameters are stored in various classes throughout the code, which are typically actor component data classes (actor components are objects that can attach to the actor class, which is the base class for objects placed in a UE level; being a data class, these components are designed to hold data. We can discuss the design decision to use actor components in its own blog post).

 

So, let's run through the process from starting a game in the main menu, to playing it. Once you get to the game setup and creation menu, you're presented with numerous settings to tweak. You need to select a map to play, so at the top of the menu is a tab for "Maps". Clicking it shows a simple interface to select the map and gametype to play, and an "add map" button. Select one of two maps currently in the game, and either team tag match (TTM) or FFA tag match, and click add map. Once your game config is all set up, click "create game" at the bottom of the window and wait a second or so for the map to load in. Assuming there's no bugs, after loading you should see the map, and either be prompted to choose a team on the TTM, or would already be spawned in.

Now, a little about Unreal's design architecture: UE stores currently loaded actors in levels, which only one main level is loaded at any given time (there are sublevels which we will mention later). Each level also contains special actors necessary for normal gameplay and designed for multiplayer functionality- in particular the gamemode, gamestate,  playercontroller and playerstate for each player, and a few others. 

Typically, our parameters we set from the main menu might get read from the gamemode, but what's the issue with this?

  • Most actors don't persist across levels, and neither do any stored variables. There are exceptions, but they don't apply here.
  • The main menu uses a different gamemode than the gameplay gamemode. The main menu's gamemode handles special functionality for the main menu not needed elsewhere.
  •  Each level has a default gamemode. We want to be able to support different ways to play, and the gamemode is the core of gameplay logic in the server, so we want a different solution.

What is our solution for this? We created an actor called a gametype, which is the actor responsible for gameplay logic specific to what gametype is being played, including managing parameters (while the gamemode handles global functionality like players joining). Upon level loading, the gametype is searched for and initialized, which we will showcase later.

Within the gametype is a data actor component that stores the necessary data, as a component for various reasons:

  • separate the logic of gameplay with the data storage and management
  • able to replace the data for different configs without replacing the actor

So, let's refer to this data component as the gametype data. Starting in the main menu, we spawn a dummy actor. Upon clicking add map, we create a gametype data into the dummy actor, and set its parameters to your settings (most notably right now, a FFA tag for the free-for-all mode).


When you click create game, this is where the interesting stuff happens. We need some way to save our gametype data so it can be loaded back when the map loads. Unreal Engine features an object known as a savegame, which provides the ability to save and load data including serializable objects into memory and disk. We use this savegame to save and load the data between levels, but one issue: actor components can't simply be saved.

How to fix? I mentioned it previously- serialization. All objects in UE support some form of serialization, because in the editor they need to be saved. So we just implement serialization for the gametype data, right? Yes, but what if we want a different data component to be saveable, like for a weapon or player? To support future classes which may need serialization, we created an interface, with functions designed to read and write serialized data. Otherwise, we use the engine's own serialization, and to write serialized data, we copy all properties from the serialized data to the object itself. After some troubleshooting, this method works pretty well- just have to make sure all the data types serialize correctly.

Alright, so we can serialize our gametype data, but what else do we need? Well, what map and what gametype will be loaded? To handle this, we've got some lists that get saved and loaded alongside each gametype's data. Regarding the gametype, The Laser Games is designed to work with one gametype at any given time, yet levels can support multiple gametypes (the current example being the singleplayer challenge, a separate gametype from multiplayer tag match). Additionally, the singleplayer challenge contains sensors that needed to be loaded into the map. This is where sublevels come in.

Sublevels are levels in UE which are loaded on top of a persistent level. The actors like gamemode and gamestate don't get loaded, but actors placed into the sublevel are. The Laser Games uses this to support multiple gametypes- each gametype separate from the default is stored in its own sublevel. We store the base map and the sublevel which to load in, then after the main map is loaded in we read which sublevel to load. Once the sublevel is loaded, we need to know which gametype to use- we do that by searching the gametypes loaded currently, and looking for the one not in the persistent level, if any (this gametype is considered the default, and is marked with a boolean). Once here, we just initialize the gametype, load up the gametype data which was set, and then the level is ready to play.

So, what's going on again? From the main menu, you customize a gametype data component. Upon clicking create game, the list of maps, gametypes, and their corresponding data is saved into a savegame, where the data uses our serialization interface for this to happen. Then the map is loaded. After, the gamemode reads which gametype to load, and loads its sublevel if applicable. Finally, it initializes the gametype, including loading the gametype data from the savegame, before some final initialization and then spawning in players.

In the end is a system that is designed to save and load objects easily into a savegame, which is to be loaded on the other end when the game starts. As we continue developing The Laser Games, we will be using the same structure to, for example, save data for players, such as their max speed, to be loaded and reused in different maps and gametypes. While this specific functionality isn't yet implemented, our current system of loading gametype data will form the foundation for more systems of saving and loading data.


This system is also mostly compatible with config files, with a few slight differences with referencing objects. This is good as we can reuse the same serialization and saving/loading system, for dedicated servers too- the end goal is to let players create config files that define different gametypes and the data, so dedicated servers can run with their own custom settings.

Overall, this system is just one step in building out The Laser Games to be more than just a shooter game. We want players to be able to make their games their own, with their own settings to apply to, and we accomplished this with a system of saving/loading, serialization, and sublevels, which is designed to be expanded upon.

I hope you enjoyed this deep dive into the game creation backend, feel free to ask any questions in the comments or elsewhere, like the discord server or the steam discussions!  And I hope you will come back the next time where we do a deep dive- maybe we'll talk about all the customizable settings in a game, or how the match flow works, or something else interesting to explore in The Laser Games!

No comments:

Post a Comment