액터 컴포넌트의 제작
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "MyCharacterStatComponent.generated.h"
UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class TEST_API UMyCharacterStatComponent : public UActorComponent
{
GENERATED_BODY()
public:
UMyCharacterStatComponent();
protected:
virtual void BeginPlay() override;
virtual void InitializeComponent() override;
};
- 액터의 PoseInitializeComponents에 대응하는 컴포넌트의 함수는 InitializeComponent 함수이다.
- 이 함수를 사용해 컴포넌트의 초기화 로직을 구현해주는데, 이 함수가 호출되려면 생성자에서 bWantsInitializeComponent값을 true로 설정해줘야한다.
UPROPERTY(EditInstanceOnly, Transient, Category = "stat", meta = (AllowPrivateAccess = true))
float CurrentHp;
- Teansient 키워드를 추가하면 해당 속성을 직렬화에서 제외시킬수있다.
KINDA_SMALL_NUMBER
- float의 값을 0과 비교할 때는 미세한 오차 범위 내에 있는지를 보고 판단하는 것이 좋은데 언리얼 엔진에서 무시 가능한 오차를 측정할 때 이 매크로를 사용한다.
AIController
#include "CAIController.h"
AMyCharacter::AMyCharacter()
{
//...
AIControllerClass = ACAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
}
- AIController 클래스를 생성하고 MyCharacter가 이를 사용하도록 AIController의 클래스 속성을 생성했던 CAIController의 클래스로 정한다.
- AI의 생성 옵션을 PlacedInWorldOrSpawned으로 설정한다.
- 설정이 되면 MyCharacter마다 CAIController 액터가 생성되고 플레이어가 조종하는 캐릭터를 제외한 모든 캐릭터는 이 CAIController의 지배를 받는다.
- NPC가 움직일 수 있게하는 보조장치는 내비게이션 메시 기반의 길 찾기 시스템이다.
#pragma once
#include "Test.h"
#include "AIController.h"
#include "CAIController.generated.h"
UCLASS()
class TEST_API ACAIController : public AAIController
{
GENERATED_BODY()
public:
ACAIController();
virtual void OnPossess(APawn* InPawn) override;
virtual void OnUnPossess() override;
private:
void OnRepeatTimer();
FTimerHandle RepeatTimerHandle;
float RepeatInterval;
};
#include "CAIController.h"
#include "NavigationSystem.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
ACAIController::ACAIController()
{
RepeatInterval = 3.0f;
}
void ACAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
GetWorld()->GetTimerManager().SetTimer(RepeatTimerHandle,
this, &ACAIController::OnRepeatTimer, RepeatInterval, true);
}
void ACAIController::OnUnPossess()
{
Super::OnUnPossess();
GetWorld()->GetTimerManager().ClearTimer(RepeatTimerHandle);
}
void ACAIController::OnRepeatTimer()
{
auto CurrentPawn = GetPawn();
ABCHECK(!!CurrentPawn);
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(GetWorld());
if (NULL == NavSystem) return;
FNavLocation NextLocation;
if (NavSystem->GetRandomPointInNavigableRadius(FVector::ZeroVector, 500.0f, NextLocation))
{
UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, NextLocation.Location);
ABLOG(Warning, TEXT("Next Location : %s"), *NextLocation.Location.ToString());
}
}
- GetRandomPointInNavigableRadius : 이동가능한 목적지를 랜덤을 가져온다.
- SimpleMoveToLocation : 목적지로 폰을 이동시킨다.
- AI 컨트롤러에는 PathFollowingComponent가 부착돼 있어서 자신이 조종하는 폰이 길찾기를 사용해 목적지까지 도달하는지 지속적으로 관리한다.
비헤이비어 트리 시스템
- 비헤이비어 트리는 NPC가 해야 할 행동을 분석하고 우선순위가 높은 행동부터 NPC가 생행할 수 있도록 트리 구조로 설계하는 기법이다.
- 비헤이비어 트리와 블랙보드 애셋을 생성한다.
- 블랙보드 : 인공지능 판단에 사용하는 데이터 집합
- 비헤이비어 트리 : 블랙보드 데이터에 기반해 설계한 비헤이비어 트리의 정보를 저장한 에셋
- 태스크는 독립적으로 실행될 수 없고 반드시 컴포짓 노드를 거쳐 실행돼야 한다.
- 컴포짓 노드에는 대표적으로 셀렉터와 시퀸스가 있다.
- C++코드에서 비헤이비어 트리 관련기능을 사용하려면 설정에서 AIModule 모듈을 추가해야한다.
태스크 실행
- 비헤이비어 트리는 태스크를 실행할 때 태스크 클래스의 ExecuteTask라는 멤버 함수를 실행한다.
- ExecuteTask 함수는 넷 중 하나의 값을 반환해야한다.
- Aborted : 태스크 실행 중에 중단 됐다.
- Failed : 태스크를 수행했지만 실패했다.
- Succeeded : 태스크를 성공적으로 수행했다.
- InProgress : 태스크를 계속 수행하고 있다.
서비스 노드
#pragma once
#include "Test.h"
#include "BehaviorTree/BTService.h"
#include "BTService_Detect.generated.h"
UCLASS()
class TEST_API UBTService_Detect : public UBTService
{
GENERATED_BODY()
public:
UBTService_Detect();
protected:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
#include "BTService_Detect.h"
#include "CAIController.h"
#include "MyCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "DrawDebugHelpers.h"
UBTService_Detect::UBTService_Detect()
{
NodeName = TEXT("Detect");
Interval = 1.0f;
}
void UBTService_Detect::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (NULL == ControllingPawn) return;
UWorld* World = ControllingPawn->GetWorld();
FVector Center = ControllingPawn->GetActorLocation();
float DetectRadius = 600.0f;
if (NULL == World) return;
TArray<FOverlapResult> OverlapResults;
FCollisionQueryParams CollisionQueryParam(NAME_None, false, ControllingPawn);
bool bResult = World->OverlapMultiByChannel(
OverlapResults,
Center,
FQuat::Identity,
ECollisionChannel::ECC_GameTraceChannel2,
FCollisionShape::MakeSphere(DetectRadius),
CollisionQueryParam
);
DrawDebugSphere(World, Center, DetectRadius, 16, FColor::Red, false, 0.2f);
}
- 비헤이비어 트리의 서비스 노드는 자신이 속한 컴포짓 노드가 활성화될 경우 주기적으로 TickNode함수를 호출한다.
- 호출하는 주기는 서비스 노드 내부에 설정된 Interval 속성값으로 지정할 수 있다.
- OverlapMultiByChannel : 반경 내에 모든 캐릭터를 감지하는 함수
- IsPlayerController : 캐릭터를 조종하는 컨트롤러가 플레이어 컨트롤러인지 파악해주는 함수
데코레이터
#pragma once
#include "Test.h"
#include "BehaviorTree/BTDecorator.h"
#include "BTDecorator_IsInAttackRange.generated.h"
UCLASS()
class TEST_API UBTDecorator_IsInAttackRange : public UBTDecorator
{
GENERATED_BODY()
public:
UBTDecorator_IsInAttackRange();
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
};
#include "BTDecorator_IsInAttackRange.h"
#include "CAIController.h"
#include "MyCharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTDecorator_IsInAttackRange::UBTDecorator_IsInAttackRange()
{
NodeName = TEXT("CanAttack");
}
bool UBTDecorator_IsInAttackRange::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
bool bResult = Super::CalculateRawConditionValue(OwnerComp, NodeMemory);
auto ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if (NULL == ControllingPawn) return false;
auto Target = Cast<AMyCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(ACAIController::TargetKey));
if (NULL == Target) return false;
bResult = (Target->GetDistanceTo(ControllingPawn) <= 200.0f);
return bResult;
}
- 데코레이터 클래스는 CalculateRawConditionValue 함수를 상속받아 원하는 조건이 달성됐는지를 파악하도록 설계됐다.
#pragma once
#include "Test.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BTTask_Attack.generated.h"
UCLASS()
class TEST_API UBTTask_Attack : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTask_Attack();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
protected:
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
#include "BTTask_Attack.h"
UBTTask_Attack::UBTTask_Attack()
{
bNotifyTick = true;
}
EBTNodeResult::Type UBTTask_Attack::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result = Super::ExecuteTask(OwnerComp, NodeMemory);
return EBTNodeResult::InProgress;
}
void UBTTask_Attack::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
}
- 공격 태스크는 공격애니메이션이 끝날 때까지 대기해야 하는 지연 태스크이므로 ExecuteTask의 결과 값을 InProgresss로 일단 반환하고 공격이 끝났을 때 태스크가 끝났다고 알려줘야 한다. 이를 알려주는 함수가 FinishLatentTask다.
이 함수를 나중에 호출하지 않을 시에 비헤이비어 트리 시스템은 현재 태스크에 계속 머물게 된다.
'Game Programming > Unreal' 카테고리의 다른 글
Unreal - Zombie FPS Game Portfolio (0) | 2022.05.06 |
---|---|
[Unreal] 이득우의 언리얼 C++ 게임개발의 정석 9 (0) | 2022.03.17 |
[Unreal] 이득우의 언리얼 C++ 게임개발의 정석 7 (0) | 2022.03.15 |
[Unreal] 이득우의 언리얼 C++ 게임개발의 정석 6 (0) | 2022.03.11 |
[Unreal] 이득우의 언리얼 C++ 게임개발의 정석 5 (0) | 2022.03.11 |