커맨드는 단어 그 자체로 본다면 명령, 요청이라는 뜻이다. 위키피디아를 보면 요청을 캡슐화한다고 써있는데, 쉽게 말하면 객체의 행동을 저장하고 필요할 때 꺼내 쓰는 방식이라고 보면 된다.


여기서는 FPS 총게임에서 무기를 사용하는 경우를 생각하겠다.


일단 유저가 키보드나 마우스로 할 수 있는 상황을 아래와 같이 정해보자.



[무기 사용 매커니즘]

좌클릭 : 총발사

R키 : 재장전

F키 : 빠른 칼 휘두르기




이를 토대로 커맨드 패턴에 입각해서 구조를 짜보도록 한다.

본 예제는 오직 커맨드 패턴의 설명을 위해서 만들었기 때문에 무기 교체, 캐릭터 이동 등의 기능은 제외했다.




우선 커맨드 패턴의 구조는 아래의 그림과 같다.

사진 출처 : https://ko.wikipedia.org/wiki/%EC%BB%A4%EB%A7%A8%EB%93%9C_%ED%8C%A8%ED%84%B4



클라이언트 : 인보커, 리시버, 커맨드를 생성하거나 세팅한다.


리시버 : 행동의 당사자. 예를 들어 '총을 발사한다'라는 상황에서 '총'이 바로 리시버이다.


커맨드 : execute()함수를 선언하는 인터페이스.


콘크리트 커맨드 : 커맨드를 상속받고 멤버변수로 리시버를 가지는 객체. 커맨드 인터페이스의 execute()함수를 여기서 정의한다. 예를 들어, '총을 발사하는 커맨드'라면 멤버변수로 Gun 객체를 가지고, execute()함수 내에서 Gun.Shoot()함수를 호출해준다.


인보커 : 클라이언트가 원하는 행동을 대신 호출해주는 객체. 예를 들어 총을 쏜다라는 행동이 있다면, 클라이언트에서 gun.shoot()을 직접 호출하는 것이 아닌, 인보커에게 명령을 내리면 인보커가 알맞은 커맨드의 execute()함수를 호출한다. 그 execute()함수 안에는 gun.shoot()이 호출되고 있을 것이다.




위의 그림을 토대로 커맨드 패턴에서 쓰는 용어들과 클래스들을 매칭시켜보겠다.


 클래스 명

 설명

 커맨드패턴 내의 명칭

WeaponSystem

 무기시스템을 관장하는 클래스.

커맨드,인보커,리시버를 생성하거나 세팅.

 클라이언트

Weapon

gun, knife의 부모클래스 

리시버

 CommandManager

커맨드를 관리 

인보커

IWeaponCommand

발사, 휘두르기 등 무기의 행동 인터페이스

커맨드

ShootGunCommand

ReloadGunCommand

StabKnifeCommand

실제동작 정의. 

콘크리트 커맨드 

 


부모자식 구조로 만들 클래스는 아래의 그림들과 같이 만들도록 하겠다.



커맨드 부모관계



참고로 Execute는 실행하다라는 뜻으로, 

ShootGunCommand에서는 쏘다,

ReloadGunCommand에서는 재장전하다,

StabKnifeCommand에서는 찌르다

라는 행동으로 정의될 것이다.







리시버 부모관계





코드는 아래와 같이 만들겠다.



//리시버
public class Weapon
{
    //모든 무기에 공통적으로 들어가는 변수 선언
    protected int damage;
    protected int coolTime;
}

public class Knife : Weapon
{
    //찌르기
    public void Stab()
    {
        Debug.Log("칼로 찌르기 동작 실행(상세내용은 생략).");
    }
}


public class Gun : Weapon
{
    //총에 공통적으로 필요한 기본 정보들
    protected int maxBulletCount;
    protected int curBulletCount;
    protected int reloadCoolTime;
    protected float maxReboundRange;

    //발사
    public virtual void Shoot()
    {
        Debug.Log("Shoot이 하위 클래스에서 정의되지 않은것으로 보입니다.");
    }

    //재장전
    public virtual void Reload()
    {
        Debug.Log("reload가 하위 클래스에서 정의되지 않은 것으로 보입니다.");
    }
}


public class M4A1 : Gun
{
    public override void Shoot()
    {
        Debug.Log("M4A1 발사");
    }

    public override void Reload()
    {
        Debug.Log("M4A1 재장전");
    }
}



//커맨드
public interface IWeaponCommand
{
    void Execute();
}


public class ReloadGunCommand : IWeaponCommand
{
    private Gun gun;

    public void Execute()
    {
        //총쏘기
        gun.Reload();
    }

    public ReloadGunCommand(Gun val)
    {
        gun = val;
    }
}


public class ShootGunCommand : IWeaponCommand
{
    private Gun gun;

    public void Execute()
    {
        //총쏘기
        gun.Shoot();
    }

    public ShootGunCommand(Gun val)
    {
        gun = val;
    }
}


public class StabKnifeCommand : IWeaponCommand
{
    private Knife knife;

    public void Execute()
    {
        //찌르기 실행
        knife.Stab();
    }

    public StabKnifeCommand(Knife val)
    {
        knife = val;
    }
}



//인보커. 커맨드 인스턴스들을 보유하고 관리.
public class CommandManager
{
    private Dictionary commandDic = new Dictionary();

    //커맨드를 세팅
    public void SetCommand(string name, IWeaponCommand command)
    {
        if(commandDic.ContainsValue(command))
        {
            Debug.Log("이미 커맨드가 리스트 포함되어있음.");
            return;
        }
        commandDic.Add(name, command);
    }

    //저장된 특정 커맨드를 실행
    public void InvokeExecute(string name)
    {
        commandDic[name].Execute();
    }
}



//커맨드와 리시버, 인보커를 세팅하고 원할 때 실행하는 클라이언트
public class WeaponSystem : MonoBehaviour
{
    CommandManager commandMgr = null;

    void Start()
    {
        //인보커 생성
        commandMgr = new CommandManager();
        
        //리시버 생성
        M4A1 m4a1 = new M4A1();
        Knife knife = new Knife();

        //커맨드를 생성하고 리시버와 연결
        ShootGunCommand shootM4a1Command = new ShootGunCommand(m4a1);
        ReloadGunCommand reloadM4a1Command = new ReloadGunCommand(m4a1);
        StabKnifeCommand stabKnifeCommand = new StabKnifeCommand(knife);

        //인보커에 커맨드를 세팅해서 인보커가 커맨드를 실행할 수 있게함.
        commandMgr.SetCommand("leftClick", shootM4a1Command);
        commandMgr.SetCommand("RKey", reloadM4a1Command);
        commandMgr.SetCommand("FKey", stabKnifeCommand);
    }

    void Update()
    {
        //좌클릭
        if(Input.GetMouseButton(0))
        {
            commandMgr.InvokeExecute("leftClick");
        }

        //재장전
        if(Input.GetKeyDown(KeyCode.R))
        {
            commandMgr.InvokeExecute("RKey");
        }

        //칼 휘두르기
        if (Input.GetKeyDown(KeyCode.F))
        {
            commandMgr.InvokeExecute("FKey");
        }
    }
}


+ Recent posts