피보나치 수열의 정의는 그 자체로 재귀적입니다.
(단, )
처음에는 이 정의를 그대로 코드로 옮겨 깔끔하게 구현하려 했습니다. 하지만 이 방식은 n이 커질수록 치명적인 결함을 보입니다.
재귀 방식의 가장 큰 문제는 중복 계산입니다.
위 그림처럼 를 구하기 위해 을 호출하고, 그 안에서 다시 여러 번 가 반복적으로 호출됩니다.
중복 계산을 피하기 위해 "이미 계산한 결과는 저장해두고 재사용하자"는 전략을 사용합니다.
int solution(int n) {
const int MOD = 1234567;
// 계산 결과를 저장할 테이블(배열) 생성
vector<int> dp(n + 1);
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
// 이미 계산된 dp[i-1]과 dp[i-2]를 사용함 (중복 계산 X)
dp[i] = (dp[i-1] + dp[i-2]) % MOD;
}
return dp[n];
}
이유: 각 항은 단 한 번만 계산되며, 배열에서 바로 값을 꺼내 쓰기 때문에 연산 횟수가 번에 비례합니다.
시간 복잡도:
"시간을 줄이기 위해 공간을 쓴다."
피보나치처럼 직전 두 개의 값만 필요한 경우, 굳이 크기 의 배열을 만들지 않아도 됩니다.
int solution(int n) {
const int MOD = 1234567;
if (n == 0) return 0;
int a = 0; // F(n-2) 역할
int b = 1; // F(n-1) 역할
for (int i = 2; i <= n; i++) {
int c = (a + b) % MOD; // 현재 값 계산
a = b; // 값 밀어내기
b = c;
}
return b;
}
시간 복잡도: 공간 복잡도: (변수 몇 개만 사용)
✅ 재귀가 효율적인 경우
코드 작성 전 다음 3가지를 체크하면 풀이 방향이 보임.
단순히 플레이어를 추적하는 AI가 아니라, 목적 지향적인 AI를 구축하는 것을 목표로 했습니다.
확장성을 위해 "강함"을 나타내는 Rank와 "행동 양식"을 나타내는 Type을 완전히 분리했습니다.
| 구분 | 항목 | 내용 |
|---|---|---|
| Rank (강함) | Normal, Elite, Boss | HP, 이동속도, 데미지 배율, 특수 패턴 여부 |
| Type (종류) | Zombie, Bomber, Sniper | 공격력, 사거리, 이동 보정, BT Override 가능 |
Data Management:
RankPresetTable과TypePresetTable이라는 별도의 DataTable로 관리하여 데이터 에셋만 수정하면 새로운 몬스터를 쉽게 생성할 수 있습니다.
몬스터 클래스에서 상황에 맞는 Behavior Tree를 유연하게 선택할 수 있도록 구현했습니다.
// Monster.h
/** 몬스터가 실행할 최종 Behavior Tree를 결정하여 반환 */
UBehaviorTree* GetBehaviorTreeToRun() const;
// Monster.cpp
UBehaviorTree* AMonster::GetBehaviorTreeToRun() const
{
// 1. TypePreset에서 특정 종류별로 덮어쓴 BT가 있다면 그것을 우선 반환 (ResolvedBehaviorTree)
// 2. 특별한 설정이 없다면 BP 자식 클래스에서 설정한 기본 BT 반환 (DefaultBehaviorTree)
return ResolvedBehaviorTree ? ResolvedBehaviorTree : DefaultBehaviorTree;
}
AIController는 무거운 로직을 담지 않고 오직 실행자(Executor)의 역할만 수행.
AIController의 책임:
C++// AIController 실행부 예시
void AMonsterAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
if (AMonster* Monster = Cast<AMonster>(InPawn))
{
UBehaviorTree* BT = Monster->GetBehaviorTreeToRun();
if (BT)
{
RunBehaviorTree(BT);
}
}
}
Blackboard는 AI가 현재 상태를 파악하고 판단하는 '기준표' 역할을 합니다.
| 키 이름 | 용도 |
|---|---|
| WarehouseActor | 최종 이동 목표 지점 |
| CurrentTarget | 경로를 막고 있어 제거해야 할 공격 대상 |
| bIsDead | 사망 여부 (BT 즉시 중단 및 전이용) |
| SpecialLogic | 엘리트/보스 몬스터의 특수 패턴 분기용 데이터 |
Behavior Tree의 각 노드는 명확한 역할 분담을 가집니다.
Selector 노드를 활용하여 우선순위에 따른 흐름을 구성했습니다.
Plaintext[Root]
└─ Selector
├─ Death (1순위)
│ └─ (Decorator: bIsDead == true) → Task: Die
│
├─ SpecialAttack (2순위)
│ └─ (Decorator: SpecialLogic != 0) → Task: SpecialAttack
│
├─ Attack (3순위)
│ └─ (Decorator: CurrentTarget IsSet) → Task: Attack
│
└─ Move (기본 행동)
├─ Service: UpdateMonsterState (상태 감시 및 BB 동기화)
├─ Task: CheckPathObstacles (경로 장애물 체크)
└─ Task: MoveTo(WarehouseActor)