Resources
Table of contents
Explanation
Resource in context of Systems Architecture is any global object that all systems should be able to access (though resources definitely aren't singletons! - they share their lifetime with owning systems world). Objects such as:
-
Configuration/settings data
For example systems run criteria that tells the conditions under what certain systems can run. Or Data Assets / Data Tables passed from game instance to systems world.
-
Cached data shared between systems
Do you have spatial partitioning system that builds RTree for world actors to speed up spatial queries, and other systems in need to find the closest actor to their location? You definitely put that data in a resource object and make other systems query and read that resource!
-
Game managers
Common Unreal Engine game development pattern is to create work units called "managers" that manage certain state. For some solutions, especially when dealing with third-party plugins, we have to still have access into these managers in our systems - definitely put them in systems world resources.
UCLASS(BlueprintType)
class EXAMPLE_API USettings : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere)
float TimeScale = 1;
};
void UExampleGameInstance::Init()
{
Super::Init();
auto* Subsystem = USystemsSubsystem::Get(GetWorld());
if (IsValid(Subsystem))
{
Subsystem->AcquireSystemsWorld(FName(),
[&](auto& Systems)
{
Systems.RegisterComponent<UVelocityComponent>();
// USettings asset set up in editor.
Systems.InstallResourceRaw(this->Settings);
Systems.InstallLambdaSystem(MovementSystem, FInstallSystemOptions("Movement"));
});
}
}
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);
}
}
Proxy Resources
Along the journey you might find yourself in a need for having to use wrapper-like resources that themselves are not unique types but they wrap unique type resource - wrappers/containers for some inner data. For that scenario you will use concept called Proxy Resources, which allows you to install resource wrapper, which of type itself is not used, instead you provide its inner type used for queries.
First we define proxy resource:
UCLASS()
class EXAMPLE_API UInventoryWrapper : public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY()
UInventory* GeneratedInventory = nullptr;
};
Then we install it in Systems World:
auto* Wrapper = NewObject<UInventoryWrapper>(Systems, UInventoryWrapper::StaticClass());
Systems.InstallProxyResource<UInventory>(Wrapper, [](auto* Wrapper) { return Wrapper->GeneratedInventory; });
And finally we access it:
Systems.ProxyResource<UInventory>()->AddItem(FItem{FItemType::Sword});
Problems that it solves
While in most cases all what you do is you process game world queries, sometimes you require to access some configuration data, or cache some data for use by many other systems, or you just need to interact with third-party game managers - these are useful use cases and Systems Architecture doesn't get rid of them, rather make their use more ergonomic, yet not giving them global lifetime like with dangerous singletons.
Documentation built with Unreal-Doc
v1.0.8 tool by PsichiX