요약
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.
파일로부터 읽어오는 방법으로 진행.
파일 입출력 방법은 크게 2가지가있다.
데이터 테이블 이라는 게 있으며, 엑셀파일로 읽어올수 있다.
[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;
}