Command 패턴
행위(Behavioral) 디자인 패턴 중 하나로, 작업 요청을 캡슐화하여
요청자(Invoker)와 수행자(Receiver)를 분리하는 것을 목표. 명령객체(중개자)를 적극적으로 사용.
- 요청자 : 일을 해달라고 명령을 내리는 역할(ex. 리모컨의 버튼)
- 수행자 : 실제로 일을 처리하는 역할(ex. TV본체)
- 명령객체(중개자) : "볼륨을 올려라"라는 메시지를 담고 있는 캡슐화된 객체

어째서 분리를 할까?
- 보통은 요청자(Invoker)가 수행자(Receiver)에게 직접 명령을 내림.
문제점 :
- 하지만, 요청자와 수행자가 너무 긴밀하게 연결되어 있으면,
수행자의 내부 구현(예: TV가 어떻게 작동하는지)이 바뀌면 요청자도 수정이 필요
- 요청자가 수행자와 직접 연결되어 있으면, 같은 요청을 다른 수행자로 보내거나
작업을 취소/재실행하는 기능을 추가하기 어려움.
해결법 :
“작업 요청을 캡슐화” 한다는 건 명령을 하나의 독립된 객체로 만드는 것
이 명령 객체는 요청자와 수행자 사이의 "중개자" 역할을 수행.
- 요청자는 명령 객체(중개자)를 통해 작업을 요청
- 명령 객체(중재자)는 수행자를 알고 있으며, 요청자가 어떤 일을 하라고 했는지
구체적인 정보를 가지고 있음.
이렇게 하면 요청자와 수행자가 직접 연결되지 않으므로
서로 독립적으로 변경이 가능
스크립트 해석:
PlayerController 스크립트
( Invoker 역할을 수행)
- static : 게임 전체에서 공통적으로 사용하는 데이터를 저장할때 또는
변하지 않는 값은 정의할때 사용. 이는 싱글톤 패턴을 구현하는 데 사용
// static 키워드로 선언된 instance는 클래스 레벨에서 하나의 공유 인스턴스를 가짐
// 싱글톤은 이 클래스의 인스턴스를 전역적으로 접근 가능하게 하며, PlayerController가 게임 내에서 하나만 존재하도록 보장
public static PlayerController instance;
Dictionary 의 기본 문법 구조
Dictionary<T Key, T Value> dictionaryName = new Dictionary<TKey, TValue>
// key 에는 데이터 타입((예: string, int, float 등)) ,
Value 에는 값의 데이터 타입(예: KeyCode, string, int 등)을 지정.
이동 키를 관리하기 위한 딕셔너리
// 키(문자열)와 Unity의 KeyCode 값을 연결
private Dictionary<string, KeyCode> moveKeys = new Dictionary<string, KeyCode>
{
{ "Forward", KeyCode.W },
{ "Back", KeyCode.S },
{ "Right", KeyCode.D },
{ "Left", KeyCode.A },
};
Awake, this
// 유니티 생명주기(Lifecycle)에서 객체가 활성화될 때 가장 먼저 호출(초기화에 사용)
void Awake()
{
// PlayerController의 전역 인스턴스를 설정합니다. 다른 클래스에서 PlayerController.instance로 접근할 수 있게 함
// this는 메서드가 속한 현재 객체를 의미. Awake 메서드는 PlayerController 클래스의 인스턴스에서 실행되므로, this는 현재 PlayerController 객체 자체를 참조
instance = this;
}
SetCommand() 메서드는 현재 실행할 명령을 설정함.
ExecuteCommand() 메서드는 설정된 명령을 실행함.
// SetCommand() 메서드는 현재 실행할 명령을 설정
// 생성된 명령 객체(moveCommand)를 CommandInvoker에 전달
commandInvoker.SetCommand(moveCommand);
// commandInvoker가 설정된 명령(moveCommand)을 실행
commandInvoker.ExecuteCommand();
SetKeys() 메서드는 외부에서 새로운 키를 설정하려고 할 때 사용
GetKeys() 메서드는 외부에서 현재 키 설정을 확일할 때 사용
// 새로운 키 매핑을 설정
// 외부에서 호출하여 WASD 대신 다른 키를 사용할 수 있게 함.
// newKeys는 새로운 딕셔너리로, 이 메서드가 호출될 때 이 딕셔너리를 moveKeys에 설정
public void SetKeys(Dictionary<string, KeyCode> newKeys)
{
// moveKeys는 클래스 내에서 정의된 딕셔너리
// moveKeys에 newKeys를 할당
// moveKeys 를 새로운 키 설정으로 대체
moveKeys = newKeys;
}
// 현재 키 매핑을 반환
// 외부에서 현재 키 설정을 확인할 때 사용
public Dictionary<string, KeyCode> GetKeys()
{
// moveKeys 라는 필드에 저장된 딕셔너리를 반환
// 이를 통해 외부에서 moveKeys의 내용을 가져와서 사용할 수 있음
return moveKeys;
}
}
CommandInvoker 스크립트
(중개자 역할을 수행)
SetCommand : 실행할 명령을 설정
ExecuteCommand: 설정한 명령을 실행
// SetCommand : 실행할 명령을 설정
// 매개변수인 newCommand 는 Command 인터페이스를 구현한 구체적인 명령 객체
// 이 메서드를 호출하면 CommandInvoker는 내부적으로 어떤 명령을 실행할지 준비를 함.
public void SetCommand(Command newCommand)
{
command = newCommand;
}
// ExecuteCommand: 설정한 명령을 실행
public void ExecuteCommand()
{
// command.Execute()를 호출하여 명령 객체가 가진 구체적인 동작을 수행
command.Execute();
}
Command 스크립트
abstract : C#에서 추상적인 개념을 나타내며, 클래스 또는 메서드에 사용할 수 있는 예약어
추상 클래스 (abstract class) : 다른 클래스에서 상속받을 수 있는 기반 클래스.
자체적으로는 객체를 생성 할 수 없음.
추상 메서드 (abstract method): 메서드의 구체적인 구현부가 없는 메서드
// 명령 패턴(Command Pattern)의 핵심 요소 중 하나인 추상 클래스 Command를 정의
// abstract : C#에서 추상적인 개념을 나타내며, 클래스 또는 메서드에 사용할 수 있는 예약어
// 추상 클래스 (abstract class) : 다른 클래스에서 상속받을 수 있는 기반 클래스. 자체적으로는 객체를 생성 할 수 없음.
// 상속받는 클래스가 반드시 구현해야 하는 추상 메서드를 정의할 수 있음
public abstract class Command
{
// 추상 메서드 (abstract method): 메서드의 구체적인 구현부가 없는 메서드
// 반드시 추상 클래스 내에 선언되며, 이를 상속받은 클래스에서 재정의(override) 해야 함.
// 명령의 실행 동작을 정의하는 역활을 수행.(각 구체적인 명령(예: MoveCommand, AttackCommand)에서 고유한 실행 동작을 제공
public abstract void Execute();
}
MoveCommand 스크립트
(Command 클래스를 상속받음. Receiver 역할을 수행)
Translate() : 객체의 위치를 이동시키는 데 사용
일반적으로 Time.deltaTime과 함께 사용하여 프레임 속도에 관계없이 일정한 속도로 움직이게 함.
public override void Execute()
{
// PlayerController 클래스의 싱글톤 인스턴스에서 이동 속도(moveSpeed) 값을 가져오고 speed 에 할당
float speed = PlayerController.instance.moveSpeed;
// Translate() 는 Transform의 위치를 변경하는 Unity 메서드
charTransform.Translate(direction * speed * Time.deltaTime);
}
ChangeKeyCommand 스크립트
(Command 클래스를 상속받음. Receiver 역할을 수행)
UIManager 스크립트
( Invoker 역할을 수행)
onValidateInput : 입력 검증(validation) 이벤트로, 사용자가 입력 필드에 문자를 입력할 때 호출
+= : 이 코드에서는, 이벤트에 새로운 검증 로직을 추가하는 연산자
char.ToUpper() 는, 문자열을 대문자로 만드는 함수
// 입력 필드(keyInputs)에서 사용자가 입력하는 모든 문자를 대문자로 자동 변환
private void InitUpper()
{
// keyInputs 은 TMP_InputField 타입의 배열로, 사용자 입력을 받을 수 있는 여러 입력 필드를 나타냄
// input 은, 배열 내 각 입력 필드 객체를 가리키는 변수
foreach (var input in keyInputs)
{
// 해당 이벤트를 통해 입력된 문자가 유효한지 확인하거나 변환할 수 있음.
// 검증 로직 : text: 입력 필드에 현재까지 입력된 문자열.
// charIndex: 추가될 문자의 인덱스 위치.
// addChar: 새로 입력된 문자.
// char.ToUpper(addChar)는 새로 입력된 문자(addChar)를 대문자로 변환. 변환된 대문자는 입력 필드에 반영
input.onValidateInput += (text, charIndex, addChar) => char.ToUpper(addChar);
}
}
ToString()은 객체를 문자열로 변환하는 .NET의 메서드
// TMP_InputField 배열의 i번째 요소로, 사용자가 키 값을 입력하거나 수정할 수 있는 입력 필드
// KeyCode 값을 문자열로 변환하여 입력 필드에 표시(KeyCode.W는 "W"로 변환)
// ToString()은 객체를 문자열로 변환하는 .NET의 메서드
keyInputs[i].text = pair.Value.ToString();
Enum.TryParse 는 변환이 성공하면 true를 반환하고, 변환된 값(KeyCode)을 변수에 저장
변환이 실패하면 false를 반환하며, 변수는 기본값
.Split(':'): 문자열을 특정 구분자로 나눔.
// Enum.TryParse 는 변환이 성공하면 true를 반환하고, 변환된 값(KeyCode)을 newkey 변수에 저장
// 변환이 실패하면 false를 반환하며, newkey는 기본값
// 이 구문은 사용자가 유효한 키 값을 입력했는지 검증하는 역할
// 사용자가 입력한 텍스트(keyInputs[i].text)를 KeyCode Enum 타입으로 변환
// out 키워드는 메서드가 호출된 후, 해당 메서드가 값을 반환하는 추가적인 방법을 제공
if (Enum.TryParse(keyInputs[i].text, out KeyCode newkey))
{
// keyTexts 배열에서 해당 키 이름을 가져옴.
// .Split(':'): 문자열을 특정 구분자로 나눔.
// .Split(':')[0]은 문자열을 ':'를 기준으로 나누고 첫 번째 부분("Forward")을 가져옴. 만약 .Split(':')[1] 이면 물자열(W, A...)를 가져옴
// 새로 변환된 newkey 값을 newKeys Dictionary에 추가
newKeys[keyTexts[i].text.Split(':')[0]] = newkey;
}
'게임 개발(유니티) > 멋쟁이 사자처럼 3기_회고록' 카테고리의 다른 글
| 유니티 디자인패턴_Factory Pattern패턴(1) (0) | 2025.01.10 |
|---|---|
| 유니티 디자인패턴_Observer 패턴 (0) | 2025.01.09 |
| Uni Task는 무엇인가? (0) | 2025.01.05 |
| [멋쟁이사자처럼 유니티 TIL] 2024_12_30~31 강의 요약 및 정리(5) (0) | 2025.01.05 |
| [멋쟁이사자처럼 유니티 TIL] 2024_12_30~31 강의 요약 및 정리(3) (0) | 2025.01.05 |