SO WHAT IS DATA DRIVEN DEVELOPMENT?

TwitterYouTubeLinkInstagramLinkGitHub

Data Driven Development is making everything into it's own configurable file which you can then to use these configuration files to execute behaviours from a main / parent class.

For example, the only difference between a Rifle & Pistol is typically their looks, damage, recoil and ammo type. Instead of having 100 different blueprints or individual weapons, we can make these into smaller files. These are DataAssets in Unreal Engine, we can feed these DataAssets into our parent class to execute our desired behaviour. This still requires programming the functionality of each weapon type, such as a grenade launcher, RPGs, and standard rifled weaponry.

However, once the base functionality is programmed in it is much easier to implement new weaponry for designers and general creators within the game development pipeline as it is just configuring a new data asset. However, you must be careful too not make weaponry or items too similar as it could make everything feel like it's the same with a different font.

You can see in this image, that the following weapons are being configured by a data asset. I have hooked all the relevant data into the first person gun template which I can provide below. Using this type of design philosophy you can very quickly make variations of items and weaponry.

The data asset i will be using. Feel free to copy.

#pragma once


#include "CoreMinimal.h"

#include "Engine/DataAsset.h"

#include "DA_GunStats.generated.h"


/**

*

*/

//Inspired by Destiny's ammo system.

UENUM()

enum EAmmoType

{

   Normal,Special,Heavy

};


//Data driven gun creation

UCLASS()

class DATADRIVENFPT_API UDA_GunStats : public UDataAsset

{

   GENERATED_BODY()

public:

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   USkeletalMesh* GunMesh;

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   FString GunName;

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   TEnumAsByte<EAmmoType> AmmoType;

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   float FireRate;

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   float ReloadRate;

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   USoundBase* ReloadSound;

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   USoundBase* FireSound;

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   UAnimMontage* FireAnimation;

   UPROPERTY(EditAnywhere,BlueprintReadOnly, Category = "Gunplay")

   FVector MuzzleOffset;

};


The updated tutorial weapon component to use data assets

// Copyright Epic Games, Inc. All Rights Reserved.



#include "TP_WeaponComponent.h"

#include "DataDrivenFPTCharacter.h"

#include "DataDrivenFPTProjectile.h"

#include "GameFramework/PlayerController.h"

#include "Camera/PlayerCameraManager.h"

#include "Kismet/GameplayStatics.h"

#include "EnhancedInputComponent.h"

#include "EnhancedInputSubsystems.h"

#include "Animation/AnimInstance.h"

#include "DataAssets/DA_GunStats.h"

#include "Engine/LocalPlayer.h"

#include "Engine/World.h"


// Sets default values for this component's properties

UTP_WeaponComponent::UTP_WeaponComponent()

{

   // Default offset from the character location for projectiles to spawn

   //MuzzleOffset = FVector(100.0f, 0.0f, 10.0f);

}



void UTP_WeaponComponent::Fire()

{

   // Added check for data asset to return incase GunConfig is null.

   if (Character == nullptr || Character->GetController() == nullptr || GunConfig == nullptr)

   {

      return;

   }


   // Try and fire a projectile

   if (ProjectileClass != nullptr)

   {

      UWorld* const World = GetWorld();

      if (World != nullptr)

      {

         APlayerController* PlayerController = Cast<APlayerController>(Character->GetController());

         const FRotator SpawnRotation = PlayerController->PlayerCameraManager->GetCameraRotation();

         // MuzzleOffset is in camera space, so transform it to world space before offsetting from the character location to find the final muzzle position

         const FVector SpawnLocation = GetOwner()->GetActorLocation() + SpawnRotation.RotateVector(GunConfig->MuzzleOffset);

  

         //Set Spawn Collision Handling Override

         FActorSpawnParameters ActorSpawnParams;

         ActorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButDontSpawnIfColliding;

  

         // Spawn the projectile at the muzzle

         World->SpawnActor<ADataDrivenFPTProjectile>(ProjectileClass, SpawnLocation, SpawnRotation, ActorSpawnParams);

      }

   }

  

   // Try and play the sound if specified

   if (GunConfig->FireSound != nullptr)

   {

      UGameplayStatics::PlaySoundAtLocation(this, GunConfig->FireSound, Character->GetActorLocation());

   }

  

   // Try and play a firing animation if specified

   if (GunConfig->FireAnimation != nullptr)

   {

      // Get the animation object for the arms mesh

      UAnimInstance* AnimInstance = Character->GetMesh1P()->GetAnimInstance();

      if (AnimInstance != nullptr)

      {

         AnimInstance->Montage_Play(GunConfig->FireAnimation, 1.f);

      }

   }

}


bool UTP_WeaponComponent::AttachWeapon(ADataDrivenFPTCharacter* TargetCharacter)

{

   Character = TargetCharacter;


   // Check that the character is valid, and has no weapon component yet

   if (Character == nullptr || Character->GetInstanceComponents().FindItemByClass<UTP_WeaponComponent>())

   {

      return false;

   }


   // Attach the weapon to the First Person Character

   FAttachmentTransformRules AttachmentRules(EAttachmentRule::SnapToTarget, true);

   AttachToComponent(Character->GetMesh1P(), AttachmentRules, FName(TEXT("GripPoint")));


   // add the weapon as an instance component to the character

   Character->AddInstanceComponent(this);


   // Set up action bindings

   if (APlayerController* PlayerController = Cast<APlayerController>(Character->GetController()))

   {

      if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))

      {

         // Set the priority of the mapping to 1, so that it overrides the Jump action with the Fire action when using touch input

         Subsystem->AddMappingContext(FireMappingContext, 1);

      }


      if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerController->InputComponent))

      {

         // Fire

         EnhancedInputComponent->BindAction(FireAction, ETriggerEvent::Triggered, this, &UTP_WeaponComponent::Fire);

      }

   }


   return true;

}


void UTP_WeaponComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)

{

   if (Character == nullptr)

   {

      return;

   }


   if (APlayerController* PlayerController = Cast<APlayerController>(Character->GetController()))

   {

      if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))

      {

         Subsystem->RemoveMappingContext(FireMappingContext);

      }

   }

}