[1/50] Data Driven Gameplay with Unreal Engine
![[1/50] Data Driven Gameplay with Unreal Engine](/content/images/size/w960/2023/05/Untitled.jpeg)
At Lost Ferry, we're fans of programming data-driven game-play. This approach involves splitting the functional behavior of a mechanic from the data it operates on. The programmer creates generic systems while the designers focus on tweaking the data that drive these systems.
For example, consider the following class representing a bullet that will cause damage to any actor it collides with but the magnitude of damage and subsequent visual effects are different for different actors it can collide with.
class AProjectile : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="LostFerry")
FText Name;
UPROPERTY(BlueprintReadWrite, Category="LostFerry")
int StaticMeshDamage;
UPROPERTY(BlueprintReadWrite, Category="LostFerry")
int PawnDamage;
UPROPERTY(BlueprintReadWrite, Category="LostFerry")
int PhysicsBodyDamage;
...
// Or use TMap<T, int> or other representation of actor type damage.
}
The designer can sub-class the AProjectile
actor to create different kinds of projectiles with different hit damage and other properties. Say, there are 10 different kinds of bullets in the game, then the designer creates 10 different sub-classes.
But this approach can get unwieldy as the volume and variety of the content increases. The individual items get tedious to manage and track as the game evolves. Especially, for content-types of which there can be hundreds of items like inventory. Updates and alterations also get tedious, for example, when a new class of actor needs to be tracked separately for damage.
We could refactor this using UDataTable and FGameplayTag.
USTRUCT(BlueprintType)
struct FProjectileRecord : public FTableRowBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="LostFerry")
TMap<FGameplayTag, int> TagDamage;
}
The projectile class can be changed to
class AProjectile : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, Category="LostFerry")
FText Name;
UPROPERTY(BlueprintReadWrite, Category="LostFerry", meta=(RowType="ProjectileRecord"))
FDataTableRowHandle DamageData;
Now when the projectile collides with the any actor, it does not have to check the type of the actor instead it could query the FGameplayTag
associated with it and look-up the DamageData
for the magnitude of damage to apply (if any) for that particular tag.
The designers too can work independently with the backing files (CSV, JSON etc.) of the FProjectileRecord
data table in their favourite text/spreadsheet editor and import the same in the engine when ready. Adding new actors is simple as adding tags to the DamageData
row and not all rows need to have all the tags either.
With the symbiotic relationship between C++ and Blueprints[1], Unreal already affords the developers some nifty tooling to kick start with data-driven gameplay. Adding FGameplayTag and UDataTable to the mix enables more flexibility in designing data-drive game-play systems.
Unable to locate the link/video where this term was originallt used to describe the relationship between Blueprints and C++ but felt pretty apt. ↩︎