Unity ML-Agents로 지능형 보스 패턴을 학습시키는 강화학습 파이프라인

Unity ML-Agents로 지능형 보스 패턴을 학습시키는 강화학습 파이프라인

Unity ML-Agents 강화학습으로 보스의 관측, 행동, 보상, 난이도 커리큘럼을 설계하고 PPO 학습 모델을 ONNX로 배포하는 실전 파이프라인을 정리합니다.

TL;DR

ML-Agents 보스는 플레이어를 이기는 정책이 아니라 읽을 수 있으면서도 상황에 맞게 압박하는 전투 정책을 학습해야 한다. 이를 위해 보스와 플레이어의 상대 상태를 관측으로 제한하고 공격 선택을 이산 행동으로 정의하며 피해량·거리·반복 패턴에 보상을 분리해 설계한다.

실무 파이프라인은 훈련 전용 Arena → PPO 학습 → TensorBoard 검증 → ONNX 추론 배포 → 규칙 기반 안전장치 순서가 가장 단순하다. ML-Agents는 Unity 장면을 학습 환경으로 사용하고 학습된 정책을 .onnx 모델로 내보내 런타임에서 추론할 수 있다. 공식 개요모델 임베딩 가이드를 함께 확인하면 좋다.

지능형 보스 패턴 학습 파이프라인이 필요한 이유

전통적인 보스 AI는 상태 머신과 행동 트리로도 충분히 만들 수 있다. 다만 플레이어와의 거리, 체력, 직전 회피 방향, 스킬 쿨다운처럼 조합이 많은 상황에서 매번 규칙을 추가하면 패턴이 빠르게 비대해진다.

강화학습은 그 규칙을 완전히 대체하는 수단이 아니다. 전투의 큰 흐름과 금지 규칙은 디자이너가 고정하고 어떤 공격을 어느 타이밍에 선택할지처럼 연속적인 판단만 정책 모델에 맡기는 방식이 안정적이다.

책임권장 구현이유
페이즈 전환, 무적, 연출C# 상태 머신결정적이어야 하고 디버깅이 쉽다.
공격 선택ML-Agents Agent 정책거리와 플레이어 반응의 조합에 대응한다.
히트 판정, 피해 적용기존 전투 시스템학습 결과가 판정 규칙을 우회하지 못하게 한다.
불가능한 행동 차단Action Mask 또는 C# 가드쿨다운·자원·연출 잠금을 보장한다.

훈련 전용 아레나와 ONNX 런타임 추론으로 나뉜 Unity ML-Agents 보스 학습 파이프라인

ML-Agents 보스의 관측과 행동은 어떻게 설계할까?

Step 1. 관측은 ‘의사결정에 필요한 상대 정보’만 넣는다

관측값은 매 Decision Step마다 보스가 볼 수 있는 정보다. 월드 좌표를 그대로 넣기보다 보스 로컬 기준의 상대 위치와 정규화된 값을 사용한다. 맵 위치에 과적합하는 것을 줄이고 입력 범위를 예측하기 쉬워진다.

관측예시 범위목적
플레이어 상대 위치 x, z[-1, 1]접근·이탈·측면 공격 판단
플레이어 상대 속도 x, z[-1, 1]회피 및 추적 예측
보스와 플레이어 체력 비율[0, 1]압박 강도 조절
현재 거리[0, 1]공격 사거리 판단
공격별 쿨다운 비율[0, 1]사용할 수 있는 기술 판단
직전 행동 one-hot0 또는 1동일 행동 반복 억제

시각 정보가 꼭 필요하지 않다면 CameraSensor보다 VectorSensor로 시작한다. 보스 전투의 핵심 상태가 이미 전투 시스템에 수치로 존재한다면 벡터 관측이 학습 속도와 원인 분석 모두에서 유리하다.

Step 2. 행동 공간은 공격 의도로 작게 시작한다

이동 입력과 스킬 선택을 모두 연속 행동으로 만들면 탐색 공간이 커진다. 첫 버전은 다음처럼 이산 행동 하나로 시작하는 편이 낫다.

행동 ID의미런타임 처리
0대기·거리 유지이동 조향만 수행
1접근플레이어 방향으로 이동
2후퇴위험 거리에서 이탈
3근접 공격사거리와 쿨다운을 C#에서 재검사
4원거리 공격투사체 또는 광역 공격 실행
5회피스태미나와 무적 규칙을 재검사

Behavior Parameters의 Actions를 Discrete Branches 1개, Branch Size 6으로 설정한다. 쿨다운 중인 공격은 WriteDiscreteActionMask()에서 마스킹한다. 학습에 ‘실패한 명령’을 반복해서 경험시키는 것보다 선택지에서 제외하는 편이 낫다.

Step 3. 보상은 승패 보상과 전투 품질 보상으로 분리한다

에피소드 종료 보상만 주면 학습 신호가 너무 희소해진다. 단, 작은 보상을 많이 쌓으면 보스가 피해를 주지 않고 거리 유지 보상만 수집하는 편법을 찾을 수 있으므로 합계를 항상 점검해야 한다.

R=2.0Rwin2.0Rlose+0.03Rdamage0.02RdamageTaken0.01RrepeatR = 2.0R_{win} - 2.0R_{lose} + 0.03R_{damage} - 0.02R_{damageTaken} - 0.01R_{repeat}
  • R_win: 플레이어를 쓰러뜨렸을 때 1
  • R_lose: 보스가 쓰러졌을 때 1
  • R_damage: 이번 스텝에 플레이어에게 준 피해를 최대 체력으로 정규화한 값
  • R_damageTaken: 이번 스텝에 받은 피해를 최대 체력으로 정규화한 값
  • R_repeat: 같은 공격을 연속 사용했을 때 1

보상은 플레이어 경험의 전부를 표현하지 못한다. ‘피할 수 없는 연속기 금지’, ‘페이즈별 필수 연출’은 보상으로 유도하지 말고 게임 규칙으로 강제한다.

Unity C# Agent는 어떻게 구현할까?

아래 예시는 기존 BossCombatPlayerTarget이 히트·이동·쿨다운을 관리한다는 전제의 최소 어댑터다. 모델은 공격을 제안할 뿐이며 실제 실행 가능 여부는 전투 시스템이 최종 판단한다.

using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;
using UnityEngine;

public sealed class BossAgent : Agent
{
    [SerializeField] private BossCombat combat;
    [SerializeField] private Transform player;
    [SerializeField] private float arenaRadius = 20f;

    public override void OnEpisodeBegin()
    {
        combat.ResetForTraining();
        transform.localPosition = Vector3.zero;
        player.localPosition = Random.insideUnitSphere * 8f;
        player.localPosition = new Vector3(player.localPosition.x, 0f, player.localPosition.z);
    }

    public override void CollectObservations(VectorSensor sensor)
    {
        Vector3 offset = player.position - transform.position;
        sensor.AddObservation(transform.InverseTransformDirection(offset) / arenaRadius);
        sensor.AddObservation(combat.Health01);
        sensor.AddObservation(combat.PlayerHealth01);
        sensor.AddObservation(combat.MeleeCooldown01);
        sensor.AddObservation(combat.RangedCooldown01);
        sensor.AddOneHotObservation(combat.LastAction, 6);
    }

    public override void WriteDiscreteActionMask(IDiscreteActionMask actionMask)
    {
        if (!combat.CanMelee) actionMask.SetActionEnabled(0, 3, false);
        if (!combat.CanRanged) actionMask.SetActionEnabled(0, 4, false);
        if (!combat.CanDodge) actionMask.SetActionEnabled(0, 5, false);
    }

    public override void OnActionReceived(ActionBuffers actions)
    {
        combat.TryExecute(actions.DiscreteActions[0]);
    }

    public void ReportDamage(float dealt01, float taken01)
    {
        AddReward(0.03f * dealt01 - 0.02f * taken01);
    }

    public void ReportBattleEnd(bool bossWon)
    {
        AddReward(bossWon ? 2f : -2f);
        EndEpisode();
    }
}

RequestDecision()을 매 프레임 호출할 필요는 없다. 공격 판단 주기가 0.1초면 충분한 게임이라면 DecisionRequester의 Decision Period를 물리 틱 기준으로 조정한다. 실제 공격 애니메이션 중에는 행동을 받더라도 전투 시스템이 입력을 보류해야 한다.

PPO로 보스 패턴을 학습하는 방법

PPO는 ML-Agents에서 기본 trainer로 제공되며 이산 행동의 보스 선택 문제를 시작하기에 적합하다. time_horizon, buffer_size, batch_size는 공격 한 사이클과 에피소드 길이에 맞춰 조정한다. 공식 문서는 PPO의 epsilon, beta, lambd와 권장 범위를 설명한다. Training Configuration File

Step 1. 훈련 전용 씬을 만든다

실제 보스 씬을 그대로 학습에 쓰지 않는다. 카메라, 컷신, UI, 저장 로직을 제거한 Arena 씬을 만들고 보스·플레이어 대리 에이전트·벽·리셋 컨트롤러만 둔다. 동일한 Behavior Name을 가진 Arena를 여러 개 복제하면 한 번의 학습에서 경험을 병렬 수집할 수 있다.

플레이어 대리 에이전트는 처음에는 규칙 기반으로 시작해도 된다. 거리 유지, 일정 확률의 회피, 피격 후 후퇴 정도만 구현하고 보스가 한 정책에 과적합하면 플레이어의 이동 속도와 회피 확률을 무작위화한다.

Step 2. YAML 설정으로 첫 기준선을 만든다

behaviors:
  BossPattern:
    trainer_type: ppo
    hyperparameters:
      batch_size: 256
      buffer_size: 4096
      learning_rate: 3.0e-4
      beta: 5.0e-3
      epsilon: 0.2
      lambd: 0.95
      num_epoch: 3
      learning_rate_schedule: linear
    network_settings:
      normalize: true
      hidden_units: 128
      num_layers: 2
    reward_signals:
      extrinsic:
        gamma: 0.99
        strength: 1.0
    max_steps: 2000000
    time_horizon: 128
    summary_freq: 10000
    checkpoint_interval: 250000
    keep_checkpoints: 5

훈련은 다음처럼 시작한다.

mlagents-learn config/boss_ppo.yaml --run-id=boss-pattern-v1

Editor에서 실행할 때는 명령을 시작한 뒤 Unity Play를 누른다. 빌드를 환경으로 사용할 때는 명령 뒤에 실행 파일 경로를 전달한다. 결과물은 results/<run-id>/<behavior-name>.onnx에 생성된다.

Step 3. 커리큘럼과 무작위화로 실제 전투에 견디게 한다

처음부터 긴 사거리, 복잡한 지형, 모든 스킬을 열면 보상이 희소해진다. 다음 순서로 난도를 올린다.

  1. 평지에서 이동·근접 공격만 학습한다.
  2. 원거리 공격과 회피를 열고 플레이어의 이동 패턴을 추가한다.
  3. 시작 거리, 이동 속도, 쿨다운, 장애물 위치를 environment_parameters로 무작위화한다.
  4. 최종적으로 실제 전투 시스템과 연결한 검증 씬에서만 평가한다.

환경 파라미터 무작위화는 학습 중 보지 못한 조건에 대한 적응력을 높이는 데 사용된다. 커리큘럼과 무작위화 설정은 trainer YAML의 environment_parameters에서 관리한다. 공식 학습 가이드를 기준으로 설정 형식을 맞춘다.

학습이 잘되는 보스와 재미있는 보스는 왜 다를까?

누적 보상이 오르는 것만으로 배포하면 안 된다. 보스가 특정 거리를 유지한 채 안전한 원거리 공격만 반복하거나 플레이어 대리 에이전트의 빈틈만 악용할 수 있다. 이는 모델 오류가 아니라 보상과 검증 환경이 실제 플레이 경험을 충분히 대표하지 못했다는 뜻이다.

배포 후보는 다음 지표를 함께 확인한다.

지표확인 방법경고 신호
승률서로 다른 플레이어 정책·세팅별 대전특정 봇에게만 높은 승률
공격 다양성행동 ID 분포와 연속 반복 횟수한 행동 비중이 지나치게 높음
반응 시간관측 변화 후 공격 변경까지의 시간프레임 단위로 흔들리거나 지나치게 둔함
공정성사람이 기록한 회피 가능성·피격 원인텔레그래프 없는 확정 피격
TensorBoard 보상평균 누적 보상과 정책 엔트로피보상 상승 중 엔트로피 급락 또는 정체

런타임에서는 모델 선택을 페이즈마다 고정하거나 최소 행동 유지 시간과 공격별 쿨다운을 둔다. 이 제약은 학습 품질을 감추는 장치가 아니라 보스의 가독성을 지키는 게임플레이 규칙이다.

자주 묻는 질문 (FAQ)

보스 하나에 PPO와 행동 트리를 함께 써도 될까?

그렇다. 페이즈·연출·공격 가능 여부는 행동 트리나 상태 머신에 두고 가능한 공격 중 하나를 고르는 문제만 PPO에 맡기면 된다.

CameraSensor가 꼭 필요한가?

아니다. 거리, 속도, 체력, 쿨다운처럼 이미 게임 코드에 있는 정보로 판단 가능한 전투는 VectorSensor가 더 단순하고 학습이 빠르다. 화면 인식 자체가 게임 규칙일 때만 카메라 관측을 고려한다.

학습 모델이 매번 다른 패턴을 보이면 그대로 배포해도 될까?

아니다. 추론 모드에서는 결정론적 정책을 우선 검토하고 의도적으로 변주가 필요할 때만 제한된 선택지에서 확률성을 추가한다. 반복 가능한 QA와 난이도 조정이 먼저다.

정리

ML-Agents 보스의 핵심은 거대한 신경망이 아니라 좋은 전투 환경이다. 작은 이산 행동 공간, 정규화된 상대 관측, 편법을 막는 보상, 훈련 전용 Arena를 먼저 만들면 PPO 기준선을 빠르게 검증할 수 있다.

그 다음에만 스킬 수, 플레이어 유형, 환경 무작위화를 늘린다. 보스의 재미를 보상 하나에 맡기지 말고 최종 가독성과 공정성은 기존 클라이언트 전투 규칙으로 고정하는 것이 안전하다.

#Unity ML-Agents#강화학습#지능형 보스#PPO#게임 AI#C#

계속 읽어보기

이런 글은 어떠세요?

< Back to Logs