UT3 Weapons Tutorial - Flak Cannon - Epic Wiki
# UT3 Weapons Tutorial - Flak Cannon
# Contents
- 1 UT3 Weapons Tutorial - Flak Cannon
- 1.1 Requirements
- 1.2 Features
- 1.3 Notes
- 1.4 Contact
- 1.5 Flak Cannon
- 1.6 Implementation
- 1.6.1 Common code
- 1.6.2 Creating Flak Cannon Classes
- 1.6.3 Setting up weapon asset references
- 1.6.4 Basic Parameters
- 1.6.5 Playtesting
- 1.6.6 Adding bounce effects
- 1.6.7 Making projectile become affected by gravity after bounce
- 1.6.8 Limiting projectile bounce count
- 1.6.9 Increasing projectile lifespan after bounce
- 1.6.10 Reducing projectile's damage over time
- 1.6.11 Making projectile apply damage only when moving fast enough
- 1.6.12 Flak Cannon main shard
- 1.6.13 Making Flak Shell spawn additional shards on explosion
- 1.6.14 Spawning multiple shards at once
- 1.6.15 Camera Shake
- 1.7 Source Code
# UT3 Weapons Tutorial - Flak Cannon
This tutorial will show you how to create Flak Cannon 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
- Spawnng multiple projectiles at once
- Controlled firing pattern for multiple projectiles
- Making projectiles spawn other projectiles upon explosion
- Adjusting projectile damage based on distance and hit location
- Adjusting projectile damage over time
# 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)
# Flak Cannon
We'll start by defining what features we want to implement:
# Primary Fire
- Fire 9 shard projectiles at once
- Main shard should be fired at crosshair
- Remaining shards should be fired in a random'ish circle around the main shard
- The firing pattern should spread shards evenly inside the firing cone
# Secondary Fire
- Fire an explosive shell projectile
# Shard Projectile
- Bounces up to 2 times
- Is not affected by gravity until it bounces
- Can deal damage only when moving fast enough
# Main Shard Projectile
Same as shard projectile except that:
- Bounces up to 3 times
- Deals additional damage & momentum when firing at point blank at center of enemy
# Shell Projectile
- High trajectory
- Explodes upon contact
- Upon explosion spawns 5 shards
# Implementation
To implement such projectile weapon we should create subclasses of AUTWeapon, AUTProjectile, UUTDamageType and AUTWeaponAttachment.
# Common code
We will implement some of the features in generic classes which can be later used for other weapons. To keep the tutorial code friendly to engine & game code updates, we will create our own generic subclasses of UTWeapon & UTProjectile instead of modifying them.
Lets start by creating an abstract subclass of AUTWeapon called AUTWeapon_Boom. This will be our common base class for UT3 weapons.
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UTWeapon.h"
#include "UTWeapon_Boom.generated.h"
UCLASS(Abstract, NotPlaceable)
class AUTWeapon_Boom : public AUTWeapon
{
GENERATED\_UCLASS\_BODY()
};
We will also need an abstract subclass of AUTProjectile called AUTProjectile_Boom. This will be our common base class for UT3 projectiles.
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "UTProjectile.h"
#include "UTProjectile_Boom.generated.h"
UCLASS(Abstract, NotPlaceable)
class AUTProjectile_Boom : public AUTProjectile
{
GENERATED\_UCLASS\_BODY()
};
Since we are going to use property values from UT3, we will need to adjust them to UT4 player scale. In UT3 player is 88 units high, in UT4 player is 192 units high. Therefore we need to multiply all projectile velocities, damage radiuses, momentums, etc, to get the same results in UT4. Add the following definition to UnrealTournament.h so it's accessible from everywhere:
// Ratio for scaling UT3 distance-related values
// See AUTCharacter CapsuleComponent HalfHeight
#define UT3_TO_UT4_SCALE (96.f / 44.f)
# Creating Flak Cannon Classes
Next, lets create classes for all the elements of Flak Cannon:
- Weapon
- Create subclass of AUTWeapon_Boom called AUTWeap_FlakCannon
- 3rd person weapon attachment
- Create subclass of AUTWeaponAttachment called AUTAttachment_FlakCannon
- Projectiles
- Create subclass of AUTProjectile_Boom called AUTProj_FlakShell
- Create subclass of AUTProjectile_Boom called AUTProj_FlakShard
- Create subclass of AUTProj_FlakShard called AUTProj_FlakShardMain
- Damage Types
- Create subclass of UUTDamageType called UTDmgType_FlakShell
- Create subclass of UUTDamageType called UTDmgType_FlakShard
# Setting up weapon asset references
It's a good idea to have a functional weapon at the start, even if the custom logic isn't there yet. We will link all the parts together and assign visual properties. Assigning asset references to properties is very easy in blueprints, but we can do it in C++ as well.
AUTWeap_FlakCannon.cpp - Setup asset references
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "UnrealTournament.h"
#include "UTWeap_FlakCannon.h"
#include "UTProj_FlakShard.h"
#include "UTProj_FlakShell.h"
#include "UTProj_FlakShardMain.h"
#include "UTAttachment_FlakCannon.h"
AUTWeap_FlakCannon::AUTWeap_FlakCannon(const FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
// Asset references
struct FConstructorStatics
{
ConstructorHelpers::FObjectFinder<USkeletalMesh\> SkeletalMesh;
ConstructorHelpers::FObjectFinder<UAnimBlueprintGeneratedClass\> AnimBlueprintGeneratedClass;
ConstructorHelpers::FObjectFinder<UAnimMontage\> FireAnimation0;
ConstructorHelpers::FObjectFinder<UAnimMontage\> FireAnimation1;
ConstructorHelpers::FObjectFinder<UAnimMontage\> BringUpAnim;
ConstructorHelpers::FObjectFinder<UAnimMontage\> PutDownAnim;
ConstructorHelpers::FObjectFinder<USoundCue\> FireSound0;
ConstructorHelpers::FObjectFinder<USoundCue\> FireSound1;
ConstructorHelpers::FObjectFinder<USoundCue\> PickupSound;
ConstructorHelpers::FObjectFinder<UParticleSystem\> MuzzleFlash;
FConstructorStatics()
: SkeletalMesh(TEXT("SkeletalMesh'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Meshes/SK\_WP\_FlakCannon\_1P.SK\_WP\_FlakCannon\_1P'"))
, AnimBlueprintGeneratedClass(TEXT("AnimBlueprintGeneratedClass'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_AnimBP.Flak\_AnimBP\_C'"))
, FireAnimation0(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_Fire\_Montage.Flak\_Fire\_Montage'"))
, FireAnimation1(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_Fire\_Montage.Flak\_Fire\_Montage'"))
, BringUpAnim(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_Equip.Flak\_Equip'"))
, PutDownAnim(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_PutDown.Flak\_PutDown'"))
, FireSound0(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireCue.A\_FlakCannon\_FireCue'"))
, FireSound1(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireAltCue.A\_FlakCannon\_FireAltCue'"))
, PickupSound(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Pickups/Audio/Weapons/Cue/A\_Pickup\_Weapons\_Flak\_Cue.A\_Pickup\_Weapons\_Flak\_Cue'"))MuzzleFlash(TEXT("ParticleSystem'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Effects/P\_WP\_FlakCannon\_Muzzle\_Flash.P\_WP\_FlakCannon\_Muzzle\_Flash'"))
{
}
};
static FConstructorStatics ConstructorStatics;
// Mesh
Mesh\-\>SkeletalMesh \= ConstructorStatics.SkeletalMesh.Object;
Mesh\-\>AnimBlueprintGeneratedClass \= ConstructorStatics.AnimBlueprintGeneratedClass.Object;
Mesh\-\>RelativeLocation \= FVector(\-2.349056, \-3.957190, \-5.411549);
Mesh\-\>RelativeScale3D \= FVector(0.750000, 0.750000, 0.750000);
FireAnimation.SetNumZeroed(2);
FireAnimation\[0\] \= ConstructorStatics.FireAnimation0.Object;
FireAnimation\[1\] \= ConstructorStatics.FireAnimation1.Object;
BringUpAnim \= ConstructorStatics.BringUpAnim.Object;
PutDownAnim \= ConstructorStatics.PutDownAnim.Object;
// Muzzle Flash
TSubobjectPtr<UParticleSystemComponent\> MuzzleComponent \= PCIP.CreateDefaultSubobject<UParticleSystemComponent\>(this, TEXT("FlakCannon-MuzzleFlash"));
MuzzleComponent\-\>Template \= ConstructorStatics.MuzzleFlash.Object;
MuzzleComponent\-\>AttachTo(Mesh, FName(TEXT("MuzzleFlashSocket")));
MuzzleFlash.SetNumZeroed(2);
MuzzleFlash\[0\] \= MuzzleComponent;
MuzzleFlash\[1\] \= MuzzleComponent;
// 3rd person
AttachmentType \= AUTAttachment\_FlakCannon::StaticClass();
// Sounds
FireSound.SetNumZeroed(2);
FireSound\[0\] \= ConstructorStatics.FireSound0.Object;
FireSound\[1\] \= ConstructorStatics.FireSound1.Object;
PickupSound \= ConstructorStatics.PickupSound.Object;
// UI
Group \= 7;
IconCoordinates \= FTextureUVs(131.000000, 429.000000, 132.000000, 52.000000);
}
Notice that assets are loaded statically only once. Asset paths can be copy-pasted straight from editor.
AUTAttachment_FlakCannon.cpp - Setup asset references
#include "UnrealTournament.h"
#include "UTAttachment_FlakCannon.h"
AUTAttachment_FlakCannon::AUTAttachment_FlakCannon(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\_FlakCannon/Meshes/SK\_WP\_FlakCannon\_3P\_Mid.SK\_WP\_FlakCannon\_3P\_Mid'"))
{
}
};
static FConstructorStatics ConstructorStatics;
Mesh\-\>SkeletalMesh \= ConstructorStatics.SkeletalMesh.Object;
}
AUTProj_FlakShell.cpp - Setup asset references
#include "UnrealTournament.h"
#include "UTProjectileMovementComponent.h"
#include "UTProj_FlakShell.h"
#include "UTProj_FlakShard.h"
#include "UTDmgType_FlakShell.h"
AUTProj_FlakShell::AUTProj_FlakShell(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
// Structure to hold one-time initialization
struct FConstructorStatics
{
ConstructorHelpers::FObjectFinder<UParticleSystem\> ExplosionEffect;
ConstructorHelpers::FObjectFinder<USoundCue\> ExplosionSound;
ConstructorHelpers::FObjectFinder<USoundCue\> AmbientSound;
ConstructorHelpers::FObjectFinder<UParticleSystem\> TrailEffect;
FConstructorStatics()
: ExplosionEffect(TEXT("ParticleSystem'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Effects/P\_WP\_Flak\_Alt\_Explosion.P\_WP\_Flak\_Alt\_Explosion'"))
, ExplosionSound(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireAltImpactExplodeCue.A\_FlakCannon\_FireAltImpactExplodeCue'"))
, AmbientSound(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireAltInAirCue.A\_FlakCannon\_FireAltInAirCue'"))
, TrailEffect(TEXT("ParticleSystem'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Effects/P\_WP\_Flak\_Alt\_Smoke\_Trail.P\_WP\_Flak\_Alt\_Smoke\_Trail'"))
{
}
};
static FConstructorStatics ConstructorStatics;
// Visuals
TSubobjectPtr<UAudioComponent\> AmbientSound \= PCIP.CreateDefaultSubobject<UAudioComponent\>(this, TEXT("FlakShell-Ambient"));
AmbientSound\-\>Sound \= ConstructorStatics.AmbientSound.Object;
AmbientSound\-\>VolumeMultiplier \= 0.5;
AmbientSound\-\>AttachTo(RootComponent);
TSubobjectPtr<UParticleSystemComponent\> TrailComponent \= PCIP.CreateDefaultSubobject<UParticleSystemComponent\>(this, TEXT("FlakShell-Trail"));
TrailComponent\-\>Template \= ConstructorStatics.TrailEffect.Object;
TrailComponent\-\>SetRelativeLocation(FVector(\-3, 0, 0));
TrailComponent\-\>SetRelativeScale3D(FVector(1.5, 1.5, 1.5));
TrailComponent\-\>AttachTo(RootComponent);
TSubobjectPtr<UPointLightComponent\> LightComponent \= PCIP.CreateDefaultSubobject<UPointLightComponent\>(this, TEXT("FlakShell-Light"));
LightComponent\-\>Intensity \= 150;
LightComponent\-\>AttenuationRadius \= 250;
LightComponent\-\>LightColor \= FColor(47, 209, 255);
LightComponent\-\>SetCastShadows(false);
LightComponent\-\>AttachTo(RootComponent);
ExplosionEffect \= ConstructorStatics.ExplosionEffect.Object;
ExplosionSound \= ConstructorStatics.ExplosionSound.Object;
}
The mesh is actually set up as part of particle emitter, hence there's no static mesh component.
AUTProj_FlakShard.cpp - Setup asset references
#include "UnrealTournament.h"
#include "UTProjectileMovementComponent.h"
#include "UTProj_FlakShard.h"
#include "UTDmgType_FlakShard.h"
AUTProj_FlakShard::AUTProj_FlakShard(const class FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
// Structure to hold one-time initialization
struct FConstructorStatics
{
ConstructorHelpers::FObjectFinder<UParticleSystem\> BounceEffect;
ConstructorHelpers::FObjectFinder<USoundCue\> BounceSound;
ConstructorHelpers::FObjectFinder<UStaticMesh\> StaticMesh;
ConstructorHelpers::FObjectFinder<UMaterial\> StaticMeshMaterial0;
ConstructorHelpers::FObjectFinder<UParticleSystem\> TrailEffect;
FConstructorStatics()
: BounceEffect(TEXT("ParticleSystem'/Game/RestrictedAssets/Weapons/Flak/Assets/Flak\_Hit\_Spark.Flak\_Hit\_Spark'"))
, BounceSound(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireImpactDirtCue.A\_FlakCannon\_FireImpactDirtCue'"))
, StaticMesh(TEXT("StaticMesh'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Meshes/S\_Flak\_Chunk.S\_Flak\_Chunk'"))
, StaticMeshMaterial0(TEXT("Material'/Game/RestrictedAssets/Weapons/Flak/Assets/M\_Shard.M\_Shard'"))
, TrailEffect(TEXT("ParticleSystem'/Game/RestrictedAssets/Weapons/Flak/Assets/Trail.Trail'"))
{
}
};
static FConstructorStatics ConstructorStatics;
// Visuals
TSubobjectPtr<UStaticMeshComponent\> StaticMeshComponent \= PCIP.CreateDefaultSubobject<UStaticMeshComponent\>(this, TEXT("FlakShard-StaticMesh"));
StaticMeshComponent\-\>StaticMesh \= ConstructorStatics.StaticMesh.Object;
StaticMeshComponent\-\>bGenerateOverlapEvents \= false;
StaticMeshComponent\-\>SetCollisionProfileName(UCollisionProfile::NoCollision\_ProfileName);
StaticMeshComponent\-\>SetMaterial(0, ConstructorStatics.StaticMeshMaterial0.Object);
StaticMeshComponent\-\>SetRelativeScale3D(FVector(0.25, 0.25, 0.25));
StaticMeshComponent\-\>AttachTo(RootComponent);
TSubobjectPtr<UParticleSystemComponent\> TrailComponent \= PCIP.CreateDefaultSubobject<UParticleSystemComponent\>(this, TEXT("FlakShard-Trail"));
TrailComponent\-\>Template \= ConstructorStatics.TrailEffect.Object;
TrailComponent\-\>AttachTo(RootComponent);
TSubobjectPtr<UPointLightComponent\> LightComponent \= PCIP.CreateDefaultSubobject<UPointLightComponent\>(this, TEXT("FlakShard-Light"));
LightComponent\-\>Intensity \= 100;
LightComponent\-\>AttenuationRadius \= 100;
LightComponent\-\>LightColor \= FColor(255, 133, 35);
LightComponent\-\>SetCastShadows(false);
LightComponent\-\>AttachTo(RootComponent);
}
# Basic Parameters
We're going to use parameter values from UT3. Set them in constructors:
AUTProj_FlakShell.cpp - We will use high TossZ value to give the projectile high trajectory. This way player can aim at distant opponents without having to aim at the sky.
// Movement
ProjectileMovement\-\>InitialSpeed \= 1200.f \* UT3\_TO\_UT4\_SCALE;
ProjectileMovement\-\>MaxSpeed \= 1200.f \* UT3\_TO\_UT4\_SCALE;
ProjectileMovement\-\>ProjectileGravityScale \= 1.0f;
CollisionComp\-\>InitSphereRadius(10);
TossZ \= 305 \* UT3\_TO\_UT4\_SCALE;
// Damage
MyDamageType \= UUTDmgType\_FlakShell::StaticClass();
DamageParams.BaseDamage \= 100;
DamageParams.OuterRadius \= 200 \* UT3\_TO\_UT4\_SCALE;
Momentum \= 75000 \* UT3\_TO\_UT4\_SCALE;
InitialLifeSpan \= 6;
AUTProj_FlakShard.cpp - Lets make it rotate
// Movement
TSubobjectPtr<URotatingMovementComponent\> RotatingMovement \= PCIP.CreateDefaultSubobject<URotatingMovementComponent\>(this, TEXT("FlakShard-RotatingMovement"));
RotatingMovement\-\>RotationRate \= FRotator(0, 0, 270);
ProjectileMovement\-\>InitialSpeed \= 3500.f \* UT3\_TO\_UT4\_SCALE;
ProjectileMovement\-\>MaxSpeed \= 3500.f \* UT3\_TO\_UT4\_SCALE;
ProjectileMovement\-\>ProjectileGravityScale \= 0.f;
ProjectileMovement\-\>bRotationFollowsVelocity \= false;
ProjectileMovement\-\>bShouldBounce \= true;
// Damage
MyDamageType \= UUTDmgType\_FlakShard::StaticClass();
DamageParams.BaseDamage \= 18.f;
Momentum \= 14000 \* UT3\_TO\_UT4\_SCALE;
InitialLifeSpan \= 2.f;
AUTWeap_FlakCannon.cpp - Setting up fire modes
// Firing
ProjClass.SetNumZeroed(2);
ProjClass\[0\] \= AUTProj\_FlakShardMain::StaticClass();
ProjClass\[1\] \= AUTProj\_FlakShell::StaticClass();
FireInterval.SetNumZeroed(2);
FireInterval\[0\] \= 1.1;
FireInterval\[1\] \= 1.1;
AmmoCost.SetNumZeroed(2);
AmmoCost\[0\] \= 1;
AmmoCost\[1\] \= 1;
Ammo \= 10;
MaxAmmo \= 30;
FireOffset \= FVector(75.f, 18.f, \-15.f);
# Playtesting
At this point we can test the weapon ingame, although it doesn't have custom logic yet. To test the weapon add an WeaponBase Blueprint to level and in its properties make it use our AUTWeap_FlakCannon.
# Adding bounce effects
AUTProjectile_Boom.h - Add properties for Bounce Effect assets
/\*\* Bounce effect \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= Projectile)
UParticleSystem\* BounceEffect;
/\*\* Sound played when projectile bounces off wall \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= Projectile)
USoundBase\* BounceSound;
AUTProjectile_Boom.cpp - Override OnBounce to make it play bounce effects
void AUTProjectile_Boom::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)
{
Super::OnBounce(ImpactResult, ImpactVelocity);
// Spawn bounce effect
if (GetNetMode() !\= NM\_DedicatedServer)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BounceEffect, ImpactResult.Location, ImpactResult.ImpactNormal.Rotation(), true);
}
// Play bounce sound
if (BounceSound !\= NULL)
{
UUTGameplayStatics::UTPlaySound(GetWorld(), BounceSound, this, SRT\_IfSourceNotReplicated, false);
}
}
AUTProj_FlakShard.cpp - Add default assets to constructor
BounceEffect \= ConstructorStatics.BounceEffect.Object;
BounceSound \= ConstructorStatics.BounceSound.Object;
After those changes, shards will spark on bounce and play sound effect.
# Bounce Effect Rotation
Notice that the sparks asset used will spawn in incorrect direction. This is because we're spawning in HitNormal direction, which has X axis pointing away from surface, and the emitter is spawning projectiles in Z axis direction, which in this case will be parallel to floor. The correct fix is to adjust the emitter asset so it spawns in X direction. We can however add a temporary workaround.
AUTProjectile_Boom.h - Lets add a BounceEffectRotation property
/\*\* Bounce effect \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= Projectile)
FRotator BounceEffectRotation;
AUTProjectile_Boom.cpp - We will use it to rotate HitNormal used for BounceEffect spawn rotation
void AUTProjectile_Boom::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)
{
Super::OnBounce(ImpactResult, ImpactVelocity);
// Spawn bounce effect
if (GetNetMode() !\= NM\_DedicatedServer)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BounceEffect, ImpactResult.Location, BounceEffectRotation.RotateVector(ImpactResult.ImpactNormal).Rotation(), true);
}
// Play bounce sound
if (BounceSound !\= NULL)
{
UUTGameplayStatics::UTPlaySound(GetWorld(), BounceSound, this, SRT\_IfSourceNotReplicated, false);
}
AUTProj_FlakShard.cpp - Set default BounceEffectRotation in constructor
BounceEffectRotation \= FRotator(90, 0, 0);
# Making projectile become affected by gravity after bounce
We want the shards to fly unaffected by gravity initially. Only after bounce they should fall towards floor.
AUTProj_FlakShard.cpp - To do so, lets override OnBounce
void AUTProj_FlakShard::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)
{
Super::OnBounce(ImpactResult, ImpactVelocity);
// Set gravity on bounce
ProjectileMovement\-\>ProjectileGravityScale \= 1.f;
}
# Limiting projectile bounce count
Next we will limit number of bounces to 2, as this is how UT3 Flak Shards work.
AUTProj_FlakShard.h - Add 2 new properties to define max number of bounces and current number of bounces
/\*\* Limit number of bounces \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
int32 BounceLimit;
/\*\* Current number of times this projectile bounced \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
int32 BounceCount;
AUTProj_FlakShard.cpp - Set default number of bounces in constructor
BounceLimit \= 2;
AUTProj_FlakShard.cpp - Count the number of bounces in OnBounce and disable bouncing once limit is reached
void AUTProj_FlakShard::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)
{
Super::OnBounce(ImpactResult, ImpactVelocity);
// Set gravity on bounce
ProjectileMovement\-\>ProjectileGravityScale \= 1.f;
// Limit number of bounces
if (++BounceCount \== BounceLimit)
{
ProjectileMovement\-\>bShouldBounce \= false;
}
}
# Increasing projectile lifespan after bounce
We want to ensure that shards will fly for a bit after bouncing. Each projectile has a InitialLifeSpan property set that will limit how long this projectile can exist. Combined with velocity it affects maximum shooting distance.
AUTProj_FlakShard.h - Add 2 new properties for increasing lifespan after bounce and an extra one after final bounce
/\*\* Increment lifespan on bounce by this amount \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
float BounceLifeSpanIncrement;
/\*\* Increment lifespan when projectile stops by this amount \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
float BounceFinalLifeSpanIncrement;
AUTProj_FlakShard.cpp - Set default values for bonus lifespan
BounceLifeSpanIncrement \= 0.5f;
BounceFinalLifeSpanIncrement \= 0.25f;
AUTProj_FlakShard.cpp - Count the number of bounces in OnBounce and disable bouncing once limit is reached
void AUTProj_FlakShard::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)
{
Super::OnBounce(ImpactResult, ImpactVelocity);
// Set gravity on bounce
ProjectileMovement\-\>ProjectileGravityScale \= 1.f;
// Extend lifespan on bounce
SetLifeSpan(GetLifeSpan() + BounceLifeSpanIncrement);
// Limit number of bounces
if (++BounceCount \== BounceLimit)
{
ProjectileMovement\-\>bShouldBounce \= false;
SetLifeSpan(GetLifeSpan() + BounceFinalLifeSpanIncrement);
}
}
# Reducing projectile's damage over time
Flak Shards lose 5 damage per second of flight. To implement that we'll need to add function that calculate actual damage instead of using only properties. While we're at it, we'll add dynamic momentum calculation as well, which will come handy later.
AUTProjectile_Boom.h - Add DamageAttenuation property that will deteermine amount of damage lost per second, and 2 new functions.
/\*\* Damage reduction per second, down to minimum damage\*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= Damage)
float DamageAttenuation;
/\*\* Base damage calculation \*/
UFUNCTION(BlueprintCallable, Category \= Projectile)
virtual float GetDamage(AActor\* OtherActor, const FVector& HitLocation);
/\*\* Momentum calculation \*/
UFUNCTION(BlueprintCallable, Category \= Projectile)
virtual float GetMomentum(AActor\* OtherActor, const FVector& HitLocation);
AUTProjectile_Boom.cpp - If DamageAttenuation is set, GetDamage() will return reduced damage.
float AUTProjectile_Boom::GetDamage(AActor* OtherActor, const FVector& HitLocation)
{
if (DamageAttenuation \> 0)
{
return FMath::Max(DamageParams.BaseDamage \- (GetWorld()\-\>TimeSeconds \- CreationTime) \* DamageAttenuation, DamageParams.MinimumDamage);
}
return DamageParams.BaseDamage;
}
AUTProjectile_Boom.cpp - GetMomentum() will return standard momentum by default.
float AUTProjectile_Boom::GetMomentum(AActor* OtherActor, const FVector& HitLocation)
{
return Momentum;
}
AUTProjectile_Boom.cpp - To adjust damage & momentum of explosions, override Explode_Implementation().
void AUTProjectile_Boom::Explode_Implementation(const FVector& HitLocation, const FVector& HitNormal)
{
if (!bExploded)
{
if (DamageParams.OuterRadius \> 0.0f)
{
TArray<AActor\*\> IgnoreActors;
if (ImpactedActor !\= NULL)
{
IgnoreActors.Add(ImpactedActor);
}
const float AdjustedDamage \= GetDamage(NULL, HitLocation);
const float AdjustedMomentum \= GetMomentum(NULL, HitLocation);
UUTGameplayStatics::UTHurtRadius(this, AdjustedDamage, DamageParams.MinimumDamage, AdjustedMomentum, HitLocation, DamageParams.InnerRadius, DamageParams.OuterRadius, DamageParams.DamageFalloff, MyDamageType, IgnoreActors, this, InstigatorController);
}
if (Role \== ROLE\_Authority)
{
bTearOff \= true;
}
bExploded \= true;
UUTGameplayStatics::UTPlaySound(GetWorld(), ExplosionSound, this, ESoundReplicationType::SRT\_IfSourceNotReplicated);
if (GetNetMode() !\= NM\_DedicatedServer)
{
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionEffect, GetActorLocation(), HitNormal.Rotation(), true);
}
ShutDown();
}
}
In current version of UT we had to copy paste entire function from AUT_projectile, as applying radius damage is not split into own function yet.
AUTWeapon_Boom.cpp - To adjust damage & momentum of direct damage, override DamageImpactedActor_Implementation
void AUTProjectile_Boom::DamageImpactedActor_Implementation(AActor* OtherActor, UPrimitiveComponent* OtherComp, const FVector& HitLocation, const FVector& HitNormal)
{
const float AdjustedDamage \= GetDamage(OtherActor, HitLocation);
const float AdjustedMomentum \= GetMomentum(OtherActor, HitLocation);
// treat as point damage if projectile has no radius
if (DamageParams.OuterRadius \> 0.0f)
{
FUTRadialDamageEvent Event;
Event.Params \= DamageParams;
Event.Params.MinimumDamage \= AdjustedDamage; // force full damage for direct hit
Event.DamageTypeClass \= MyDamageType;
Event.Origin \= HitLocation;
Event.BaseMomentumMag \= AdjustedMomentum;
new(Event.ComponentHits) FHitResult(OtherActor, OtherComp, HitLocation, HitNormal);
Event.ComponentHits\[0\].TraceStart \= HitLocation \- GetVelocity();
Event.ComponentHits\[0\].TraceEnd \= HitLocation + GetVelocity();
OtherActor\-\>TakeDamage(AdjustedDamage, Event, InstigatorController, this);
}
else
{
FUTPointDamageEvent Event;
Event.Damage \= AdjustedDamage;
Event.DamageTypeClass \= MyDamageType;
Event.HitInfo \= FHitResult(OtherActor, OtherComp, HitLocation, HitNormal);
Event.ShotDirection \= GetVelocity().SafeNormal();
Event.Momentum \= Event.ShotDirection \* AdjustedMomentum;
OtherActor\-\>TakeDamage(AdjustedDamage, Event, InstigatorController, this);
}
}
AUTProj_FlakShard.cpp - Finally set default DamageAttentuation in Flak Shard constructor
DamageAttenuation \= 5.f;
# Making projectile apply damage only when moving fast enough
Flak Shards deal damage only when moving over 400 UU/s. This way shards that lost much of the velocity after bounce won't hit you.
AUTProj_FlakShard.h - Add MinDamageSpeed property to determine minimum speed required to apply damage.
/\*\* Minimum speed at which damage can be applied \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
float MinDamageSpeed;
AUTProj_FlakShard.cpp - Set default MinDamageSpeed in FlakShard constructor.
MinDamageSpeed \= 400.f \* UT3\_TO\_UT4\_SCALE;
AUTProj_FlakShard.cpp - With our custom GetDamage() function in place, override it in FlakShard to adjust its damage.
float AUTProj_FlakShard::GetDamage(AActor* OtherActor, const FVector& HitLocation)
{
// Apply damage only when moving fast enough
if (GetVelocity().Size() \> MinDamageSpeed)
{
return Super::GetDamage(OtherActor, HitLocation);
}
return 0.f;
}
# Flak Cannon main shard
Flak Cannon fires a special shard at the center. It always fires at crosshair, can bounce 3 times and deals additional damage at close range.
AUTProj_FlakShardMain.cpp - Increase BounceLimit to 3 in FlakShardMain constructor.
BounceLimit \= 3;
# Adjusting damage based on how much in-the-face the shot was
Main Flak Shard deals bonus damage at very close range. Additionally it deals more damage when aiming at center of enemy.
AUTProj_FlakShardMain.h - Add properties for bonus damage and momentum. To check how far the projectile travelled we will use the same method as in UT3 - checking elapsed LifeSpan
/\*\* Momentum bonus for point blank shots \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
float CenteredMomentumBonus;
/\*\* Damage bonus for point blank shots \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
float CenteredDamageBonus;
/\*\* Timeout for point blank shots \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
float MaxBonusTime;
AUTProj_FlakShardMain.cpp - Set default properties in constructor
CenteredMomentumBonus \= 90000 \* UT3\_TO\_UT4\_SCALE;
CenteredDamageBonus \= 100.0;
MaxBonusTime \= 0.2;
AUTProj_FlakShardMain.cpp - Override GetDamage() & GetMomentum() to return adjusted valued
/**
* Increase damage to UTPawns based on how centered this shard is on target. If it is within the time MaxBonusTime time period.
* e.g. point blank shot with the flak cannon you will do mega damage. Once MaxBonusTime passes then this shard becomes a normal shard.
*/
float AUTProj_FlakShardMain::GetDamage(AActor* OtherActor, const FVector& HitLocation)
{
const float CalculatedDamage \= Super::GetDamage(OtherActor, HitLocation);
// When hitting a pawn within bonus point blank time
AUTCharacter\* OtherCharacter \= Cast<AUTCharacter\>(OtherActor);
const float BonusTime \= GetLifeSpan() \- InitialLifeSpan + MaxBonusTime;
if (CalculatedDamage \> 0.f && OtherCharacter !\= NULL && BonusTime \> 0)
{
// Apply bonus damage
const float CharacterRadius \= OtherCharacter\-\>GetSimpleCollisionRadius();
const float OffCenterDistance \= FMath::PointDistToLine(OtherActor\-\>GetActorLocation(), GetVelocity().SafeNormal(), HitLocation);
const float OffCenterMultiplier \= FMath::Max(0.f, 2.f \* (CharacterRadius \- OffCenterDistance)) / CharacterRadius;
const float BonusDamage \= CenteredDamageBonus \* BonusTime \* OffCenterMultiplier;
return CalculatedDamage + BonusDamage;
}
return CalculatedDamage;
}
/**
* Increase momentum imparted based on how recently this shard was fired
*/
float AUTProj_FlakShardMain::GetMomentum(AActor* OtherActor, const FVector& HitLocation)
{
// When hitting something within bonus point blank time
const float Momentum \= Super::GetMomentum(OtherActor, HitLocation);
const float BonusTime \= GetLifeSpan() \- InitialLifeSpan + MaxBonusTime;
if (BonusTime \> 0)
{
// Apply bonus momentum
return Momentum + CenteredMomentumBonus \* BonusTime;
}
return Momentum;
}
# Making Flak Shell spawn additional shards on explosion
Flak Shell should spawn 5 shards on explosion. The shards should fly away from hit surface.
AUTProj_FlakShell.h - Add properties to FlakShell for number of shards to spawn, angle to spawn and class to use.
/\*\* Number of shards to spawn \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
int32 ShardSpawnCount;
/\*\* Angle for spawning shards, relative to hit normal \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
float ShardSpawnAngle;
/\*\* Shard class type \*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
TSubclassOf<AUTProjectile\> ShardClass;
AUTProj_FlakShell.cpp - Set default properties in FlakShell constructor
ShardClass \= AUTProj\_FlakShard::StaticClass();
ShardSpawnCount \= 5;
ShardSpawnAngle \= 85;
AUTProj_FlakShell.cpp - Override Explode_Implementation() to spawn additional projectiles
void AUTProj_FlakShell::Explode_Implementation(const FVector& HitLocation, const FVector& HitNormal)
{
// On explosion spawn additional flak shards
if (!bExploded && Role \== ROLE\_Authority && ShardClass && ShardSpawnCount \> 0)
{
// Setup spawn parameters
FActorSpawnParameters Params;
Params.Instigator \= Instigator;
Params.Owner \= Instigator;
Params.bNoCollisionFail \= true;
for (int32 i \= 0; i < ShardSpawnCount; ++i)
{
// Randomize spawn direction along hit normal
const FRotator SpawnRotation \= FMath::VRandCone(HitNormal, FMath::DegreesToRadians(ShardSpawnAngle)).Rotation();
// Spawn shard
GetWorld()\-\>SpawnActor<AUTProjectile\>(ShardClass, HitLocation, SpawnRotation, Params);
}
}
Super::Explode\_Implementation(HitLocation, HitNormal);
}
# Spawning multiple shards at once
Last but not least, we're going to add ability to spawn multiple projectiles at once from a weapon.
AUTWeapon_Boom.h - Add properties for number of projectiles to fire and class for additional projectiles
/\*\* Number of projectiles to fire.
* When firing multiple projectiles at once, main projectile will be fired at crosshair.
* Remaining projectiles will be fired in a circle pattern */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
TArray<int32\> MultiShotCount;
/\*\* Projectile class to use when firing multiple projectiles at once.
* This is only for additional projectiles, main projectile will use ProjClass.
* If not specified, ProjClass will be used. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
TArray< TSubclassOf<AUTProjectile\> \> MultiShotProjClass;
AUTWeapon_Boom.cpp - Override FireProjectile() to spawn multiple projectiles at once. In this snippet projectiles will be spawned using default weapon's spread.
AUTProjectile* AUTWeapon_Boom::FireProjectile()
{
if (GetUTOwner() \== NULL)
{
UE\_LOG(UT, Warning, TEXT("%s::FireProjectile(): Weapon is not owned (owner died during firing sequence)"));
return NULL;
}
else if (Role \== ROLE\_Authority)
{
// try and fire a projectile
checkSlow(ProjClass.IsValidIndex(CurrentFireMode) && ProjClass\[CurrentFireMode\] !\= NULL);
// increment 3rd person muzzle flash count
UTOwner\-\>IncrementFlashCount(CurrentFireMode);
// Setup spawn parameters
FActorSpawnParameters Params;
Params.Instigator \= UTOwner;
Params.Owner \= UTOwner;
Params.bNoCollisionFail \= true;
// Get muzzle location and rotation
const FVector SpawnLocation \= GetFireStartLoc();
const FRotator SpawnRotation \= GetAdjustedAim(SpawnLocation);
// Fire projectiles
AUTProjectile\* MainProjectile \= NULL;
if (MultiShotCount.IsValidIndex(CurrentFireMode) && MultiShotCount\[CurrentFireMode\] \> 1)
{
for (int32 i \= 0; i < MultiShotCount\[CurrentFireMode\]; ++i)
{
// Get firing location and rotation for this projectile
const FVector MultiShotLocation \= GetFireStartLoc();
const FRotator MultiShotRotation \= GetAdjustedAim(SpawnLocation);
// Get projectile class
TSubclassOf<AUTProjectile\> ProjectileClass \= ProjClass\[CurrentFireMode\];
if (i !\= 0 && MultiShotProjClass.IsValidIndex(CurrentFireMode) && MultiShotProjClass\[CurrentFireMode\] !\= NULL)
{
ProjectileClass \= MultiShotProjClass\[CurrentFireMode\];
}
// Spawn projectile
AUTProjectile\* MultiShot \= GetWorld()\-\>SpawnActor<AUTProjectile\>(ProjectileClass, MultiShotLocation, MultiShotRotation, Params);
if (MainProjectile \== NULL)
{
MainProjectile \= MultiShot;
}
}
}
else
{
// Spawn projectile
MainProjectile \= GetWorld()\-\>SpawnActor<AUTProjectile\>(ProjClass\[CurrentFireMode\], SpawnLocation, SpawnRotation, Params);
}
return MainProjectile;
}
else
{
return NULL;
}
}
This will spawn MultiShotCount projectiles on firing.
AUTWeap_FlakCannon.cpp - Add multishot default properties
MultiShotCount.SetNumZeroed(1);
MultiShotCount\[0\] \= 9;
MultiShotProjClass.SetNumZeroed(1);
MultiShotProjClass\[0\] \= AUTProj\_FlakShard::StaticClass();
# Projectile firing pattern
Flak Cannon fires shards in distinct semi-random pattern. Main shard is fired at crosshair. Each additional projectile then is fired at equally spaced fragment of firing cone circle. Slight location & rotation randomization is still applied but the shards are guaranteed to cover entire firing cone. This makes the weapon more predictable which is good for pro gaming. We're going to implement this pattern as default for multi-shot weapons.
AUTWeapon_Boom.h - Add property for angle of firing pattern and two functions to return firing location & rotation of individual projectiles.
/\*\* Firing cone angle in degrees.
* Applies to individual projectiles when firing multiple at once. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
TArray<FRotator\> MultiShotAngle;
/\*\* Returns projectile spawn location when firing multiple projectiles at once \*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category \= "Weapon")
FVector GetFireLocationForMultiShot(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation);
/\*\* Returns projectile spawn rotation when firing multiple projectiles at once \*/
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category \= "Weapon")
FRotator GetFireRotationForMultiShot(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation);
AUTWeap_FlakCannon.cpp - Add multishot default properties
MultiShotAngle.SetNumZeroed(1);
MultiShotAngle\[0\] \= FRotator(0, 3, 0);
AUTWeapon_Boom.cpp - Implement GetFireLocationForMultiShot(). We will add randomization later.
FVector AUTWeapon_Boom::GetFireLocationForMultiShot_Implementation(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation)
{
// Main projectile fires straight from muzzle center
return FireLocation;
}
AUTWeapon_Boom.cpp - Implement GetFireRotationForMultiShot(). We will add randomization later.
FRotator AUTWeapon_Boom::GetFireRotationForMultiShot_Implementation(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation)
{
if (MultiShotIndex \> 0 && MultiShotAngle.IsValidIndex(CurrentFireMode))
{
// Each additional projectile can have own fragment of firing cone.
// This way there are no empty spots in firing cone due to randomness.
// While still randomish, the pattern is predictable, which is good for pro gaming.
// Get direction at fragment of firing cone
const float Alpha \= (float)(MultiShotIndex \- 1) / (float)(MultiShotCount\[CurrentFireMode\] \- 1);
const FRotator ConeSector \= FRotator(0, 0, 360.f \* Alpha);
FVector FireDirection \= ConeSector.RotateVector(MultiShotAngle\[CurrentFireMode\].Vector());
// Return firing cone rotated by player's firing rotation
return FireRotation.RotateVector(FireDirection).Rotation();
}
// Main projectile fires straight at crosshair
return FireRotation;
}
AUTWeapon_Boom.cpp - Modify FireProjectile() so it uses our functions
AUTProjectile* AUTWeapon_Boom::FireProjectile()
{
if (GetUTOwner() \== NULL)
{
UE\_LOG(UT, Warning, TEXT("%s::FireProjectile(): Weapon is not owned (owner died during firing sequence)"));
return NULL;
}
else if (Role \== ROLE\_Authority)
{
// try and fire a projectile
checkSlow(ProjClass.IsValidIndex(CurrentFireMode) && ProjClass\[CurrentFireMode\] !\= NULL);
// increment 3rd person muzzle flash count
UTOwner\-\>IncrementFlashCount(CurrentFireMode);
// Setup spawn parameters
FActorSpawnParameters Params;
Params.Instigator \= UTOwner;
Params.Owner \= UTOwner;
Params.bNoCollisionFail \= true;
// Get muzzle location and rotation
const FVector SpawnLocation \= GetFireStartLoc();
const FRotator SpawnRotation \= GetAdjustedAim(SpawnLocation);
// Fire projectiles
AUTProjectile\* MainProjectile \= NULL;
if (MultiShotCount.IsValidIndex(CurrentFireMode) && MultiShotCount\[CurrentFireMode\] \> 1)
{
for (int32 i \= 0; i < MultiShotCount\[CurrentFireMode\]; ++i)
{
// Get firing location and rotation for this projectile
const FVector MultiShotLocation \= GetFireLocationForMultiShot(i, SpawnLocation, SpawnRotation);
const FRotator MultiShotRotation \= GetFireRotationForMultiShot(i, SpawnLocation, SpawnRotation);
// Get projectile class
TSubclassOf<AUTProjectile\> ProjectileClass \= ProjClass\[CurrentFireMode\];
if (i !\= 0 && MultiShotProjClass.IsValidIndex(CurrentFireMode) && MultiShotProjClass\[CurrentFireMode\] !\= NULL)
{
ProjectileClass \= MultiShotProjClass\[CurrentFireMode\];
}
// Spawn projectile
AUTProjectile\* MultiShot \= GetWorld()\-\>SpawnActor<AUTProjectile\>(ProjectileClass, MultiShotLocation, MultiShotRotation, Params);
if (MainProjectile \== NULL)
{
MainProjectile \= MultiShot;
}
}
}
else
{
// Spawn projectile
MainProjectile \= GetWorld()\-\>SpawnActor<AUTProjectile\>(ProjClass\[CurrentFireMode\], SpawnLocation, SpawnRotation, Params);
}
return MainProjectile;
}
else
{
return NULL;
}
}
# Projectile firing pattern randomization
Right now projectiles are fired in perfect circle pattern. We're going to randomize starting location and rotation.
AUTWeapon_Boom.h - Add properrties for location & rotation randomization
/\*\* Firing location randomness, in unreal units.
* Applies to individual projectiles when firing multiple at once */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
TArray<FVector\> MultiShotLocationSpread;
/\*\* Firing direction randomness, in degrees.
* Applies to individual projectiles when firing multiple at once */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
TArray<float\> MultiShotRotationSpread;
AUTWeapon_Boom.cpp - Modify GetFireLocationForMultiShot() so it returns adjusted location.
FVector AUTWeapon_Boom::GetFireLocationForMultiShot_Implementation(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation)
{
if (MultiShotIndex \> 0 && MultiShotLocationSpread.IsValidIndex(CurrentFireMode))
{
// Randomise each projectile's spawn location if needed.
return FireLocation + FireRotation.RotateVector(FMath::VRand() \* MultiShotLocationSpread\[CurrentFireMode\]);
}
// Main projectile fires straight from muzzle center
return FireLocation;
}
AUTWeapon_Boom.cpp - Modify GetFireRotationForMultiShot() so it returns adjusted rotation.
FRotator AUTWeapon_Boom::GetFireRotationForMultiShot_Implementation(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation)
{
if (MultiShotIndex \> 0 && MultiShotAngle.IsValidIndex(CurrentFireMode))
{
// Each additional projectile can have own fragment of firing cone.
// This way there are no empty spots in firing cone due to randomness.
// While still randomish, the pattern is predictable, which is good for pro gaming.
// Get direction at fragment of firing cone
const float Alpha \= (float)(MultiShotIndex \- 1) / (float)(MultiShotCount\[CurrentFireMode\] \- 1);
const FRotator ConeSector \= FRotator(0, 0, 360.f \* Alpha);
FVector FireDirection \= ConeSector.RotateVector(MultiShotAngle\[CurrentFireMode\].Vector());
// Randomise each projectile's spawn rotation if needed
if (MultiShotRotationSpread.IsValidIndex(CurrentFireMode))
{
FireDirection \= FMath::VRandCone(FireDirection, FMath::DegreesToRadians(MultiShotRotationSpread\[CurrentFireMode\]));
}
// Return firing cone rotated by player's firing rotation
return FireRotation.RotateVector(FireDirection).Rotation();
}
// Main projectile fires straight at crosshair
return FireRotation;
}
AUTWeap_FlakCannon.cpp - Add firing pattern default properties
MultiShotLocationSpread.SetNumZeroed(1);
MultiShotLocationSpread\[0\] \= FVector(0, 3, 3);
MultiShotRotationSpread.SetNumZeroed(1);
MultiShotRotationSpread\[0\] \= 3;
# Camera Shake
We can add camera shake as well to our base class. This way it can be used by all weapons.
AUTWeapon_Boom.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_Boom.cpp - Add PlayCameraShake() implementation
void AUTWeapon_Boom::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_Boom.cpp - Override PlayFiringEffects() so it calls our PlayCameraShake() function
void AUTWeapon_Boom::PlayFiringEffects()
{
Super::PlayFiringEffects();
// Play camera shake after optional delay
if (CameraShakeDelay.IsValidIndex(CurrentFireMode) && CameraShakeDelay\[CurrentFireMode\] \> 0)
{
GetWorldTimerManager().SetTimer(this, &AUTWeapon\_Boom::PlayCameraShake, CameraShakeDelay\[CurrentFireMode\], false);
}
else
{
PlayCameraShake();
}
}
AUTWeap_FlakCannon.cpp - Add CameraShake assets & default properties to FlakCannon 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.05f;
CameraShakeDelay\[1\] \= 0.05f;
CameraShakeScale.SetNumZeroed(2);
CameraShakeScale\[0\] \= 1.f;
CameraShakeScale\[1\] \= 1.f;
# Source Code
* https://github.com/roman-dzieciol/UnrealTournament/commit/1a148e6381233f654aa4a1b84b88e94496c6b0c8 * https://github.com/EpicGames/UnrealTournament/pull/26
Retrieved from "https://wiki.unrealengine.com/index.php?title=UT3_Weapons_Tutorial_-_Flak_Cannon&oldid=10610"