이번 포스팅은
게임디자인패턴 2번째!
스트래티지와 스테이트에 대해서 알아본다.
스트래티지는 알고리즘을 객체로 캡슐화하는 것이고,
스테이트는 상태 전환을 객체로 분리하는 패턴이다.
코드의 문서화와 가독성을 위해서 좋으니 알고 가길 바란다!
싱글톤, 옵저버, 팩토리는 이전 포스팅에 있으니 참고를 위한 링크를 첨부합니닷!
[멋쟁이사자처럼 Unity 게임 부트캠프 4기] 25일차 - 게임디자인패턴 : 싱글톤, 옵저버, 팩토리
[ 목차 ] 오늘은 게임 디자인 패턴에 대해서 알아본다. 그중 싱글톤, 옵저버, 팩토리 3개를 알아보기! 게임 디자인 패턴은 코드를 문서화하는 거다.게임 내 상호작용의 반복적 구성요소로, 플레
gang-design.com
1. 스트래티지 패턴 (Strategy)
스트래티지란?
스트래티지 패턴은 알고리즘을 캡슐화하고 필요에 따라 교체할 수 있게 하는 패턴이다.
왜 사용할까?
동일한 기능을 다양한 방식으로 실행하고 싶을 때, 유연하게 알고리즘을 바꾸기 위해서이다.
어디에 쓰일까?
Unity에서는 AI 행동, 캐릭터 움직임 등에 유용하다.
이제 스트래티지 패턴을 사용해 적이 다양한 방식으로 움직일 수 있도록 만드는 스크립트를 작성할 것이다.
IMovementStrategy 인터페이스를 작성한다.
이것은 모든 이동 방식이 따라야 할 공통 인터페이스를 정의한다.
void Move() 메서드를 정의해 모든 이동 전략 클래스에서는 이 함수를 가져야 한다.
public interface IMovementStratgy
{
void Move(Transform transform, float speed);
}
직선 이동을 작성해 보자.
Move() 메서드를 사용해 Vector3.forward 방향으로 계속 이동한다.
프레임 속도와 관계없이 일정한 속도로 이동된다.
public class StraightMovement : IMovementStratgy
{
public void Move(Transform transform, float speed)
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);
}
}
지그재그로 이동을 작성해 보자.
좌우 움직임 크기, 지그재그의 속도, 시간 값을 정한다.
Mathf.Sin() 공식 함수를 통해 좌우를 이동하는 것을 작성한다.
public class ZigzagMovement : IMovementStratgy
{
private float _amplitude = 2f; // 좌우 움직임 크기
private float _frequency = 2f; // 지그재그 속도
private float _time = 0f; // 시간 값
public void Move(Transform transform, float speed)
{
_time += Time.deltaTime;
// 직선 이동
transform.Translate(Vector3.forward * speed * Time.deltaTime);
// 좌우 지그재그 공식: Sin 함수를 이용해 좌우 이동
float xOffset = Mathf.Sin(_time * _frequency) * _amplitude;
transform.position = new Vector3(xOffset, transform.position.y, transform.position.z);
}
}
원형으로 이동하는 코드를 작성해 보자.
원의 반지름, 회전 속도, 현재 각도, 원의 중심점, 중심점이 초기화되었는지 확인한다.
x, z좌표를 이용해 원형으로 이동한다. (y는 위아래여서 필요 없다.)
적이 회전 방향을 바라보도록 설정하는 LookAt() 메서드의
공식 함수 Mathf.Cos(), Mathf.Sin()를 통해 원을 그리며 이동하는 코드를 작성한다.
public class CircularMovement : IMovementStratgy
{
private float _radius = 5f; // 원의 반지름
private float _angularSpeed = 50f; // 회전 속도
private float _angle = 0; // 현재 각도
private Vector3 _center; // 원의 중심점
private bool _isInitialized = false; // 중심점이 초기화되었는지 확인
public void Move(Transform transform, float speed)
{
if(!_isInitialized)
{
_center = transform.position; // 현재 위치를 중심점으로 설정
_isInitialized = true;
}
_angle += _angularSpeed * Time.deltaTime; // 회전 각도 증가
// x, z 좌표를 이용해 원형 이동
float x = _center.x + Mathf.Cos(_angle * Mathf.Deg2Rad) * _radius;
float z = _center.z + Mathf.Sin(_angle * Mathf.Deg2Rad) * _radius;
transform.position = new Vector3(x, transform.position.y, z);
// 적이 회전 방향을 바라보도록 설정
transform.LookAt(new Vector3(
_center.x + Mathf.Cos((_angle + 90) * Mathf.Deg2Rad) * _radius,
transform.position.y,
_center.z + Mathf.Sin((_angle + 90) * Mathf.Deg2Rad) * _radius
));
}
}
이제 이동 전략을 변경할 수 있는 코드를 작성해 본다.
Update()에서 현재 설정된 이동 전략을 사용하여 적이 이동하는 것을 작성한다.
이제 실행 중에 이동 전략을 변경하는 메서드인 SetMovementStratgy()를 호출하면 이동 방식을 변경할 수 있다.
public class Enemy : MonoBehaviour
{
[SerializeField] private float moveSpeed = 3f; // 이동 속도
private IMovementStratgy _movementStratgy; // 현재 이동 전략
void Start()
{
// 기본 이동 전략 설정 (직선 이동)
_movementStratgy = new StraightMovement();
}
// 이동 전략을 변경하는 메서드
public void SetMovementStratgy(IMovementStratgy stratgy)
{
_movementStratgy = stratgy;
}
void Update()
{
// 현재 설정된 이동 방식으로 이동 실행
if (_movementStratgy != null)
{
_movementStratgy.Move(transform, moveSpeed);
}
}
}
전체 코드이다.
using UnityEngine;
//4. 스트래티지 패턴
//알고리즘을 캡슐화하고 필요에 따라 교체할 수 있게 하는 패턴이다.
//Unity에서는 Ai 행동, 캐릭터 움직임 등에 유용하다.
//이동 전략 인터페이스
public interface IMovementStratgy
{
void Move(Transform transform, float speed);
}
//직선 이동
public class StraightMovement : IMovementStratgy
{
public void Move(Transform transform, float speed)
{
transform.Translate(Vector3.forward * speed * Time.deltaTime);
}
}
//지그재그 이동 전략
public class ZigzagMovement : IMovementStratgy
{
private float _amplitude = 2f;
private float _frequency = 2f;
private float _time = 0f;
public void Move(Transform transform, float speed)
{
_time += Time.deltaTime;
//직선 이동
transform.Translate(Vector3.forward * speed * Time.deltaTime);
//좌우 움직임 추가 (공식)
float xOffset = Mathf.Sin(_time * _frequency) * _amplitude;
transform.position = new Vector3(xOffset, transform.position.y, transform.position.z);
}
}
//원형 이동 전략
public class CircularMovement : IMovementStratgy
{
private float _radius = 5f;
private float _angularSpeed = 50f;
private float _angle = 0;
private Vector3 _center;
private bool _isInitialized = false;
public void Move(Transform transform, float speed)
{
//bool이 false일 때
if(!_isInitialized)
{
_center = transform.position; //초기화
_isInitialized = true;
}
_angle += _angularSpeed * Time.deltaTime;
float x = _center.x * Mathf.Cos(_angle * Mathf.Deg2Rad) * _radius;
float z = _center.z * Mathf.Sin(_angle * Mathf.Deg2Rad) * _radius;
transform.position = new Vector3(x, transform.position.y, z);
//회전 방향 고려
transform.LookAt(new Vector3(_center.x + Mathf.Cos((_angle + 90) * Mathf.Deg2Rad) * _radius,
transform.position.y,
_center.z + Mathf.Sin((_angle + 90) * Mathf.Deg2Rad) * _radius));
}
}
//전략 사용 클래스
public class Enemy : MonoBehaviour
{
[SerializeField] private float moveSpeed = 3f;
//현재 이동 전략
private IMovementStratgy _movementStratgy;
void Start()
{
//기본이동전략
_movementStratgy = new StraightMovement();
}
//이동 전략 변경 메서드 (기본 이동 전략을 바꿔주기)
public void SetMovementStratgy(IMovementStratgy stratgy)
{
_movementStratgy = stratgy;
}
void Update()
{
if(_movementStratgy != null)
{
_movementStratgy.Move(transform, moveSpeed);
}
}
}
이제 EnemyController 코드를 생성해 키 입력을 통한 이동 전략 변경을 실습한다.
Enemy 클래스를 가져오고
Update()에서 숫자 1 2 3에 따른 이동 전략 변경 키입력을 작성한다.
using UnityEngine;
public class EnemyController : MonoBehaviour
{
private Enemy _enemy;
void Start()
{
_enemy = GetComponent<Enemy>();
}
void Update()
{
//키 입력에 따라 이동 전략 변경(테스트용)
if(Input.GetKeyDown(KeyCode.Alpha1)) //숫자 1
{
_enemy.SetMovementStratgy(new StraightMovement());
Debug.Log("직선 이동 전략으로 변경");
}
else if (Input.GetKeyDown(KeyCode.Alpha2)) //숫자 2
{
_enemy.SetMovementStratgy(new ZigzagMovement());
Debug.Log("지그재그 이동 전략으로 변경");
}
else if (Input.GetKeyDown(KeyCode.Alpha3)) //숫자 3
{
_enemy.SetMovementStratgy(new CircularMovement());
Debug.Log("원형 이동 전략으로 변경");
}
}
}
테스트를 하기 위해 유니티에서 실행을 해보자.
여기에서의 꿀팁!
카메라(Hierarchy에 있는 Main Camera)를 클릭하고,
ctrl + shift + f : Scene에서 내가 보는 시점으로 카메라를 옮길 수 있다.

2. 스테이트 패턴 (State)
스테이트란?
객체의 상태에 따라 다른 행동을 하도록 만드는 디자인 패턴이다.
별도의 클래스로 분리하고, 현재 상태에 따라 적절한 행동을 수행하는 방식이다.
왜 사용할까?
객체가 여러 상태를 가지면서 상태에 따라 다른 행동을 해야 할 때 깔끔하게 분리하기 위해 사용된다.
어디에 쓰일까?
유니티에서는 Animator 기능 (idle → run) 같은 애니메이션 전환을 코드로 구현하는 것이라고 할 수 있다.
그렇다면 애니메이션과 스테이트 패턴의 차이는 뭘까?
애니메이션 vs 스테이트 패턴 차이점
상태 | 애니메이션 클립(Idle, Run, Attack) | 상태 클래스(PatrolState, ChaseState 등) |
전환 조건 | Transition 설정 (예: 속도 > 1 → Run) | ChangeState() 메서드 호출 |
제어 방식 | Unity가 자동 처리 | 코드로 직접 처리 |
이벤트 발생 | Animation Events 사용 가능 | 코드에서 직접 처리 가능 |
스테이트 패턴을 사용하면 애니메이션을 쉽게 연결할 수 있다.
새로운 행동을 쉽게 추가하고 변경이 가능하다는 것이다.
이제 스테이트 패턴을 구현할 기본 상태 인터페이스를 구현한다.
IState 스크립트를 생성한다.
여기에서의 인터페이스는 모든 상태들이 반드시 가져야 할 공통 기능을 정의한다.
상태 진입, 상태 유지, 상태 종료 3가지의 메서드를 작성한다.
using UnityEngine;
public interface IState
{
void Enter(); //상태 진입 시 실행
void Update(); //상태 유지 중 실행
void Exit(); //상태 종료 시 실행
}
이제 애니메이션의 idle 상태를 코드로 구현해 볼 것이다.
using UnityEngine;
public class IdleState : IState
{
public void Enter()
{
Debug.Log("Idle 상태 시작");
}
public void Exit()
{
Debug.Log("Idle 상태 유지 중");
}
public void Update()
{
Debug.Log("Idle 상태 종료");
}
}
이렇게 달릴 때의 상태 코드
using UnityEngine;
public class RunState : IState
{
public void Enter()
{
Debug.Log("Run 상태 시작");
}
public void Exit()
{
Debug.Log("Run 상태 유지 중");
}
public void Update()
{
Debug.Log("Run 상태 종료");
}
}
점프할 때의 상태 코드
using UnityEngine;
public class JumpState : IState
{
public void Enter()
{
Debug.Log("Jump 상태 시작");
}
public void Exit()
{
Debug.Log("Jump 상태 유지 중");
}
public void Update()
{
Debug.Log("Jump 상태 종료");
}
}
이제 스테이트 패턴을 구현하기 위한 상태 관리 클래스를 작성한다.
ChangeState(IState newState) 메서드를 사용해 현재 상태를 새로운 상태로 변경한다.
이전 상태를 종료하고, 새 상태로 변경한다.
using UnityEngine;
public class StateMachine
{
private IState currentState;
public void ChangeState(IState newState)
{
currentState?.Exit(); //이전 상태의 Exit() 실행
currentState = newState; //새 상태로 변경
currentState.Enter(); //새 상태의 Enter() 실행
}
public void Update()
{
currentState?.Update(); //현재 상태의 Update() 실행
}
}
이제 플레이어의 상태를 관리하는 구조를 만든다.
플레이어가 어떤 행동을 하고 있는지 상태(State)로 구분하고,
특정 입력에 따라 상태를 변경하도록 한다.
그렇게 PlayerControl 스크립트를 만들어 작성한다.
StateMachine 객체를 생성하여 플레이어의 상태를 관리한다.
public class PlayerControl : MonoBehaviour
{
private StateMachine stateMachine;
Start()에서 상태 초기화를 한다.
void Start()
{
stateMachine = new StateMachine();
stateMachine.ChangeState(new IdleState()); // 처음엔 Idle 상태
}
Update()에서 상태를 변경한다.
키 입력에 따라 Jump, Run, Idle로 변경을 한다.
스페이스바를 누르면 JumpState 실행
방향키를 누르면 RunState 실행
아무것도 누르지 않으면 IdleState 실행
void Update()
{
stateMachine.Update(); // 현재 상태의 Update() 실행
if (Input.GetKeyDown(KeyCode.Space))
stateMachine.ChangeState(new JumpState()); // 스페이스바 누르면 점프 상태로 변경
else if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.LeftArrow))
stateMachine.ChangeState(new RunState()); // 방향키 누르면 Run 상태로 변경
else if (!Input.anyKey)
stateMachine.ChangeState(new IdleState()); // 아무 키도 안 누르면 Idle 상태
}
그렇게 PlayerControl 스크립트의 전체 코드이다.
using UnityEngine;
public class PlayerControl : MonoBehaviour
{
private StateMachine stateMachine;
void Start()
{
stateMachine = new StateMachine();
stateMachine.ChangeState(new IdleState()); //처음엔 Idle 상태
}
void Update()
{
stateMachine.Update();
if (Input.GetKeyDown(KeyCode.Space))
stateMachine.ChangeState(new JumpState()); //스페이스바 누르면 점프 상태로 변경
else if (Input.GetKey(KeyCode.RightArrow) || Input.GetKey(KeyCode.LeftArrow))
stateMachine.ChangeState(new RunState()); //방향키 누르면 Run 상태로 변경
else if (!Input.anyKey)
stateMachine.ChangeState(new IdleState()); //아무 키도 안 누르면 idle 상태
}
}
게임 디자인 패턴 스트래티지와 스테이트에 대해서 알아보았다.
사실상 처음 배울 때에는 뭔가 더 복잡해지는 것 같은 느낌이라
이게 더 좋은 방법이 맞나?
라는 생각이 들었다.. 어려웠기 때문에..
근데 멀리 보면 좋은 방법인 것 같다.
아직은 어려워도 추후에 팀프로젝트나 업데이트를 생각하면서
진행하면 좋을 것 같다!
'Development > 멋쟁이사자처럼 게임개발 부트캠프' 카테고리의 다른 글
[멋쟁이사자처럼 Unity 게임 부트캠프 4기] 27일차 (1) - 상속 횡스크롤 2D (0) | 2025.04.17 |
---|---|
[멋쟁이사자처럼 Unity 게임 부트캠프 4기] 26일차 (2) - 2D 횡스크롤 (0) | 2025.04.16 |
[멋쟁이사자처럼 Unity 게임 부트캠프 4기] 25일차 - 게임디자인패턴 : 싱글톤, 옵저버, 팩토리 (6) | 2025.04.13 |
[멋쟁이사자처럼 Unity 게임 부트캠프 4기] 24일차 - URP 2D Light (0) | 2025.04.12 |
[멋쟁이사자처럼 Unity 게임 부트캠프 4기] 23일차 - 유니티 게임 수학 & 물리 (0) | 2025.04.08 |