UT3 Weapons Tutorial - Flak Cannon - Epic Wiki

# UT3 Weapons Tutorial - Flak Cannon

# Contents

# 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.

# 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

  1. Fire 9 shard projectiles at once
  2. Main shard should be fired at crosshair
  3. Remaining shards should be fired in a random'ish circle around the main shard
  4. The firing pattern should spread shards evenly inside the firing cone

# Secondary Fire

  1. Fire an explosive shell projectile

# Shard Projectile

  1. Bounces up to 2 times
  2. Is not affected by gravity until it bounces
  3. Can deal damage only when moving fast enough

# Main Shard Projectile

Same as shard projectile except that:

  1. Bounces up to 3 times
  2. Deals additional damage & momentum when firing at point blank at center of enemy

# Shell Projectile

  1. High trajectory
  2. Explodes upon contact
  3. 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.

  1. // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.

  2. #pragma once

  3. #include "UTWeapon.h"

  4. #include "UTWeapon_Boom.generated.h"

  5. UCLASS(Abstract, NotPlaceable)

  6. class AUTWeapon_Boom : public AUTWeapon

  7. {

  8. GENERATED\_UCLASS\_BODY()
    
  9. };

We will also need an abstract subclass of AUTProjectile called AUTProjectile_Boom. This will be our common base class for UT3 projectiles.

  1. // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.

  2. #pragma once

  3. #include "UTProjectile.h"

  4. #include "UTProjectile_Boom.generated.h"

  5. UCLASS(Abstract, NotPlaceable)

  6. class AUTProjectile_Boom : public AUTProjectile

  7. {

  8. GENERATED\_UCLASS\_BODY()
    
  9. };

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:

  1. // Ratio for scaling UT3 distance-related values

  2. // See AUTCharacter CapsuleComponent HalfHeight

  3. #define UT3_TO_UT4_SCALE (96.f / 44.f)

# Creating Flak Cannon Classes

Next, lets create classes for all the elements of Flak Cannon:

  1. Weapon
    1. Create subclass of AUTWeapon_Boom called AUTWeap_FlakCannon
  2. 3rd person weapon attachment
    1. Create subclass of AUTWeaponAttachment called AUTAttachment_FlakCannon
  3. Projectiles
    1. Create subclass of AUTProjectile_Boom called AUTProj_FlakShell
    2. Create subclass of AUTProjectile_Boom called AUTProj_FlakShard
    3. Create subclass of AUTProj_FlakShard called AUTProj_FlakShardMain
  4. Damage Types
    1. Create subclass of UUTDamageType called UTDmgType_FlakShell
    2. 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

  1. // Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.

  2. #include "UnrealTournament.h"

  3. #include "UTWeap_FlakCannon.h"

  4. #include "UTProj_FlakShard.h"

  5. #include "UTProj_FlakShell.h"

  6. #include "UTProj_FlakShardMain.h"

  7. #include "UTAttachment_FlakCannon.h"

  8. AUTWeap_FlakCannon::AUTWeap_FlakCannon(const FPostConstructInitializeProperties& PCIP)

  9. : Super(PCIP)

  10. {

  11. // Asset references
    
  12. struct FConstructorStatics
    
  13. {
    
  14. 	ConstructorHelpers::FObjectFinder<USkeletalMesh\> SkeletalMesh;
    
  15. 	ConstructorHelpers::FObjectFinder<UAnimBlueprintGeneratedClass\> AnimBlueprintGeneratedClass;
    
  16. 	ConstructorHelpers::FObjectFinder<UAnimMontage\> FireAnimation0;
    
  17. 	ConstructorHelpers::FObjectFinder<UAnimMontage\> FireAnimation1;
    
  18. 	ConstructorHelpers::FObjectFinder<UAnimMontage\> BringUpAnim;
    
  19. 	ConstructorHelpers::FObjectFinder<UAnimMontage\> PutDownAnim;
    
  20. 	ConstructorHelpers::FObjectFinder<USoundCue\> FireSound0;
    
  21. 	ConstructorHelpers::FObjectFinder<USoundCue\> FireSound1;
    
  22. 	ConstructorHelpers::FObjectFinder<USoundCue\> PickupSound;
    
  23. 	ConstructorHelpers::FObjectFinder<UParticleSystem\> MuzzleFlash;
    
  24. 	FConstructorStatics()
    
  25. 		: SkeletalMesh(TEXT("SkeletalMesh'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Meshes/SK\_WP\_FlakCannon\_1P.SK\_WP\_FlakCannon\_1P'"))
    
  26. 		, AnimBlueprintGeneratedClass(TEXT("AnimBlueprintGeneratedClass'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_AnimBP.Flak\_AnimBP\_C'"))
    
  27. 		, FireAnimation0(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_Fire\_Montage.Flak\_Fire\_Montage'"))
    
  28. 		, FireAnimation1(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_Fire\_Montage.Flak\_Fire\_Montage'"))
    
  29. 		, BringUpAnim(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_Equip.Flak\_Equip'"))
    
  30. 		, PutDownAnim(TEXT("AnimMontage'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Anim/Flak\_PutDown.Flak\_PutDown'"))
    
  31. 		, FireSound0(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireCue.A\_FlakCannon\_FireCue'"))
    
  32. 		, FireSound1(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireAltCue.A\_FlakCannon\_FireAltCue'"))
    
  33. 		, 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'"))
    
  34. 	{
    
  35. 	}
    
  36. };
    
  37. static FConstructorStatics ConstructorStatics;
    
  38. // Mesh
    
  39. Mesh\-\>SkeletalMesh \= ConstructorStatics.SkeletalMesh.Object;
    
  40. Mesh\-\>AnimBlueprintGeneratedClass \= ConstructorStatics.AnimBlueprintGeneratedClass.Object;
    
  41. Mesh\-\>RelativeLocation \= FVector(\-2.349056, \-3.957190, \-5.411549);
    
  42. Mesh\-\>RelativeScale3D \= FVector(0.750000, 0.750000, 0.750000);
    
  43. FireAnimation.SetNumZeroed(2);
    
  44. FireAnimation\[0\] \= ConstructorStatics.FireAnimation0.Object;
    
  45. FireAnimation\[1\] \= ConstructorStatics.FireAnimation1.Object;
    
  46. BringUpAnim \= ConstructorStatics.BringUpAnim.Object;
    
  47. PutDownAnim \= ConstructorStatics.PutDownAnim.Object;
    
  48. // Muzzle Flash
    
  49. TSubobjectPtr<UParticleSystemComponent\> MuzzleComponent \= PCIP.CreateDefaultSubobject<UParticleSystemComponent\>(this, TEXT("FlakCannon-MuzzleFlash"));
    
  50. MuzzleComponent\-\>Template \= ConstructorStatics.MuzzleFlash.Object;
    
  51. MuzzleComponent\-\>AttachTo(Mesh, FName(TEXT("MuzzleFlashSocket")));
    
  52. MuzzleFlash.SetNumZeroed(2);
    
  53. MuzzleFlash\[0\] \= MuzzleComponent;
    
  54. MuzzleFlash\[1\] \= MuzzleComponent;
    
  55. // 3rd person
    
  56. AttachmentType \= AUTAttachment\_FlakCannon::StaticClass();
    
  57. // Sounds
    
  58. FireSound.SetNumZeroed(2);
    
  59. FireSound\[0\] \= ConstructorStatics.FireSound0.Object;
    
  60. FireSound\[1\] \= ConstructorStatics.FireSound1.Object;
    
  61. PickupSound \= ConstructorStatics.PickupSound.Object;
    
  62. // UI
    
  63. Group \= 7;
    
  64. IconCoordinates \= FTextureUVs(131.000000, 429.000000, 132.000000, 52.000000);
    
  65. }

Notice that assets are loaded statically only once. Asset paths can be copy-pasted straight from editor.

AUTAttachment_FlakCannon.cpp - Setup asset references

  1. #include "UnrealTournament.h"

  2. #include "UTAttachment_FlakCannon.h"

  3. AUTAttachment_FlakCannon::AUTAttachment_FlakCannon(const class FPostConstructInitializeProperties& PCIP)

  4.  : Super(PCIP)
    
  5. {

  6.  // Structure to hold one-time initialization
    
  7.  struct FConstructorStatics
    
  8.  {
    
  9. 	ConstructorHelpers::FObjectFinder<USkeletalMesh\> SkeletalMesh;
    
  10. 	FConstructorStatics()
    
  11. 		: SkeletalMesh(TEXT("SkeletalMesh'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Meshes/SK\_WP\_FlakCannon\_3P\_Mid.SK\_WP\_FlakCannon\_3P\_Mid'"))
    
  12. 	{
    
  13. 	}
    
  14. };
    
  15. static FConstructorStatics ConstructorStatics;
    
  16. Mesh\-\>SkeletalMesh \= ConstructorStatics.SkeletalMesh.Object;
    
  17. }

AUTProj_FlakShell.cpp - Setup asset references

  1. #include "UnrealTournament.h"

  2. #include "UTProjectileMovementComponent.h"

  3. #include "UTProj_FlakShell.h"

  4. #include "UTProj_FlakShard.h"

  5. #include "UTDmgType_FlakShell.h"

  6. AUTProj_FlakShell::AUTProj_FlakShell(const class FPostConstructInitializeProperties& PCIP)

  7.  : Super(PCIP)
    
  8. {

  9. // Structure to hold one-time initialization
    
  10. struct FConstructorStatics
    
  11. {
    
  12. 	ConstructorHelpers::FObjectFinder<UParticleSystem\> ExplosionEffect;
    
  13. 	ConstructorHelpers::FObjectFinder<USoundCue\> ExplosionSound;
    
  14. 	ConstructorHelpers::FObjectFinder<USoundCue\> AmbientSound;
    
  15. 	ConstructorHelpers::FObjectFinder<UParticleSystem\> TrailEffect;
    
  16. 	FConstructorStatics()
    
  17. 		: ExplosionEffect(TEXT("ParticleSystem'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Effects/P\_WP\_Flak\_Alt\_Explosion.P\_WP\_Flak\_Alt\_Explosion'"))
    
  18. 		, ExplosionSound(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireAltImpactExplodeCue.A\_FlakCannon\_FireAltImpactExplodeCue'"))
    
  19. 		, AmbientSound(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireAltInAirCue.A\_FlakCannon\_FireAltInAirCue'"))
    
  20. 		, TrailEffect(TEXT("ParticleSystem'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Effects/P\_WP\_Flak\_Alt\_Smoke\_Trail.P\_WP\_Flak\_Alt\_Smoke\_Trail'"))
    
  21. 	{
    
  22. 	}
    
  23. };
    
  24. static FConstructorStatics ConstructorStatics;
    
  25. // Visuals
    
  26. TSubobjectPtr<UAudioComponent\> AmbientSound \= PCIP.CreateDefaultSubobject<UAudioComponent\>(this, TEXT("FlakShell-Ambient"));
    
  27. AmbientSound\-\>Sound \= ConstructorStatics.AmbientSound.Object;
    
  28. AmbientSound\-\>VolumeMultiplier \= 0.5;
    
  29. AmbientSound\-\>AttachTo(RootComponent);
    
  30. TSubobjectPtr<UParticleSystemComponent\> TrailComponent \= PCIP.CreateDefaultSubobject<UParticleSystemComponent\>(this, TEXT("FlakShell-Trail"));
    
  31. TrailComponent\-\>Template \= ConstructorStatics.TrailEffect.Object;
    
  32. TrailComponent\-\>SetRelativeLocation(FVector(\-3, 0, 0));
    
  33. TrailComponent\-\>SetRelativeScale3D(FVector(1.5, 1.5, 1.5));
    
  34. TrailComponent\-\>AttachTo(RootComponent);
    
  35. TSubobjectPtr<UPointLightComponent\> LightComponent \= PCIP.CreateDefaultSubobject<UPointLightComponent\>(this, TEXT("FlakShell-Light"));
    
  36. LightComponent\-\>Intensity \= 150;
    
  37. LightComponent\-\>AttenuationRadius \= 250;
    
  38. LightComponent\-\>LightColor \= FColor(47, 209, 255);
    
  39. LightComponent\-\>SetCastShadows(false);
    
  40. LightComponent\-\>AttachTo(RootComponent);
    
  41. ExplosionEffect \= ConstructorStatics.ExplosionEffect.Object;
    
  42. ExplosionSound \= ConstructorStatics.ExplosionSound.Object;
    
  43. }

The mesh is actually set up as part of particle emitter, hence there's no static mesh component.

AUTProj_FlakShard.cpp - Setup asset references

  1. #include "UnrealTournament.h"

  2. #include "UTProjectileMovementComponent.h"

  3. #include "UTProj_FlakShard.h"

  4. #include "UTDmgType_FlakShard.h"

  5. AUTProj_FlakShard::AUTProj_FlakShard(const class FPostConstructInitializeProperties& PCIP)

  6.  : Super(PCIP)
    
  7. {

  8.  // Structure to hold one-time initialization
    
  9. struct FConstructorStatics
    
  10. {
    
  11. 	ConstructorHelpers::FObjectFinder<UParticleSystem\> BounceEffect;
    
  12. 	ConstructorHelpers::FObjectFinder<USoundCue\> BounceSound;
    
  13. 	ConstructorHelpers::FObjectFinder<UStaticMesh\> StaticMesh;
    
  14. 	ConstructorHelpers::FObjectFinder<UMaterial\> StaticMeshMaterial0;
    
  15. 	ConstructorHelpers::FObjectFinder<UParticleSystem\> TrailEffect;
    
  16. 	FConstructorStatics()
    
  17. 		: BounceEffect(TEXT("ParticleSystem'/Game/RestrictedAssets/Weapons/Flak/Assets/Flak\_Hit\_Spark.Flak\_Hit\_Spark'"))
    
  18. 		, BounceSound(TEXT("SoundCue'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Audio/CUE/A\_FlakCannon\_FireImpactDirtCue.A\_FlakCannon\_FireImpactDirtCue'"))
    
  19. 		, StaticMesh(TEXT("StaticMesh'/Game/RestrictedAssets/Proto/UT3\_Weapons/WP\_FlakCannon/Meshes/S\_Flak\_Chunk.S\_Flak\_Chunk'"))
    
  20. 		, StaticMeshMaterial0(TEXT("Material'/Game/RestrictedAssets/Weapons/Flak/Assets/M\_Shard.M\_Shard'"))
    
  21. 		, TrailEffect(TEXT("ParticleSystem'/Game/RestrictedAssets/Weapons/Flak/Assets/Trail.Trail'"))
    
  22. 	{
    
  23. 	}
    
  24. };
    
  25. static FConstructorStatics ConstructorStatics;
    
  26. // Visuals
    
  27. TSubobjectPtr<UStaticMeshComponent\> StaticMeshComponent \= PCIP.CreateDefaultSubobject<UStaticMeshComponent\>(this, TEXT("FlakShard-StaticMesh"));
    
  28. StaticMeshComponent\-\>StaticMesh \= ConstructorStatics.StaticMesh.Object;
    
  29. StaticMeshComponent\-\>bGenerateOverlapEvents \= false;
    
  30. StaticMeshComponent\-\>SetCollisionProfileName(UCollisionProfile::NoCollision\_ProfileName);
    
  31. StaticMeshComponent\-\>SetMaterial(0, ConstructorStatics.StaticMeshMaterial0.Object);
    
  32. StaticMeshComponent\-\>SetRelativeScale3D(FVector(0.25, 0.25, 0.25));
    
  33. StaticMeshComponent\-\>AttachTo(RootComponent);
    
  34. TSubobjectPtr<UParticleSystemComponent\> TrailComponent \= PCIP.CreateDefaultSubobject<UParticleSystemComponent\>(this, TEXT("FlakShard-Trail"));
    
  35. TrailComponent\-\>Template \= ConstructorStatics.TrailEffect.Object;
    
  36. TrailComponent\-\>AttachTo(RootComponent);
    
  37. TSubobjectPtr<UPointLightComponent\> LightComponent \= PCIP.CreateDefaultSubobject<UPointLightComponent\>(this, TEXT("FlakShard-Light"));
    
  38. LightComponent\-\>Intensity \= 100;
    
  39. LightComponent\-\>AttenuationRadius \= 100;
    
  40. LightComponent\-\>LightColor \= FColor(255, 133, 35);
    
  41. LightComponent\-\>SetCastShadows(false);
    
  42. LightComponent\-\>AttachTo(RootComponent);
    
  43. }

# 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.

  1.  // Movement
    
  2.  ProjectileMovement\-\>InitialSpeed \= 1200.f \* UT3\_TO\_UT4\_SCALE;
    
  3.  ProjectileMovement\-\>MaxSpeed \= 1200.f \* UT3\_TO\_UT4\_SCALE;
    
  4.  ProjectileMovement\-\>ProjectileGravityScale \= 1.0f;
    
  5.  CollisionComp\-\>InitSphereRadius(10);
    
  6.  TossZ \= 305 \* UT3\_TO\_UT4\_SCALE;
    
  7. // Damage
    
  8. MyDamageType \= UUTDmgType\_FlakShell::StaticClass();
    
  9. DamageParams.BaseDamage \= 100;
    
  10. DamageParams.OuterRadius \= 200 \* UT3\_TO\_UT4\_SCALE;
    
  11. Momentum \= 75000 \* UT3\_TO\_UT4\_SCALE;
    
  12. InitialLifeSpan \= 6;
    

AUTProj_FlakShard.cpp - Lets make it rotate

  1.  // Movement
    
  2.  TSubobjectPtr<URotatingMovementComponent\> RotatingMovement \= PCIP.CreateDefaultSubobject<URotatingMovementComponent\>(this, TEXT("FlakShard-RotatingMovement"));
    
  3.  RotatingMovement\-\>RotationRate \= FRotator(0, 0, 270);
    
  4.  ProjectileMovement\-\>InitialSpeed \= 3500.f \* UT3\_TO\_UT4\_SCALE;
    
  5.  ProjectileMovement\-\>MaxSpeed \= 3500.f \* UT3\_TO\_UT4\_SCALE;
    
  6.  ProjectileMovement\-\>ProjectileGravityScale \= 0.f;
    
  7.  ProjectileMovement\-\>bRotationFollowsVelocity \= false;
    
  8. ProjectileMovement\-\>bShouldBounce \= true;
    
  9. // Damage
    
  10. MyDamageType \= UUTDmgType\_FlakShard::StaticClass();
    
  11. DamageParams.BaseDamage \= 18.f;
    
  12. Momentum \= 14000 \* UT3\_TO\_UT4\_SCALE;
    
  13. InitialLifeSpan \= 2.f;
    

AUTWeap_FlakCannon.cpp - Setting up fire modes

  1.  // Firing
    
  2.  ProjClass.SetNumZeroed(2);
    
  3.  ProjClass\[0\] \= AUTProj\_FlakShardMain::StaticClass();
    
  4.  ProjClass\[1\] \= AUTProj\_FlakShell::StaticClass();
    
  5.  FireInterval.SetNumZeroed(2);
    
  6.  FireInterval\[0\] \= 1.1;
    
  7.  FireInterval\[1\] \= 1.1;
    
  8. AmmoCost.SetNumZeroed(2);
    
  9. AmmoCost\[0\] \= 1;
    
  10. AmmoCost\[1\] \= 1;
    
  11. Ammo \= 10;
    
  12. MaxAmmo \= 30;
    
  13. 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

  1.  /\*\* Bounce effect \*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= Projectile)
    
  3.  UParticleSystem\* BounceEffect;
    
  4.  /\*\* Sound played when projectile bounces off wall \*/
    
  5.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= Projectile)
    
  6.  USoundBase\* BounceSound;
    

AUTProjectile_Boom.cpp - Override OnBounce to make it play bounce effects

  1. void AUTProjectile_Boom::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)

  2. {

  3.  Super::OnBounce(ImpactResult, ImpactVelocity);
    
  4.  // Spawn bounce effect
    
  5.  if (GetNetMode() !\= NM\_DedicatedServer)
    
  6.  {
    
  7.  	UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BounceEffect, ImpactResult.Location, ImpactResult.ImpactNormal.Rotation(), true);
    
  8.  }
    
  9. // Play bounce sound
    
  10. if (BounceSound !\= NULL)
    
  11. {
    
  12. 	UUTGameplayStatics::UTPlaySound(GetWorld(), BounceSound, this, SRT\_IfSourceNotReplicated, false);
    
  13. }
    
  14. }

AUTProj_FlakShard.cpp - Add default assets to constructor

  1.  BounceEffect \= ConstructorStatics.BounceEffect.Object;
    
  2.  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

  1.  /\*\* Bounce effect \*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= Projectile)
    
  3.  FRotator BounceEffectRotation;
    

AUTProjectile_Boom.cpp - We will use it to rotate HitNormal used for BounceEffect spawn rotation

  1. void AUTProjectile_Boom::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)

  2. {

  3.  Super::OnBounce(ImpactResult, ImpactVelocity);
    
  4.  // Spawn bounce effect
    
  5.  if (GetNetMode() !\= NM\_DedicatedServer)
    
  6.  {
    
  7.  	UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), BounceEffect, ImpactResult.Location, BounceEffectRotation.RotateVector(ImpactResult.ImpactNormal).Rotation(), true);
    
  8.  }
    
  9. // Play bounce sound
    
  10. if (BounceSound !\= NULL)
    
  11. {
    
  12. 	UUTGameplayStatics::UTPlaySound(GetWorld(), BounceSound, this, SRT\_IfSourceNotReplicated, false);
    
  13. }
    

AUTProj_FlakShard.cpp - Set default BounceEffectRotation in constructor

  1.  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

  1. void AUTProj_FlakShard::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)

  2. {

  3.  Super::OnBounce(ImpactResult, ImpactVelocity);
    
  4.  // Set gravity on bounce
    
  5.  ProjectileMovement\-\>ProjectileGravityScale \= 1.f;
    
  6. }

# 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

  1.  /\*\* Limit number of bounces \*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  3.  int32 BounceLimit;
    
  4.  /\*\* Current number of times this projectile bounced \*/
    
  5.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  6.  int32 BounceCount;
    

AUTProj_FlakShard.cpp - Set default number of bounces in constructor

  1.  BounceLimit \= 2;
    

AUTProj_FlakShard.cpp - Count the number of bounces in OnBounce and disable bouncing once limit is reached

  1. void AUTProj_FlakShard::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)

  2. {

  3.  Super::OnBounce(ImpactResult, ImpactVelocity);
    
  4.  // Set gravity on bounce
    
  5.  ProjectileMovement\-\>ProjectileGravityScale \= 1.f;
    
  6.  // Limit number of bounces
    
  7.  if (++BounceCount \== BounceLimit)
    
  8. {
    
  9. 	ProjectileMovement\-\>bShouldBounce \= false;
    
  10. }
    
  11. }

# 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

  1.  /\*\* Increment lifespan on bounce by this amount \*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  3.  float BounceLifeSpanIncrement;
    
  4.  /\*\* Increment lifespan when projectile stops by this amount \*/
    
  5.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  6.  float BounceFinalLifeSpanIncrement;
    

AUTProj_FlakShard.cpp - Set default values for bonus lifespan

  1.  BounceLifeSpanIncrement \= 0.5f;
    
  2.  BounceFinalLifeSpanIncrement \= 0.25f;
    

AUTProj_FlakShard.cpp - Count the number of bounces in OnBounce and disable bouncing once limit is reached

  1. void AUTProj_FlakShard::OnBounce(const struct FHitResult& ImpactResult, const FVector& ImpactVelocity)

  2. {

  3.  Super::OnBounce(ImpactResult, ImpactVelocity);
    
  4.  // Set gravity on bounce
    
  5.  ProjectileMovement\-\>ProjectileGravityScale \= 1.f;
    
  6.  // Extend lifespan on bounce
    
  7.  SetLifeSpan(GetLifeSpan() + BounceLifeSpanIncrement);
    
  8. // Limit number of bounces
    
  9. if (++BounceCount \== BounceLimit)
    
  10. {
    
  11. 	ProjectileMovement\-\>bShouldBounce \= false;
    
  12. 	SetLifeSpan(GetLifeSpan() + BounceFinalLifeSpanIncrement);
    
  13. }
    
  14. }

# 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.

  1.  /\*\* Damage reduction per second, down to minimum damage\*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= Damage)
    
  3.  float DamageAttenuation;
    
  4.  /\*\* Base damage calculation \*/
    
  5.  UFUNCTION(BlueprintCallable, Category \= Projectile)
    
  6.  virtual float GetDamage(AActor\* OtherActor, const FVector& HitLocation);
    
  7.  /\*\* Momentum calculation \*/
    
  8. UFUNCTION(BlueprintCallable, Category \= Projectile)
    
  9. virtual float GetMomentum(AActor\* OtherActor, const FVector& HitLocation);
    

AUTProjectile_Boom.cpp - If DamageAttenuation is set, GetDamage() will return reduced damage.

  1. float AUTProjectile_Boom::GetDamage(AActor* OtherActor, const FVector& HitLocation)

  2. {

  3.  if (DamageAttenuation \> 0)
    
  4.  {
    
  5.  	return FMath::Max(DamageParams.BaseDamage \- (GetWorld()\-\>TimeSeconds \- CreationTime) \* DamageAttenuation, DamageParams.MinimumDamage);
    
  6.  }
    
  7.  return DamageParams.BaseDamage;
    
  8. }

AUTProjectile_Boom.cpp - GetMomentum() will return standard momentum by default.

  1. float AUTProjectile_Boom::GetMomentum(AActor* OtherActor, const FVector& HitLocation)

  2. {

  3.  return Momentum;
    
  4. }

AUTProjectile_Boom.cpp - To adjust damage & momentum of explosions, override Explode_Implementation().

  1. void AUTProjectile_Boom::Explode_Implementation(const FVector& HitLocation, const FVector& HitNormal)

  2. {

  3.  if (!bExploded)
    
  4.  {
    
  5.  	if (DamageParams.OuterRadius \> 0.0f)
    
  6.  	{
    
  7.  		TArray<AActor\*\> IgnoreActors;
    
  8.  		if (ImpactedActor !\= NULL)
    
  9.  		{
    
  10. 			IgnoreActors.Add(ImpactedActor);
    
  11. 		}
    
  12. 		const float AdjustedDamage \= GetDamage(NULL, HitLocation);
    
  13. 		const float AdjustedMomentum \= GetMomentum(NULL, HitLocation);
    
  14. 		UUTGameplayStatics::UTHurtRadius(this, AdjustedDamage, DamageParams.MinimumDamage, AdjustedMomentum, HitLocation, DamageParams.InnerRadius, DamageParams.OuterRadius, DamageParams.DamageFalloff, MyDamageType, IgnoreActors, this, InstigatorController);
    
  15. 	}
    
  16. 	if (Role \== ROLE\_Authority)
    
  17. 	{
    
  18. 		bTearOff \= true;
    
  19. 	}
    
  20. 	bExploded \= true;
    
  21. 	UUTGameplayStatics::UTPlaySound(GetWorld(), ExplosionSound, this, ESoundReplicationType::SRT\_IfSourceNotReplicated);
    
  22. 	if (GetNetMode() !\= NM\_DedicatedServer)
    
  23. 	{
    
  24. 		UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionEffect, GetActorLocation(), HitNormal.Rotation(), true);
    
  25. 	}
    
  26. 	ShutDown();
    
  27. }
    
  28. }

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

  1. void AUTProjectile_Boom::DamageImpactedActor_Implementation(AActor* OtherActor, UPrimitiveComponent* OtherComp, const FVector& HitLocation, const FVector& HitNormal)

  2. {

  3.  const float AdjustedDamage \= GetDamage(OtherActor, HitLocation);
    
  4.  const float AdjustedMomentum \= GetMomentum(OtherActor, HitLocation);
    
  5.  // treat as point damage if projectile has no radius
    
  6.  if (DamageParams.OuterRadius \> 0.0f)
    
  7.  {
    
  8.  	FUTRadialDamageEvent Event;
    
  9.  	Event.Params \= DamageParams;
    
  10. 	Event.Params.MinimumDamage \= AdjustedDamage; // force full damage for direct hit
    
  11. 	Event.DamageTypeClass \= MyDamageType;
    
  12. 	Event.Origin \= HitLocation;
    
  13. 	Event.BaseMomentumMag \= AdjustedMomentum;
    
  14. 	new(Event.ComponentHits) FHitResult(OtherActor, OtherComp, HitLocation, HitNormal);
    
  15. 	Event.ComponentHits\[0\].TraceStart \= HitLocation \- GetVelocity();
    
  16. 	Event.ComponentHits\[0\].TraceEnd \= HitLocation + GetVelocity();
    
  17. 	OtherActor\-\>TakeDamage(AdjustedDamage, Event, InstigatorController, this);
    
  18. }
    
  19. else
    
  20. {
    
  21. 	FUTPointDamageEvent Event;
    
  22. 	Event.Damage \= AdjustedDamage;
    
  23. 	Event.DamageTypeClass \= MyDamageType;
    
  24. 	Event.HitInfo \= FHitResult(OtherActor, OtherComp, HitLocation, HitNormal);
    
  25. 	Event.ShotDirection \= GetVelocity().SafeNormal();
    
  26. 	Event.Momentum \= Event.ShotDirection \* AdjustedMomentum;
    
  27. 	OtherActor\-\>TakeDamage(AdjustedDamage, Event, InstigatorController, this);
    
  28. }
    
  29. }

AUTProj_FlakShard.cpp - Finally set default DamageAttentuation in Flak Shard constructor

  1.  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.

  1.  /\*\* Minimum speed at which damage can be applied \*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  3.  float MinDamageSpeed;
    

AUTProj_FlakShard.cpp - Set default MinDamageSpeed in FlakShard constructor.

  1.  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.

  1. float AUTProj_FlakShard::GetDamage(AActor* OtherActor, const FVector& HitLocation)

  2. {

  3.  // Apply damage only when moving fast enough
    
  4.  if (GetVelocity().Size() \> MinDamageSpeed)
    
  5.  {
    
  6.  	return Super::GetDamage(OtherActor, HitLocation);
    
  7.  }
    
  8.  return 0.f;
    
  9. }

# 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.

  1.  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

  1.  /\*\* Momentum bonus for point blank shots \*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  3.  float CenteredMomentumBonus;
    
  4.  /\*\* Damage bonus for point blank shots \*/
    
  5.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  6.  float CenteredDamageBonus;
    
  7.  /\*\* Timeout for point blank shots \*/
    
  8. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  9. float MaxBonusTime;
    

AUTProj_FlakShardMain.cpp - Set default properties in constructor

  1.  CenteredMomentumBonus \= 90000 \* UT3\_TO\_UT4\_SCALE;
    
  2.  CenteredDamageBonus \= 100.0;
    
  3.  MaxBonusTime \= 0.2;
    

AUTProj_FlakShardMain.cpp - Override GetDamage() & GetMomentum() to return adjusted valued

  1. /**

  2. * Increase damage to UTPawns based on how centered this shard is on target. If it is within the time MaxBonusTime time period.

  3. * e.g. point blank shot with the flak cannon you will do mega damage. Once MaxBonusTime passes then this shard becomes a normal shard.

  4. */

  5. float AUTProj_FlakShardMain::GetDamage(AActor* OtherActor, const FVector& HitLocation)

  6. {

  7.  const float CalculatedDamage \= Super::GetDamage(OtherActor, HitLocation);
    
  8.  // When hitting a pawn within bonus point blank time
    
  9. AUTCharacter\* OtherCharacter \= Cast<AUTCharacter\>(OtherActor);
    
  10. const float BonusTime \= GetLifeSpan() \- InitialLifeSpan + MaxBonusTime;
    
  11. if (CalculatedDamage \> 0.f && OtherCharacter !\= NULL && BonusTime \> 0)
    
  12. {
    
  13. 	// Apply bonus damage
    
  14. 	const float CharacterRadius \= OtherCharacter\-\>GetSimpleCollisionRadius();
    
  15. 	const float OffCenterDistance \= FMath::PointDistToLine(OtherActor\-\>GetActorLocation(), GetVelocity().SafeNormal(), HitLocation);
    
  16. 	const float OffCenterMultiplier \= FMath::Max(0.f, 2.f \* (CharacterRadius \- OffCenterDistance)) / CharacterRadius;
    
  17. 	const float BonusDamage \= CenteredDamageBonus \* BonusTime \* OffCenterMultiplier;
    
  18. 	return CalculatedDamage + BonusDamage;
    
  19. }
    
  20. return CalculatedDamage;
    
  21. }

  22. /**

  23. * Increase momentum imparted based on how recently this shard was fired

  24. */

  25. float AUTProj_FlakShardMain::GetMomentum(AActor* OtherActor, const FVector& HitLocation)

  26. {

  27. // When hitting something within bonus point blank time
    
  28. const float Momentum \= Super::GetMomentum(OtherActor, HitLocation);
    
  29. const float BonusTime \= GetLifeSpan() \- InitialLifeSpan + MaxBonusTime;
    
  30. if (BonusTime \> 0)
    
  31. {
    
  32. 	// Apply bonus momentum
    
  33. 	return Momentum + CenteredMomentumBonus \* BonusTime;
    
  34. }
    
  35. return Momentum;
    
  36. }

# 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.

  1.  /\*\* Number of shards to spawn \*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  3.  int32 ShardSpawnCount;
    
  4.  /\*\* Angle for spawning shards, relative to hit normal \*/
    
  5.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  6.  float ShardSpawnAngle;
    
  7.  /\*\* Shard class type \*/
    
  8. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Flak Cannon")
    
  9. TSubclassOf<AUTProjectile\> ShardClass;
    

AUTProj_FlakShell.cpp - Set default properties in FlakShell constructor

  1.  ShardClass \= AUTProj\_FlakShard::StaticClass();
    
  2.  ShardSpawnCount \= 5;
    
  3.  ShardSpawnAngle \= 85;
    

AUTProj_FlakShell.cpp - Override Explode_Implementation() to spawn additional projectiles

  1. void AUTProj_FlakShell::Explode_Implementation(const FVector& HitLocation, const FVector& HitNormal)

  2. {

  3.  // On explosion spawn additional flak shards
    
  4.  if (!bExploded && Role \== ROLE\_Authority && ShardClass && ShardSpawnCount \> 0)
    
  5.  {
    
  6.  	// Setup spawn parameters
    
  7.  	FActorSpawnParameters Params;
    
  8.  	Params.Instigator \= Instigator;
    
  9.  	Params.Owner \= Instigator;
    
  10. 	Params.bNoCollisionFail \= true;
    
  11. 	for (int32 i \= 0; i < ShardSpawnCount; ++i)
    
  12. 	{
    
  13. 		// Randomize spawn direction along hit normal
    
  14. 		const FRotator SpawnRotation \= FMath::VRandCone(HitNormal, FMath::DegreesToRadians(ShardSpawnAngle)).Rotation();
    
  15. 		// Spawn shard
    
  16. 		GetWorld()\-\>SpawnActor<AUTProjectile\>(ShardClass, HitLocation, SpawnRotation, Params);
    
  17. 	}
    
  18. }
    
  19. Super::Explode\_Implementation(HitLocation, HitNormal);
    
  20. }

# 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

  1.  /\*\* Number of projectiles to fire.
    
  2. * When firing multiple projectiles at once, main projectile will be fired at crosshair.

  3. * Remaining projectiles will be fired in a circle pattern */

  4.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
    
  5.  TArray<int32\> MultiShotCount;
    
  6.  /\*\* Projectile class to use when firing multiple projectiles at once.
    
  7. * This is only for additional projectiles, main projectile will use ProjClass.

  8. * If not specified, ProjClass will be used. */

  9. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
    
  10. 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.

  1. AUTProjectile* AUTWeapon_Boom::FireProjectile()

  2. {

  3.  if (GetUTOwner() \== NULL)
    
  4.  {
    
  5.  	UE\_LOG(UT, Warning, TEXT("%s::FireProjectile(): Weapon is not owned (owner died during firing sequence)"));
    
  6.  	return NULL;
    
  7.  }
    
  8.  else if (Role \== ROLE\_Authority)
    
  9.  {
    
  10. 	// try and fire a projectile
    
  11. 	checkSlow(ProjClass.IsValidIndex(CurrentFireMode) && ProjClass\[CurrentFireMode\] !\= NULL);
    
  12. 	// increment 3rd person muzzle flash count
    
  13. 	UTOwner\-\>IncrementFlashCount(CurrentFireMode);
    
  14. 	// Setup spawn parameters
    
  15. 	FActorSpawnParameters Params;
    
  16. 	Params.Instigator \= UTOwner;
    
  17. 	Params.Owner \= UTOwner;
    
  18. 	Params.bNoCollisionFail \= true;
    
  19. 	// Get muzzle location and rotation
    
  20. 	const FVector SpawnLocation \= GetFireStartLoc();
    
  21. 	const FRotator SpawnRotation \= GetAdjustedAim(SpawnLocation);
    
  22. 	// Fire projectiles
    
  23. 	AUTProjectile\* MainProjectile \= NULL;
    
  24. 	if (MultiShotCount.IsValidIndex(CurrentFireMode) && MultiShotCount\[CurrentFireMode\] \> 1)
    
  25. 	{
    
  26. 		for (int32 i \= 0; i < MultiShotCount\[CurrentFireMode\]; ++i)
    
  27. 		{
    
  28. 			// Get firing location and rotation for this projectile
    
  29. 			const FVector MultiShotLocation \= GetFireStartLoc();
    
  30. 			const FRotator MultiShotRotation \= GetAdjustedAim(SpawnLocation);
    
  31. 			// Get projectile class
    
  32. 			TSubclassOf<AUTProjectile\> ProjectileClass \= ProjClass\[CurrentFireMode\];
    
  33. 			if (i !\= 0 && MultiShotProjClass.IsValidIndex(CurrentFireMode) && MultiShotProjClass\[CurrentFireMode\] !\= NULL)
    
  34. 			{
    
  35. 				ProjectileClass \= MultiShotProjClass\[CurrentFireMode\];
    
  36. 			}
    
  37. 			// Spawn projectile
    
  38. 			AUTProjectile\* MultiShot \= GetWorld()\-\>SpawnActor<AUTProjectile\>(ProjectileClass, MultiShotLocation, MultiShotRotation, Params);
    
  39. 			if (MainProjectile \== NULL)
    
  40. 			{
    
  41. 				MainProjectile \= MultiShot;
    
  42. 			}
    
  43. 		}
    
  44. 	}
    
  45. 	else
    
  46. 	{
    
  47. 		// Spawn projectile
    
  48. 		MainProjectile \= GetWorld()\-\>SpawnActor<AUTProjectile\>(ProjClass\[CurrentFireMode\], SpawnLocation, SpawnRotation, Params);
    
  49. 	}
    
  50. 	return MainProjectile;
    
  51. }
    
  52. else
    
  53. {
    
  54. 	return NULL;
    
  55. }
    
  56. }

This will spawn MultiShotCount projectiles on firing.

AUTWeap_FlakCannon.cpp - Add multishot default properties

  1.  MultiShotCount.SetNumZeroed(1);
    
  2.  MultiShotCount\[0\] \= 9;
    
  3.  MultiShotProjClass.SetNumZeroed(1);
    
  4.  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.

  1.  /\*\* Firing cone angle in degrees.
    
  2. * Applies to individual projectiles when firing multiple at once. */

  3.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
    
  4.  TArray<FRotator\> MultiShotAngle;
    
  5.  /\*\* Returns projectile spawn location when firing multiple projectiles at once \*/
    
  6.  UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category \= "Weapon")
    
  7.  FVector GetFireLocationForMultiShot(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation);
    
  8. /\*\* Returns projectile spawn rotation when firing multiple projectiles at once \*/
    
  9. UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category \= "Weapon")
    
  10. FRotator GetFireRotationForMultiShot(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation);
    

AUTWeap_FlakCannon.cpp - Add multishot default properties

  1.  MultiShotAngle.SetNumZeroed(1);
    
  2.  MultiShotAngle\[0\] \= FRotator(0, 3, 0);
    

AUTWeapon_Boom.cpp - Implement GetFireLocationForMultiShot(). We will add randomization later.

  1. FVector AUTWeapon_Boom::GetFireLocationForMultiShot_Implementation(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation)

  2. {

  3.  // Main projectile fires straight from muzzle center
    
  4.  return FireLocation;
    
  5. }

AUTWeapon_Boom.cpp - Implement GetFireRotationForMultiShot(). We will add randomization later.

  1. FRotator AUTWeapon_Boom::GetFireRotationForMultiShot_Implementation(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation)

  2. {

  3.  if (MultiShotIndex \> 0 && MultiShotAngle.IsValidIndex(CurrentFireMode))
    
  4.  {
    
  5.  	// Each additional projectile can have own fragment of firing cone.
    
  6.  	// This way there are no empty spots in firing cone due to randomness.
    
  7.  	// While still randomish, the pattern is predictable, which is good for pro gaming.
    
  8.  	// Get direction at fragment of firing cone
    
  9. 	const float Alpha \= (float)(MultiShotIndex \- 1) / (float)(MultiShotCount\[CurrentFireMode\] \- 1);
    
  10. 	const FRotator ConeSector \= FRotator(0, 0, 360.f \* Alpha);
    
  11. 	FVector FireDirection \= ConeSector.RotateVector(MultiShotAngle\[CurrentFireMode\].Vector());
    
  12. 	// Return firing cone rotated by player's firing rotation
    
  13. 	return FireRotation.RotateVector(FireDirection).Rotation();
    
  14. }
    
  15. // Main projectile fires straight at crosshair
    
  16. return FireRotation;
    
  17. }

AUTWeapon_Boom.cpp - Modify FireProjectile() so it uses our functions

  1. AUTProjectile* AUTWeapon_Boom::FireProjectile()

  2. {

  3.  if (GetUTOwner() \== NULL)
    
  4.  {
    
  5.  	UE\_LOG(UT, Warning, TEXT("%s::FireProjectile(): Weapon is not owned (owner died during firing sequence)"));
    
  6.  	return NULL;
    
  7.  }
    
  8.  else if (Role \== ROLE\_Authority)
    
  9.  {
    
  10. 	// try and fire a projectile
    
  11. 	checkSlow(ProjClass.IsValidIndex(CurrentFireMode) && ProjClass\[CurrentFireMode\] !\= NULL);
    
  12. 	// increment 3rd person muzzle flash count
    
  13. 	UTOwner\-\>IncrementFlashCount(CurrentFireMode);
    
  14. 	// Setup spawn parameters
    
  15. 	FActorSpawnParameters Params;
    
  16. 	Params.Instigator \= UTOwner;
    
  17. 	Params.Owner \= UTOwner;
    
  18. 	Params.bNoCollisionFail \= true;
    
  19. 	// Get muzzle location and rotation
    
  20. 	const FVector SpawnLocation \= GetFireStartLoc();
    
  21. 	const FRotator SpawnRotation \= GetAdjustedAim(SpawnLocation);
    
  22. 	// Fire projectiles
    
  23. 	AUTProjectile\* MainProjectile \= NULL;
    
  24. 	if (MultiShotCount.IsValidIndex(CurrentFireMode) && MultiShotCount\[CurrentFireMode\] \> 1)
    
  25. 	{
    
  26. 		for (int32 i \= 0; i < MultiShotCount\[CurrentFireMode\]; ++i)
    
  27. 		{
    
  28. 			// Get firing location and rotation for this projectile
    
  29. 			const FVector MultiShotLocation \= GetFireLocationForMultiShot(i, SpawnLocation, SpawnRotation);
    
  30. 			const FRotator MultiShotRotation \= GetFireRotationForMultiShot(i, SpawnLocation, SpawnRotation);
    
  31. 			// Get projectile class
    
  32. 			TSubclassOf<AUTProjectile\> ProjectileClass \= ProjClass\[CurrentFireMode\];
    
  33. 			if (i !\= 0 && MultiShotProjClass.IsValidIndex(CurrentFireMode) && MultiShotProjClass\[CurrentFireMode\] !\= NULL)
    
  34. 			{
    
  35. 				ProjectileClass \= MultiShotProjClass\[CurrentFireMode\];
    
  36. 			}
    
  37. 			// Spawn projectile
    
  38. 			AUTProjectile\* MultiShot \= GetWorld()\-\>SpawnActor<AUTProjectile\>(ProjectileClass, MultiShotLocation, MultiShotRotation, Params);
    
  39. 			if (MainProjectile \== NULL)
    
  40. 			{
    
  41. 				MainProjectile \= MultiShot;
    
  42. 			}
    
  43. 		}
    
  44. 	}
    
  45. 	else
    
  46. 	{
    
  47. 		// Spawn projectile
    
  48. 		MainProjectile \= GetWorld()\-\>SpawnActor<AUTProjectile\>(ProjClass\[CurrentFireMode\], SpawnLocation, SpawnRotation, Params);
    
  49. 	}
    
  50. 	return MainProjectile;
    
  51. }
    
  52. else
    
  53. {
    
  54. 	return NULL;
    
  55. }
    
  56. }

# 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

  1.  /\*\* Firing location randomness, in unreal units.
    
  2. * Applies to individual projectiles when firing multiple at once */

  3.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
    
  4.  TArray<FVector\> MultiShotLocationSpread;
    
  5.  /\*\* Firing direction randomness, in degrees. 
    
  6. * Applies to individual projectiles when firing multiple at once */

  7.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
    
  8.  TArray<float\> MultiShotRotationSpread;
    

AUTWeapon_Boom.cpp - Modify GetFireLocationForMultiShot() so it returns adjusted location.

  1. FVector AUTWeapon_Boom::GetFireLocationForMultiShot_Implementation(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation)

  2. {

  3.  if (MultiShotIndex \> 0 && MultiShotLocationSpread.IsValidIndex(CurrentFireMode))
    
  4.  {
    
  5.  	// Randomise each projectile's spawn location if needed.
    
  6.  	return FireLocation + FireRotation.RotateVector(FMath::VRand() \* MultiShotLocationSpread\[CurrentFireMode\]);
    
  7.  }
    
  8.  // Main projectile fires straight from muzzle center
    
  9. return FireLocation;
    
  10. }

AUTWeapon_Boom.cpp - Modify GetFireRotationForMultiShot() so it returns adjusted rotation.

  1. FRotator AUTWeapon_Boom::GetFireRotationForMultiShot_Implementation(int32 MultiShotIndex, const FVector& FireLocation, const FRotator& FireRotation)

  2. {

  3.  if (MultiShotIndex \> 0 && MultiShotAngle.IsValidIndex(CurrentFireMode))
    
  4.  {
    
  5.  	// Each additional projectile can have own fragment of firing cone.
    
  6.  	// This way there are no empty spots in firing cone due to randomness.
    
  7.  	// While still randomish, the pattern is predictable, which is good for pro gaming.
    
  8.  	// Get direction at fragment of firing cone
    
  9. 	const float Alpha \= (float)(MultiShotIndex \- 1) / (float)(MultiShotCount\[CurrentFireMode\] \- 1);
    
  10. 	const FRotator ConeSector \= FRotator(0, 0, 360.f \* Alpha);
    
  11. 	FVector FireDirection \= ConeSector.RotateVector(MultiShotAngle\[CurrentFireMode\].Vector());
    
  12. 	// Randomise each projectile's spawn rotation if needed 
    
  13. 	if (MultiShotRotationSpread.IsValidIndex(CurrentFireMode))
    
  14. 	{
    
  15. 		FireDirection \= FMath::VRandCone(FireDirection, FMath::DegreesToRadians(MultiShotRotationSpread\[CurrentFireMode\]));
    
  16. 	}
    
  17. 	// Return firing cone rotated by player's firing rotation
    
  18. 	return FireRotation.RotateVector(FireDirection).Rotation();
    
  19. }
    
  20. // Main projectile fires straight at crosshair
    
  21. return FireRotation;
    
  22. }

AUTWeap_FlakCannon.cpp - Add firing pattern default properties

  1.  MultiShotLocationSpread.SetNumZeroed(1);
    
  2.  MultiShotLocationSpread\[0\] \= FVector(0, 3, 3);
    
  3.  MultiShotRotationSpread.SetNumZeroed(1);
    
  4.  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

  1.  /\*\* delay between firing and camera shake being played \*/
    
  2.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
    
  3.  TArray<float\> CameraShakeDelay;
    
  4.  /\*\* how strong camera shake should be \*/
    
  5.  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
    
  6.  TArray<float\> CameraShakeScale;
    
  7. /\*\* camera shake type \*/
    
  8. UPROPERTY(EditAnywhere, BlueprintReadWrite, Category \= "Weapon")
    
  9. TArray< TSubclassOf<class UCameraShake\> \> CameraShakeType;
    
  10. /\*\* Plays camera shake immediately \*/
    
  11. UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category \= "Weapon")
    
  12. void PlayCameraShake();
    

AUTWeapon_Boom.cpp - Add PlayCameraShake() implementation

  1. void AUTWeapon_Boom::PlayCameraShake_Implementation()

  2. {

  3.  if (UTOwner !\= NULL)
    
  4.  {
    
  5.  	AUTPlayerController\* PC \= Cast<AUTPlayerController\>(UTOwner\-\>Controller);
    
  6.  	if (PC !\= NULL)
    
  7.  	{
    
  8.  		// Play camera shake
    
  9.  		if (CameraShakeType.IsValidIndex(CurrentFireMode) && CameraShakeType\[CurrentFireMode\] !\= NULL && CameraShakeScale.IsValidIndex(CurrentFireMode))
    
  10. 		{
    
  11. 			PC\-\>ClientPlayCameraShake(CameraShakeType\[CurrentFireMode\], CameraShakeScale\[CurrentFireMode\]);
    
  12. 		}
    
  13. 	}
    
  14. }
    
  15. }

AUTWeapon_Boom.cpp - Override PlayFiringEffects() so it calls our PlayCameraShake() function

  1. void AUTWeapon_Boom::PlayFiringEffects()

  2. {

  3.  Super::PlayFiringEffects();
    
  4.  // Play camera shake after optional delay
    
  5.  if (CameraShakeDelay.IsValidIndex(CurrentFireMode) && CameraShakeDelay\[CurrentFireMode\] \> 0)
    
  6.  {
    
  7.  	GetWorldTimerManager().SetTimer(this, &AUTWeapon\_Boom::PlayCameraShake, CameraShakeDelay\[CurrentFireMode\], false);
    
  8.  }
    
  9. else
    
  10. {
    
  11. 	PlayCameraShake();
    
  12. }
    
  13. }

AUTWeap_FlakCannon.cpp - Add CameraShake assets & default properties to FlakCannon constructor

  1.  struct FConstructorStatics
    
  2.  {
    
  3.  	...
    
  4.  	ConstructorHelpers::FClassFinder<UCameraShake\> CameraShakeType0;
    
  5.  	ConstructorHelpers::FClassFinder<UCameraShake\> CameraShakeType1;
    
  6.  	FConstructorStatics()
    
  7.  		...
    
  8.  		, CameraShakeType0(TEXT("BlueprintGeneratedClass'/Game/RestrictedAssets/Blueprints/WIP/Nick/CameraAnims/Camerashake2.Camerashake2\_C'"))
    
  9.  		, CameraShakeType1(TEXT("BlueprintGeneratedClass'/Game/RestrictedAssets/Blueprints/WIP/Nick/CameraAnims/Camerashake2.Camerashake2\_C'"))
    
  10. 	{
    
  11. 	}
    
  12. };
    
  13. ...
    
  14. CameraShakeType.SetNumZeroed(2);
    
  15. CameraShakeType\[0\] \= ConstructorStatics.CameraShakeType0.Class;
    
  16. CameraShakeType\[1\] \= ConstructorStatics.CameraShakeType1.Class;
    
  17. CameraShakeDelay.SetNumZeroed(2);
    
  18. CameraShakeDelay\[0\] \= 0.05f;
    
  19. CameraShakeDelay\[1\] \= 0.05f;
    
  20. CameraShakeScale.SetNumZeroed(2);
    
  21. CameraShakeScale\[0\] \= 1.f;
    
  22. 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"

Categories: