NPC 헤더를 직접 참조하지 않고 인터페이스를 통해 간접적으로 필요한 값을 얻어오도록 구조 수정
AIInterface 클래스에 필요한 값을 가져오는 가상함수를 추가 후 NonPlayer클래스에서 그 함수를 구현, PatrolPos 클래스에서 인터페이스를 캐스팅 해 그 함수를 가져와 간접적으로 값을 얻는 구조
BTService 클래스를 상속받은 유저감지 Detect 노드 추가
서비스를 부착한 컴포짓 노드가 활성화 되면 TickNode가 지정한 Interval로 계속해서 호출된다.
내용물을 구현하려면 TickNode에 구현해서 Interval마다 구현한 작업을 수행하도록 구성
구현한 Detect 서비스를 Sequence 노드에 부착
BlackBoard에 Target 변수 생성, 타입 Pawn으로 지정
여기까지 한 후 실행을 눌러보면, NPC의 감지 반경과 감지 성공시 플레이어와의 거리가 뷰포트에 출력되는걸 확인 가능
플레이어를 쫓아가서 공격하도록 Selector 추가
언리얼 엔진이 기본으로 제공하는 Blackboard 데코레이터 부여, Key값을 Target으로 지정해 타겟이 설정되있을때만 셀렉터 컴포짓이 수행되도록 설정
이후 Attack, AttackInRange 함수 구현.
델리게이트로 NonPlayer에서 공격하는 로직이 호출되도록 구성
NPC도 플레이어처럼 PressCombo 함수를 호출해주고, 콤보가 끝나는 타이밍을 계산하기 위한 함수 CharacterBase에 추가
과정이 꽤 길고 다양한 클래스들을 수정해 코드를 넣진 못했지만, CharacterBase에 공격이 끝난것을 알려주는 가상 함수를 선언하고 ComboActionEnd 함수 마지막에 호출되도록 추가 -> 가상함수는 NonPlayer 클래스에서 받아서 구현한 후 Attack 클래스에서 델리게이트를 묶어줘서 흐름이 굴러가게끔 설계
빌드 후 실행해보면 캐릭터를 잘 쫓아와서 공격하지만, 캐릭터가 쓰러진 후에도 계속해서 공격 -> 셀렉터에 Detect 서비스 추가하면 캐릭터가 쓰러진 후 타겟이 없어지므로 해결
캐릭터가 NPC 공격 중 회피를 했지만 NPC가 몸을 돌리지 않고 처음 공격 방향으로만 공격하는 현상 발생
BTTask 노드 추가 - TurnToTarget 클래스 생성해 이를 해결
Simple Parallel 컴포짓으로 Selector를 대체하고, Turn to Target 노드 추가
프로젝트 폴더에 GameData 폴더를 만들고, 엑셀 파일을 UTF-8의 csv 파일로 저장
위의 CSV 파일을 사용하기 위해 인덱스 이름과 동일한 이름을 가진 구조체를 생성
-> 소스파일 폴더에서 구조체 헤더파일 작성 -> Generated project file 실행해 헤더파일을 프로젝트에 추가
엑셀 데이터의 임포트
DataAsset과 유사하게 FTableRowBase를 상속받은 구조체를 선언
엑셀의 Name 컬럼 (얘가 key값이 된다)을 제외한 컬럼과 동일하게 UPROPERTY 속성 선언
엑셀 데이터를 csv로 익스포트 한 후 언리얼 엔진에 임포트
기타->DataTable로 생성 후 Reimport를 클릭해 위에서 만든 Csv 파일을 가져온다
데이터를 관리할 싱글톤 클래스의 설정
언리얼 엔진에서 제공하는 싱글톤 클래스
게임 인스턴스
에셋 매니저
게임 플레이 관련 액터 (GameMode, GameState)
프로젝트에 싱글톤으로 등록한 언리얼 오브젝트
언리얼 오브젝트 생성자에서 사용하지 않도록 주의
위의 사진에 싱글톤 클래스를 설정하면 엔진이 초기화 될 때 자동으로 GEngine이라는 전역변수에 싱글톤을 만들어줌
// ABGameSingleton.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "ABCharacterStat.h"
#include "ABGameSingleton.generated.h"
DECLARE_LOG_CATEGORY_EXTERN(LogABGameSingleton, Error, All);
/**
*
*/
UCLASS()
class ARENABATTLE_API UABGameSingleton : public UObject
{
GENERATED_BODY()
public:
UABGameSingleton();
static UABGameSingleton& Get();
// Character Stat Data Section
public:
FORCEINLINE FABCharacterStat GetCharacterStat(int32 InLevel) const { return CharacterStatTable.IsValidIndex(InLevel) ? CharacterStatTable[InLevel] : FABCharacterStat(); }
UPROPERTY()
int32 CharacterMaxLevel;
private:
// Stat Data 내부 보관용 배열 - 추후 필요한 객체들에게 이 배열로 뿌림
TArray<FABCharacterStat> CharacterStatTable;
};
// ABGameSingleton.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "GameData/ABGameSingleton.h"
DEFINE_LOG_CATEGORY(LogABGameSingleton);
UABGameSingleton::UABGameSingleton()
{
// UDataTable -> (Key, Value)의 한쌍으로 이루어짐
static ConstructorHelpers::FObjectFinder<UDataTable> DataTableRef(TEXT("/Script/Engine.DataTable'/Game/ArenaBattle/GameData/ABCharacterStatTable.ABCharacterStatTable'"));
if (nullptr != DataTableRef.Object)
{
const UDataTable* DataTable = DataTableRef.Object;
check(DataTable->GetRowMap().Num() > 0);
TArray<uint8*> ValueArray;
DataTable->GetRowMap().GenerateValueArray(ValueArray);
// Algo의 Transform 알고리즘으로 value값만 가져오도록 설계
Algo::Transform(ValueArray, CharacterStatTable,
[](uint8* Value)
{
return *reinterpret_cast<FABCharacterStat*>(Value);
}
);
}
CharacterMaxLevel = CharacterStatTable.Num();
ensure(CharacterMaxLevel > 0);
}
UABGameSingleton& UABGameSingleton::Get()
{
// 엔진 초기화시 바로 활성화 되기 때문에 Get 함수로 받을 수 없다면 잘못된것이므로 CastCheck로 형변환 됬는지 강력히 검사
UABGameSingleton* Singleton = CastChecked<UABGameSingleton>(GEngine->GameSingleton);
if (Singleton) {
return *Singleton;
}
UE_LOG(LogABGameSingleton, Error, TEXT("Invalid Game Singleton"));
// 코드 흐름을 위해 작성한것일 뿐 실제 여기까지 가는 일이 일어나지 않도록 앞에서 철저히 검사
return *NewObject<UABGameSingleton>();
}
싱글톤 클래스의 로직. 싱글톤 클래스가 엔진 초기화시 제대로 생성되었는지 검사하며, 요청한 객체가 잘 쓸수 있도록 생성자에서 DataTable의 value들을 가져온다.
프로젝트의 주요 레이어
게임 레이어: 기믹과 NPC
미들웨어 레이어: 캐릭터의 스탯 컴포넌트, 아이템 박스
데이터 레이어: 스탯 데이터, 데이터 관리를 위한 싱글톤 클래스
위에서 아래로만 참조하도록
캐릭터 스탯 시스템
위의 구조대로 Stat Component가 Base Stat과 무기의 능력치에 따른 modifier Stat을 합산하여 반영하도록 ABCharacterStatComponent의 구조를 변경, 아래의 함수와 변수를 추가