15. - Monster AI, Attack, Etc

Overcle·2023년 2월 20일
0

학원

목록 보기
7/29

요약
1. 레벨이 전환 될때마다 월드세팅은 똑같은것이더라도 재생성 작업이 진행된다. 이 때문에 레벨이 전환되면 몬스터 정보도 다시 읽어와야하는 상황이 발생한다. 인게임에 영향을 줄 정도는 아니지만 이런 알고리즘이 발생한다는건 알고있자.
2. 1번의 방식을 보안하기 위해 레벨이 전환 되더라도 데이터를 유지할 수 있도록 하는 방법이 존재한다. GameInstance 쪽을 수정하면 된다.
3. DataTable은 에셋 내용을 읽어와서 TMap으로 구성되는 자료구조에 저장된다.
3-1. TMap은 RDB처럼 구성된다, 원하는 데이터를 찾을경우 해당 Datatable을 만들때 기준으로 만들었던 구조체의 포인터로 검색 할 수 있다.

오전 : 몬스터 정보, GameInstance 설정.
오후 :

용어 설명
1. 블랙보드 : 비헤이비어트리를 이용한 데이터를 저장하는 공간.
2. 비헤이비어트리 : 트리구조로 AI에게 어느 상황일 때 어떻게 하라는 구조.
3. 테스크 : 인공지능이 실제 동작하는 노드.
4. 데코레이터 : 노드에 추가 할 수 있으며 노드의 성공 여부를 결정할 수 있다.
5. 서비스 : 노드에 실시간으로 동작하는 기능을 추가로 만들어 줄 떄 사용한다. (플레이어 감지 같은?)
6. Composite : 비헤이비어 트리에서 인공지능의 분기를 만들어 줄 때 사용.
6-1. Selector :
자식 Sequence중 왼쪽부터 실행하며 실패하면 오른쪽 시퀀스를 실행 하지만 성공하면 그 다음 시퀀스는 실행을 하지 않는다.
6-2. Sequence :
Selector와 비슷하지만 Sequence는 성공해야 다음 노드를 실행한다.

Tip
1.

따로 공부해서 정리해둘것
1.

Monster State.


파일로부터 읽어오는 방법으로 진행.
파일 입출력 방법은 크게 2가지가있다.

  • 엑셀로 읽어오는 방법.
  • Txt, 일반 파일 등으로 읽어오는 방법.

데이터 테이블 이라는 게 있으며, 엑셀파일로 읽어올수 있다.

  1. 데이터 테이블.
    데이터 테이블을 만들기 위해서는 무조건 구조체 1개는 있어야 함.
    c++, 블루 프린터 둘다 가능하며. c++로 하는게 편하다?
    블루프린트 방식에서는 에셋을 저장할떄 에셋이 아닌 경로를 저장한다.
[GameInfo.h]

USTRUCT(BlueprintType)
struct FMonsterTableInfo : public FTableRowBase
{
	GENERATED_USTRUCT_BODY()

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	FName Name;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	int32 AttackPoint;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	int32 ArmorPoint;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	int32 HP;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	int32 MP;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	int32 Level;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	int32 Exp;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	int32 Gold;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	float MoveSpeed;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	float AttackDistance;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AlloPrivateAccess = true))
	float TraceDistance;

};

ALL > 콘텐츠 > Monster 에서 우클릭 > 기타 > 데이터 테이블 > 앞서 만든 MonstTableInfo를 지정하여 MonsterTable 이란 이름으로 생성한다.

추가 버튼을 클릭하여 정보 1개를 추가하여. 저번 수업에 설정한 몬스터 정보를 할당한다. 이후 저번 수업에 설정한 정보는 삭제한다.

MinionWarrior헤더파일에 BeginPlay를 선언 및 정의 하여, 부모 클래스까지 불러온다.

void AMinionWarrior::BeginPlay()
{

	Super::BeginPlay();
}

레벨이 전환 될때마다 월드세팅은 똑같은것이더라도 재생성 작업이 진행된다.
이를 보안하기 위해 GameInstance를 수정하면 해결이 된다
프로젝트 세팅 > 맵 & 모드 > 맨 하단에 GameInstance를 변경할 수 있다.
생성은 c++생성 > 모든 클래스 > GameInstance 검색
생성된 Instance에 해더파일을 수정한다
그 후 프로젝트 세팅에 만든 게임인스턴스로 설정한다.
헤더파일에 virtual void Init, 생성자와 소멸자까지 선언 및 구현한다.

class UE11_API UUE11GameInstance : public UGameInstance
{
	GENERATED_BODY()

public:
	UUE11GameInstance();
	~UUE11GameInstance();

private:
	UDataTable* m_MonsterTable;

public: 
	virtual void Init();
    
public:
	const FMonsterTableInfo* FindMonsterTable(const FName& Name);
	
};

앞서 만든 MonsterTable을 레퍼런스 복사 해서 지정한다


UUE11GameInstance::UUE11GameInstance()
{
	static ConstructorHelpers::FObjectFinder<UDataTable> MonsterTable(TEXT("/Script/Engine.DataTable'/Game/Monster/MonsterTable.MonsterTable'"));

	if (MonsterTable.Succeeded())
		m_MonsterTable = MonsterTable.Object;
}

Monster.h
파일에 UPRORERTY. FName, mMonterTableRowName변수를 선언 후
MinionWarrior 생성자에 MinionWarrior룰 설정하도록 구현한다

[MinionWarrior.cpp]

	mMonsterTableRowName = TEXT("MinionWarrior");

Monster.cpp 파일에 GameInstance를 Include하고
BeginPlay부분을 수정한다.

void AMonster::BeginPlay()
{
	Super::BeginPlay();

	UUE11GameInstance* GameInst =	GetWorld()->GetGameInstance<UUE11GameInstance>();

	const FMonsterTableInfo* Info = GameInst->FindMonsterTable(mMonsterTableRowName);

	if (Info)
	{
		mInfo.Name = Info->Name;
		mInfo.Level = Info->Level;
		mInfo.Exp = Info->Exp;
		mInfo.Gold = Info->Gold;
		mInfo.AttackDistance = Info->AttackDistance;
		mInfo.AttackPoint = Info->AttackPoint;
		mInfo.HP = Info->HP;
		mInfo.HPMax = Info->HP;
		mInfo.MP = Info->MP;
		mInfo.MPMax = Info->MP;
		mInfo.TraceDistance = Info->TraceDistance;
		mInfo.MoveSpeed = Info->MoveSpeed;
	}
	
	mAnimInst = Cast<UMonsterAnimInstance>(GetMesh()->GetAnimInstance());
}

GameInfo에 FCharacterInfo부분에 FName 타입으로 Name 변수를 추가한다.

인공지능


수업에서는 언리얼에 제공하는 비헤이비어트리 를 사용한다
무조건 AI Constroller가 몬스터에 할당 되어야 한다.

C++클래스 > 몬스터 > 우클릭 > c++ 생성 > 모든 클래스 > AI Controller 검색 > 생성.

생성 후 헤더파일 수정.

기존 AI Controller를 지금 생성한 Controller로 설정한다.
설정은 Monster.cpp 에서 진행 하며. 방금 만든 컨트롤러를 Include를 하고
생성자에서 설정한다

[Monster.cpp]

	AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
	AIControllerClass = AMonsterAIController::StaticClass(); << 추가!

콘텐츠 > Monster > 새폴더 "AI" 생성 > 블랙보드 "BBMonster", 비헤이비어트리 "BTMonster" 2개 생성.

수업에서는 C++코드로 진행한다.
UE11.Build.cs 에서 AIModule을 추가한다

[UE11.Build.cs]

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore","AIModule","GameplayTasks" });
 //AIModule, GameplayTasks 추가 !

Gmaeinfo.h 파일에 ai라이브러리 헤더와 블랙보드 헤더를 추가한다

#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"
[Monster.h]
public:
	virtual void PossessedBy(AController* NewController);
	virtual void UnPossessed();
    
[MonsterAIController.h]

class UE11_API AMonsterAIController : public AAIController
{
	GENERATED_BODY()
	
private:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = true))
	UBehaviorTree* mAITree;
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = true))
	UBlackboardData* mAIBlackboard;


public:
	AMonsterAIController();


protected:
	virtual void OnPossess(APawn* aPawn) override;
	virtual void OnUnPossess() override;

public:
	void SetBehaviorTree(const FString& Path);
	void SetBlackboard(const FString& Path);
};
[MinionWarrior.h]


public:
	virtual void PossessedBy(AController* NewController);
	virtual void UnPossessed();

MinionWarrior.cpp엔 부모함수 선언만 구현

[Monster.cpp]

void AMonster::PossessedBy(AController* NewController)
{
	Super::PossessedBy(NewController);

	AMonsterAIController* AI = Cast<AMonsterAIController>(NewController);

	if (IsValid(AI))
	{
		AI->SetBehaviorTree(TEXT("/Script/AIModule.BehaviorTree'/Game/Monster/AI/BTMonster.BTMonster'"));
		AI->SetBlackboard(TEXT("/Script/AIModule.BlackboardData'/Game/Monster/AI/BBMonster.BBMonster'"));
	}
}

void AMonster::UnPossessed()
{
	Super::UnPossessed();
}
[MonsterAIController.cpp]

void AMonsterAIController::OnPossess(APawn* aPawn)
{
	Super::OnPossess(aPawn);

	if (IsValid(mAITree) && IsValid(mAIBlackboard))
	{
		UBlackboardComponent* BlackboardRef = Blackboard;
		if (UseBlackboard(mAIBlackboard, BlackboardRef))
		{
			RunBehaviorTree(mAITree);
		}
	}
}

void AMonsterAIController::OnUnPossess()
{
	Super::OnUnPossess();
}

void AMonsterAIController::SetBehaviorTree(const FString& Path)
{
	mAITree = LoadObject<UBehaviorTree>(nullptr, *Path);
}

void AMonsterAIController::SetBlackboard(const FString& Path)
{
	

	mAIBlackboard = LoadObject<UBlackboardData>(nullptr, *Path);
}

c++ > MOnster > AI 폴더 생성 > 우클릭 > 모든클래스 > BTService > "BTService_TargetDetect" 생성

[BTService_TargetDetect.h]

class UE11_API UBTService_TargetDetect : public UBTService
{
	GENERATED_BODY()

public:
	UBTService_TargetDetect();

protected:
	virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;

};
[BTService_TargetDetect.cpp]

UBTService_TargetDetect::UBTService_TargetDetect()
{
	NodeName = TEXT("TargetDetect");
	Interval = 0.5f;
	RandomDeviation = 0.1f;
}

void UBTService_TargetDetect::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);


	// UBehaviorTreeComponent를 가지고있는 AIController를 가져올 수 있다.
	AMonsterAIController* Controller = 
		Cast<AMonsterAIController>(OwnerComp.GetAIOwner());

	if (!IsValid(Controller))
		return;

	AMonster* Monster = Cast<AMonster>(Controller->GetPawn());

	if (!IsValid(Monster))
		return;
	FCollisionQueryParams	param(NAME_None, false, Monster);
	const FMonsterInfo& Info = Monster->GetMonsterInfo();

	TArray<FOverlapResult>	ResultArray;

	bool Collision = GetWorld()->OverlapMultiByChannel(ResultArray,Monster->GetActorLocation(),FQuat::Identity, ECollisionChannel::ECC_GameTraceChannel6,
		FCollisionShape::MakeSphere(Info.TraceDistance), param);

#if ENABLE_DRAW_DEBUG
	FColor DrawColor = Collision ? FColor::Red : FColor::Green;

	DrawDebugSphere(GetWorld(), Monster->GetActorLocation(),Info.TraceDistance,20,DrawColor, false, 0.3f);

#endif	
}


	if (CollisionEnable)
		Controller->GetBlackboardComponent()->SetValueAsObject(TEXT("Target"), ResultArray[0].GetActor());
	else
		Controller->GetBlackboardComponent()->SetValueAsObject(TEXT("Target"), nullptr);


TraceChannel 추가.
이름 : MonsterDetect
기본 반응 : ignore

Player프로파일 설정.

DefaultEngine.ini 파일에서 새로 MonsterDetect 채널 이름 확인.

[Monster.h]

public:
	const FMonsterInfo& GetMonsterInfo()	const
	{
		return mInfo;
	}

상속받아 c++로 만들기
c++ > monster > ai > 우클릭 > 모든클래스 > "BTDecorator" >
이름 : BTDecorator_CheckDistance 으로 생성

생성 후 헤더파일 수정.

생성자 선언 및 구현.

public:
	UBTDecorator_CheckDistance();

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (AllowPrivateAccess = true))
	ECheckDistanceType	mCheckType;
    
protected:
	virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const;
#include "BTDecorator_CheckDistance.h"
#include "../MonsterAIController.h"
#include "../Monster.h"

UBTDecorator_CheckDistance::UBTDecorator_CheckDistance()
{
}

bool UBTDecorator_CheckDistance::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
	Super::CalculateRawConditionValue(OwnerComp, NodeMemory);


	// UBehaviorTreeComponent를 가지고있는 AIController를 가져올 수 있다.
	AMonsterAIController* Controller =
		Cast<AMonsterAIController>(OwnerComp.GetAIOwner());

	if (!IsValid(Controller))
		return false;

	//blackboard에 저장된 Target정보를 얻어와야 한다.
	AActor* Target = Cast<AActor>(Controller->GetBlackboardComponent()->GetValueAsObject(TEXT("Target")));

	AMonster* Monster = Cast<AMonster>(Controller->GetPawn());

	if (!IsValid(Monster))
		return false;
	const FMonsterInfo& Info = Monster->GetMonsterInfo();

	return true;
}
profile
게임 프로그래머 지망생의 발자취

0개의 댓글