개발 노트 — 보스 페이즈 시스템
페이즈가 없는 보스전의 문제
보스몹의 체력이 아무리 높아도, 전투 내내 같은 패턴만 반복된다면 플레이어는 금방 지루해집니다. “체력바가 줄어들수록 뭔가 달라진다”는 감각이 있어야 보스전이 하나의 이야기처럼 느껴집니다.
개인 프로젝트 Enter the Room의 보스전은 체력 구간에 따라 3페이즈로 구성했습니다.
| 페이즈 | 시각 변화 | 패턴 |
|---|---|---|
| 1페이즈 | 기본 색상 | 추적, 샷건 |
| 2페이즈 | 색상 변화 | 추적, 샷건, 돌진 |
| 3페이즈 | 색상 변화 | 추적, 샷건, 돌진, 전방향 난사 |
핵심 설계는 단순합니다. 페이즈가 넘어갈 때마다 이전 패턴은 유지하고, 새 패턴을 하나씩 추가하는 방식입니다. 이 글에서는 이 구조를 코드로 어떻게 구현하는지 정리합니다.
왜 “유지 + 추가” 방식을 택했나
페이즈마다 패턴을 완전히 새로 갈아엎는 방식(1페이즈 패턴을 버리고 2페이즈 패턴만 쓰는 방식)도 가능하지만, 이렇게 하면 두 가지 문제가 생깁니다.
- 페이즈가 바뀔 때마다 난이도가 들쭉날쭉해질 위험이 있습니다 (다음 페이즈가 오히려 더 쉬워 보일 수 있음).
- 각 페이즈를 처음부터 설계해야 해서 작업량이 커집니다.
반면 “이전 패턴 유지 + 새 패턴 추가” 방식은 페이즈가 진행될수록 자연스럽게 난이도가 누적되어 상승하고, 새로 추가할 패턴만 설계하면 되므로 작업 부담도 적습니다.
구현: 페이즈 전환과 패턴 목록 관리
1. 페이즈 전환 조건
체력 비율을 기준으로 페이즈를 전환합니다.
public enum BossPhase
{
Phase1,
Phase2,
Phase3
}
public class Boss : MonoBehaviour
{
private BossPhase _currentPhase = BossPhase.Phase1;
private float _maxHp;
private float _currentHp;
private void CheckPhaseTransition()
{
float hpRatio = _currentHp / _maxHp;
BossPhase newPhase = hpRatio switch
{
> 0.66f => BossPhase.Phase1,
> 0.33f => BossPhase.Phase2,
_ => BossPhase.Phase3
};
if (newPhase != _currentPhase)
{
_currentPhase = newPhase;
OnPhaseChanged(newPhase);
}
}
private void OnPhaseChanged(BossPhase newPhase)
{
UpdateVisual(newPhase); // 색상 변경
UpdateActivePatterns(newPhase); // 사용 가능 패턴 갱신
}
}
2. 페이즈별 활성 패턴 목록
각 페이즈에서 어떤 패턴이 활성화되는지를 리스트로 관리하면, 패턴을 “유지 + 추가”하는 구조를 명확하게 표현할 수 있습니다.
private Dictionary<BossPhase, List<IBossPattern>> _phasePatterns;
private void InitializePatterns()
{
var chase = new ChasePattern();
var shotgun = new ShotgunPattern();
var dash = new DashPattern();
var omniSpray = new OmniDirectionalSprayPattern();
_phasePatterns = new Dictionary<BossPhase, List<IBossPattern>>
{
{ BossPhase.Phase1, new List<IBossPattern> { chase, shotgun } },
{ BossPhase.Phase2, new List<IBossPattern> { chase, shotgun, dash } },
{ BossPhase.Phase3, new List<IBossPattern> { chase, shotgun, dash, omniSpray } }
};
}
private void UpdateActivePatterns(BossPhase phase)
{
_activePatterns = _phasePatterns[phase];
}
각 패턴은 공통 인터페이스로 정의해서, 보스의 메인 루프에서는 어떤 패턴이 몇 개 활성화되어 있든 동일한 방식으로 실행할 수 있게 합니다.
public interface IBossPattern
{
void Execute(Boss boss, Transform player);
float Cooldown { get; }
}
3. 활성 패턴 중 하나를 선택해서 실행
private float _patternCooldown;
private void Update()
{
CheckPhaseTransition();
if (_patternCooldown <= 0f)
{
var pattern = _activePatterns[Random.Range(0, _activePatterns.Count)];
pattern.Execute(this, player);
_patternCooldown = pattern.Cooldown;
}
_patternCooldown -= Time.deltaTime;
}
활성화된 패턴 목록 중 무작위로 하나를 골라 실행하는 구조입니다. 페이즈가 진행될수록 _activePatterns에 담긴 패턴 수가 늘어나므로, 자연스럽게 플레이어가 대응해야 할 경우의 수도 늘어납니다.
시각적 변화도 함께 챙기기
페이즈 전환 시 색상을 바꾸는 것만으로도 플레이어에게 “뭔가 달라졌다”는 신호를 명확히 줄 수 있습니다.
private void UpdateVisual(BossPhase phase)
{
Color color = phase switch
{
BossPhase.Phase1 => Color.white,
BossPhase.Phase2 => new Color(1f, 0.6f, 0.2f), // 주황
BossPhase.Phase3 => Color.red,
_ => Color.white
};
spriteRenderer.color = color;
}
기능적인 패턴 변화와 별개로, 이런 시각적 피드백이 있어야 플레이어가 “체력만 깎이는 중”이 아니라 “보스가 점점 위협적으로 변하고 있다”는 걸 직관적으로 느낍니다.
정리
보스 페이즈 시스템을 설계할 때 고려한 원칙은 세 가지였습니다.
- 패턴은 페이즈마다 완전히 바꾸지 말고, 유지하면서 추가한다 — 자연스러운 난이도 누적
- 패턴을 인터페이스로 추상화한다 — 페이즈별 패턴 목록 관리와 실행 로직을 분리해서 확장이 쉬워짐
- 기능적 변화와 시각적 변화를 함께 준다 — 플레이어가 페이즈 전환을 명확히 체감할 수 있게 함
보스전을 설계하실 때, 패턴을 늘리는 것 자체보다 “이전 패턴과 새 패턴이 어떻게 쌓여가는지”를 먼저 설계하시면 훨씬 자연스러운 난이도 곡선을 만들 수 있습니다.