[박치영]

- AI 공격 패턴 추가
- AI 공격 시 충돌 추가
- 리팩토링
main
PCYPC\pcy35 2023-08-31 21:49:48 +09:00
parent 418e5a08b7
commit 8fcba9fa77
30 changed files with 608 additions and 112 deletions

View File

@ -136,4 +136,6 @@ ManualIPAddress=
+PropertyRedirects=(OldName="/Script/D1.MasterAI.TargetingWidget",NewName="/Script/D1.MasterAI.TargetingWidgetComponent")
+PropertyRedirects=(OldName="/Script/D1.HumanoidEnemy.HealthBar",NewName="/Script/D1.HumanoidEnemy.HealthBarComponent")
+PropertyRedirects=(OldName="/Script/D1.T_FindNextPatrolPoint.TargetLocation",NewName="/Script/D1.T_FindNextPatrolPoint.BlackboardKey_TargetLocation")
+PropertyRedirects=(OldName="/Script/D1.T_FindNextPatrolPoint.PatrolIndex",NewName="/Script/D1.T_FindNextPatrolPoint.BlackboardKey_PatrolIndex")
+PropertyRedirects=(OldName="/Script/D1.T_FindNextPatrolPoint.PatrolIndex",NewName="/Script/D1.T_FindNextPatrolPoint.BlackboardKey_PatrolIndex")
+PropertyRedirects=(OldName="/Script/D1.CombatCharacter.ToggleCombatAction",NewName="/Script/D1.CombatCharacter.ToggleCombatInputAction")
+FunctionRedirects=(OldName="/Script/D1.BaseWeapon.ToggleCombat",NewName="/Script/D1.BaseWeapon.ToggleWeaponCombat")

View File

@ -6,6 +6,8 @@
#include "GameplayTagAssetInterface.h"
#include "MasterAI.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Components/CombatComponent.h"
#include "Definitions/CombatGameplayTags.h"
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AISenseConfig_Damage.h"
#include "Perception/AISenseConfig_Sight.h"
@ -38,11 +40,15 @@ void ACombatAIController::OnPossess(APawn* InPawn)
Super::OnPossess(InPawn);
AMasterAI* AIpawn = Cast<AMasterAI>(InPawn);
if(AIpawn)
{
MasterAI = AIpawn;
RunBehaviorTree(MasterAI->GetBeHaviorTree());
}
if(!AIpawn)
return;
MasterAI = AIpawn;
UCombatComponent* combatComponent = MasterAI->GetComponentByClass<UCombatComponent>();
combatComponent->OnCombatToggled.AddUObject(this, &ACombatAIController::OnCombatToggle);
RunBehaviorTree(MasterAI->GetBeHaviorTree());
}
void ACombatAIController::OnUpdatePerception(const TArray<AActor*>& PerceivedActors)
@ -81,3 +87,8 @@ void ACombatAIController::OnUpdatePerception(const TArray<AActor*>& PerceivedAct
}
}
}
void ACombatAIController::OnCombatToggle(bool IsCombatEnabled)
{
Blackboard->SetValueAsBool(TEXT("bCombatEnabled"), IsCombatEnabled);
}

View File

@ -19,8 +19,12 @@ public:
protected: //Inherited Func
virtual void OnPossess(APawn* InPawn) override;
public: //Delegate Func
//UAIPerceptionComponent Delegate
UFUNCTION(BlueprintCallable)
void OnUpdatePerception(const TArray<AActor*>& PerceivedActors);
//UCombatComponent Delegate
void OnCombatToggle(bool IsCombatEnabled);
private:
UPROPERTY()
TObjectPtr<class AMasterAI> MasterAI;

View File

@ -4,14 +4,22 @@
#include "AI/HumanoidEnemy.h"
#include "Blueprint/UserWidget.h"
#include "Components/CollisionComponent.h"
#include "Components/WidgetComponent.h"
#include "Components/CombatComponent.h"
#include "DamageType/AttackDamageType.h"
#include "Kismet/GameplayStatics.h"
#include "UI/UI_HealthBar.h"
AHumanoidEnemy::AHumanoidEnemy()
{
TargetingWidgetComponent->SetRelativeLocation(FVector(0.f, 0.f, 132.f));
//Setting SocketName
AttachSocketName = TEXT("SwordHipAttachSocket");
WeaponHandSocketName = TEXT("RightWeaponSocket");
//Setting UWidgetComponent
HealthBarComponent = CreateDefaultSubobject<UWidgetComponent>(TEXT("HealthBar"));
static ConstructorHelpers::FClassFinder<UUserWidget> WidgetRef(TEXT("/Game/CombatSystem/UI/WBP_HealthBar.WBP_HealthBar_C"));
if(WidgetRef.Class)
@ -23,11 +31,28 @@ AHumanoidEnemy::AHumanoidEnemy()
HealthBarComponent->SetRelativeLocation(FVector(0.f, 0.f, 200.f));
HealthBarComponent->SetVisibility(false);
}
//Setting WeaponMeshComponent
WeaponMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponMeshComponent"));
WeaponMeshComponent->SetRelativeLocationAndRotation(FVector(0.f, 0.f, 0.f), FRotator(0.f, 0.f, 0.f));
WeaponMeshComponent->SetupAttachment(GetMesh(), AttachSocketName);
WeaponMeshComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision);
//Setting MainWeaponCollisionComponent
//Setting others is Parents
MainWeaponCollisionComponent->OnHitDelegate.BindUObject(this, &AHumanoidEnemy::OnHit);
//Setting CombatComponent
//Setting others is Parents
CombatComponent->OnCombatToggled.AddUObject(this, &AHumanoidEnemy::OnCombatToggled);
}
void AHumanoidEnemy::BeginPlay()
{
Super::BeginPlay();
MainWeaponCollisionComponent->SetCollisionMeshComponent(WeaponMeshComponent);
MainWeaponCollisionComponent->AddActorToIgnore(this);
if(APlayerController* playerController = UGameplayStatics::GetPlayerController(GetWorld(), 0))
{
@ -45,3 +70,36 @@ void AHumanoidEnemy::OnTargeted(bool bIsTargeted)
Super::OnTargeted(bIsTargeted);
HealthBarComponent->SetVisibility(bIsTargeted);
}
void AHumanoidEnemy::OnHit(FHitResult hitResult)
{
ICombatInterface* pActor = Cast<ICombatInterface>(hitResult.GetActor());
if (pActor)
{
if (pActor->Execute_CanReceiveDamage(hitResult.GetActor()))
UGameplayStatics::ApplyPointDamage(hitResult.GetActor(), StatsComponent->GetCurrentStatValue(EStats::Damage), this->GetActorForwardVector(), hitResult, GetController(), this, UAttackDamageType::StaticClass());
}
}
void AHumanoidEnemy::OnCombatToggled(bool IsCombatEnabled)
{
FName SocketName;
if(IsCombatEnabled)
SocketName = WeaponHandSocketName;
else
SocketName = AttachSocketName;
FAttachmentTransformRules rules(EAttachmentRule::SnapToTarget, EAttachmentRule::SnapToTarget, EAttachmentRule::SnapToTarget, true);
WeaponMeshComponent->AttachToComponent(GetMesh(), rules, SocketName);
}
void AHumanoidEnemy::PerformDeath()
{
Super::PerformDeath();
SimulateWeaponPhysics();
}
void AHumanoidEnemy::SimulateWeaponPhysics()
{
WeaponMeshComponent->SetCollisionProfileName(TEXT("PhysicsActor"), true);
WeaponMeshComponent->SetSimulatePhysics(true);
}

View File

@ -19,6 +19,8 @@ public:
private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="UI", meta=(AllowPrivateAccess="true"))
TObjectPtr<class UWidgetComponent> HealthBarComponent;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Component", meta=(AllowPrivateAccess="true"))
TObjectPtr<class UStaticMeshComponent> WeaponMeshComponent;
protected:
virtual void BeginPlay() override;
@ -26,4 +28,19 @@ protected:
protected:
// Inherited via ITargetingInterface
virtual void OnTargeted(bool bIsTargeted) override;
public: // Delegate
//UCollisionComponent
void OnHit(FHitResult hitResult);
//UCombatComponent
void OnCombatToggled(bool IsCombatEnabled);
public:
virtual void PerformDeath() override;
void SimulateWeaponPhysics();
private:
UPROPERTY(EditAnywhere, Blueprintable, Category="Initialization", meta=(AllowPrivateAccess="true"))
FName AttachSocketName;
UPROPERTY(EditAnywhere, Blueprintable, Category="Initialization", meta=(AllowPrivateAccess="true"))
FName WeaponHandSocketName;
};

View File

@ -6,12 +6,13 @@
#include "Components/InputComponent.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/Controller.h"
#include "Actor/BaseWeapon.h"
#include "Components/CombatComponent.h"
#include "Components/StatsComponent.h"
#include "Components/StateManagerComponent.h"
#include "Definitions/CombatGameplayTags.h"
#include "Engine/DamageEvents.h"
#include "NiagaraFunctionLibrary.h"
#include "Components/CollisionComponent.h"
#include "Components/WidgetComponent.h"
#include "DamageType/AttackDamageType.h"
#include "Kismet/KismetMathLibrary.h"
@ -71,6 +72,17 @@ AMasterAI::AMasterAI()
StatsComponent = CreateDefaultSubobject<UStatsComponent>(TEXT("StatsComponent"));
StatsComponent->OnCurrentStatValueUpdated.AddUObject(this, &AMasterAI::CharacterCurrentStatValueUpdated);
// Setting MainWeaponCollisionComponent
// Setting OnHit Func is Child Class
MainWeaponCollisionComponent = CreateDefaultSubobject<UCollisionComponent>(TEXT("CollisionComponent"));
MainWeaponCollisionComponent->SetTraceRaius(20.f);
MainWeaponCollisionComponent->SetStartSocketName(TEXT("WeaponStart"));
MainWeaponCollisionComponent->SetEndSocketName(TEXT("WeaponEnd"));
TArray<TEnumAsByte<EObjectTypeQuery>> InputCollisionObjectTypes;
InputCollisionObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_Pawn));
MainWeaponCollisionComponent->SetCollisionObjectTypes(InputCollisionObjectTypes);
MainWeaponCollisionComponent->SetDrawDebugType(EDrawDebugTrace::None);
//Setting MovementSpeed
MovementSpeedMode = EMovementSpeedMode::Jogging;
WalkingSpeed = 200.f;
@ -164,6 +176,90 @@ void AMasterAI::SetCanMove_Implementation(bool inputCanMove)
bCanMove = inputCanMove;
}
void AMasterAI::ActivateCollision_Implementation(ECollisionPart CollisionPart)
{
if(StateManagerComponent->GetCurrentState() != FCombatGameplayTags::Get().Character_State_Dead) //TODO : 마지막 틱에서 충돌활성화 되는 이슈 방지...가 안됨.
MainWeaponCollisionComponent->ActivateCollision();
}
void AMasterAI::DeactivateCollision_Implementation(ECollisionPart CollisionPart)
{
MainWeaponCollisionComponent->DeactivateCollision();
}
void AMasterAI::ToggleCombat_Implementation()
{
if (!IsValid(CombatComponent))
return;
CombatComponent->SetCombatEnabled(!CombatComponent->GetCombatEnabled());
}
float AMasterAI::PerformCombatAction(FGameplayTag ActionTag, FGameplayTag StateTag, int32 MontageIndex, bool bRandomIndex)
{
TArray<UAnimMontage*> montages = GetActionMontage(ActionTag);
int32 montageIdx = MontageIndex;
if(bRandomIndex)
montageIdx = FMath::RandRange(0, montages.Num() - 1);
else if (montages.Num() <= montageIdx)
montageIdx = 0;
if (!montages.IsValidIndex(montageIdx))
return false;
UAnimMontage* actionMontage = montages[montageIdx];
float actionDuration = 0.f;
if (IsValid(actionMontage))
{
StateManagerComponent->SetCurrentState(StateTag);
StateManagerComponent->SetCurrentAction(ActionTag);
actionDuration = PlayAnimMontage(actionMontage);
}
else
{
FString str = FString::Printf(TEXT("Dodge Index %n is NOT VALID!!"), montageIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
}
return actionDuration;
}
float AMasterAI::PerformCombatAttack(FGameplayTag AttackType, int32 AttackIndex, bool bRandomIndex)
{
TArray<UAnimMontage*> montages = GetActionMontage(AttackType);
int32 attackIdx = AttackIndex;
if(bRandomIndex)
attackIdx = FMath::RandRange(0, montages.Num() - 1);
else if (montages.Num() <= attackIdx)
attackIdx = 0;
if (!montages.IsValidIndex(attackIdx))
return 0.f;
UAnimMontage* attackMontage = montages[attackIdx];
float attackDuration = 0.f;
if (!IsValid(attackMontage))
{
FString debugStr = FString::Printf(TEXT("Index %d Is NOT VALID!!"), attackIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, debugStr);
}
else
{
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Attacking);
StateManagerComponent->SetCurrentAction(AttackType);
attackDuration = PlayAnimMontage(attackMontage);
int idx = attackIdx + 1;
if (idx >= montages.Num())
idx = 0;
CombatComponent->SetAttackCount(idx);
}
return attackDuration;
}
bool AMasterAI::CanBeTargeted()
{
return (StateManagerComponent->GetCurrentState() != FCombatGameplayTags::Get().Character_State_Dead);
@ -239,32 +335,13 @@ void AMasterAI::CharacterCurrentStatValueUpdated(EStats statType, float value)
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Dead);
}
void AMasterAI::ToggleCombatEvent()
{
if (!CanPerformToggleCombat())
return;
if (!CombatComponent->GetCombatEnabled())
PerformAction(FCombatGameplayTags::Get().Character_State_GeneralActionState, FCombatGameplayTags::Get().Character_Action_EnterCombat, 0);
else
PerformAction(FCombatGameplayTags::Get().Character_State_GeneralActionState, FCombatGameplayTags::Get().Character_Action_ExitCombat, 0);
}
void AMasterAI::AttackEvent()
{
if (!CanPerformAttack())
return;
if (CombatComponent->GetCombatEnabled())
PerformAttack(GetDesiredAttackType(), CombatComponent->GetAttackCount());
else
ToggleCombatEvent();
}
void AMasterAI::ChargeAttackEvent()
{
if (CanPerformAttack())
PerformAttack(FCombatGameplayTags::Get().Character_Action_Attack_ChargedAttack, CombatComponent->GetAttackCount());
PerformCombatAttack(GetDesiredAttackType(), CombatComponent->GetAttackCount(), false);
}
void AMasterAI::ApplyHitReactionPhysicsVelocity(float InitialSpeed)
@ -339,64 +416,6 @@ void AMasterAI::ApplyImpactEffect(EDamageType InDamageType)
UNiagaraFunctionLibrary::SpawnSystemAtLocation(GetWorld(), HitEmitter, LastHitInfo.Location);
}
void AMasterAI::PerformAttack(FGameplayTag attackType, int32 attackIndex)
{
TArray<UAnimMontage*> montages = GetActionMontage(attackType);
int32 attackIdx = attackIndex;
if (montages.Num() <= attackIdx)
attackIdx = 0;
if (!montages.IsValidIndex(attackIdx))
return;
UAnimMontage* attackMontage = montages[attackIdx];
if (!IsValid(attackMontage))
{
FString debugStr = FString::Printf(TEXT("Index %d Is NOT VALID!!"), attackIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, debugStr);
return;
}
else
{
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Attacking);
StateManagerComponent->SetCurrentAction(attackType);
PlayAnimMontage(attackMontage);
int idx = attackIdx + 1;
if (idx >= montages.Num())
idx = 0;
CombatComponent->SetAttackCount(idx);
}
}
bool AMasterAI::PerformAction(FGameplayTag characterState, FGameplayTag characterAction, int32 montageIndex)
{
TArray<UAnimMontage*> montages = GetActionMontage(characterAction);
int32 montageIdx = montageIndex;
if (montages.Num() <= montageIdx)
montageIdx = 0;
if (!montages.IsValidIndex(montageIdx))
return false;
UAnimMontage* actionMontage = montages[montageIdx];
if (IsValid(actionMontage))
{
StateManagerComponent->SetCurrentState(characterState);
StateManagerComponent->SetCurrentAction(characterAction);
PlayAnimMontage(actionMontage);
return true;
}
else
{
FString str = FString::Printf(TEXT("Dodge Index %n is NOT VALID!!"), montageIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
return false;
}
}
void AMasterAI::PerformDeath()
{
EnableRagdoll();

View File

@ -3,11 +3,9 @@
#pragma once
#include "CoreMinimal.h"
#include "Components/StatsComponent.h"
#include "GameFramework/Character.h"
#include "Interface/CombatInterface.h"
#include "Components/StateManagerComponent.h"
#include "Components/StatsComponent.h"
#include "Components/TargetingComponent.h"
#include "Definitions/GameEnums.h"
#include "Engine/TargetPoint.h"
#include "Interface/TargetingInterface.h"
@ -28,9 +26,12 @@ protected:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<class UCombatComponent> CombatComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<UStateManagerComponent> StateManagerComponent;
TObjectPtr<class UStateManagerComponent> StateManagerComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<UStatsComponent> StatsComponent;
TObjectPtr<class UStatsComponent> StatsComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<class UCollisionComponent> MainWeaponCollisionComponent;
protected:
// APawn interface
virtual void BeginPlay() override;
@ -56,9 +57,20 @@ public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void SetCanMove(bool inputCanMove);
virtual void SetCanMove_Implementation(bool inputCanMove) override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ActivateCollision(ECollisionPart CollisionPart);
virtual void ActivateCollision_Implementation(ECollisionPart CollisionPart) override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void DeactivateCollision(ECollisionPart CollisionPart);
virtual void DeactivateCollision_Implementation(ECollisionPart CollisionPart) override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ToggleCombat();
virtual void ToggleCombat_Implementation() override;
virtual EMovementSpeedMode GetCombatMovementSpeedMode() override { return GetMovementSpeedMode(); }
virtual float PerformCombatAction(FGameplayTag ActionTag, FGameplayTag StateTag, int32 MontageIndex, bool bRandomIndex);
virtual float PerformCombatAttack(FGameplayTag AttackType, int32 AttackIndex, bool bRandomIndex);
// Inherited via ITargetingInterface
virtual bool CanBeTargeted() override;
virtual void OnTargeted(bool bIsTargeted) override;
@ -72,9 +84,7 @@ private://Delegate
void CharacterCurrentStatValueUpdated(EStats statType, float value);
private:
void ToggleCombatEvent();
void AttackEvent();
void ChargeAttackEvent();
void ApplyHitReactionPhysicsVelocity(float InitialSpeed);
void EnableRagdoll();
void DisableSprint();
@ -82,10 +92,8 @@ private:
void ApplyHitReaction(EDamageType InDamageType);
void ApplyImpactEffect(EDamageType InDamageType);
private:
void PerformAttack(FGameplayTag attackType, int32 attackIndex);
bool PerformAction(FGameplayTag characterState, FGameplayTag characterAction, int32 montageIndex);
void PerformDeath();
protected:
virtual void PerformDeath();
bool PerformHitStun();
bool PerformKnockdown();

View File

@ -0,0 +1,70 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/S_UpdateBehavior.h"
#include "MasterAI.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "BehaviorTree/BTFunctionLibrary.h"
#include "Components/StateManagerComponent.h"
#include "Definitions/CombatGameplayTags.h"
void US_UpdateBehavior::OnSearchStart(FBehaviorTreeSearchData& SearchData)
{
Super::OnSearchStart(SearchData);
ControlledMasterAI = Cast<AMasterAI>(SearchData.OwnerComp.GetAIOwner()->GetPawn());
check(ControlledMasterAI);
BlackboardComponent = SearchData.OwnerComp.GetBlackboardComponent();
OwnerController = Cast<AAIController>(ControlledMasterAI->GetController());
check(OwnerController);
}
void US_UpdateBehavior::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
UpdateBehavior();
}
void US_UpdateBehavior::OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
Super::OnBecomeRelevant(OwnerComp, NodeMemory);
SetBehavior(EAIBehavior::Patrol);
}
void US_UpdateBehavior::UpdateBehavior()
{
if(!IsValid(ControlledMasterAI) || !IsValid(OwnerController))
return;
UStateManagerComponent* StateManagerComponent = ControlledMasterAI->GetComponentByClass<UStateManagerComponent>();
if(!StateManagerComponent)
return;
if(StateManagerComponent->GetCurrentState() == FCombatGameplayTags::Get().Character_State_Dead)
{
SetBehavior(EAIBehavior::Nothing);
return;
}
const AActor* targetActor = Cast<AActor>(BlackboardComponent->GetValueAsObject(BlackboardKey_TargetKey.SelectedKeyName));
if(!targetActor)
{
SetBehavior(EAIBehavior::Patrol);
return;
}
CanSeeEnemy = true;
float dist = targetActor->GetDistanceTo(ControlledMasterAI);
if(dist <= AttackingRange)
SetBehavior(EAIBehavior::Attack);
else
SetBehavior(EAIBehavior::Chase);
}
void US_UpdateBehavior::SetBehavior(EAIBehavior NewBehavior)
{
BlackboardComponent->SetValueAsEnum(BlackboardKey_BehaviorKey.SelectedKeyName, (uint8)NewBehavior);
}

View File

@ -0,0 +1,39 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTService.h"
#include "Definitions/GameEnums.h"
#include "S_UpdateBehavior.generated.h"
/**
*
*/
UCLASS()
class D1_API US_UpdateBehavior : public UBTService
{
GENERATED_BODY()
protected:
virtual void OnSearchStart(FBehaviorTreeSearchData& SearchData) override;
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
virtual void OnBecomeRelevant(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
public:
void UpdateBehavior();
void SetBehavior(EAIBehavior NewBehavior);
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BlackBoard", meta=(AllowPrivateAccess="true"))
FBlackboardKeySelector BlackboardKey_BehaviorKey;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BlackBoard", meta=(AllowPrivateAccess="true"))
FBlackboardKeySelector BlackboardKey_TargetKey;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="BlackBoard", meta=(AllowPrivateAccess="true"))
float AttackingRange;
private:
TObjectPtr<class UBlackboardComponent> BlackboardComponent;
TObjectPtr<class AMasterAI> ControlledMasterAI;
TObjectPtr<class AAIController> OwnerController;
bool CanSeeEnemy;
};

View File

@ -0,0 +1,31 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/T_PerformAction.h"
#include "AIController.h"
#include "Interface/CombatInterface.h"
EBTNodeResult::Type UT_PerformAction::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type result = Super::ExecuteTask(OwnerComp, NodeMemory);
if(result == EBTNodeResult::Failed)
return EBTNodeResult::Failed;
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if(ControllingPawn == nullptr)
return EBTNodeResult::Failed;
ICombatInterface* CombatInterface = Cast<ICombatInterface>(ControllingPawn);
if(!CombatInterface)
return EBTNodeResult::Failed;
float actionDuration = CombatInterface->PerformCombatAction(ActionTag, StateTag, MontageIndex, RandomIndex);
actionDuration += ActionDurationModifier;
FTimerHandle ActionTimer;
GetWorld()->GetTimerManager().SetTimer(ActionTimer, FTimerDelegate::CreateLambda([&]()
{
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}), actionDuration, false);
return EBTNodeResult::InProgress;
}

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/BTTaskNode.h"
#include "T_PerformAction.generated.h"
/**
*
*/
UCLASS()
class D1_API UT_PerformAction : public UBTTaskNode
{
GENERATED_BODY()
public:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
FGameplayTag ActionTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
FGameplayTag StateTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
int32 MontageIndex;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
bool RandomIndex;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
float ActionDurationModifier;
};

View File

@ -0,0 +1,31 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/T_PerformAttack.h"
#include "AIController.h"
#include "Interface/CombatInterface.h"
EBTNodeResult::Type UT_PerformAttack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type result = Super::ExecuteTask(OwnerComp, NodeMemory);
if(result == EBTNodeResult::Failed)
return EBTNodeResult::Failed;
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if(ControllingPawn == nullptr)
return EBTNodeResult::Failed;
ICombatInterface* CombatInterface = Cast<ICombatInterface>(ControllingPawn);
if(!CombatInterface)
return EBTNodeResult::Failed;
float actionDuration = CombatInterface->PerformCombatAttack(AttackTypeTag, AttackIndex, RandomIndex);
actionDuration += ActionDurationModifier;
FTimerHandle ActionTimer;
GetWorld()->GetTimerManager().SetTimer(ActionTimer, FTimerDelegate::CreateLambda([&]()
{
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}), actionDuration, false);
return EBTNodeResult::InProgress;
}

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/BTTaskNode.h"
#include "T_PerformAttack.generated.h"
/**
*
*/
UCLASS()
class D1_API UT_PerformAttack : public UBTTaskNode
{
GENERATED_BODY()
public:
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
private:
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
FGameplayTag AttackTypeTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
int32 AttackIndex;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
bool RandomIndex;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta=(AllowPrivateAccess="true"))
float ActionDurationModifier;
};

View File

@ -94,9 +94,9 @@ void ABaseDualWeapon::DeactivateCollision(ECollisionPart CollsionPart)
}
}
void ABaseDualWeapon::ToggleCombat(bool EnableCombat)
void ABaseDualWeapon::ToggleWeaponCombat(bool EnableCombat)
{
Super::ToggleCombat(EnableCombat);
Super::ToggleWeaponCombat(EnableCombat);
if(EnableCombat)
AttachOffhandWeapon(SecondWeaponHandSocket);

View File

@ -24,7 +24,7 @@ protected:
virtual void OnEquipped() override;
virtual void ActivateCollision(ECollisionPart CollisionPart) override;
virtual void DeactivateCollision(ECollisionPart CollsionPart) override;
virtual void ToggleCombat(bool EnableCombat) override;
virtual void ToggleWeaponCombat(bool EnableCombat) override;
public: //Delegate
void OnHit_OffhandWeapon(FHitResult HitResult);

View File

@ -115,7 +115,7 @@ void ABaseWeapon::DeactivateCollision(ECollisionPart CollsionPart)
CollisionComponent->DeactivateCollision();
}
void ABaseWeapon::ToggleCombat(bool EnableCombat)
void ABaseWeapon::ToggleWeaponCombat(bool EnableCombat)
{
if(!CombatComponent)
return;

View File

@ -41,7 +41,7 @@ public:
UFUNCTION(BlueprintCallable)
virtual void DeactivateCollision(ECollisionPart CollsionPart);
UFUNCTION(BlueprintCallable)
virtual void ToggleCombat(bool EnableCombat);
virtual void ToggleWeaponCombat(bool EnableCombat);
public:
TArray<UAnimMontage*> GetActionMontage(FGameplayTag characterAction);

View File

@ -214,7 +214,7 @@ void ACombatCharacter::SetupPlayerInputComponent(class UInputComponent* PlayerIn
EnhancedInputComponent->BindAction(InteractAction, ETriggerEvent::Started, this, &ACombatCharacter::Interact);
//ToggleCombat
EnhancedInputComponent->BindAction(ToggleCombatAction, ETriggerEvent::Started, this, &ACombatCharacter::ToggleCombat);
EnhancedInputComponent->BindAction(ToggleCombatInputAction, ETriggerEvent::Started, this, &ACombatCharacter::ToggleCombatAction);
//LightAttack
EnhancedInputComponent->BindAction(LightAttackAction, ETriggerEvent::Triggered, this, &ACombatCharacter::LightChargeAttack);
@ -243,6 +243,106 @@ void ACombatCharacter::SetCanMove_Implementation(bool inputCanMove)
bCanMove = inputCanMove;
}
void ACombatCharacter::ActivateCollision_Implementation(ECollisionPart CollisionPart)
{
ABaseWeapon* weapon = CombatComponent->GetMainWeapon();
if(IsValid(weapon))
weapon->ActivateCollision(CollisionPart);
}
void ACombatCharacter::DeactivateCollision_Implementation(ECollisionPart CollisionPart)
{
ABaseWeapon* weapon = CombatComponent->GetMainWeapon();
if(IsValid(weapon))
weapon->DeactivateCollision(CollisionPart);
}
void ACombatCharacter::ToggleCombat_Implementation()
{
ABaseWeapon* weapon = CombatComponent->GetMainWeapon();
if (IsValid(weapon))
weapon->ToggleWeaponCombat(!CombatComponent->GetCombatEnabled());
}
float ACombatCharacter::PerformCombatAction(FGameplayTag ActionTag, FGameplayTag StateTag, int32 MontageIndex, bool bRandomIndex)
{
//TODO : 나중에 이 함수로 바꿔서 쓰기 혹시 몰라 구현만 해놓음
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return false;
TArray<UAnimMontage*> montages = CurrentWeapon->GetActionMontage(ActionTag);
int32 montageIdx = MontageIndex;
if(bRandomIndex)
montageIdx = FMath::RandRange(0, montages.Num() - 1);
else if (montages.Num() <= montageIdx)
montageIdx = 0;
if (!montages.IsValidIndex(montageIdx))
return false;
UAnimMontage* actionMontage = montages[montageIdx];
float actionDuration = 0.f;
if (IsValid(actionMontage))
{
StateManagerComponent->SetCurrentState(StateTag);
StateManagerComponent->SetCurrentAction(ActionTag);
actionDuration = PlayAnimMontage(actionMontage);
return true;
}
else
{
FString str = FString::Printf(TEXT("Dodge Index %n is NOT VALID!!"), montageIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, str);
}
return actionDuration;
}
float ACombatCharacter::PerformCombatAttack(FGameplayTag AttackType, int32 AttackIndex, bool bRandomIndex)
{
//TODO : 나중에 이 함수로 바꿔서 쓰기 혹시 몰라 구현만 해놓음
if(!CombatComponent->GetCombatEnabled())
return 0.f;
ABaseWeapon* CurrentWeapon = CombatComponent->GetMainWeapon();
if (!CurrentWeapon)
return 0.f;
TArray<UAnimMontage*> montages = CurrentWeapon->GetActionMontage(AttackType);
int32 attackIdx = AttackIndex;
if(bRandomIndex)
attackIdx = FMath::RandRange(0, montages.Num() - 1);
else if (montages.Num() <= attackIdx)
attackIdx = 0;
if (!montages.IsValidIndex(attackIdx))
return 0.f;
UAnimMontage* attackMontage = montages[attackIdx];
float attackDuration = 0.f;
if (!IsValid(attackMontage))
{
FString debugStr = FString::Printf(TEXT("Index %d Is NOT VALID!!"), attackIdx);
GEngine->AddOnScreenDebugMessage(-1, 3.f, FColor::Red, debugStr);
}
else
{
StateManagerComponent->SetCurrentState(FCombatGameplayTags::Get().Character_State_Attacking);
StateManagerComponent->SetCurrentAction(AttackType);
attackDuration = PlayAnimMontage(attackMontage);
int idx = attackIdx + 1;
if (idx >= montages.Num())
idx = 0;
CombatComponent->SetAttackCount(idx);
}
return attackDuration;
}
void ACombatCharacter::SetMovementSpeedMode(EMovementSpeedMode NewSpeedMode)
{
if (NewSpeedMode == MovementSpeedMode)
@ -340,7 +440,7 @@ void ACombatCharacter::Interact(const FInputActionValue& Value)
}
}
void ACombatCharacter::ToggleCombat(const FInputActionValue& Value)
void ACombatCharacter::ToggleCombatAction(const FInputActionValue& Value)
{
bool bInput = Value.Get<bool>();

View File

@ -51,7 +51,7 @@ class ACombatCharacter : public ACharacter, public ICombatInterface, public IGam
/* ToggleCombat Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* ToggleCombatAction;
class UInputAction* ToggleCombatInputAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
class UInputAction* LightAttackAction;
@ -108,9 +108,20 @@ public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void SetCanMove(bool inputCanMove);
virtual void SetCanMove_Implementation(bool inputCanMove) override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ActivateCollision(ECollisionPart CollisionPart);
virtual void ActivateCollision_Implementation(ECollisionPart CollisionPart) override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void DeactivateCollision(ECollisionPart CollisionPart);
virtual void DeactivateCollision_Implementation(ECollisionPart CollisionPart) override;
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ToggleCombat();
virtual void ToggleCombat_Implementation() override;
virtual EMovementSpeedMode GetCombatMovementSpeedMode() override { return GetMovementSpeedMode(); }
virtual float PerformCombatAction(FGameplayTag ActionTag, FGameplayTag StateTag, int32 MontageIndex, bool bRandomIndex);
virtual float PerformCombatAttack(FGameplayTag AttackType, int32 AttackIndex, bool bRandomIndex);
// Inherited via IGameplayTagAssetInterface
virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override { TagContainer = OwnedGameplayTags; }
public:
@ -123,7 +134,7 @@ protected:
void Look(const FInputActionValue& Value);
void Jumping(const FInputActionValue& Value);
void Interact(const FInputActionValue& Value);
void ToggleCombat(const FInputActionValue& Value);
void ToggleCombatAction(const FInputActionValue& Value);
void LightAttack(const FInputActionValue& Value);
void LightChargeAttack(const FInputActionInstance& Instance);
void HeavyAttack(const FInputActionValue& Value);

View File

@ -67,7 +67,7 @@ private:
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Initialization", meta = (AllowPrivateAccess = "true"))
TArray<TEnumAsByte<EObjectTypeQuery>> CollisionObjectTypes;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Initialization", meta = (AllowPrivateAccess = "true"))
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Initialization", meta = (AllowPrivateAccess = "true"))
TEnumAsByte<EDrawDebugTrace::Type> DrawDebugType;
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Components", meta = (AllowPrivateAccess = "true"))

View File

@ -15,6 +15,7 @@ enum class EStats : uint8
Health UMETA(DisplayName = "Health"),
Stamina UMETA(DisplayName = "Stamina"),
Armor UMETA(DisplayName = "Armor"),
Damage UMETA(DisplayName = "Damage"),
};
USTRUCT(BlueprintType)

View File

@ -36,3 +36,13 @@ enum class EDamageType : uint8
MeleeDamage UMETA(DisplayName = "MeleeDamage"),
KnockdownDamage UMETA(DisplayName = "KnockdownDamage"),
};
UENUM(BlueprintType)
enum class EAIBehavior : uint8
{
Nothing UMETA(DisplayName = "Nothing"),
Attack UMETA(DisplayName = "Attack"),
Chase UMETA(DisplayName = "Chase"),
Patrol UMETA(DisplayName = "Patrol"),
Hit UMETA(DisplayName = "Hit"),
};

View File

@ -4,3 +4,13 @@
#include "Interface/CombatInterface.h"
// Add default functionality here for any ICombatInterface functions that are not pure virtual.
float ICombatInterface::PerformCombatAction(FGameplayTag ActionTag, FGameplayTag StateTag, int32 MontageIndex,
bool bRandomIndex)
{
return 0.f;
}
float ICombatInterface::PerformCombatAttack(FGameplayTag AttackType, int32 AttackIndex, bool bRandomIndex)
{
return 0.f;
}

View File

@ -3,6 +3,7 @@
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Definitions/GameEnums.h"
#include "UObject/Interface.h"
#include "CombatInterface.generated.h"
@ -35,6 +36,19 @@ public:
bool CanReceiveDamage();
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void SetCanMove(bool inputCanMove);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ActivateCollision(ECollisionPart CollisionPart);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void DeactivateCollision(ECollisionPart CollisionPart);
UFUNCTION(BlueprintCallable, BlueprintNativeEvent)
void ToggleCombat();
UFUNCTION()
virtual EMovementSpeedMode GetCombatMovementSpeedMode() = 0;
UFUNCTION(Category="CombatActions")
virtual float PerformCombatAction(FGameplayTag ActionTag, FGameplayTag StateTag, int32 MontageIndex, bool bRandomIndex);
UFUNCTION(Category="CombatActions")
virtual float PerformCombatAttack(FGameplayTag AttackType, int32 AttackIndex, bool bRandomIndex);
virtual EMovementSpeedMode GetCombatMovementSpeedMode() = 0;
};