Section 15: AI and Behavior Trees - Lecture 297
Weapon Trails
This commit is contained in:
parent
d005e4543f
commit
a1ec63742e
BIN
Content/ParagonGrux/Characters/Heroes/Grux/Meshes/Grux_Skeleton.uasset (Stored with Git LFS)
BIN
Content/ParagonGrux/Characters/Heroes/Grux/Meshes/Grux_Skeleton.uasset (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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/TwinBlastCharacterBP.uasset (Stored with Git LFS)
BIN
Content/_Game/Character/TwinBlast/TwinBlastCharacterBP.uasset (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Content/_Game/Enemies/Animations/EnemyHit.uasset (Stored with Git LFS)
BIN
Content/_Game/Enemies/Animations/EnemyHit.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/_Game/Enemies/EnemyBP.uasset (Stored with Git LFS)
BIN
Content/_Game/Enemies/EnemyBP.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/_Game/Enemies/GruxAnimBP.uasset (Stored with Git LFS)
BIN
Content/_Game/Enemies/GruxAnimBP.uasset (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Content/_Game/EnemyController/EnemyBehaviorTree.uasset (Stored with Git LFS)
BIN
Content/_Game/EnemyController/EnemyBehaviorTree.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/_Game/EnemyController/EnemyBlackboard.uasset (Stored with Git LFS)
BIN
Content/_Game/EnemyController/EnemyBlackboard.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/_Game/GameMode/ShooterGameModeBaseBP.uasset (Stored with Git LFS)
BIN
Content/_Game/GameMode/ShooterGameModeBaseBP.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/_Game/HUD/AmmoCountBP.uasset (Stored with Git LFS)
BIN
Content/_Game/HUD/AmmoCountBP.uasset (Stored with Git LFS)
Binary file not shown.
Binary file not shown.
BIN
Content/_Game/HUD/EnemyHealthBarBP.uasset (Stored with Git LFS)
BIN
Content/_Game/HUD/EnemyHealthBarBP.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/_Game/HUD/ShooterHUDOverlayBP.uasset (Stored with Git LFS)
BIN
Content/_Game/HUD/ShooterHUDOverlayBP.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/_Game/HUD/WeaponSlot.uasset (Stored with Git LFS)
BIN
Content/_Game/HUD/WeaponSlot.uasset (Stored with Git LFS)
Binary file not shown.
BIN
Content/_Game/Maps/DefaultMap.umap (Stored with Git LFS)
BIN
Content/_Game/Maps/DefaultMap.umap (Stored with Git LFS)
Binary file not shown.
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue