Section 15: AI and Behavior Trees - Lecture 284

Stunned Blackboard Decorator
This commit is contained in:
charnet3d 2024-03-06 14:39:12 +01:00
parent 860e2d43cd
commit d005e4543f
10 changed files with 108 additions and 16 deletions

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.

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

Binary file not shown.

View File

@ -11,6 +11,8 @@
#include "DrawDebugHelpers.h" #include "DrawDebugHelpers.h"
#include "EnemyController.h" #include "EnemyController.h"
#include "BehaviorTree/BlackboardComponent.h" #include "BehaviorTree/BlackboardComponent.h"
#include "Components/SphereComponent.h"
#include "ShooterCharacter.h"
// Sets default values // Sets default values
AEnemy::AEnemy() : AEnemy::AEnemy() :
@ -20,10 +22,16 @@ AEnemy::AEnemy() :
HitReactTimeMin(.5f), HitReactTimeMin(.5f),
HitReactTimeMax(1.f), HitReactTimeMax(1.f),
bCanHitReact(true), bCanHitReact(true),
HitNumberDestroyTime(1.5f) HitNumberDestroyTime(1.5f),
bStunned(false),
StunChance(.5f)
{ {
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. // 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; PrimaryActorTick.bCanEverTick = true;
// Create the Agro Sphere
AgroSphere = CreateDefaultSubobject<USphereComponent>(TEXT("AgroSphere"));
AgroSphere->SetupAttachment(GetRootComponent());
} }
// Called when the game starts or when spawned // Called when the game starts or when spawned
@ -31,6 +39,8 @@ void AEnemy::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
AgroSphere->OnComponentBeginOverlap.AddDynamic(this, &AEnemy::AgroSphereOverlap);
GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block); GetMesh()->SetCollisionResponseToChannel(ECollisionChannel::ECC_Visibility, ECollisionResponse::ECR_Block);
// Get the AI Controller // Get the AI Controller
@ -39,12 +49,15 @@ void AEnemy::BeginPlay()
const FVector WorldPatrolPoint = UKismetMathLibrary::TransformLocation(GetActorTransform(), PatrolPoint); 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);
if (EnemyController) if (EnemyController)
{ {
EnemyController->GetBlackboardComponent()->SetValueAsVector("PatrolPoint", WorldPatrolPoint); EnemyController->GetBlackboardComponent()->SetValueAsVector("PatrolPoint", WorldPatrolPoint);
EnemyController->GetBlackboardComponent()->SetValueAsVector("PatrolPoint2", WorldPatrolPoint2);
EnemyController->RunBehaviorTree(BehaviorTree); EnemyController->RunBehaviorTree(BehaviorTree);
} }
} }
void AEnemy::ShowHealthBar_Implementation() void AEnemy::ShowHealthBar_Implementation()
@ -108,6 +121,25 @@ void AEnemy::UpdateHitNumbers()
} }
} }
void AEnemy::AgroSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
if (!OtherActor) return;
auto Character = Cast<AShooterCharacter>(OtherActor);
if (!Character) return;
// Set the value of the Target blackboard key
EnemyController->GetBlackboardComponent()->SetValueAsObject(TEXT("Target"), Character);
}
void AEnemy::SetStunned(bool Stunned)
{
bStunned = Stunned;
if (!EnemyController) return;
EnemyController->GetBlackboardComponent()->SetValueAsBool(TEXT("Stunned"), Stunned);
}
// Called every frame // Called every frame
void AEnemy::Tick(float DeltaTime) void AEnemy::Tick(float DeltaTime)
{ {
@ -137,7 +169,14 @@ void AEnemy::BulletHit_Implementation(FHitResult HitResult)
ShowHealthBar(); ShowHealthBar();
PlayHitMontage(FName("HitReactFront")); // Determine whether bullet hit stuns
const float Stunned = FMath::FRandRange(0.f, 1.f);
if (Stunned <= StunChance)
{
// Stun the Enemy
PlayHitMontage(FName("HitReactFront"));
SetStunned(true);
}
} }
float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, float AEnemy::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator,

View File

@ -12,6 +12,7 @@ class USoundCue;
class UAnimMontage; class UAnimMontage;
class UBehaviorTree; class UBehaviorTree;
class AEnemyController; class AEnemyController;
class USphereComponent;
UCLASS() UCLASS()
class SHOOTER_API AEnemy : public ACharacter, public IBulletHitInterface class SHOOTER_API AEnemy : public ACharacter, public IBulletHitInterface
@ -46,6 +47,15 @@ protected:
void DestroyHitNumber(UUserWidget* HitNumber); void DestroyHitNumber(UUserWidget* HitNumber);
void UpdateHitNumbers(); void UpdateHitNumbers();
/** Called when something overlaps with the agro sphere */
UFUNCTION()
void AgroSphereOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep,
const FHitResult& SweepResult);
UFUNCTION(BlueprintCallable)
void SetStunned(bool Stunned);
private: private:
/** Particles to spawn when hit by bullets */ /** Particles to spawn when hit by bullets */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true)) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
@ -102,7 +112,23 @@ private:
UPROPERTY(EditAnywhere, Category = "Behavior Tree", meta = (AllowPrivateAccess = true, MakeEditWidget = true)) UPROPERTY(EditAnywhere, Category = "Behavior Tree", meta = (AllowPrivateAccess = true, MakeEditWidget = true))
FVector PatrolPoint; FVector PatrolPoint;
/** 2nd Point for the enemy to move to */
UPROPERTY(EditAnywhere, Category = "Behavior Tree", meta = (AllowPrivateAccess = true, MakeEditWidget = true))
FVector PatrolPoint2;
AEnemyController* EnemyController; AEnemyController* EnemyController;
/** Overlap sphere for when the enemy becomes hostile */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Behavior Tree", meta = (AllowPrivateAccess = true))
USphereComponent* AgroSphere;
/** True when playing the get hit animation */
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
bool bStunned;
/** Chance of being stunned. 0: no stun chance, 1: 100% stun chance */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Combat, meta = (AllowPrivateAccess = true))
float StunChance;
public: public:
// Called every frame // Called every frame
virtual void Tick(float DeltaTime) override; virtual void Tick(float DeltaTime) override;

View File

@ -3,3 +3,17 @@
#include "GruxAnimInstance.h" #include "GruxAnimInstance.h"
#include "Enemy.h"
void UGruxAnimInstance::UpdateAnimationProperties(float DeltaTime)
{
if (!Enemy)
{
Enemy = Cast<AEnemy>(TryGetPawnOwner());
if (!Enemy) return;
}
FVector Velocity { Enemy->GetVelocity() };
Velocity.Z = 0.f;
Speed = Velocity.Size();
}

View File

@ -6,6 +6,8 @@
#include "Animation/AnimInstance.h" #include "Animation/AnimInstance.h"
#include "GruxAnimInstance.generated.h" #include "GruxAnimInstance.generated.h"
class AEnemy;
/** /**
* *
*/ */
@ -14,4 +16,15 @@ class SHOOTER_API UGruxAnimInstance : public UAnimInstance
{ {
GENERATED_BODY() GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void UpdateAnimationProperties(float DeltaTime);
private:
/** Lateral Movement Speed */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = true))
float Speed;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = true))
AEnemy* Enemy;
}; };