SO WHAT IS DATA DRIVEN DEVELOPMENT?
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);
}
}
}