Compare commits

..

24 Commits

Author SHA1 Message Date
baz 1f3e8a09ea misc schanges 2024-02-29 17:29:24 +00:00
baz c71d9478d4 new worker 2024-02-29 17:29:23 +00:00
baz d4af5488b7 Move and make Worker Blackboard generic 2024-02-29 17:29:23 +00:00
baz f2938941ca Move generic behavior trees into dedicated folder 2024-02-29 17:29:23 +00:00
baz 2e39be6354 Refine Ranged Enemy functionality and hide on low health 2024-02-29 17:29:23 +00:00
baz 8be9a8269e Add Weapon Cooldown Decorator 2024-02-29 17:29:22 +00:00
baz 3b5da9c0ad Break behaviour tree components into individual behaviour trees 2024-02-29 17:29:22 +00:00
baz d0f9330498 Add Find Cover EQS and Behaviour Tree tasks 2024-02-29 17:29:22 +00:00
baz a2cb36120d Update Worker Blackboard and Behaviour Tree 2024-02-29 17:29:22 +00:00
baz 34ab92b52a Add Strafe EQS 2024-02-29 17:29:22 +00:00
baz 412e3f1fc4 Misc Enemy Updates 2024-02-29 17:29:21 +00:00
baz 251f840a43 Add MoveToIdealRange Task 2024-02-29 17:29:21 +00:00
baz eb9178f4e3 Add IsWithinRange Decorator 2024-02-29 17:29:21 +00:00
baz 265561dffb Add new Sprint enemy movement speed 2024-02-29 17:29:21 +00:00
baz 429cf58627 Make hearing and damaged perception events 2024-02-29 17:29:20 +00:00
baz 763706ac5c Add new Perception Senses 2024-02-29 17:29:20 +00:00
baz 5b90d26bc6 Create AI State Enum 2024-02-29 17:29:20 +00:00
baz c6c92ed296 Create Has Patrol Route Decorator 2024-02-29 17:29:20 +00:00
baz 2f9cc901e2 Create Set Movement Speed Task 2024-02-29 17:29:20 +00:00
baz beb18734b1 Add default GetSplinePointAtWorld function 2024-02-29 17:29:19 +00:00
baz 3d16e8e24a Add CurrentPatrolRoute reference to EnemyCharacter 2024-02-29 17:29:19 +00:00
baz 0fd27b6745 Create Move Along Patrol Route task 2024-02-29 17:29:19 +00:00
baz 7cb89a7d2c Create PatrolRoute 2024-02-29 17:29:18 +00:00
baz 4d214f9883 Create Clear Focus Task 2024-02-29 17:29:18 +00:00
54 changed files with 821 additions and 23 deletions

BIN
Content/Enemy/BB_Base_Enemy.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/BehaviorTrees/BT_Attacking_State.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/BehaviorTrees/BT_Find_Cover.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/BehaviorTrees/BT_Fire_Weapon.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/BehaviorTrees/BT_Investigating_State.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/BehaviorTrees/BT_Passive_State.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/EQS/AttackTarget.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/EQS/EQSTestingPawn.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/EQS/EQS_FindCover.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/EQS/EQS_FindIdealRange.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/EQS/EQS_Strafe.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/FireWeaponTasks.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Content/Enemy/Tasks/BTTMoveToIdealRange.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/Worker/AIC_Worker.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Enemy/Worker/BB_Worker.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Content/Enemy/Worker/BT_Worker.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Content/Enemy/Worker/C_Worker.uasset (Stored with Git LFS)

Binary file not shown.

BIN
Content/Levels/TestLevel.umap (Stored with Git LFS)

Binary file not shown.

BIN
Content/TestPatrolRoute.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Weapons/Pistol/Pistol.uasset (Stored with Git LFS)

Binary file not shown.

View File

@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "../Decorators/BTDCanSeeTarget.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
bool UBTDCanSeeTarget::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
FHitResult HitResult;
FVector Start = OwnerComp.GetAIOwner()->GetPawn()->GetActorLocation();
UBlackboardComponent* BlackboardComponent = OwnerComp.GetBlackboardComponent();
AActor* TargetActor = Cast<AActor>(BlackboardComponent->GetValueAsObject(TargetActorKey.SelectedKeyName));
FVector End = TargetActor->GetActorLocation();
GetWorld()->LineTraceSingleByChannel(HitResult, Start, End, ECC_Pawn);
if (HitResult.GetActor())
{
return true;
}
return false;
}

View File

@ -0,0 +1,24 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTDecorator.h"
#include "BTDCanSeeTarget.generated.h"
/**
*
*/
UCLASS()
class NAKATOMI_API UBTDCanSeeTarget : public UBTDecorator
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Options",
Meta = (AllowPrivateAccess = "true", DisplayName = "Target Actor Key"))
FBlackboardKeySelector TargetActorKey;
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};

View File

@ -0,0 +1,14 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "../Decorators/BTDHasPatrolRoute.h"
#include "Nakatomi/EnemyAIController.h"
bool UBTDHasPatrolRoute::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
AEnemyAIController* enemyController = Cast<AEnemyAIController>(OwnerComp.GetAIOwner());
AEnemyCharacter* enemyPawn = Cast<AEnemyCharacter>(enemyController->GetPawn());
return enemyPawn->CurrentPatrolRoute ? true : false;
}

View File

@ -0,0 +1,19 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTDecorator.h"
#include "BTDHasPatrolRoute.generated.h"
/**
*
*/
UCLASS(Blueprintable)
class NAKATOMI_API UBTDHasPatrolRoute : public UBTDecorator
{
GENERATED_BODY()
public:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};

View File

@ -0,0 +1,14 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "../Decorators/BTDIsHealthBelowThreshold.h"
#include "BehaviorTree/BlackboardComponent.h"
bool UBTDIsHealthBelowThreshold::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
UBlackboardComponent* BlackboardComponent = OwnerComp.GetBlackboardComponent();
float currentHealth = BlackboardComponent->GetValueAsFloat(CurrentHealthKey.SelectedKeyName);
return currentHealth <= HealthThreshold ? true : false;
}

View File

@ -0,0 +1,29 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTDecorator.h"
#include "BTDIsHealthBelowThreshold.generated.h"
/**
*
*/
UCLASS()
class NAKATOMI_API UBTDIsHealthBelowThreshold : public UBTDecorator
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Options",
Meta = (AllowPrivateAccess = "true", DisplayName = "Current Health Key"))
FBlackboardKeySelector CurrentHealthKey;
UPROPERTY(EditAnywhere, Category = "Options",
Meta = (AllowPrivateAccess = "true", DisplayName = "Health Threshold"))
float HealthThreshold = 25.0f;
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};

View File

@ -0,0 +1,29 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "../Decorators/BTDIsWithinRange.h"
#include "AIController.h"
#include "navigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Nakatomi/NakatomiCharacter.h"
bool UBTDIsWithinRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
UBlackboardComponent* BlackboardComponent = OwnerComp.GetBlackboardComponent();
float Dist = BlackboardComponent->GetValueAsFloat(DistanceKey.SelectedKeyName);
UObject* Target = BlackboardComponent->GetValueAsObject(TargetActorKey.SelectedKeyName);
auto TargetLocation = Cast<AActor>(Target)->GetActorLocation();
UNavigationSystemV1* NavigationSystem = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
APawn* pawn = OwnerComp.GetAIOwner()->GetPawn();
if (pawn && NavigationSystem && TargetLocation != FVector::ZeroVector)
{
double Distance = -1.0;
NavigationSystem->GetPathLength(TargetLocation, pawn->GetTransform().GetLocation(), Distance);
return Distance <= Dist ? true : false;
}
return false;
}

View File

@ -0,0 +1,31 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTDecorator.h"
#include "BTDIsWithinRange.generated.h"
/**
*
*/
UCLASS()
class NAKATOMI_API UBTDIsWithinRange : public UBTDecorator
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Options",
Meta = (AllowPrivateAccess = "true", DisplayName = "Target Actor Key"))
FBlackboardKeySelector TargetActorKey;
UPROPERTY(EditAnywhere, Category = "Options",
Meta = (AllowPrivateAccess = "true", DisplayName = "Distance Key"))
FBlackboardKeySelector DistanceKey;
public:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};

View File

@ -0,0 +1,23 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "../Decorators/BTDWeaponCooldown.h"
#include "AIController.h"
#include "Nakatomi/EnemyCharacter.h"
void UBTDWeaponCooldown::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
AEnemyCharacter* EnemyCharacter = Cast<AEnemyCharacter>(OwnerComp.GetAIOwner()->GetPawn());
if (EnemyCharacter && EnemyCharacter->GetCurrentWeapon())
{
const float Cooldown = EnemyCharacter->GetCurrentWeapon()->GetWeaponProperties()->WeaponCooldown;
if (CoolDownTime != Cooldown)
{
CoolDownTime = Cooldown;
}
}
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
}

View File

@ -0,0 +1,19 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/Decorators/BTDecorator_Cooldown.h"
#include "BTDWeaponCooldown.generated.h"
/**
*
*/
UCLASS()
class NAKATOMI_API UBTDWeaponCooldown : public UBTDecorator_Cooldown
{
GENERATED_BODY()
protected:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};

View File

@ -0,0 +1,5 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "EAIState.h"

View File

@ -0,0 +1,18 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
/**
*
*/
UENUM(BlueprintType)
enum class EAIState : uint8
{
PASSIVE UMETA(DisplayName = "Passive"),
ATTACKING UMETA(DisplayName = "Attacking"),
FROZEN UMETA(DisplayName = "Frozen"),
INVESTIGATING UMETA(DisplayName = "Investigating"),
DEAD UMETA(DisplayName = "Dead"),
};

View File

@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "EPatrolMovementEnum.h"

View File

@ -0,0 +1,16 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
/**
*
*/
UENUM(BlueprintType)
enum class EPatrolMovementEnum : uint8
{
SLOWWALK UMETA(DisplayName = "Slow Walk"),
WALK UMETA(DisplayName = "Walk"),
SPRINT UMETA(DisplayName = "Sprint"),
};

View File

@ -2,6 +2,8 @@
#include "EnemyAIController.h" #include "EnemyAIController.h"
#include <Kismet/GameplayStatics.h> #include <Kismet/GameplayStatics.h>
#include "EAIState.h"
#include "NakatomiGameInstance.h" #include "NakatomiGameInstance.h"
#include "BehaviorTree/BehaviorTree.h" #include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h" #include "BehaviorTree/BlackboardComponent.h"
@ -25,8 +27,19 @@ AEnemyAIController::AEnemyAIController(const FObjectInitializer& object_initiali
SightConfig->DetectionByAffiliation.bDetectEnemies = true; SightConfig->DetectionByAffiliation.bDetectEnemies = true;
SightConfig->DetectionByAffiliation.bDetectNeutrals = true; SightConfig->DetectionByAffiliation.bDetectNeutrals = true;
HearingConfig = CreateDefaultSubobject<UAISenseConfig_Hearing>(TEXT("Hearing Sense Config"));
HearingConfig->HearingRange = 800.0f;
HearingConfig->SetMaxAge(3.0f);
HearingConfig->DetectionByAffiliation.bDetectEnemies = true;
HearingConfig->DetectionByAffiliation.bDetectNeutrals = true;
DamageConfig = CreateDefaultSubobject<UAISenseConfig_Damage>(TEXT("Damage Sense Config"));
DamageConfig->SetMaxAge(5.0f);
AIPerception->SetDominantSense(SightConfig->GetSenseImplementation()); AIPerception->SetDominantSense(SightConfig->GetSenseImplementation());
AIPerception->ConfigureSense(*SightConfig); AIPerception->ConfigureSense(*SightConfig);
AIPerception->ConfigureSense(*HearingConfig);
AIPerception->ConfigureSense(*DamageConfig);
} }
void AEnemyAIController::OnPossess(APawn* InPawn) void AEnemyAIController::OnPossess(APawn* InPawn)
@ -49,6 +62,11 @@ void AEnemyAIController::OnPossess(APawn* InPawn)
Blackboard->SetValueAsObject("SelfActor", enemy); Blackboard->SetValueAsObject("SelfActor", enemy);
} }
Blackboard->SetValueAsEnum("State", static_cast<uint8>(EAIState::PASSIVE));
Blackboard->SetValueAsFloat("AttackRadius", enemy->AttackRadius);
Blackboard->SetValueAsFloat("DefendRadius", enemy->DefendRadius);
//ensure(enemy->GetMovementComponent()->UseAccelerationForPathFollowing()); //ensure(enemy->GetMovementComponent()->UseAccelerationForPathFollowing());
} }
@ -116,7 +134,7 @@ void AEnemyAIController::OnDeath(FDamageInfo info)
void AEnemyAIController::OnPerceptionUpdated(const TArray<AActor*>& actors) void AEnemyAIController::OnPerceptionUpdated(const TArray<AActor*>& actors)
{ {
for (auto actor : actors) for (AActor* actor : actors)
{ {
if (!actor->GetClass()->IsChildOf(APlayerCharacter::StaticClass())) if (!actor->GetClass()->IsChildOf(APlayerCharacter::StaticClass()))
{ {
@ -126,13 +144,46 @@ void AEnemyAIController::OnPerceptionUpdated(const TArray<AActor*>& actors)
FActorPerceptionBlueprintInfo perceptionInfo; FActorPerceptionBlueprintInfo perceptionInfo;
PerceptionComponent->GetActorsPerception(actor, perceptionInfo); PerceptionComponent->GetActorsPerception(actor, perceptionInfo);
for (auto& stimulus : perceptionInfo.LastSensedStimuli) for (FAIStimulus& stimulus : perceptionInfo.LastSensedStimuli)
{ {
if (!stimulus.IsValid() || stimulus.IsExpired()) if (!stimulus.IsValid() || stimulus.IsExpired() ||
static_cast<EAIState>(Blackboard->GetValueAsEnum("State")) == EAIState::DEAD)
{ {
continue; continue;
} }
FAISenseID SightID;
FAISenseID HearingID;
FAISenseID DamageID;
if (SightConfig)
{
SightID = SightConfig->GetSenseID();
}
if (HearingConfig)
{
HearingID = HearingConfig->GetSenseID();
}
if (DamageConfig)
{
DamageID = DamageConfig->GetSenseID();
}
if (stimulus.Type == SightID)
{
SensedSight(actor, stimulus);
}
else if (stimulus.Type == HearingID)
{
SensedHearing(actor, stimulus);
}
else if (stimulus.Type == DamageID)
{
SensedDamaged(actor, stimulus);
}
Blackboard->SetValueAsObject("TargetActor", actor); Blackboard->SetValueAsObject("TargetActor", actor);
if (stimulus.IsActive()) if (stimulus.IsActive())
@ -179,3 +230,51 @@ bool AEnemyAIController::GetHasAttackToken()
{ {
return HasAttackToken; return HasAttackToken;
} }
void AEnemyAIController::SetState(EAIState state)
{
Blackboard->SetValueAsEnum("State", static_cast<uint8>(state));
}
void AEnemyAIController::SetStateAsPassive()
{
SetState(EAIState::PASSIVE);
}
void AEnemyAIController::SetStateAsAttacking(AActor* target)
{
Blackboard->SetValueAsObject("TargetActor", target);
SetState(EAIState::ATTACKING);
}
AActor* AEnemyAIController::GetTargetActor()
{
return Cast<AActor>(Blackboard->GetValueAsObject("TargetActor"));
}
void AEnemyAIController::SensedSight(AActor* actor, FAIStimulus& stimulus)
{
EAIState CurrentState = static_cast<EAIState>(Blackboard->GetValueAsEnum("State"));
if (CurrentState == EAIState::PASSIVE || CurrentState == EAIState::INVESTIGATING)
{
SetStateAsAttacking(actor);
}
}
void AEnemyAIController::SensedHearing(AActor* actor, FAIStimulus& stimulus)
{
EAIState CurrentState = static_cast<EAIState>(Blackboard->GetValueAsEnum("State"));
if (CurrentState == EAIState::PASSIVE || CurrentState == EAIState::INVESTIGATING)
{
SetState(EAIState::INVESTIGATING);
Blackboard->SetValueAsVector("InvestigationLocation", stimulus.StimulusLocation);
}
}
void AEnemyAIController::SensedDamaged(AActor* actor, FAIStimulus& stimulus)
{
SetStateAsAttacking(actor);
}

View File

@ -4,8 +4,11 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "AIController.h" #include "AIController.h"
#include "EAIState.h"
#include "EnemyCharacter.h" #include "EnemyCharacter.h"
#include "PlayerCharacter.h" #include "PlayerCharacter.h"
#include "Perception/AISenseConfig_Damage.h"
#include "Perception/AISenseConfig_Hearing.h"
#include "Perception/AISenseConfig_Sight.h" #include "Perception/AISenseConfig_Sight.h"
#include "EnemyAIController.generated.h" #include "EnemyAIController.generated.h"
@ -29,6 +32,10 @@ private:
UAISenseConfig_Sight* SightConfig; UAISenseConfig_Sight* SightConfig;
UAISenseConfig_Hearing* HearingConfig;
UAISenseConfig_Damage* DamageConfig;
bool HasAttackToken = false; bool HasAttackToken = false;
public: public:
@ -59,4 +66,24 @@ public:
UFUNCTION() UFUNCTION()
bool GetHasAttackToken(); bool GetHasAttackToken();
UFUNCTION()
void SetState(EAIState state);
UFUNCTION()
void SetStateAsPassive();
UFUNCTION()
void SetStateAsAttacking(AActor* target);
UFUNCTION(BlueprintCallable)
AActor* GetTargetActor();
private:
void SensedSight(AActor* actor, FAIStimulus& stimulus);
void SensedHearing(AActor* actor, FAIStimulus& stimulus);
void SensedDamaged(AActor* actor, FAIStimulus& stimulus);
}; };

View File

@ -2,7 +2,11 @@
#include "EnemyCharacter.h" #include "EnemyCharacter.h"
#include "EnemyAIController.h" #include "EnemyAIController.h"
#include "EnemyHealthComponent.h"
#include "InteractableComponent.h" #include "InteractableComponent.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BlackboardData.h"
#define COLLISION_WEAPON ECC_GameTraceChannel1 #define COLLISION_WEAPON ECC_GameTraceChannel1
@ -10,6 +14,10 @@ AEnemyCharacter::AEnemyCharacter(const FObjectInitializer& ObjectInitializer) :
{ {
RandomWeaponParameters = CreateDefaultSubobject<URandomWeaponParameters>(TEXT("Random Weapon Parameters")); RandomWeaponParameters = CreateDefaultSubobject<URandomWeaponParameters>(TEXT("Random Weapon Parameters"));
auto healthComponent = CreateDefaultSubobject<UEnemyHealthComponent>(TEXT("Health Component"));
SetHealthComponent(healthComponent);
GetHealthComponent()->OnDamaged.BindUFunction(this, "OnDamaged");
GetHealthComponent()->OnDeath.BindUFunction(this, "OnDeath");
GetHealthComponent()->SetMaxHealth(100.0f); GetHealthComponent()->SetMaxHealth(100.0f);
this->Tags.Add(FName("Enemy")); this->Tags.Add(FName("Enemy"));
@ -49,6 +57,10 @@ void AEnemyCharacter::WeaponCooldownHandler()
void AEnemyCharacter::BeginPlay() void AEnemyCharacter::BeginPlay()
{ {
Super::BeginPlay(); Super::BeginPlay();
AEnemyAIController* controller = Cast<AEnemyAIController>(GetController());
controller->GetBlackboardComponent()->SetValueAsFloat("CurrentHealth", GetHealthComponent()->GetCurrentHealth());
GetHealthComponent()->OnDamaged.BindUFunction(this, "OnDamaged");
} }
void AEnemyCharacter::PlayOnFireAnimations() void AEnemyCharacter::PlayOnFireAnimations()
@ -125,3 +137,12 @@ void AEnemyCharacter::ProcessHits(TArray<FHitResult> hits)
} }
} }
} }
void AEnemyCharacter::OnDamaged()
{
Super::OnDamaged();
AEnemyAIController* controller = Cast<AEnemyAIController>(GetController());
controller->GetBlackboardComponent()->SetValueAsFloat("CurrentHealth", GetHealthComponent()->GetCurrentHealth());
}

View File

@ -4,6 +4,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "NakatomiCharacter.h" #include "NakatomiCharacter.h"
#include "PatrolRoute.h"
#include "BehaviorTree/BehaviorTreeComponent.h" #include "BehaviorTree/BehaviorTreeComponent.h"
#include "RandomWeaponParameters.h" #include "RandomWeaponParameters.h"
#include "EnemyCharacter.generated.h" #include "EnemyCharacter.generated.h"
@ -17,6 +18,16 @@ class NAKATOMI_API AEnemyCharacter : public ANakatomiCharacter
{ {
GENERATED_BODY() GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Meta = (AllowPrivateAccess = "true"))
APatrolRoute* CurrentPatrolRoute;
UPROPERTY(EditAnywhere)
float AttackRadius = 300.0;
UPROPERTY(EditAnywhere)
float DefendRadius = 500.0f;
private: private:
UPROPERTY(EditDefaultsOnly, Meta = (AllowPrivateAccess = "true")) UPROPERTY(EditDefaultsOnly, Meta = (AllowPrivateAccess = "true"))
UBehaviorTree* BehaviourTree; UBehaviorTree* BehaviourTree;
@ -44,4 +55,7 @@ private:
virtual void CalculateHits(TArray<FHitResult>* hits) override; virtual void CalculateHits(TArray<FHitResult>* hits) override;
virtual void ProcessHits(TArray<FHitResult> hits) override; virtual void ProcessHits(TArray<FHitResult> hits) override;
protected:
virtual void OnDamaged() override;
}; };

View File

@ -0,0 +1,36 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "EnemyHealthComponent.h"
#include "EnemyAIController.h"
#include "EnemyCharacter.h"
void UEnemyHealthComponent::BeginPlay()
{
Super::BeginPlay();
}
void UEnemyHealthComponent::TakeDamage(AActor* damagedActor, float damage, const UDamageType* damageType,
AController* instigatedBy, AActor* damageCauser)
{
if (damagedActor == nullptr || IsDead || !CanDamage)
{
return;
}
CurrentHealth -= damage;
AEnemyCharacter* enemyCharacter = Cast<AEnemyCharacter>(damagedActor);
UAISense_Damage::ReportDamageEvent(GetWorld(), damagedActor, damageCauser, 1,
damageCauser->GetTransform().GetLocation(),
damageCauser->GetTransform().GetLocation());
OnDamaged.ExecuteIfBound({damagedActor, damage, damageType, instigatedBy, damageCauser});
if (CurrentHealth <= 0.0f)
{
IsDead = true;
OnDeath.ExecuteIfBound({damagedActor, damage, damageType, instigatedBy, damageCauser});
}
}

View File

@ -0,0 +1,22 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "HealthComponent.h"
#include "EnemyHealthComponent.generated.h"
/**
*
*/
UCLASS()
class NAKATOMI_API UEnemyHealthComponent : public UHealthComponent
{
GENERATED_BODY()
protected:
virtual void BeginPlay() override;
virtual void TakeDamage(AActor* damagedActor, float damage, const UDamageType* damageType, AController* instigatedBy, AActor* damageCauser) override;
};

View File

@ -39,7 +39,7 @@ public:
FOnDamageDelegate OnDamaged; FOnDamageDelegate OnDamaged;
FOnDeathDelegate OnDeath; FOnDeathDelegate OnDeath;
private: protected:
UPROPERTY(EditDefaultsOnly) UPROPERTY(EditDefaultsOnly)
float MaxHealth = 100.f; float MaxHealth = 100.f;
@ -55,7 +55,7 @@ public:
UHealthComponent(); UHealthComponent();
UFUNCTION() UFUNCTION()
void TakeDamage(AActor* damagedActor, float damage, const UDamageType* damageType, AController* instigatedBy, virtual void TakeDamage(AActor* damagedActor, float damage, const UDamageType* damageType, AController* instigatedBy,
AActor* damageCauser); AActor* damageCauser);
UFUNCTION() UFUNCTION()

View File

@ -19,6 +19,12 @@ void UNakatomiCMC::InitializeComponent()
NakatomiCharacterOwner = Cast<ANakatomiCharacter>(GetOwner()); NakatomiCharacterOwner = Cast<ANakatomiCharacter>(GetOwner());
} }
void UNakatomiCMC::BeginPlay()
{
Super::BeginPlay();
DefaultMaxWalkSpeed = Walk_MaxWalkSpeed;
}
// Checks if we can combine the NewMove with the current move to save on data. // Checks if we can combine the NewMove with the current move to save on data.
// If all the data in a saved move is identical, if true, we cam tell the server to run the existing move instead. // If all the data in a saved move is identical, if true, we cam tell the server to run the existing move instead.
bool UNakatomiCMC::FSavedMove_Nakatomi::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InCharacter, bool UNakatomiCMC::FSavedMove_Nakatomi::CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InCharacter,
@ -271,6 +277,16 @@ bool UNakatomiCMC::IsCustomMovementMode(ECustomMovementMove InCustomMovementMode
return MovementMode == MOVE_Custom && CustomMovementMode == InCustomMovementMode; return MovementMode == MOVE_Custom && CustomMovementMode == InCustomMovementMode;
} }
void UNakatomiCMC::SetMaxWalkSpeed(float newMaxSpeed)
{
Walk_MaxWalkSpeed = newMaxSpeed;
}
void UNakatomiCMC::SetMaxWalkSpeedToDefault()
{
Walk_MaxWalkSpeed = DefaultMaxWalkSpeed;
}
void UNakatomiCMC::EnterSlide() void UNakatomiCMC::EnterSlide()
{ {
Safe_bWantsToSlide = true; Safe_bWantsToSlide = true;

View File

@ -108,6 +108,8 @@ class NAKATOMI_API UNakatomiCMC : public UCharacterMovementComponent
UPROPERTY(Transient) UPROPERTY(Transient)
ANakatomiCharacter* NakatomiCharacterOwner; ANakatomiCharacter* NakatomiCharacterOwner;
float DefaultMaxWalkSpeed;
public: public:
UPROPERTY(BlueprintAssignable) UPROPERTY(BlueprintAssignable)
FDashStartDelegate DashStartDelegate; FDashStartDelegate DashStartDelegate;
@ -118,6 +120,8 @@ public:
protected: protected:
virtual void InitializeComponent() override; virtual void InitializeComponent() override;
virtual void BeginPlay() override;
virtual FNetworkPredictionData_Client* GetPredictionData_Client() const override; virtual FNetworkPredictionData_Client* GetPredictionData_Client() const override;
protected: protected:
@ -167,6 +171,12 @@ public:
UFUNCTION() UFUNCTION()
bool IsCustomMovementMode(ECustomMovementMove InCustomMovementMode) const; bool IsCustomMovementMode(ECustomMovementMove InCustomMovementMode) const;
UFUNCTION()
void SetMaxWalkSpeed(float newMaxSpeed);
UFUNCTION()
void SetMaxWalkSpeedToDefault();
private: private:
void EnterSlide(); void EnterSlide();
void ExitSlide(); void ExitSlide();

View File

@ -14,9 +14,12 @@ ANakatomiCharacter::ANakatomiCharacter(const FObjectInitializer& ObjectInitializ
NakatomiCMC = Cast<UNakatomiCMC>(GetCharacterMovement()); NakatomiCMC = Cast<UNakatomiCMC>(GetCharacterMovement());
HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("Health Component")); // if (!HealthComponent)
HealthComponent->OnDamaged.BindUFunction(this, "OnDamaged"); // {
HealthComponent->OnDeath.BindUFunction(this, "OnDeath"); // HealthComponent = CreateDefaultSubobject<UHealthComponent>(TEXT("Health Component"));
// HealthComponent->OnDamaged.BindUFunction(this, "OnDamaged");
// HealthComponent->OnDeath.BindUFunction(this, "OnDeath");
// }
} }
// Called when the game starts or when spawned // Called when the game starts or when spawned

View File

@ -0,0 +1,37 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "PatrolRoute.h"
// Sets default values
APatrolRoute::APatrolRoute()
{
PrimaryActorTick.bCanEverTick = false;
Spline = CreateDefaultSubobject<USplineComponent>(TEXT("Spline"));
Spline->SetupAttachment(RootComponent);
}
void APatrolRoute::IncrementPatrolRoute()
{
if (PatrolIndex == Spline->GetNumberOfSplinePoints() - 1)
{
Direction = -1;
}
else if (PatrolIndex == 0)
{
Direction = 1;
}
PatrolIndex += Direction;
}
FVector APatrolRoute::GetSplinePointAtWorld()
{
return GetSplinePointAtWorld(PatrolIndex);
}
FVector APatrolRoute::GetSplinePointAtWorld(int pointIndex)
{
return Spline->GetLocationAtSplinePoint(pointIndex, ESplineCoordinateSpace::World);
}

View File

@ -0,0 +1,34 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Components/SplineComponent.h"
#include "GameFramework/Actor.h"
#include "PatrolRoute.generated.h"
UCLASS()
class NAKATOMI_API APatrolRoute : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite)
USplineComponent* Spline;
private:
int PatrolIndex = 0;
int Direction;
public:
// Sets default values for this actor's properties
APatrolRoute();
void IncrementPatrolRoute();
FVector GetSplinePointAtWorld();
FVector GetSplinePointAtWorld(int pointIndex);
};

View File

@ -30,6 +30,10 @@ APlayerCharacter::APlayerCharacter(const FObjectInitializer& ObjectInitializer)
//bUseControllerRotationYaw = true; //bUseControllerRotationYaw = true;
//bUseControllerRotationRoll = false; //bUseControllerRotationRoll = false;
SetHealthComponent(CreateDefaultSubobject<UHealthComponent>(TEXT("Health Component")));
GetHealthComponent()->OnDamaged.BindUFunction(this, "OnDamaged");
GetHealthComponent()->OnDeath.BindUFunction(this, "OnDeath");
// Setup the camera boom // Setup the camera boom
CameraSpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArmComponent")); CameraSpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArmComponent"));
CameraSpringArmComponent->SetupAttachment(RootComponent); CameraSpringArmComponent->SetupAttachment(RootComponent);
@ -398,6 +402,7 @@ void APlayerCharacter::ProcessHits(TArray<FHitResult> hits)
{ {
healthComponent->TakeDamage(Hit.GetActor(), CurrentWeapon->GetWeaponProperties()->WeaponDamage, nullptr, healthComponent->TakeDamage(Hit.GetActor(), CurrentWeapon->GetWeaponProperties()->WeaponDamage, nullptr,
GetController(), this); GetController(), this);
if (!healthComponent->GetIsDead()) if (!healthComponent->GetIsDead())
{ {
OnEnemyHit.ExecuteIfBound(); OnEnemyHit.ExecuteIfBound();
@ -437,6 +442,8 @@ void APlayerCharacter::ProcessHits(TArray<FHitResult> hits)
true); true);
} }
} }
MakeNoise(1, this, Hit.Location);
} }
} }
@ -639,6 +646,8 @@ void APlayerCharacter::OnFire()
CurrentWeapon->PlayFireSoundAtLocation(this->GetTransform().GetLocation()); CurrentWeapon->PlayFireSoundAtLocation(this->GetTransform().GetLocation());
MakeNoise(1,this, this->GetTransform().GetLocation());
PlayOnFireAnimations(); PlayOnFireAnimations();
CurrentWeapon->SetCurrentWeaponStatus(Cooldown); CurrentWeapon->SetCurrentWeaponStatus(Cooldown);

View File

@ -9,6 +9,5 @@ EBTNodeResult::Type UBTTClearFocus::ExecuteTask(UBehaviorTreeComponent& owner, u
{ {
auto enemyController = Cast<AEnemyAIController>(owner.GetAIOwner()); auto enemyController = Cast<AEnemyAIController>(owner.GetAIOwner());
enemyController->ClearFocus(EAIFocusPriority::Default); enemyController->ClearFocus(EAIFocusPriority::Default);
enemyController->SetFocus();
return EBTNodeResult::Succeeded; return EBTNodeResult::Succeeded;
} }

View File

@ -0,0 +1,28 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "../Tasks/BTTMoveAlongPatrolRoute.h"
#include "navigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Nakatomi/EnemyAIController.h"
#include "Nakatomi/NakatomiCMC.h"
EBTNodeResult::Type UBTTMoveAlongPatrolRoute::ExecuteTask(UBehaviorTreeComponent& owner, uint8* memory)
{
AEnemyAIController* enemyController = Cast<AEnemyAIController>(owner.GetAIOwner());
AEnemyCharacter* enemyPawn = Cast<AEnemyCharacter>(enemyController->GetPawn());
if (enemyPawn->CurrentPatrolRoute)
{
FVector location = enemyPawn->CurrentPatrolRoute->GetSplinePointAtWorld();
UBlackboardComponent* blackboardComponent = owner.GetBlackboardComponent();
blackboardComponent->SetValueAsVector(PatrolLocationKey.SelectedKeyName, location);
enemyPawn->CurrentPatrolRoute->IncrementPatrolRoute();
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}

View File

@ -0,0 +1,24 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTMoveAlongPatrolRoute.generated.h"
/**
*
*/
UCLASS()
class NAKATOMI_API UBTTMoveAlongPatrolRoute : public UBTTaskNode
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Options",
Meta = (AllowPrivateAccess = "true", DisplayName = "Patrol Location Key"))
FBlackboardKeySelector PatrolLocationKey;
public:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& owner, uint8* memory) override;
};

View File

@ -0,0 +1,33 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "../Tasks/BTTSetMovementSpeed.h"
#include "Nakatomi/EnemyAIController.h"
#include "Nakatomi/NakatomiCMC.h"
enum class EPatrolMovementEnum : uint8;
EBTNodeResult::Type UBTTSetMovementSpeed::ExecuteTask(UBehaviorTreeComponent& owner, uint8* memory)
{
AEnemyAIController* enemyController = Cast<AEnemyAIController>(owner.GetAIOwner());
AEnemyCharacter* enemyPawn = Cast<AEnemyCharacter>(enemyController->GetPawn());
switch (MaxWalkSpeedKey)
{
case EPatrolMovementEnum::SLOWWALK:
enemyPawn->GetCharacterMovementComponent()->SetMaxWalkSpeed(slowWalkSpeed);
break;
case EPatrolMovementEnum::WALK:
enemyPawn->GetCharacterMovementComponent()->SetMaxWalkSpeed(walkSpeed);
break;
case EPatrolMovementEnum::SPRINT:
enemyPawn->GetCharacterMovementComponent()->SetMaxWalkSpeed(sprintSpeed);
break;
default:
enemyPawn->GetCharacterMovementComponent()->SetMaxWalkSpeedToDefault();
break;
}
return EBTNodeResult::Succeeded;
}

View File

@ -0,0 +1,34 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "Nakatomi/EPatrolMovementEnum.h"
#include "BTTSetMovementSpeed.generated.h"
/**
*
*/
UCLASS()
class NAKATOMI_API UBTTSetMovementSpeed : public UBTTaskNode
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Options",
Meta = (AllowPrivateAccess = "true", DisplayName = "Max Walk Speed Key"))
EPatrolMovementEnum MaxWalkSpeedKey;
private:
float slowWalkSpeed = 250.0f;
float walkSpeed = 500.0f;
float sprintSpeed = 750.0f;
public:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& owner, uint8* memory) override;
};

View File

@ -0,0 +1,17 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "../Tasks/BTTSetState.h"
#include "Nakatomi/EnemyAIController.h"
EBTNodeResult::Type UBTTSetState::ExecuteTask(UBehaviorTreeComponent& owner, uint8* memory)
{
if (AEnemyAIController* EnemyController = Cast<AEnemyAIController>(owner.GetAIOwner()))
{
EnemyController->SetState(NewState);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}

View File

@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "Nakatomi/EAIState.h"
#include "BTTSetState.generated.h"
/**
*
*/
UCLASS()
class NAKATOMI_API UBTTSetState : public UBTTaskNode
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Options",
Meta = (AllowPrivateAccess = "true", DisplayName = "New State"))
EAIState NewState;
public:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& owner, uint8* memory) override;
};