23.06.12 ~ 2023.06.18 개발일지
bool이나 enum값으로 체크하던 상태이상들을 Gameplay Tag
를 사용하도록 바꾸었다
각각의 묶음을 DataTable
을 통해 묶고, 이를 연동시켜주었다
DataTable
을 생성하고,
테이블 리스트 추가를 통해 추가해주었다
이에 따라 Get, Set을 컨테이너가 태그를 보유중인지를 체크하는 방식으로 코드를 수정하였고
AIFEnemy.cpp
bool AIFEnemy::GetStunState()
{
return EnemyState.HasTag(FGameplayTag::RequestGameplayTag(TEXT("Enemy.Stunned")));
}
애님 인스턴스 등에서도 이를 통해 값을 받아오도록 변경하였다
다만 게임 플레이 태그를 사용하며 기대한 부분 중 하나가 BT에서의 활용이었는데,
이는 아직 해결방법을 찾지 못해 조금 더 공부가 필요할 것 같다
데코레이터 BlackBoard
대신
CheckGameplayTagsOnActor
를 사용하고자 했는데, 잘 적용되지 않는 것 같다
FGameplayTagContainer
변수를 선언해서 사용하는 것이 맞는지 한번 다시 체크가 필요할 것 같다
이전에는 각각의 적 객체마다 캐릭터를 판정하는 영역을 두고,
그 영역안에 캐릭터가 진입하거나 탈출할 시 캐릭터의 Target 변수에 자신을 할당하는 방식을 사용했다
생각해보니, 적이 계속적으로 캐릭터를 찾기 위해 Overlap 함수를 시행할 필요성도 없었고,
적이 자신을 할당하는 방식은 적이 죽게 되었을때 댕글링 포인터 등의 문제가 생길 위험도 높았기에 이번에 구조를 바꾸게 되었다
바꾼 방식은 GetOverlappingActors
를 사용하였다
AIFCharacter.cpp
void AIFCharacter::WeakAttack()
{
TSet<AActor*> Enemies;
GetOverlappingActors(Enemies, AIFEnemy::StaticClass());
if (Enemies.Num())
{
for (const auto& Enemy : Enemies)
{
// check if enemy is in character's sight (DotProduct on chracter's forward vector and character to enemy vector)
float DotProduct = FVector::DotProduct(GetActorForwardVector(),
(Enemy->GetActorLocation() - GetActorLocation()).GetSafeNormal());
if (DotProduct > 0.4)
{
// set the closest Enemy to Target
Target = Cast<AIFEnemy>(Enemy);
if(GetDistanceTo(Enemy) <= GetDistanceTo(Target))
Target = Cast<AIFEnemy>(Enemy);
}
...
}
적의 배열을 받고, 그 중 가장 가까운 적을 타겟으로 하여 이동값을 보정하는 방식을 사용했다
처형 모션을 취할때는 이와 비슷하게 가장 가까운 적 중 스턴 상태인 적에게 처형을 시행하였다
UI를 불러오는 방식을 GetWidgetFromName
함수에서 UPROPERTY(meta = (BindWidget))
로 바꿔주었다
UI와 변수의 이름을 통일시켜주면 바로 적용이 되기 때문에 훨씬 편하게 UI를 불러올 수 있게 되었다
일정 거리를 도약해서 공격하는 방식을 추가해주었다
적과 캐릭터가 600만큼의 거리가 있을때 시행하게 하기 위해,
먼저 Vector 값을 정해주는 SetRangedAttackVector
Task 를 만들었다
BTTask_SetRangedAttackVector
// Fill out your copyright notice in the Description page of Project Settings.
#include "AI/BTTask_SetRangedAttackVector.h"
#include "AIController.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTask_SetRangedAttackVector::UBTTask_SetRangedAttackVector()
{
NodeName = TEXT("SetVector");
}
EBTNodeResult::Type UBTTask_SetRangedAttackVector::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
APawn* ControllingPawn = Cast<APawn>(OwnerComp.GetAIOwner()->GetPawn());
if (nullptr == ControllingPawn)
{
return EBTNodeResult::Failed;
}
APawn* TargetPawn = Cast<APawn>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(TEXT("Target")));
if (nullptr == TargetPawn)
{
return EBTNodeResult::Failed;
}
FVector Direction = (ControllingPawn->GetActorLocation() - TargetPawn->GetActorLocation()).GetSafeNormal();
float DesiredDistance = 600.0f;
FVector DesiredLocation = TargetPawn->GetActorLocation() + (Direction * DesiredDistance);
OwnerComp.GetBlackboardComponent()->SetValueAsVector(TEXT("RangeAttackVector"), DesiredLocation);
return EBTNodeResult::Succeeded;
}
지정한 위치로 이동하고, 캐릭터를 바라보고 공격하도록 설정하였다
결과는 다음과 같다
다음으로는 적 유닛이 일정한 확률로 전에 만든 근접공격과 이번에 만든 공격을 섞어서 할 수 있게 랜덤성을 추가해주었다
UBTDecorator_RandomChance
bool UBTDecorator_RandomChance::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
float Rand = FMath::FRand();
return SucceedRate >= Rand;
}
간단하게 설정한 값이 랜덤으로 나오는 변수보다 큰지 작은지를 통해 판별하도록 했다
이떄 변수 SucceedRate
는 UPROPERTY에서 ClampMin
, ClampMax
를 설정해
0~1의 값만 설정 가능하도록 구현했다
처형 애니메이션이 나가는 중 캐릭터를 무적으로 설정하지 않아 적에게 공격 받는 버그를 수정했다
이를 위해 각각의 애니메이션이 끝나는 시간을 담는 변수를 두고,
그 변수의 값만큼 기다렸다 무적을 해제하는 Delegate를 호출하는 방식을 사용했다
(이제 Data Asset이 시간 변수도 가지게 된다)
공격을 받았을시, 내적하여 정면에 위치했다면 공격받지 않고 밀리기만 하도록 구현하였다
간단하게 공격을 받을때 방어 모션을 취하는 중이라면 데미지를 받지 않도록 설정하였다