Section 15: AI and Behavior Trees - Lecture 310

Retargeting New Montages
This commit is contained in:
charnet3d 2024-03-10 00:25:36 +01:00
parent a1ec63742e
commit 700f9ab66f
54 changed files with 379 additions and 57 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/FXVillesBloodVFXPack/Textures/T_Blood_Clip_A.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/FXVillesBloodVFXPack/Textures/T_Blood_Clip_N.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/FXVillesBloodVFXPack/Textures/T_Noise_N_02.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/_Game/Character/Belica/Death/DeathMontage.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Character/Belica/Death/Death_A.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Character/Belica/Death/Death_A_end.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/_Game/Character/Revenant/Death/DeathMontage.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Character/Revenant/Death/Death_A.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Character/Revenant/Death/Death_A_end.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/_Game/Character/TwinBlast/Death/DeathMontage.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Character/TwinBlast/Death/Death_A.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/_Game/Character/TwinBlast/Death/Death_A_end.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

Binary file not shown.

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

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

Binary file not shown.

View File

@ -15,6 +15,7 @@
#include "ShooterCharacter.h"
#include "Components/BoxComponent.h"
#include "Components/CapsuleComponent.h"
#include "Engine/SkeletalMeshSocket.h"
// Sets default values
AEnemy::AEnemy() :
@ -31,7 +32,13 @@ AEnemy::AEnemy() :
AttackRFast(TEXT("AttackRFast")),
AttackL(TEXT("AttackL")),
AttackR(TEXT("AttackR")),
BaseDamage(20.f)
BaseDamage(20.f),
LeftWeaponSocket(TEXT("FX_Trail_L_02")),
RightWeaponSocket(TEXT("FX_Trail_R_02")),
bCanAttack(true),
AttackWaitTime(1.f),
bDying(false),
DeathTime(4.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;
@ -83,11 +90,16 @@ void AEnemy::BeginPlay()
// Get the AI Controller
EnemyController = Cast<AEnemyController>(GetController());
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("CanAttack"), true);
}
const FVector WorldPatrolPoint = UKismetMathLibrary::TransformLocation(GetActorTransform(), PatrolPoint);
DrawDebugSphere(GetWorld(), WorldPatrolPoint, 25.f, 12, FColor::Red, true);
//DrawDebugSphere(GetWorld(), WorldPatrolPoint, 25.f, 12, FColor::Red, true);
const FVector WorldPatrolPoint2 = UKismetMathLibrary::TransformLocation(GetActorTransform(), PatrolPoint2);
DrawDebugSphere(GetWorld(), WorldPatrolPoint2, 25.f, 12, FColor::Blue, true);
//DrawDebugSphere(GetWorld(), WorldPatrolPoint2, 25.f, 12, FColor::Blue, true);
if (EnemyController)
{
@ -105,7 +117,34 @@ void AEnemy::ShowHealthBar_Implementation()
void AEnemy::Die()
{
if (bDying) return;
bDying = true;
HideHealthBar();
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool("Dead", true);
EnemyController->StopMovement();
}
if (!DeathMontage) return;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (!AnimInstance) return;
AnimInstance->Montage_Play(DeathMontage);
}
void AEnemy::FinishDeath()
{
GetMesh()->bPauseAnims = true;
GetWorldTimerManager().SetTimer(DeathTimer, this, &AEnemy::DestroyEnemy, DeathTime);
}
void AEnemy::DestroyEnemy()
{
Destroy();
}
void AEnemy::PlayHitMontage(FName Section, float PlayRate)
@ -188,7 +227,7 @@ void AEnemy::CombatRangeOverlap(UPrimitiveComponent* OverlappedComponent, AActor
if (!EnemyController) return;
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("InAttackRange"), true);
UE_LOG(LogTemp, Warning, TEXT("overlap player"));
//UE_LOG(LogTemp, Warning, TEXT("overlap player"));
}
void AEnemy::CombatRangeEndOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
@ -201,7 +240,7 @@ void AEnemy::CombatRangeEndOverlap(UPrimitiveComponent* OverlappedComponent, AAc
if (!EnemyController) return;
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("InAttackRange"), false);
UE_LOG(LogTemp, Warning, TEXT("end overlap player"));
//UE_LOG(LogTemp, Warning, TEXT("end overlap player"));
}
void AEnemy::PlayAttackMontage(FName Section, float PlayRate)
@ -212,6 +251,12 @@ void AEnemy::PlayAttackMontage(FName Section, float PlayRate)
AnimInstance->Montage_Play(AttackMontage, PlayRate);
AnimInstance->Montage_JumpToSection(Section, AttackMontage);
}
bCanAttack = false;
GetWorldTimerManager().SetTimer(AttackWaitTimer, this, &AEnemy::ResetCanAttack, AttackWaitTime);
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("CanAttack"), false);
}
}
FName AEnemy::GetAttackSectionName() const
@ -231,29 +276,73 @@ FName AEnemy::GetAttackSectionName() const
}
}
void AEnemy::DoDamage(AActor* Victim)
void AEnemy::DoDamage(AShooterCharacter* Victim)
{
if (!Victim) return;
if (auto const Character = Cast<AShooterCharacter>(Victim))
UGameplayStatics::ApplyDamage(Victim, BaseDamage, EnemyController, this, UDamageType::StaticClass());
if (USoundCue* MeleeImpactSound = Victim->GetMeleeImpactSound())
{
UGameplayStatics::ApplyDamage(Character, BaseDamage, EnemyController, this, UDamageType::StaticClass());
if (USoundCue* MeleeImpactSound = Character->GetMeleeImpactSound())
{
UGameplayStatics::PlaySoundAtLocation(this, MeleeImpactSound, GetActorLocation());
}
UGameplayStatics::PlaySoundAtLocation(this, MeleeImpactSound, GetActorLocation());
}
}
void AEnemy::OnLeftWeaponOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
void AEnemy::SpawnBlood(AShooterCharacter* Victim, FName WeaponSocket)
{
DoDamage(OtherActor);
if (!Victim) return;
UParticleSystem* Particles = Victim->GetBloodParticles();
if (!Particles) return;
const USkeletalMeshSocket* TipSocket{ GetMesh()->GetSocketByName(WeaponSocket) };
if (!TipSocket) return;
const FTransform SocketTransform{ TipSocket->GetSocketTransform(GetMesh()) };
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), Particles, SocketTransform);
}
void AEnemy::StunCharacter(AShooterCharacter* Victim)
{
if (!Victim) return;
const float Stun{ FMath::RandRange(0.f,1.f) };
if (Stun <= Victim->GetStunChance())
{
Victim->Stun();
}
}
void AEnemy::ResetCanAttack()
{
bCanAttack = true;
if (!EnemyController) return;
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("CanAttack"), true);
}
void AEnemy::OnLeftWeaponOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
auto const Character = Cast<AShooterCharacter>(OtherActor);
if (Character)
{
DoDamage(Character);
SpawnBlood(Character, LeftWeaponSocket);
StunCharacter(Character);
}
}
void AEnemy::OnRightWeaponOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
DoDamage(OtherActor);
auto const Character = Cast<AShooterCharacter>(OtherActor);
if (Character)
{
DoDamage(Character);
SpawnBlood(Character, RightWeaponSocket);
StunCharacter(Character);
}
}
void AEnemy::ActivateLeftWeapon()
@ -303,6 +392,8 @@ void AEnemy::BulletHit_Implementation(FHitResult HitResult)
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ImpactParticles, HitResult.Location, FRotator(0.f), true);
}
if (bDying) return;
ShowHealthBar();
// Determine whether bullet hit stuns
@ -318,6 +409,12 @@ void AEnemy::BulletHit_Implementation(FHitResult HitResult)
float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator,
AActor* DamageCauser)
{
// Set the Target blackboard key to aggro the character
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsObject(FName("Target"), DamageCauser);
}
float DamageInflicted = DamageAmount;
if (Health - DamageAmount <= 0.f)
{

View File

@ -14,6 +14,7 @@ class UBehaviorTree;
class AEnemyController;
class USphereComponent;
class UBoxComponent;
class AShooterCharacter;
UCLASS()
class SHOOTER_API AEnemy : public ACharacter, public IBulletHitInterface
@ -99,7 +100,18 @@ protected:
UFUNCTION(BlueprintCallable)
void DeactivateRightWeapon();
void DoDamage(AActor* Victim);
void DoDamage(AShooterCharacter* Victim);
void SpawnBlood(AShooterCharacter* Victim, FName WeaponSocket);
// Attemp to stun character
void StunCharacter(AShooterCharacter* Victim);
void ResetCanAttack();
UFUNCTION(BlueprintCallable)
void FinishDeath();
void DestroyEnemy();
private:
/** Particles to spawn when hit by bullets */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
@ -202,6 +214,34 @@ private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
float BaseDamage;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
FName LeftWeaponSocket;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
FName RightWeaponSocket;
/** True when Enemy can attack */
UPROPERTY(VisibleAnywhere, Category = Combat, meta = (AllowPrivateAccess = true))
bool bCanAttack;
FTimerHandle AttackWaitTimer;
/** Min wait time between attacks */
UPROPERTY(EditAnywhere, Category = Combat, meta = (AllowPrivateAccess = true))
float AttackWaitTime;
/** Death anim montage */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
UAnimMontage* DeathMontage;
bool bDying;
FTimerHandle DeathTimer;
/** Time after death until Destroy */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
float DeathTime;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;

View File

@ -18,6 +18,8 @@
#include "BulletHitInterface.h"
#include "Enemy.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "EnemyController.h"
#include "BehaviorTree/BlackboardComponent.h"
// Sets default values
AShooterCharacter::AShooterCharacter() :
@ -79,7 +81,8 @@ AShooterCharacter::AShooterCharacter() :
// Icon animation property
HighlightedSlot(-1),
Health(100.f),
MaxHealth(100.f)
MaxHealth(100.f),
StunChance(.25f)
{
// 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;
@ -212,7 +215,13 @@ float AShooterCharacter::TakeDamage(float DamageAmount, FDamageEvent const& Dama
{
DamageInflicted = Health;
Health = 0.f;
//Die();
Die();
auto EnemyController = Cast<AEnemyController>(EventInstigator);
if (EnemyController)
{
EnemyController->GetBlackboardComponent()->SetValueAsBool(FName("CharacterDead"), true);
}
}
else
{
@ -221,6 +230,26 @@ float AShooterCharacter::TakeDamage(float DamageAmount, FDamageEvent const& Dama
return DamageInflicted;
}
void AShooterCharacter::Die()
{
if (!DeathMontage) return;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (!AnimInstance) return;
AnimInstance->Montage_Play(DeathMontage);
}
void AShooterCharacter::FinishDeath()
{
GetMesh()->bPauseAnims = true;
APlayerController* PC = UGameplayStatics::GetPlayerController(this, 0);
if (PC)
{
DisableInput(PC);
}
}
// Called every frame
void AShooterCharacter::Tick(float DeltaTime)
{
@ -309,7 +338,9 @@ void AShooterCharacter::LookUp(float Value)
void AShooterCharacter::AimingButtonPressed()
{
bAimingButtonPressed = true;
if (CombatState != ECombatState::ECS_Reloading && CombatState != ECombatState::ECS_Equipping)
if (CombatState != ECombatState::ECS_Reloading &&
CombatState != ECombatState::ECS_Equipping &&
CombatState != ECombatState::ECS_Stunned)
{
Aim();
}
@ -504,6 +535,16 @@ EPhysicalSurface AShooterCharacter::GetSurfaceType()
return UPhysicalMaterial::DetermineSurfaceType(HitResult.PhysMaterial.Get());
}
void AShooterCharacter::EndStun()
{
CombatState = ECombatState::ECS_Unoccupied;
if (bAimingButtonPressed)
{
Aim();
}
}
void AShooterCharacter::UnhighlightInventorySlot()
{
if (HighlightedSlot != -1)
@ -513,6 +554,19 @@ void AShooterCharacter::UnhighlightInventorySlot()
}
}
void AShooterCharacter::Stun()
{
if (Health <= 0.f) return;
if (!HitReactMontage) return;
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
if (!AnimInstance) return;
CombatState = ECombatState::ECS_Stunned;
AnimInstance->Montage_Play(HitReactMontage);
}
int32 AShooterCharacter::GetInterpLocationIndex()
{
int32 LowestIndex = 1;
@ -632,6 +686,7 @@ void AShooterCharacter::StartFireTimer()
void AShooterCharacter::AutoFireReset()
{
if (!EquippedWeapon) return;
if (CombatState == ECombatState::ECS_Stunned) return;
CombatState = ECombatState::ECS_Unoccupied;
@ -1135,6 +1190,7 @@ void AShooterCharacter::ReloadWeapon()
void AShooterCharacter::FinishReloading()
{
if (!EquippedWeapon) return;
if (CombatState == ECombatState::ECS_Stunned) return;
CombatState = ECombatState::ECS_Unoccupied;
@ -1175,6 +1231,8 @@ void AShooterCharacter::FinishReloading()
void AShooterCharacter::FinishEquipping()
{
if (CombatState == ECombatState::ECS_Stunned) return;
CombatState = ECombatState::ECS_Unoccupied;
if (bAimingButtonPressed)

View File

@ -21,12 +21,13 @@ class AAmmo;
UENUM(BlueprintType)
enum class ECombatState : uint8
{
ECS_Unoccupied UMETA(DisplayName = "Unoccupied"),
ECS_Unoccupied UMETA(DisplayName = "Unoccupied"),
ECS_FireTimerInProgress UMETA(DisplayName = "FireTimerInProgress"),
ECS_Reloading UMETA(DisplayName = "Reloading"),
ECS_Equipping UMETA(DisplayName = "Equipping"),
ECS_Reloading UMETA(DisplayName = "Reloading"),
ECS_Equipping UMETA(DisplayName = "Equipping"),
ECS_Stunned UMETA(DisplayName = "Stunned"),
ECS_MAX UMETA(DisplayName = "DefaultMax")
ECS_MAX UMETA(DisplayName = "DefaultMax")
};
USTRUCT(BlueprintType)
@ -221,6 +222,13 @@ protected:
UFUNCTION(BlueprintCallable)
EPhysicalSurface GetSurfaceType();
UFUNCTION(BlueprintCallable)
void EndStun();
void Die();
UFUNCTION(BlueprintCallable)
void FinishDeath();
private:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = true))
USpringArmComponent* CameraBoom;
@ -500,6 +508,22 @@ private:
/** Sound made when character gets hit by a melee attack */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
USoundCue* MeleeImpactSound;
/** Blood splatter particles for melee hit */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
UParticleSystem* BloodParticles;
/** Hit react anim montage; for when the character is stunned */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
UAnimMontage* HitReactMontage;
/** Chance of being stunned when hit by an enemy */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
float StunChance;
/** Character death anim montage */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
UAnimMontage* DeathMontage;
public:
/* Returns CameraBoom SubObject */
FORCEINLINE USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
@ -557,4 +581,8 @@ public:
float GetMaxHealth() const { return MaxHealth; }
FORCEINLINE USoundCue* GetMeleeImpactSound() const { return MeleeImpactSound; }
FORCEINLINE UParticleSystem* GetBloodParticles() const { return BloodParticles; }
void Stun();
FORCEINLINE float GetStunChance() const { return StunChance; }
};