Systems
Table of contents
Explanation
Systems are small but precise units of work, work that's mainly about processing game world data using queries. These queries are processed using iterators, and that makes queries the whole point of systems ergonomics since they simplify both computational and comprehensive complexity and make that part of game logic really easy to reason about, which in turn makes adding, removing and changing game logic easy and quicker than with monolithic code or features highly dependent on each other, scattered around the codebase.
Types of systems
There are few types of systems, but the ones that you will mostly use are stateless (lambdas/functions) and stateful (classes) systems.
Stateless systems
These are the most common ones. Stateless systems are just lambdas/functions that expect reference
to USystemsWorld
to operate on it. Stateless systems do not require any internal state
to operate.
void MovementSystem(USystemsWorld& Systems)
{
const auto* Settings = Systems.Resource<USettings>();
if (IsValid(Settings) == false)
{
return;
}
const auto TimeScale = Settings->TimeScale;
const auto DeltaTime = Systems.GetWorld()->GetDeltaSeconds() * TimeScale;
for (auto& QueryItem : Systems.Query<UVelocityComponent>())
{
auto* Actor = QueryItem.Get<0>();
const auto* Velocity = QueryItem.Get<1>();
const auto Position = Actor->GetActorLocation();
Actor->SetActorLocation(Position + Velocity->Value * DeltaTime);
}
}
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UVelocityComponent>();
Systems.InstallLambdaSystem(MovementSystem, FInstallSystemOptions("Movement"));
});
}
}
Stateful systems
Stateful systems are useful in cases where system requires some internal state that cannot be shared with other systems via resources (for example internal cache that is used to track changes in the system across multiple game ticks).
UCLASS()
class EXAMPLE_API ULogBirdsNumberChangeSystem : public USystem
{
GENERATED_BODY()
public:
virtual void Run(USystemsWorld& Systems) override;
UPROPERTY()
uint32 LastCount = 0;
};
void ULogBirdsNumberChangeSystem::Run(USystemsWorld& Systems)
{
Super::Run(Systems);
if (Systems.ComponentsDidChanged<UBirdComponent>() == false)
{
return;
}
const auto Number = static_cast<int>(Systems.Query<UBirdComponent>().Count());
const Difference = Number - this->LastCount;
this->LastCount = Number;
if (Difference > 0)
{
UE_LOG(LogTemp, Warning, TEXT("Added %i birds"), Difference);
}
else if (Difference < 0)
{
UE_LOG(LogTemp, Warning, TEXT("Removed %i birds"), -Difference);
}
}
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UBirdComponent>();
Systems.InstallSystem<ULogBirdsNumberChangeSystem>(FInstallSystemOptions("LogBirdsNumberChange"));
});
}
}
Problems that it solves
There is very common (bad) practice in the industry that we make a game prototype, then we build actual game on top of that prototype codebase, instead of treating it purely as a reference for an actual, properly implemented solutions in actual project - this can and will quickly lead to poisoned codebase months and years later. It is really hard to break that habit, mostly in professional game development because time budget and famous last words such as "we will refactor that later", but that "later" do not come as soon as we would like, making us having a weak foundation and we build new features on top of that weak foundation, basically playing Jenga with our codebase, codebase which parts of will fall later as the project grows, causing The Fuck Cascade TM. and frustration later on - we can do better than that.
Systems Architecture helps with that by forcing simple and modular codebase from the get go, making switching from prototype to actual implementation a matter of just swapping prototype systems with actual properly implemented systems, sometimes not even having to change them at all.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX