역할(이영호): 공격형 RL 설계·개선(1.0→1.4) + 평가 자동화(CSV Logger) — 공격형 BT는 공동 작업
한눈에 보기
Unity ML-Agents 환경에서 1:1 전투 에이전트를 행동 트리(BT)와 강화학습(RL) 두 방식으로 각각 구현하고 같은 조건에서 성능을 비교한 인공지능 수업 팀 프로젝트입니다. 팀은 공격형과 수비형을 각각 BT·RL로 나눠 만들었고 저는 그중 공격형 RL을 맡아 관측·보상·행동 마스크를 설계하고 1.0에서 1.4까지 여섯 번의 학습 사이클로 다듬었습니다. 개선 근거를 남기려고 평가 자동화(CSV Logger) 도 직접 만들어 매 버전을 30 episode(최종 비교는 100 episode) 기준으로 집계했습니다.
결과: 공격형 RL 최종 1.4 모델은 방어형 baseline 상대 30판 중 28승, 공격 대비 명중(hit) 비율 55.4%, 방어 중 헛공격 비율 1.7% 를 기록했습니다. 보상만으로 풀리지 않던 문제(방어 중 공격 91.9%)를 행동 마스크(Action Mask) 로 끊어낸 것이 핵심 전환점이었습니다.
역할 분담
| 구분 | 담당 | 비고 |
|---|---|---|
| 공격형 RL | 이영호 (핵심) | 관측 18종·보상 설계·Action Mask, 1.0→1.4 개선 사이클 |
| 평가 자동화 | 이영호 (핵심) | CombatEvaluationCsvLogger 등 — 승패·시간·데미지·행동 분포 CSV 집계 |
| 공격형 BT | 공동 | 9순위 대응형 공격 트리 (RL의 비교 기준이자 baseline) |
| 수비형 BT · RL | 팀원 | Defensive Counter 전략 |
| 결과 비교·시각화 | 팀원 | 9개 모델(공격 3 × 수비 3) 대전 그래프 |
이 문서는 제가 맡은 공격형 RL과 평가를 중심으로 정리하되, 이해를 돕기 위해 공격형 BT와 팀 전체 비교 결과도 함께 담았습니다.
프로젝트 개요
- 환경: Unity 1:1 Arena. Episode 최대 60초, 상대 체력이 0이 되면 승리, 시간 초과 시 무승부.
- 공통 전투 시스템: 이동·공격·방어·회피·판정을
CombatActionController등 공통 API로 처리해, BT와 RL이 완전히 같은 전투 규칙 위에서 비교되도록 했습니다. 체력·판정을 직접 건드리지 않고 행동 API만 호출합니다. - 비교 축: 같은 “공격형” 전략을 사람이 규칙으로 짠 BT와, 보상으로 학습시킨 RL로 각각 만들어 명시적 규칙 vs 학습된 타이밍을 대조했습니다.
공격형 BT (공동 작업)
RL의 비교 대상이자 초기 baseline이 된 상대 상태에 반응하는 대응형 공격 트리입니다. 보스몬스터 AI를 가정해 단순히 공격만 반복하지 않고 상대가 공격하면 막고 상대가 방어·회피하기 어려운 빈틈에 공격하도록 설계했습니다.
Root Selector → (전투 Sequence | Victory) 구조이며, 전투 Sequence는 TargetAlive → UpdateTargetInfo → 전투 행동 Selector 순으로 흐릅니다. 전투 행동 Selector는 아래 9개 행동을 우선순위대로 평가합니다.
| 순위 | 행동 | 언제 |
|---|---|---|
| 1 | 상대 공격 방어 | 상대가 공격 중이고 방어가 가능할 때 |
| 2 | 빈틈 공격 | 상대 스킬이 모두 쿨타임이라 반격 못 할 때 |
| 3 | 콤보 공격 | 사거리·정면·빈틈이 맞고 콤보 쿨타임(15초)이 찼을 때 |
| 4 | 조준 | 사거리 안이지만 정면이 아닐 때 |
| 5 | 압박 유지 | 상대가 보이면 접근하거나 거리를 좁혀 유지 |
| 6 | 기본 공격 | 사거리·정면이 맞고 상대가 방어 중이 아닐 때 |
| 7 | 돌격 | 상대가 시야 끝·사거리 밖이면 빠르게 접근 |
| 8 | 추적 | 상대가 보이지만 멀면 지속 이동 |
| 9 | 수색 | 상대를 놓치면 마지막 목격 위치 또는 무작위 수색 |
수업의 BT 필수 요건은 다음 노드로 충족했습니다.
- Decorator — 콤보 후 15초 쿨타임(
CooldownNode), 사거리 밖 동안 돌격을 반복하는UntilFailNode, 수색 행동을 일정 시간 유지하는TimedActionNode. - Parallel — 콤보/빈틈/기본 공격의 사전 조건(공격 가능·사거리·정면·상대 빈틈·상대 비방어)을 함께 검사.
- Non-deterministic — 상대를 놓쳤을 때
RandomSelectorNode로 좌회전·우회전·전진 중 하나를 무작위로 골라 수색.
BT 결과: 수비형 baseline BT와 100회 대전에서 100승 0패, 평균 28.36초, 평균 100 피해 · 자기 체력 100 유지, 방어 중 헛공격 0회. 규칙이 잘 맞는 상대에겐 빠르고 안정적이지만, 뒤에서 보듯 학습형 수비 상대에는 같은 실수를 반복하는 한계도 함께 드러납니다.
공격형 RL — 설계 (담당)
공격형 RL의 목표는 “오래 버티는 모델”이 아니라 “실제로 때리고 이기는 모델” 입니다. 그래서 승률뿐 아니라 hit 수 · 공격 대비 hit 비율 · 방어 중 공격 비율 · 평균 episode 시간을 함께 평가 지표로 삼았습니다.
Observation (18종)
StudentCombatAgentAtk.cs의 CollectObservations()에서 18개 값을 정규화(01 또는 -11)해 전달합니다. Unity Behavior Parameters의 Vector Observation Size와 정확히 일치시켰습니다.
| 범주 | 관측값 | 설계 의도 |
|---|---|---|
| 체력 | 자기·상대 체력 비율 | 우세/열세 판단 |
| 위치·거리 | 상대 X/Z offset, 거리 | 접근·후퇴 판단 |
| 방향 | 자기 forward X/Z, 상대를 바라보는 정도(dot) | 조준 상태 판단 |
| 자기 쿨타임 | 공격·방어·회피 쿨타임 | 지금 가능한 행동 판단 |
| 상대 쿨타임 | 공격·방어·회피 쿨타임 | 상대 빈틈 판단 (방어·회피는 1.4 추가) |
| 상대 상태 | 공격 중·방어 중·무적 | 공격/방어 타이밍 판단 (무적은 1.4 추가) |
| 사거리 | 상대가 공격 사거리 안인지 | 공격 가능 여부 |
Action (discrete 2 branch)
| Branch | 크기 | 값 |
|---|---|---|
| 이동 | 5 | 없음 / 전진 / 후진 / 좌 / 우 |
| 스킬 | 4 | 없음 / 공격 / 방어 / 회피 |
이동은 월드 좌표가 아니라 상대 기준 방향으로 처리해, 모델이 맵 위치가 아니라 전투 관계(접근·후퇴·좌우)를 학습하도록 했습니다.
Reward (최종 1.4)
공격 “횟수”가 아니라 실제 명중과 승리를 우선하도록 설계했습니다.
| 분류 | 항목 | 값 | 의도 |
|---|---|---|---|
| 데미지 | damageRewardScale | +0.08 × 데미지 | 실제 명중 우선 |
| 승리 | winReward | +3 | episode 목표 명확화 |
| 유효 공격 | validAttackReward | +0.02 | 공격 탐색 유도 |
| 빈틈 공격 | defensiveCooldownAttackReward | +0.04 | 상대 방어 쿨타임 중 공격 유도 |
| 기다림 | defensePatienceReward | +0.002 | 방어 중 상대에 무리한 공격 대신 대기 |
| 기회 상실 | missedAttackOpportunityPenalty | -0.025 | 열린 기회에서 무행동 방지 |
| 방어 중 공격 | blockedAttackPenalty | -0.12 | 방어 중 헛공격 억제 |
| 잘못된 공격 | invalidAttackPenalty | -0.02 | 사거리 밖 공격 억제 |
| 피격 | damagePenaltyScale | -0.006 × 데미지 | 맞는 행동 억제 |
| 무행동 | idleActionPenalty | -0.006 | 정지 정책 방지 |
| 시간 초과 | timeoutPenalty | -1 | 무승부 회피 |
Action Mask (1.4 핵심)
1.3까지는 “방어 중인 상대를 공격하는” 행동을 보상 패널티만으로 줄이려 했지만, 오히려 방어 중 공격 비율이 91.9%까지 올랐습니다. 그래서 1.4에서는 WriteDiscreteActionMask()로 애초에 잘못된 공격을 선택지에서 제거했습니다.
| 상황 | 처리 |
|---|---|
| 자기 행동 중(busy) | 공격·방어·회피 비활성화 |
| 공격 쿨타임 남음 | 공격 비활성화 |
| 상대 방어 중 / 무적 | 공격 비활성화 |
| 방어·회피 쿨타임 남음 | 방어·회피 각각 비활성화 |
공격형 RL — 개선 사이클 (1.0 → 1.4)
핵심 교훈은 “보상만으로는 전투 타이밍을 다 가르치기 어렵다” 였습니다. 각 버전을 CSV로 평가해 병목을 찾고 다음 버전의 방향을 정했습니다.
| 버전 | 핵심 문제 | 개선 | 결과 (30 episode) |
|---|---|---|---|
| 1.0 | 회피·방어에 치우침, 공격 성과 없음 | 생존형 보상 구조 문제 확인 | 30판 시간초과, 데미지 0, 회피 360 |
| 1.1 | 무행동 정책으로 수렴 | 공격 탐색 강화 필요 확인 | 공격 1회, 데미지 0 |
| 1.2 | 공격은 늘었지만 다 막힘 | 무행동 패널티·공격 보상 강화 | 공격 574, hit 0, 방어 중 공격 82.8% |
| 1.3 | 첫 데미지, 그러나 타이밍 실패 | 데미지 보상·방어 중 공격 패널티 강화 | 데미지 280, hit 14, 방어 중 공격 91.9% |
| 1.4 | 보상만으로는 억제 한계 | Action Mask + 상대 쿨타임·무적 관측 추가 | 28승/30, hit 160, 방어 중 공격 1.7% |
1.3 → 1.4 변화가 이 프로젝트의 하이라이트입니다. 관측을 15→18로 늘리고 잘못된 공격을 마스킹하자, 공격 횟수는 570→289회로 줄었는데도 hit는 14→160회로 늘었습니다. “많이 때리기”에서 “맞을 때 때리기” 로 정책이 바뀌었습니다.
남은 한계: 공격 중심으로 기울며 받은 데미지가 80→320으로 늘었습니다. 승률을 지키면서 피격을 줄이는 방어 균형이 다음 과제로 남았습니다.
PPO 학습 설정
| 항목 | 값 | 항목 | 값 |
|---|---|---|---|
| trainer | PPO | hidden units / layers | 128 / 2 |
| behavior | CombatAgent | batch / buffer | 64 / 2048 |
| learning rate | 0.0003 | gamma / lambd | 0.99 / 0.95 |
| beta / epsilon | 0.005 / 0.2 | time horizon | 64 |
| max steps | 1,000,000 | time scale | 20 |
| run id | combat_rl_014 |
mlagents-learn Assets/Config/combat_ppo.yaml --run-id=combat_rl_014 --force --timeout-wait 600
평가 자동화 — CSV Logger (담당)
개선 사이클을 근거 있게 돌리려면 매번 똑같은 조건에서 자동으로 지표를 뽑는 도구가 필요했습니다. 그래서 CombatEvaluationCsvLogger를 직접 만들었습니다.
- 지정한 평가 횟수(예: 30회, 최종 비교용 100회)에 도달할 때까지 episode 결과를 자동 기록한 뒤 종료합니다.
- 승패·무승부, episode 시간, 데미지, 공격·방어·회피 횟수, hit 수, 방어 중 공격 횟수를 CSV로 저장합니다.
- 평가를 빠르게 돌리기 위한
EvaluationTimeScaleAtk(배속), 관전용CombatCameraFollowAtk(카메라 추적)도 함께 만들었습니다.
이 CSV 로그가 있었기에 “방어 중 공격 비율 91.9%” 같은 정확한 병목 수치를 짚어 다음 버전 방향을 정할 수 있었습니다. 보고서와 발표의 모든 BT/RL 수치도 이 로그에서 집계했습니다.
평가 신뢰도 메모: 일부 로그에서 데미지가 체력 100을 넘는 행이 관측됐는데, Logger의 자체 timeout이 EpisodeManager reset보다 먼저 동작한 동기화 문제로 파악했습니다. 그래서 승률·hit 수·방어 중 공격 비율을 중심 지표로 쓰고 이 동기화 보정을 다음 사이클 과제로 기록해 두었습니다.
결과 — BT vs RL 비교
같은 “공격형” 전략인데도 두 방식의 행동은 뚜렷이 달랐습니다.
- 공격형 BT = 규칙 기반 돌진형. 사거리·정면 조건만 맞으면 바로 공격합니다. 예측 가능한 약한 상대는 빠르게 제압하지만, 학습형 수비(TankBT)를 만나면 공격 621회에 hit 0회(방어 중 공격 100%) 로, 상대가 방어 중인지 못 피하고 계속 막힙니다.
- 공격형 RL = 관측 기반 타이밍형. 상대의 방어·쿨타임·무적까지 보고 공격을 고릅니다. 같은 TankBT 상대로 498회 공격 중 413 hit(82.9%) — BT가 0 hit였던 상대를 유효타로 뚫었습니다.
- 다만 RL도 만능은 아님. 강한 수비형 RL을 만나면 공격 2173회에도 hit는 5회에 그치고 장기전·무승부가 늘었습니다. RL은 무작정 공격 대신 기회를 찾지만, 상대 방어가 아주 강하면 결정력이 부족해질 수 있습니다.
팀 종합 결론: BT는 사람이 설계한 조건이 맞는 상황에서 빠르고 명확하지만 상대 패턴이 바뀌면 약점을 반복합니다. RL은 보상·관측으로 상황별 타이밍을 학습해 더 유연하지만, 보상 구조에 따라 결정력이 부족하거나 장기전으로 흐를 수 있습니다. 명확한 규칙 전략엔 BT, 복잡한 전투 타이밍·상대 변화 대응엔 RL 이 더 적합하다고 정리했습니다.
기술 스택
Unity · ML-Agents (PPO) · C# · Python · Behavior Tree · Reinforcement Learning · CSV 평가 자동화
배운 점
- 보상 설계 ≠ 행동 통제. reward만으로 억지로 밀어붙이기보다, 애초에 잘못된 선택지를 Action Mask로 제거하는 편이 훨씬 깨끗하게 먹혔습니다.
- 관측이 판단의 상한을 정한다. “상대가 방어 중”만 알던 모델에 “상대 방어·회피 쿨타임·무적”을 더해 주자 비로소 빈틈 공격을 배웠습니다.
- 측정 없이 개선 없음. 직접 만든 CSV 평가기가 6번의 사이클을 근거 있게 이끌었습니다.