게임을 개발하다 보면 처음에는 단순한 '공격' 기능만 있으면 됐는데, 기획이 확장되면서 '불꽃 공격', '얼음 공격', '흡혈 공격' 등 수많은 변종이 생겨나기 시작한다.
대부분은 가장 먼저 '상속(Inheritance)'을 생각한다. 하지만 기능이 늘어날수록 클래스 계층 구조는 복잡해지고, 비슷한 코드를 여러 곳에 복사 붙여넣기 하고있다.
(Gemini 왈/대충 저도 비슷한 생각)
"새로운 기능을 만들 때마다 매번 코드를 새로 짜야 할까? 인스펙터에서 레고 블록처럼 기능을 조립할 수는 없을까?"
이 고민을 해결해 주는 패턴이 바로 전략 패턴(Strategy Pattern)이다. 특히 유니티의 ScriptableObject와 결합하면, 코드 한 줄 수정하지 않고도 에디터에서 드래그 앤 드롭만으로 완전히 새로운 스킬과 유닛을 창조할 수 있는 강력한 시스템을 구축할 수 있다.
| AbilityEffect |
|---|
![]() |
| Unit |
|---|
![]() |
| 데미지 효과 |
|---|
![]() |
| 이펙트 효과 |
|---|
![]() |
| 버프 효과 |
|---|
![]() |
| 힐 효과 |
|---|
![]() |
| ScriptableObject 생성 |
|---|
![]() |
![]() |
CreateAssetMenu로 생성할 수 있다.전략 패턴의 장점은 '조합'이다. 예를 들어, '공격'과 '힐'를 각각 만들었다면, 이 둘을 코딩 없이 합쳐서 '흡혈 공격'이라는 새로운 스킬을 만들 수 있다.
이를 위해 Unit 클래스를 구현했다.
| Unit |
|---|
![]() |
Unit 을 구현했지만, 더 좋은 방법은 AbilityEffect를 상속 받은 흡혈 공격 클래스를 구현하는 것이다.| CompositeAbility |
|---|
![]() |
| Drain Attack Effect |
|---|
![]() |
재사용성 극대화: DamageEffect 하나만 잘 만들어두면, 일반 공격, 독 화살, 폭발 마법 등 모든 곳에 드래그해서 재사용할 수 있다.
재귀적 구조: CompositeAbility 역시 AbilityEffect를 상속받기 때문에, 복합 기능 안에 또 다른 복합 기능을 넣는 것도 가능.
기획자와의 협업: 프로그래머가 기능을 하나 추가할 때마다 기획자는 수십 가지의 새로운 조합을 스스로 테스트해 볼 수 있다.
해당 방법을 사용할 때, 주의할 점이 있는데 바로 실행 순서이다.
직렬화로 넣은 순서대로 작성하기 때문에, Buff와 Damage를 조합한 "강화 공격" 만든다고 가정하면, Damage가 먼저 실행되는 경우에는 데미지가 예상과 다르게 들어갈 수 있다.
| O | X |
|---|---|
![]() | ![]() |
| GameManager |
|---|
![]() |
![]() |
| Player | Log | Target |
|---|---|---|
![]() | ![]() | ![]() |
| Player | Log | Target |
|---|---|---|
![]() | ![]() | ![]() |
전략 패턴과 ScriptableObject를 결합한 시스템은 확실히 장점이 많다.
1. "코드 수정 없는" 무한한 확장성
개방-폐쇄 원칙(Open-Closed Principle)에 따라, 기존 코드를 건드리지 않고도 새로운 기능을 추가할 수 있다.
새로운 공격 방식이 필요하다면 새로운 ScriptableObject 클래스를 하나 더 만들기만 하면 된다.
기존의 캐릭터 로직이나 시스템 코드를 열어볼 필요가 없으므로 예기치 못한 버그로부터 자유로워진다.
2. 개발자와 기획자의 "협업성 증가"
이 아키텍처의 가장 큰 매력은 데이터 중심 설계라는 점이다. 프로그래머는 '기능 조각'이라는 도구를 만들고, 기획자는 그 도구를 조합해 '콘텐츠'를 만든다.
기획자가 스킬 데미지를 10에서 20으로 바꾸거나, 이펙트를 교체하기 위해 프로그래머를 찾을 필요가 없다.
인스펙터에서 에셋을 교체하는 것만으로 밸런스 조정과 기능 테스트가 가능해지므로 개발 속도가 비약적으로 상승한다.
3. 유닛 테스트와 디버깅의 간소화
거대한 하나의 클래스는 어디서 버그가 터졌는지 찾기 어렵다. 하지만 전략 패턴으로 쪼개진 기능들은 각각 독립적인 단위이므로, '데미지' 로직에 문제가 있다면 해당 클래스만 확인하면 된다.
기능이 모듈화되어 있어 특정 상황을 재현하거나 단위 테스트(Unit Test)를 작성하기에도 훨씬 수월하다.