Section 15: AI and Behavior Trees - Lecture 297

Weapon Trails
This commit is contained in:
charnet3d 2024-03-09 19:28:12 +01:00
parent d005e4543f
commit a1ec63742e
34 changed files with 338 additions and 31 deletions

BIN
Content/_Game/Assets/Sounds/WeaponSwings/AxeSwing1.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/AxeSwing2.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/AxeSwing3.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/HeavySwing1.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/HeavySwing2.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/StickSwing1.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/StickSwing2.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/StickSwing3.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/Swing1.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/Swing2.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/Swing3.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/Swing4.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/Swing5.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Assets/Sounds/WeaponSwings/WeaponSwing.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
Content/_Game/Enemies/Animations/EnemyAttack.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
Content/_Game/Enemies/EnemyBP.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Content/_Game/Enemies/GruxAnimBP.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Content/_Game/EnemyController/BTT_Attack.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/_Game/HUD/AmmoCountBP.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Content/_Game/HUD/CharacterHealthBar.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/HUD/EnemyHealthBarBP.uasset (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

BIN
Content/_Game/HUD/WeaponSlot.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Content/_Game/Maps/DefaultMap.umap (Stored with Git LFS)

Binary file not shown.

View File

@ -13,6 +13,8 @@
#include "BehaviorTree/BlackboardComponent.h"
#include "Components/SphereComponent.h"
#include "ShooterCharacter.h"
#include "Components/BoxComponent.h"
#include "Components/CapsuleComponent.h"
// Sets default values
AEnemy::AEnemy() :
@ -24,7 +26,12 @@ AEnemy::AEnemy() :
bCanHitReact(true),
HitNumberDestroyTime(1.5f),
bStunned(false),
StunChance(.5f)
StunChance(.5f),
AttackLFast(TEXT("AttackLFast")),
AttackRFast(TEXT("AttackRFast")),
AttackL(TEXT("AttackL")),
AttackR(TEXT("AttackR")),
BaseDamage(20.f)
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
@ -32,6 +39,17 @@ AEnemy::AEnemy() :
// Create the Agro Sphere
AgroSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AgroSphere"));
AgroSphere->SetupAttachment(GetRootComponent());
// Create the Combat Range Sphere
CombatRangeSphere = CreateDefaultSubobject<USphereComponent>(TEXT("CombatRangeSphere"));
CombatRangeSphere->SetupAttachment(GetRootComponent());
// Construct left and right weapon collision boxes
LeftWeaponCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("Left Weapon Box"));
LeftWeaponCollision->SetupAttachment(GetMesh(), FName("LeftWeaponBone"));
RightWeaponCollision = CreateDefaultSubobject<UBoxComponent>(TEXT("Right Weapon Box"));
RightWeaponCollision->SetupAttachment(GetMesh(), FName("RightWeaponBone"));
}
// Called when the game starts or when spawned
@ -40,8 +58,27 @@ void AEnemy::BeginPlay()
Super::BeginPlay();
AgroSphere->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::AgroSphereOverlap);
CombatRangeSphere->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::CombatRangeOverlap);
CombatRangeSphere->OnComponentEndOverlap.AddDynamic(this, &AEnemy::CombatRangeEndOverlap);
// Bind functions to overlap events for weapon boxes
LeftWeaponCollision->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::OnLeftWeaponOverlap);
RightWeaponCollision->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::OnRightWeaponOverlap);
// Set collision presets for weapon boxes
LeftWeaponCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
LeftWeaponCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
LeftWeaponCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
LeftWeaponCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
RightWeaponCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
RightWeaponCollision->SetCollisionObjectType(ECollisionChannel::ECC_WorldDynamic);
RightWeaponCollision->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
RightWeaponCollision->SetCollisionResponseToChannel(ECollisionChannel::ECC_Pawn, ECollisionResponse::ECR_Overlap);
GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block);
GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
GetCapsuleComponent()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Camera, ECollisionResponse::ECR_Ignore);
// Get the AI Controller
EnemyController = Cast<AEnemyController>(GetController());
@ -75,7 +112,8 @@ void AEnemy::PlayHitMontage(FName Section, float PlayRate)
{
if (!bCanHitReact) return;
if (UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance())
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && HitMontage)
{
AnimInstance->Montage_Play(HitMontage, PlayRate);
AnimInstance->Montage_JumpToSection(Section, HitMontage);
@ -140,6 +178,104 @@ void AEnemy::SetStunned(bool Stunned)
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("Stunned"), Stunned);
}
void AEnemy::CombatRangeOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (!OtherActor) return;
if (Cast<AShooterCharacter>(OtherActor) == nullptr) return;
bInAttackRange = true;
if (!EnemyController) return;
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("InAttackRange"), true);
UE_LOG(LogTemp, Warning, TEXT("overlap player"));
}
void AEnemy::CombatRangeEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
if (!OtherActor) return;
if (Cast<AShooterCharacter>(OtherActor) == nullptr) return;
bInAttackRange = false;
if (!EnemyController) return;
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("InAttackRange"), false);
UE_LOG(LogTemp, Warning, TEXT("end overlap player"));
}
void AEnemy::PlayAttackMontage(FName Section, float PlayRate)
{
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (AnimInstance && AttackMontage)
{
AnimInstance->Montage_Play(AttackMontage, PlayRate);
AnimInstance->Montage_JumpToSection(Section, AttackMontage);
}
}
FName AEnemy::GetAttackSectionName() const
{
switch (FMath::RandRange(1, 4))
{
case 1:
return AttackLFast;
case 2:
return AttackRFast;
case 3:
return AttackL;
case 4:
return AttackR;
default:
return AttackLFast;
}
}
void AEnemy::DoDamage(AActor* Victim)
{
if (!Victim) return;
if (auto const Character = Cast<AShooterCharacter>(Victim))
{
UGameplayStatics::ApplyDamage(Character, BaseDamage, EnemyController, this, UDamageType::StaticClass());
if (USoundCue* MeleeImpactSound = Character->GetMeleeImpactSound())
{
UGameplayStatics::PlaySoundAtLocation(this, MeleeImpactSound, GetActorLocation());
}
}
}
void AEnemy::OnLeftWeaponOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
DoDamage(OtherActor);
}
void AEnemy::OnRightWeaponOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
DoDamage(OtherActor);
}
void AEnemy::ActivateLeftWeapon()
{
LeftWeaponCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
void AEnemy::DeactivateLeftWeapon()
{
LeftWeaponCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
void AEnemy::ActivateRightWeapon()
{
RightWeaponCollision->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
}
void AEnemy::DeactivateRightWeapon()
{
RightWeaponCollision->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
// Called every frame
void AEnemy::Tick(float DeltaTime)
{

View File

@ -13,6 +13,7 @@ class UAnimMontage;
class UBehaviorTree;
class AEnemyController;
class USphereComponent;
class UBoxComponent;
UCLASS()
class SHOOTER_API AEnemy : public ACharacter, public IBulletHitInterface
@ -48,6 +49,12 @@ protected:
void UpdateHitNumbers();
UFUNCTION(BlueprintCallable)
float GetHealth() const { return Health; }
UFUNCTION(BlueprintCallable)
float GetMaxHealth() const { return MaxHealth; }
/** Called when something overlaps with the agro sphere */
UFUNCTION()
void AgroSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
@ -56,6 +63,43 @@ protected:
UFUNCTION(BlueprintCallable)
void SetStunned(bool Stunned);
UFUNCTION()
void CombatRangeOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep,
const FHitResult& SweepResult);
UFUNCTION()
void CombatRangeEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);
UFUNCTION(BlueprintCallable)
void PlayAttackMontage(FName Section, float PlayRate = 1.0f);
UFUNCTION(BlueprintPure)
FName GetAttackSectionName() const;
UFUNCTION()
void OnLeftWeaponOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep,
const FHitResult& SweepResult);
UFUNCTION()
void OnRightWeaponOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep,
const FHitResult& SweepResult);
// Activate/Deactivate collision for weapon boxes
UFUNCTION(BlueprintCallable)
void ActivateLeftWeapon();
UFUNCTION(BlueprintCallable)
void DeactivateLeftWeapon();
UFUNCTION(BlueprintCallable)
void ActivateRightWeapon();
UFUNCTION(BlueprintCallable)
void DeactivateRightWeapon();
void DoDamage(AActor* Victim);
private:
/** Particles to spawn when hit by bullets */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
@ -119,7 +163,7 @@ private:
AEnemyController* EnemyController;
/** Overlap sphere for when the enemy becomes hostile */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Behavior Tree", meta = (AllowPrivateAccess = true))
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
USphereComponent* AgroSphere;
/** True when playing the get hit animation */
@ -129,6 +173,35 @@ private:
/** Chance of being stunned. 0: no stun chance, 1: 100% stun chance */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
float StunChance;
/** True when in attack rangel; time to attack */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
bool bInAttackRange;
/** Overlap sphere for attack range */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
USphereComponent* CombatRangeSphere;
/** Montage containing different attacks */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
UAnimMontage* AttackMontage;
/** The 4 attack montage section names */
FName AttackLFast;
FName AttackRFast;
FName AttackL;
FName AttackR;
/** Collision volume for the left weapon */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
UBoxComponent* LeftWeaponCollision;
/** Collision volume for the right weapon */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
UBoxComponent* RightWeaponCollision;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
float BaseDamage;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;

View File

@ -77,7 +77,9 @@ AShooterCharacter::AShooterCharacter() :
PickupSoundResetTime(0.2f),
EquipSoundResetTime(0.2f),
// Icon animation property
HighlightedSlot(-1)
HighlightedSlot(-1),
Health(100.f),
MaxHealth(100.f)
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
@ -202,6 +204,23 @@ void AShooterCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
PlayerInputComponent->BindAction("5Key", IE_Pressed, this, &AShooterCharacter::Key5Pressed);
}
float AShooterCharacter::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator,
AActor* DamageCauser)
{
float DamageInflicted = DamageAmount;
if (Health - DamageAmount <= 0.f)
{
DamageInflicted = Health;
Health = 0.f;
//Die();
}
else
{
Health -= DamageAmount;
}
return DamageInflicted;
}
// Called every frame
void AShooterCharacter::Tick(float DeltaTime)
{

View File

@ -63,6 +63,8 @@ public:
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
virtual float TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser) override;
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
@ -486,7 +488,18 @@ private:
/** The index for the currently highlighted slot */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Inventory, meta = (AllowPrivateAccess = true))
int32 HighlightedSlot;
/** Character health */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
float Health;
/** Character max health */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
float MaxHealth;
/** Sound made when character gets hit by a melee attack */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
USoundCue* MeleeImpactSound;
public:
/* Returns CameraBoom SubObject */
FORCEINLINE USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
@ -528,5 +541,20 @@ public:
void UnhighlightInventorySlot();
FORCEINLINE AWeapon* GetEquippedWeapon() const { return EquippedWeapon; }
UFUNCTION(BlueprintCallable)
AWeapon* GetEquippedWeapon() const { return EquippedWeapon; }
UFUNCTION(BlueprintCallable)
TMap<EAmmoType, int32> GetAmmoMap() const { return AmmoMap; }
UFUNCTION(BlueprintCallable)
TArray<AItem*> GetInventory() const { return Inventory; }
UFUNCTION(BlueprintCallable)
float GetHealth() const { return Health; }
UFUNCTION(BlueprintCallable)
float GetMaxHealth() const { return MaxHealth; }
FORCEINLINE USoundCue* GetMeleeImpactSound() const { return MeleeImpactSound; }
};