2 | Oddsparks Architecture - MassiveMiniteam/OddModKit GitHub Wiki

Disclaimer

Please keep in mind that this project is a real world game project with 20+ people working on it for more than 3 years. It is messy in some parts. You will be fine.

Loc & LocCore module

We split the project into two core modules, LocCore and Loc. LocCore has the core code of the Lockstep (see below), Serialization and Networking. "Loc" was the project and working title before we decided on "Oddsparks". It is not trivial to rename an unreal project so the name stayed.

Naming conventions

https://github.com/Allar/ue5-style-guide

Lockstep

Oddsparks is using a deterministic Lockstep. We separate everything between the Simulation and Presentation. In multiplayer all cients receive the same inputs of all clients and simulate the next frame. Since it is deterministic, all clients calculate the same result. That means no game objects need to be synchronized - only the initial savegame and a command for each frame. To be deterministic across all architectures, floating point operations are prohibited. Everything has to be deterministic meaning randoms need to be correctly seeded and the simulation cannot operate on non simulation data (like reading mouse input, animation notifies, timers, etc.). For floating point operations there is a deterministic fixed point implementation Fixed and Fixed64.

Simulation

The Simulation ticks with fixed 10 fps. For a frame to be allowed to tick, it needs a command from all clients. It will halt the simulation until that is the case. The clients send their commands to the hosting client which distributes them to all clients. The simulation has no visuals and will always run independet of where the player is (except regarding world generation). The simulation actually distributes the single lockstep frame (ticking all SimulationActors) over the 100ms. In singleplayer the subticks is based on the current framerate, in multiplayer its 6 subticks.

Presentation

The Presentation is not fixed and runs with the target framerate (normally 60fps or vsynced). It will interpolate the data from the Simulation to make movements smooth. The presentation will only be loaded around the players and handle the visual representation of the simulation objects. Meaning meshes, animations, vfx, sfx etc.

FFixed & FFixed64

Provides floating point operations on an integer. Internally a 32bit integer (16.16 bit) for Fixed and 64 bit integer for Fixed64 (32.32 bit).

Commands

A command is used to send inputs from the player to the simulation (and gets distributed to all the clients). A command is scheduled 3 frames (300ms) into the future so there is always a delay of at least 300ms. An example command is transfering an item from one inventory to another or building a sawbench. The command needs to input all necessary data like inventory to pay the costs from, position (as FIntVector), rotation etc.

Desyncs

A desync occurs when two clients game state desync from each other due to not beeing deterministic. THe c++ part of the game state will be hashed in fragments each frame and compared to each clients frame. If they mismatch, a desync occurs and the offending client will be kicked. Currently there is no way to hash blueprint (and as such mod properties) so if you desync the game via a mod, you will notice it as soon as you spawn or despawn something differently or for example roll the next random etc.

Simulation vs Presentation

We also split all actors into Simulation and Presentation. A sawbench for example has a simulation actor BP_S_Sawbench subclass of GridObject and BP_P_Sawbench subclass of PresentationActor. Presentation actors will be spawned and despawned based on distance to players (some are pooled).

SimulationActor & SimulationActorComponent

All actors in the simulation need to derrive from SimulationActor. It has implementable events for OnSimulationPreBegin, OnSimulationBegin, OnTickSimulaion and OnSimulationEnd. It references the (optional) presentation actor class.

All actor components in the simulation need to derrive from SimulationActorComponent. It has the same events as the SimulationActor.

PresentationActor & PresentationActorComponent

Visual repreresentations of simulation actors need to derrive from PresentationActor and components from PresentationActorComponent. Both have events for OnPresentationBegin and OnPresentatonEnd.

SimulationScheduler

Schedules commands and handles the distribution. It also manages the fixed tickrate of 10fps.

SimulationManager

Manages the SimulationActors, registeres, deregisteres and ticks them.

Serialization & Savegame

All properties in the simulation need to be marked as "SaveGame" to make it saved in the savegame. All command properties also needs to be marked as SaveGame otherwise it will not properly serialize the variables and desync. The savegame only saves the SimulationActors and SimulationObjects. It is compressed. All savegames have a "_meta" equivalent to save metadata like duration and show it in the savegame list.

Input

Inputs are handle with DataAssets InputAction. It provides events OnInputActionStateChanged and OnInputAxisStateChanged. Input actions are remappable.

InputContexts

The InputContextSubsystem handles a stack of InputContainers that contains one ore more instances of InputContexts. You can push and pop InputContexts or containers. To handle the train building for example, the container contains a move InputContext for moving the player, the TrainPoleBuildInputContext and some other for camera movement etc.

UI

The class NavUserWidget handles our UI. Calling Show adds it to the viewport. NavUserWidgets provide a function OnGainFocus and OnProcessInput. NavVerticalBox, NavHorizontalBox and NavUniformGrid give their input further down to the children. This provides navigation and input with controller as well as mouse clicks with NavButton.

Localization

Localization is done via StringTables and the LocalizationDashboard. The game is setup to chunk and stage "Game" localization targets.