UT3 Weapons Tutorial - Enforcer - Epic Wiki
# UT3 Weapons Tutorial - Enforcer
# Contents
- 1 UT3 Weapons Tutorial - Enforcer
- 1.1 Requirements
- 1.2 Features
- 1.3 Notes
- 1.4 Contact
- 1.5 Enforcer
- 1.6 Implementation
- 1.6.1 Creating Enforcer Classes
- 1.6.2 Setting up weapon asset references
- 1.6.3 Weapon firing properties
- 1.6.4 Camera Shake
- 1.6.5 Adding custom weapon firing state
- 1.6.6 Implementing burst firing state
- 1.6.7 Customizing the burst properties per-weapon
- 1.6.8 Burst animations & sounds
- 1.6.9 Dual weapons
- 1.7 Source Code
# UT3 Weapons Tutorial - Enforcer
This tutorial will show you how to create Enforcer from Unreal Tournament 3 using C++ only.
NOTE:
- UT has just upgraded to UE 4.2.1, this tutorial will be verified for compatibility shortly.
# Requirements
Some existing C++ & Unreal Engine knowledge is needed.
- Engine version: 4.2
- Skill level: intermediate
- Unreal Tournament commit: 9fe9fc679a26a0ea816e9fd3db080255394bf4dc
# Features
- Firing multiple projectiles in a burst
- Dual weapons
# Notes
- When overriding a function in subclass always add a definition to the header file as well.
- Functions with BlueprintNativeEvent attribute generate additional virtual function called "FunctionName_Implementation". Override the _Implementation one instead.
- Code snippets are located in grey expandable boxes. Click Expand on the right to see the code.
- Yellow lines in code snippets highlight only the code that needs to be changed.
# Contact
--Neai (talk) 17:49, 30 June 2014 (UTC)
# Enforcer
We'll start by defining what features we want to implement:
# Primary Fire
- Standard hitscan
# Secondary Fire
- Fire burst of 3 hitscan shots
# Implementation
To implement such projectile weapon we should create subclasses of AUTWeapon, UUTWeaponStateFiring, UUTDamageType and AUTWeaponAttachment.
# Creating Enforcer Classes
Create classes for all the elements of Enforcer:
- Weapon
- Create subclass of AUTWeapon called AUTWeap_Enforcer
- 3rd person weapon attachment
- Create subclass of AUTWeaponAttachment called AUTAttachment_Enforcer
- Burst firing mode
- Create subclass of UUTWeaponStateFiring called UUTWeaponStateFiringBurst
- Damage Types
- Create subclass of UUTDamageType called UTDmgType_Enforcer
- Create subclass of UUTDamageType called UTDmgType_DualEnforcers
# Setting up weapon asset references
We will link all the parts together, add asset references and setup basic properties.
AUTWeap_Enforcer.cpp - Setup asset references
#include "UnrealTournament.h"
#include "UTWeap_Enforcer.h"
#include "UTAttachment_Enforcer.h"
#include "UTDmgType_Enforcer.h"
#include "UTDmgType_DualEnforcer.h"
AUTWeap_Enforcer::AUTWeap_Enforcer(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
// Structure to hold one-time initializationstruct FConstructorStatics{ConstructorHelpers::FObjectFinder<USkeletalMesh\> SkeletalMesh;ConstructorHelpers::FObjectFinder<UAnimBlueprintGeneratedClass\> AnimBlueprintGeneratedClass;ConstructorHelpers::FObjectFinder<UParticleSystem\> MuzzleFlash;ConstructorHelpers::FObjectFinder<USoundCue\> FireSound0;ConstructorHelpers::FObjectFinder<USoundCue\> BurstFireSound1;ConstructorHelpers::FObjectFinder<UAnimMontage\> FireAnimation0;ConstructorHelpers::FObjectFinder<UAnimMontage\> BurstFireAnimation1;ConstructorHelpers::FObjectFinder<UAnimMontage\> BringUpAnim;ConstructorHelpers::FObjectFinder<UAnimMontage\> PutDownAnim;ConstructorHelpers::FObjectFinder<UParticleSystem\> FireEffect0;ConstructorHelpers::FObjectFinder<USoundCue\> PickupSound;FConstructorStatics(): SkeletalMesh(TEXT("SkeletalMesh'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Meshes/SK\_WP\_Enforcers\_1P.SK\_WP\_Enforcers\_1P'")), AnimBlueprintGeneratedClass(TEXT("AnimBlueprintGeneratedClass'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Anims/Enforcer\_AnimBP.Enforcer\_AnimBP\_C'")), MuzzleFlash(TEXT("ParticleSystem'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Effects/P\_WP\_Enforcers\_MuzzleFlash.P\_WP\_Enforcers\_MuzzleFlash'")), FireSound0(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Audio/CUE/A\_Weapon\_Enforcer\_Fire\_Cue.A\_Weapon\_Enforcer\_Fire\_Cue'")), BurstFireSound1(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Audio/CUE/A\_Weapon\_Enforcer\_AltFire\_Cue.A\_Weapon\_Enforcer\_AltFire\_Cue'")), FireAnimation0(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Anims/Enforcer\_ShootSingle\_Montage.Enforcer\_ShootSingle\_Montage'")), BurstFireAnimation1(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Anims/Enforcer\_ShootSecondary\_Montage.Enforcer\_ShootSecondary\_Montage'")), BringUpAnim(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Anims/Enforcer\_Equip.Enforcer\_Equip'")), PutDownAnim(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Anims/Enforcer\_Putdown.Enforcer\_Putdown'")), FireEffect0(TEXT("ParticleSystem'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Effects/P\_WP\_Enforcers\_Tracer.P\_WP\_Enforcers\_Tracer'")), PickupSound(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Pickups/Audio/Weapons/Cue/A\_Pickup\_Weapons\_Enforcer\_Cue.A\_Pickup\_Weapons\_Enforcer\_Cue'")){}};static FConstructorStatics ConstructorStatics;Mesh\-\>SkeletalMesh \= ConstructorStatics.SkeletalMesh.Object;Mesh\-\>AnimBlueprintGeneratedClass \= ConstructorStatics.AnimBlueprintGeneratedClass.Object;Mesh\-\>RelativeLocation \= FVector(\-30, 0, \-0);TSubobjectPtr<UParticleSystemComponent\> MuzzleComponent \= PCIP.CreateDefaultSubobject<UParticleSystemComponent\>(this, TEXT("Enforcer-Muzzle"));MuzzleComponent\-\>Template \= ConstructorStatics.MuzzleFlash.Object;MuzzleComponent\-\>AttachTo(Mesh, FName(TEXT("MuzzleFlashSocket")));// VisualMuzzleFlash.SetNumZeroed(2);MuzzleFlash\[0\] \= MuzzleComponent;MuzzleFlash\[1\] \= MuzzleComponent;AttachmentType \= AUTAttachment\_Enforcer::StaticClass();BringUpAnim \= ConstructorStatics.BringUpAnim.Object;PutDownAnim \= ConstructorStatics.PutDownAnim.Object;FireAnimation.SetNumZeroed(1);FireAnimation\[0\] \= ConstructorStatics.FireAnimation0.Object;FireEffect.SetNumZeroed(2);FireEffect\[0\] \= ConstructorStatics.FireEffect0.Object;FireEffect\[1\] \= ConstructorStatics.FireEffect0.Object;// SoundsFireSound.SetNumZeroed(1);FireSound\[0\] \= ConstructorStatics.FireSound0.Object;PickupSound \= ConstructorStatics.PickupSound.Object;}
AUTAttachment_Enforcer.cpp - Setup asset references
AUTAttachment_Enforcer::AUTAttachment_Enforcer(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP){
// Structure to hold one-time initializationstruct FConstructorStatics{ConstructorHelpers::FObjectFinder<USkeletalMesh\> SkeletalMesh;FConstructorStatics(): SkeletalMesh(TEXT("SkeletalMesh'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_Enforcers/Meshes/SK\_WP\_Enforcer\_3P\_Mid.SK\_WP\_Enforcer\_3P\_Mid'")){}};static FConstructorStatics ConstructorStatics;Mesh\-\>SkeletalMesh \= ConstructorStatics.SkeletalMesh.Object;}
# Weapon firing properties
We're going to use weapon properties from UT3. Set them in constructors:
AUTWeap_Enforcer.cpp - Add weapon firing properties to constructor
ProjClass.SetNumZeroed(0);FInstantHitDamageInfo PrimaryFireInfo;PrimaryFireInfo.Momentum \= 1000 \* UT3\_TO\_UT4\_SCALE;PrimaryFireInfo.Damage \= 20;PrimaryFireInfo.DamageType \= UUTDmgType\_Enforcer::StaticClass();FInstantHitDamageInfo SecondaryFireInfo;SecondaryFireInfo.Momentum \= 1000 \* UT3\_TO\_UT4\_SCALE;SecondaryFireInfo.Damage \= 20;SecondaryFireInfo.DamageType \= UUTDmgType\_Enforcer::StaticClass();InstantHitInfo.SetNumZeroed(2);InstantHitInfo\[0\] \= PrimaryFireInfo;InstantHitInfo\[1\] \= SecondaryFireInfo;FireInterval.SetNumZeroed(2);FireInterval\[0\] \= 0.5;FireInterval\[1\] \= 1.0;AmmoCost.SetNumZeroed(2);AmmoCost\[0\] \= 1;AmmoCost\[1\] \= 1;Ammo \= 20;MaxAmmo \= 50;FireOffset \= FVector(50, 0, 0);Group \= 4;
# Camera Shake
Add camera shake to AUTWeapon. If you already have added this code before, just add default properties for Enforcer.
AUTWeapon.h - Add camera shake properties and function
/\*\* delay between firing and camera shake being played \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")TArray<float\> CameraShakeDelay;/\*\* how strong camera shake should be \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")TArray<float\> CameraShakeScale;/\*\* camera shake type \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")TArray< TSubclassOf<class UCameraShake\> \> CameraShakeType;/\*\* Plays camera shake immediately \*/UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category \= "Weapon")void PlayCameraShake();
AUTWeapon.cpp - Add PlayCameraShake() implementation
void AUTWeapon::PlayCameraShake_Implementation()
{
if (UTOwner !\= NULL){AUTPlayerController\* PC \= Cast<AUTPlayerController\>(UTOwner\-\>Controller);if (PC !\= NULL){// Play camera shakeif (CameraShakeType.IsValidIndex(CurrentFireMode) && CameraShakeType\[CurrentFireMode\] !\= NULL && CameraShakeScale.IsValidIndex(CurrentFireMode)){PC\-\>ClientPlayCameraShake(CameraShakeType\[CurrentFireMode\], CameraShakeScale\[CurrentFireMode\]);}}}}
AUTWeapon.cpp - Override PlayFiringEffects() so it calls our PlayCameraShake() function
void AUTWeapon::PlayFiringEffects()
{
Super::PlayFiringEffects();// Play camera shake after optional delayif (CameraShakeDelay.IsValidIndex(CurrentFireMode) && CameraShakeDelay\[CurrentFireMode\] \> 0){GetWorldTimerManager().SetTimer(this, &AUTWeapon::PlayCameraShake, CameraShakeDelay\[CurrentFireMode\], false);}else{PlayCameraShake();}}
AUTWeap_Enforcer.cpp - Add CameraShake assets & default properties to constructor
struct FConstructorStatics{//...ConstructorHelpers::FClassFinder<UCameraShake\> CameraShakeType0;ConstructorHelpers::FClassFinder<UCameraShake\> CameraShakeType1;FConstructorStatics()//..., CameraShakeType0(TEXT("BlueprintGeneratedClass'/Game/RestrictedAssets/Blueprints/WIP/Nick/CameraAnims/Camerashake2.Camerashake2\_C'")), CameraShakeType1(TEXT("BlueprintGeneratedClass'/Game/RestrictedAssets/Blueprints/WIP/Nick/CameraAnims/Camerashake2.Camerashake2\_C'")){}};//...CameraShakeType.SetNumZeroed(2);CameraShakeType\[0\] \= ConstructorStatics.CameraShakeType0.Class;CameraShakeType\[1\] \= ConstructorStatics.CameraShakeType1.Class;CameraShakeDelay.SetNumZeroed(2);CameraShakeDelay\[0\] \= 0.f;CameraShakeDelay\[1\] \= 0.f;CameraShakeScale.SetNumZeroed(2);CameraShakeScale\[0\] \= 1.f;CameraShakeScale\[1\] \= 1.f;
# Adding custom weapon firing state
In the Enforcer's constructor we will disable creation of default weapon firing states and create our own ones instead.
AUTWeap_Enforcer.cpp - Create our own weapon firing states
AUTWeap_Enforcer::AUTWeap_Enforcer(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP
.DoNotCreateDefaultSubobject(TEXT("FiringState0"))
.DoNotCreateDefaultSubobject(TEXT("FiringState1"))
)
{
//...if (!GCompilingBlueprint){UUTWeaponStateFiring\* PrimaryState \= PCIP.CreateDefaultSubobject<UUTWeaponStateFiring, UUTWeaponStateFiring\>(this, FName(TEXT("Enforcer-WeaponState0")), false, false, false);if (PrimaryState){FiringState.Add(PrimaryState);FiringStateType.Add(PrimaryState\-\>StaticClass());}UUTWeaponStateFiringBurst\* AlternateState \= PCIP.CreateDefaultSubobject<UUTWeaponStateFiringBurst, UUTWeaponStateFiringBurst\>(this, FName(TEXT("Enforcer-WeaponState1")), false, false, false);if (AlternateState){FiringState.Add(AlternateState);FiringStateType.Add(AlternateState\-\>StaticClass());}}}
# Implementing burst firing state
Weapon firing works as follows (simplified):
- When user holds fire and weapon can fire, weapon goes to WeaponStateFiring state, WeaponStateFiring::BeginState() is called
- WeaponStateFiring::BeginState() fires a shot and starts a RefireCheckTimer. Weapon can't fire again nor be switched away until this timer finishes.
- When RefireCheckTimer times out, weapon checks its state. Firing ends when:
- There is a pending weapon change
- User no longer presses fire
- There is no more ammo
- Otherwise weapon fires again and timer is restarted.
To create a burst firing mode, we're going to count the shots and modify RefireCheckTimer so it will automatically fire again until burst completes.
UUTWeaponFiringStateBurst.h - Add burst count variables
int32 BurstRounds;int32 BurstRoundsFired;
UUTWeaponFiringStateBurst.cpp - Add default values for burst count variables in constructor
UUTWeaponStateFiringBurst::UUTWeaponStateFiringBurst(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP){
BurstRounds \= 3;BurstRoundsFired \= 0;}
UUTWeaponFiringStateBurst.cpp - Override FireShot() to increment burst rounds counter
void UUTWeaponStateFiringBurst::FireShot()
{
Super::FireShot();// Increment number of burst rounds fired++BurstRoundsFired;}
UUTWeaponFiringStateBurst.cpp - Override RefireCheckTimer() so it will automatically fire remaining burst rounds as long as there is ammo.
void UUTWeaponStateFiringBurst::RefireCheckTimer()
{
UE\_LOG(LogTemp, Warning, TEXT("%s : BurstRoundsFired:%d"), TEXT(\_\_FUNCTION\_\_), BurstRoundsFired);if (BurstRoundsFired < BurstRounds){if (!GetOuterAUTWeapon()\-\>HasAmmo(GetOuterAUTWeapon()\-\>GetCurrentFireMode())){GetOuterAUTWeapon()\-\>GotoActiveState();}else{FireShot();}}else{BurstRoundsFired \= 0;Super::RefireCheckTimer();}}
Notice that when burst is finished we reset the burst counter.
UUTWeaponFiringStateBurst.cpp - Override BeginState() to reset burst counter whenever weapon goes to firing state
void UUTWeaponStateFiringBurst::BeginState(const UUTWeaponState* PrevState)
{
BurstRoundsFired \= 0;Super::BeginState(PrevState);}
# Using faster FireInterval for burst firing
So far the burst works but we want to have a fast refire time for burst shots and longer delay between bursts.
UUTWeaponFiringStateBurst.h - Add BurstRefireRate variable
float BurstRefireRate;
UUTWeaponFiringStateBurst.cpp - Set default value for BurstRefireRate in constructor
BurstRefireRate \= 0.15;
UUTWeaponFiringStateBurst.cpp - Override UpdateTiming() to use custom shorter refire time if next shot is a part of burst
void UUTWeaponStateFiringBurst::UpdateTiming()
{
// TODO: we should really restart the timer at the percentage it currently is, but FTimerManager has no facility to do this// If the next round will be a part of this burst, change fire rate to burst rate, otherwise reset it to default.float NextRefireRate \= (BurstRoundsFired < BurstRounds)? BurstRefireRate: GetOuterAUTWeapon()\-\>GetRefireTime(GetOuterAUTWeapon()\-\>GetCurrentFireMode());GetOuterAUTWeapon()\-\>GetWorldTimerManager().SetTimer(this, &UUTWeaponStateFiring::RefireCheckTimer, NextRefireRate, true);}
UUTWeaponFiringStateBurst.cpp - Modify FireShot() to update refire timer after each shot
void UUTWeaponStateFiringBurst::FireShot()
{
Super::FireShot();// Increment number of burst rounds fired++BurstRoundsFired;// Burst fire uses different firing rate, update according to current stateUpdateTiming();}
# Customizing the burst properties per-weapon
Should we want to create a blueprint of this weapon to tweak the properties, we need to setup the properties in AUTWeap_Enforer. Weapon state objects are not supported by editor property GUI yet.
AUTWeap_Enforcer.h - Add properties to Enforcer
/\*\* Number of rounds to fire in a burst \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Enforcer")int32 BurstRounds;/\*\* Time between each round in a burst \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Enforcer")float BurstRefireRate;
AUTWeap_Enforcer.cpp - Set default properties in Enforcer constructor
AUTWeap_Enforcer::AUTWeap_Enforcer(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP
.DoNotCreateDefaultSubobject(TEXT("FiringState0"))
.DoNotCreateDefaultSubobject(TEXT("FiringState1"))
)
{
//...BurstRounds \= 3;BurstRefireRate \= 0.15;if (!GCompilingBlueprint){UUTWeaponStateFiring\* PrimaryState \= PCIP.CreateDefaultSubobject<UUTWeaponStateFiring, UUTWeaponStateFiring\>(this, FName(TEXT("Enforcer-WeaponState0")), false, false, false);if (PrimaryState){FiringState.Add(PrimaryState);FiringStateType.Add(PrimaryState\-\>StaticClass());}UUTWeaponStateFiringBurst\* AlternateState \= PCIP.CreateDefaultSubobject<UUTWeaponStateFiringBurst, UUTWeaponStateFiringBurst\>(this, FName(TEXT("Enforcer-WeaponState1")), false, false, false);if (AlternateState){AlternateState\-\>BurstRounds \= BurstRounds;AlternateState\-\>BurstRefireRate \= BurstRefireRate;FiringState.Add(AlternateState);FiringStateType.Add(AlternateState\-\>StaticClass());}}}
# Burst animations & sounds
We have an animation & firing sound that covers entire burst. Lets add a function that will play animation & sound when weapon first starts firing, and when it refires after completed firing sequence.
AUTWeapon.h - Add properties & functions
/\*\* AnimMontage to play when weapon initially starts firing \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")TArray<UAnimMontage\*\> StartedFireAnimation;/\*\* AnimMontage to play when weapon completes firing sequence and starts firing another one \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")TArray<UAnimMontage\*\> ContinuedFireAnimation;/\*\* Sound to play when weapon initially starts firing \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")TArray<USoundBase\*\> StartedFireSound;/\*\* Sound to play when weapon completes firing sequence and starts firing another one \*/UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")TArray<USoundBase\*\> ContinuedFireSound;/\*\* Called when weapon initially starts firing \*/UFUNCTION(BlueprintCallable, Category \= "Weapon")virtual void PlayStartedFiringEffects();/\*\* Called when weapon completes firing sequence and starts firing another one \*/UFUNCTION(BlueprintCallable, Category \= "Weapon")virtual void PlayContinuedFiringEffects();
AUTWeapon.cpp - Add PlayStartedFiringEffects() & PlayContinuedFiringEffects() implementation
void AUTWeapon::PlayStartedFiringEffects()
{
if (GetNetMode() !\= NM\_DedicatedServer && UTOwner !\= NULL){// try and play the sound if specifiedif (StartedFireSound.IsValidIndex(CurrentFireMode) && StartedFireSound\[CurrentFireMode\] !\= NULL){UUTGameplayStatics::UTPlaySound(GetWorld(), StartedFireSound\[CurrentFireMode\], UTOwner, SRT\_AllButOwner);}// Play animationif (StartedFireAnimation.IsValidIndex(CurrentFireMode) && StartedFireAnimation\[CurrentFireMode\] !\= NULL){UAnimInstance\* AnimInstance \= Mesh\-\>GetAnimInstance();if (AnimInstance !\= NULL){AnimInstance\-\>Montage\_Play(StartedFireAnimation\[CurrentFireMode\], 1.f);}}}}
void AUTWeapon::PlayContinuedFiringEffects()
{
if (GetNetMode() !\= NM\_DedicatedServer && UTOwner !\= NULL){// try and play the sound if specifiedif (ContinuedFireSound.IsValidIndex(CurrentFireMode) && ContinuedFireSound\[CurrentFireMode\] !\= NULL){UUTGameplayStatics::UTPlaySound(GetWorld(), ContinuedFireSound\[CurrentFireMode\], UTOwner, SRT\_AllButOwner);}// Play animationif (ContinuedFireAnimation.IsValidIndex(CurrentFireMode) && ContinuedFireAnimation\[CurrentFireMode\] !\= NULL){UAnimInstance\* AnimInstance \= Mesh\-\>GetAnimInstance();if (AnimInstance !\= NULL){AnimInstance\-\>Montage\_Play(ContinuedFireAnimation\[CurrentFireMode\], 1.f);}}}}
AUTWeapon.cpp - Modify OnStartedFiring_Implementation() & OnContinuedFiring_Implementation() to make them call our functions
void AUTWeapon::OnStartedFiring_Implementation()
{
PlayStartedFiringEffects();}
void AUTWeapon::OnContinuedFiring_Implementation()
{
PlayContinuedFiringEffects();}
# Dual weapons
TODO
# Source Code
Retrieved from "https://wiki.unrealengine.com/index.php?title=UT3_Weapons_Tutorial_-_Enforcer&oldid=10611"
![]()