블로그 이미지
지누구루

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

2014. 12. 11. 14:51 공부

오랜만에 업무와 관련된 내용으로 글을 써봅니다.

꽤 오래전에 만든 구조이긴 한데, 현재까지 사용해본 결과 나름 괜찮은 구조라고 생각해서 글로 남겨봅니다.

 

관련된 내용은

"게임에서 아이템을 사용했을때의 효과" 를 서버측에서 필요한 기능을 구현 하는 내용입니다.

어렵지 않은 내용이고,

이미 존재하는 패턴이라서 특별히 좋은 구조다 라고 할만한 건 아닙니다.

 

일단 기본적인 생각은

1) 아이템을 사용 하고 차감하는 기능

2) 아이템을 사용함으로써 발동되는 기능

을 분리하자고 생각한 것에서 출발한 구조입니다.

 

분리하자고 느낀 이유는 가장 처음에 몸담았던 게임의 아이템 사용 구조가,

아이템에 새로운 기능을 추가할때마다, 완전히 각각 새로 구현해야 하는게 비효율적이라고 느꼈기 때문이며,

그리고 같은 기능인데, 적용하는 수치가 다르다거나 하는 정도의 변경만 가지는 새로운 아이템 추가,

또는 완전히 같은 기능인데, 아이템을 새로 만들어야 하는 경우

(같은 옵션인데 이벤트로 지급하기 위해 기간제로 만들거나 하는 경우)

그때마다, 아이템 작업을 프로그래머가 직접 해야 하는 것이 비효율적이라고 생각했기 때문입니다.

 

+ 아이템 사용쪽 코드 흐름

- 네이밍 규칙은 회사에서 사용중인 부분이 좀 있습니다. 함수 이름에 'on' 을 붙이는건 어떤 행동이 일어 났을 때 처리되어야 하는 함수를 뜻합니다. onUseItem -> 아이템이 사용되었을때 처리해야 하는 일

void useItem(playerInfo,itemInfo,parameter)
{
    // 1. 아이템 사용 조건체크 - 수량 체크나 쿨타임 체크 등등
    if( isItemUsableState(playerInfo,itemInfo) == false )
        return;

    // 2. 아이템에 연결된 기능을 실행
    if( executeItemFunction(playerInfo,itemInfo,parameter) != no_error )
        return;

    // 3. 아이템 사용 후속 조치 - 쿨타임 갱신 등등
    onUseItem(playerInfo,itemInfo);

    // 4. 사용 아이템 삭제(내용에 따라 한번에 여러개 삭제도 가능)
    deleteItem(playerInfo,itemInfo,parameter);
}

 

받는 파라메터는

playerInfo : 아이템을 사용한 플레이어 정보(캐릭터 정보가 포함될수도)

itemInfo : 사용한 아이템의 정보(현재 가지고 있는)

parameter : 아이템 사용에 필요한 추가 정보

추가 정보의 경우, 예를 들어, 장비에 사용하는 아이템이라면 해당 장비의 정보까지 포함이 됩니다. 이 내용은 아이템마다 내용이 다르므로 범용적으로 받을 수 있어야 합니다.

범용적으로 데이터를 받는 방법에는 여러가지가 있겠지만, 저는 그냥 스트링으로 넘기고 사용하는 쪽에서 파싱해서 쓰도록 했습니다.

 

"아이템에 연결된 기능이 실패하면 아이템 사용이 되지 않습니다"

 

이중에서 밑줄 그은 executeFunction() 함수의 내부는 아래와 같습니다.

error_code executeFunction(playerInfo,itemInfo,parater)
{
    functionType = itemInfo->getFunctionType();
    functionExcutor = getProperExecutor(functionType);

    return functionExecutor->execute(playerInfo,itemInfo,parameter);
}

내용을 간단히 보면.

(1) 아이템에 연결된 기능이 무엇인지 찾은 다음

(2) 해당 기능을 실행할 executor 를 찾아서

(3) 해당 executor의 기능을 실행 합니다.

 

여기서 아이템에 연결된 기능, 코드에서는 functionType만 파라메터로 사용해도 무관합니다.

사실 itemInfo는 여기까지만 존재해도 되는경우가 많습니다.

이 코드에서 itemInfo를 다 넘기는 이유는,

기능과 연결된 아이템의 정보를 다시 검증하기 위한 용도와,

기능 사용에 필요한 추가적인 데이터가 아이템 정보에 있을 경우를 위함입니다.

반드시 있어야 하는 파라메터는 아닙니다.

 

위 내용중 getProperExecutor() 함수는 함수로 빼놓기는 햇지만 큰 기능이 있는건 아니고,

type에 연결된 executor를 그냥 돌려주는 용도입니다.

저는 배열로 만들어 놓고, type에 해당하는 index에 있는 executor를 그냥 돌려주도록 했습니다.

 

이제부터는 실행되는 Function Executor에 대한 내용을 살펴볼텐데,

template method 패턴이 사용되기 때문에 미리 코드를 보고 다시 설명하겠습니다.

class CFunctionExecutor
{
private :
    playerInfo_;
    itemInfo_;

public :
    error_code execute(playerInfo,itemInfo,paramter)
    {
        itemInfo_ = itemInfo;
        playerInfo_ = playerInfo;
        if( _parseParameter(parameter) == false )
            return error_parse;

        error_code = _execute();
        _clear();

        return error_code;
    }

protected :
    virtual bool        _parseParameter(parameter){};
    virtual error_code  _execute()=0;
    virtual void        _clear() {};
};

class CEmptyExecutor : public CFunctionExecutor
{
protected :
    virtual error_code  _execute() {return no_error;}
};

 

CFunctionExecutor 가 최상위 클래스이며,

CEmptyExecutor 는 상속받아 만들어진 클래스 입니다.

(생성자,파괴자는 생략하였습니다)

 

CFunctionExecutor의 execute() 함수를 보시면

_parseParamter(), _execute(), _clear() 가 차례대로 사용되고 있습니다.

 

이중에 _execute() 함수만 순수 가상함수로, 상속 받은 클래스들은 반드시 이 함수를 구현해야 합니다.

parse와 clear의 경우는 할 필요가 없는 기능들이 많아서 빈 함수로 일단 넣어놓고,

상속받은 클래스에서 재정의 할 필요가 있으면 재정의 해서 사용하도록 하였습니다.

 

일단 아무 기능도 하지 않는 EmptyExeucotr()의 경우 parse와 clear역시 할게 없기 때문에 _execute() 함수만 재정의 하였습니다.

 

_parseParameter() 함수는 넘어온 파라메터에서 필요한 파라메터를 다시 정리하는 부분입니다.

_execute() 함수에서는 파싱된 데이터를 기반으로 기능을 실행하고

_clear() 함수에서는 기능 실행에 사용된 데이터를 초기화 하도록 합니다.

 

예를 들어, 사용할 경우 플레이어에게 버프를 주는 아이템을 만들고 싶다. 라고 하면

class CBuffExecutor : public CFunctionExecutor
{
protected :
    buff_info_;

protected :
    virtual bool        _parseParameter(parameter);
    virtual error_code  _execute();
};

bool CBuffExecutor::_parseParameter(parameter)
{
    // buff_info_ = 데이터 만들기.
}

error_code CBuffExecutor::_execute()
{
    // 버프 시스템 호출
    // CBuffSystem::buff(playerInfo_,buffinfo);
}

 

정도로 구현됩니다.

(실제 적용된 코드와는 많은 차이가 있습니다)

 

이렇게 되면 새로운 아이템 기능 추가의 경우

새로운 클래스를 CFunctionExecutor 상속받아서 생성하고

경우에 따라 _parseParameter(), _execute(), _clear() 를 구현하면 완성 됩니다.

 

이미 있는 기능을 새 아이템에 넣고 싶다면

아이템 정보에서 functionType을 같은 걸로 연결만 하면 가능합니다.

(이건 아이템 스크립트 작업자의 몫!!)

 

좀더 복잡한 기능이 필요한 경우에는,

복잡한 기능에 필요한 데이터를 parameter에 같이 넘기거나,

아이템 정보에 포함시켜 놓아서 연결해도 괜찮습니다.

 

구조 자체는 예~~~전에 보았던 Test Unit 쪽 코드와 구조가 완전히 동일합니다.....(어디서 봤더라-_-)

prepare 하고 test 하고 post 뭐시기 하고 이런 과정이었던거 같습니다.

 

글이 꽤 길어졌습니다.

이만 마무리!!!

 

 

 

posted by 지누구루