[Unreal] 이득우의 언리얼 C++ 게임개발의 정석 8

2022. 3. 16. 23:56·Game Programming/Unreal
액터 컴포넌트의 제작
#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
'Game Programming/Unreal' 카테고리의 다른 글
  • Unreal - Zombie FPS Game Portfolio
  • [Unreal] 이득우의 언리얼 C++ 게임개발의 정석 9
  • [Unreal] 이득우의 언리얼 C++ 게임개발의 정석 7
  • [Unreal] 이득우의 언리얼 C++ 게임개발의 정석 6
chanheess
chanheess
'왜' 그렇게 했는가?에 대한 생각으로 공부 및 작업의 저장관리
  • chanheess
    왜 그렇게 생각했는가?
    chanheess
  • 전체
    오늘
    어제
    • 분류 전체보기
      • Backend Programming
      • Game Programming
        • Unreal
        • DirectX
      • C++
        • Memo
        • Basic
        • Effective Modern
      • Algorithm
        • Memo
        • Baekjoon
        • Programmers
        • HackerRank, LeetCode
      • Data Structure
      • Design Pattern
      • Etc
        • Memo
        • Daily Log
        • Book
  • 최근 글

  • 최근 댓글

  • 태그

    프로그래머스
    c++ 기초 플러스
    JPA
    백준
    spring
    알고리즘
    JWT
    Java
    위클리 챌린지
    티스토리챌린지
    dp
    오블완
    SpringSecurity
    dfs
  • hELLO· Designed By정상우.v4.10.0
chanheess
[Unreal] 이득우의 언리얼 C++ 게임개발의 정석 8
상단으로

티스토리툴바