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 initialization
struct 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")));
// Visual
MuzzleFlash.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;
// Sounds
FireSound.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 initialization
struct 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 shake
if (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 delay
if (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 state
UpdateTiming();
}
# 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 specified
if (StartedFireSound.IsValidIndex(CurrentFireMode) && StartedFireSound\[CurrentFireMode\] !\= NULL)
{
UUTGameplayStatics::UTPlaySound(GetWorld(), StartedFireSound\[CurrentFireMode\], UTOwner, SRT\_AllButOwner);
}
// Play animation
if (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 specified
if (ContinuedFireSound.IsValidIndex(CurrentFireMode) && ContinuedFireSound\[CurrentFireMode\] !\= NULL)
{
UUTGameplayStatics::UTPlaySound(GetWorld(), ContinuedFireSound\[CurrentFireMode\], UTOwner, SRT\_AllButOwner);
}
// Play animation
if (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"