최상단 광고

2012년 4월 11일 수요일

XML 스크립트를 게임에서 사용하는 간단 사례


필자는 이제까지 스크립트를 사용하는 회사에서 일을 해본 적이 없어 스크립트 사용에 대해서는 좀 막연한
생각을 가지고 있었어 미숙하지만 사용해보자는 생각으로 아주 낮은 수준의 스크립트를 만들어 사용하게 되었다.

스크립트의 사용 수준에 대한 설명은 AI WISDOM 2 의 내용을 참고로 하면
수준 0 : 모든 것을 원본 언어(C/C++)로 하드 코딩한다.
수준 1 : 캐릭터/물체의 상태와 위치를 지정하는 자료를 파일에 담는다.
수준 2 : 컷신 시퀀스(비상호작용적)를 스크립트로 지정한다.
수준 3 : 가벼운 로직을 도구나 스크립트로 지정한다. 트리거 시스템 등.
수준 4 : C/C++로 작성된 핵심 기능들에 의존하는 중요하고 복잡한 로직들 까지 스크립트로 지정한다.
수준 5 : 모든 것을 스크립트로 코딩한다. 즉 C/C++ 대신 스크립팅 언어만 사용한다.

내가 사용한 단계는 수준 1 단계이다. 즉 데이터와 코드의 분리 정도이다.

내가 사용했던 것을 미흡하지만 문서로 정리하여 공개하는 이유는
1. 내가 했던 것을 정리하여 다른 사람들과 지식 공유를 하고 싶었어
2. 이거 이외의 xml을 사용하는 다른 방법을 듣고 싶고 내가 사용한 방법보다 더 좋은 방법에
   대해서 다른 사람의 의견을 듣고 싶었어
3. 일본에서 필자가 만든 게임을 맡을 분에게 도움을 좀 드리고자(문서화가 부족하여 그 분이 검토하는데 어려움이
   있을 것 같아 조금이라도 도움을 드리고 싶었어..)
4. 요즘 구직 중인데 필자가 이력서에 xml 스크립트를 사용했다고 하니 궁금해 하시는 분들이 있었어
   대단하게 아니고 초 간단하게 사용했음을 알려드리고 싶어서 -_-;;;


대충 4가지의 이유로 정리를 하여 공개를 한다. 지금 사용한 것은 수준 1 정도 이지만 개인적으로 수준 2까지는
아무 문제 없이 사용하고 수준 3까지도 가능하지 않을까 생각을 한다. 그러나 필자는 앞으로 기회가 되면
수준 3 이후는 Ruby라는 언어를 사용하던가 아니면 Boost의 Splite 라이브러리를 사용하여 만들어 볼까 하는
실천 확률이 극히 떨어지는 생각을 하고 있다.-_-;;;





1. 게임에서 사용 하는 XML 파일의 데이터 구조

 xml 스크립트는 필자가 맡은 온라인 보드 게임의 클라이언트에서 사용하였다. 클라이언트의 UI의 데이터를
xml로 뽑아낸 후 프로그램에서 이것을 로딩하여 사용하게 되었다.

xml 데이터 파일의 내용은 대략 다음과 같다.
       
               
        ………………
        LOBBY_CONTROL>
       
                TITLE_POS>
                MARQUEE_POS>
                ROOMLIST_INFO>
               …………..
         
                VIRTUAL_CAPTIONBAR>
                BACK_IMAGE>
               
                LEFT-TOPAVATAR_POS>
…………….

이런 데이터를 클라이언트 프로그램에서 초기에 로딩을 할 때 불러와서 UI 배치를 데이터를 기준으로 하여 배치한다.
(물론 게임 실행 도중에서 로딩을 해도 문제 없다..데이터의 크기가 작으니..개발 도중은 데이터 수정을 계속
해야 될 테니 중간 중간 로딩을 할 수 있도록 해야 될 것이다).





2. XML 스크립트의 구조

XML을 스크립트로 사용하기 위해서는 가장 먼저 XML 파서가 있어여 한다. 필자는 처음에는 MS의 파서를 사용하였으나
이 파서의 문제는 MS Parse 라이브러리가 컴퓨터에 설치 되어 있지 않다면 작동을 하지 않는 문제가 있었어 대안 방안을
찾던 중 TinyXML 파서에 대해서 알게 되었고 아주 심플하면서 필자가 원하는 기능을 가지고 있었어 이것을 사용하게
되었다(http://jacking75.cafe24.com/Tip/TinyXml_SimpleUse.htm 여기에 초 간단하게 정리하였다..-_-;; 사실 필자도
영어가 약한지라 딱 사용한 부분만 알지 아직도 전 기능에 대해서는 잘 모르고 있다).

그러나 Tiny 파서기 자체만 사용하면 좀 불편하다 그래서 이것을 랩퍼하는 클래스가 있으면 좀 더 사용하기 편하다.
특히 이 랩퍼 클래스를 잘 만들면 범용적으로 사용하면 아주 좋으리라 생각한다. 필자도 이것을 생각을 하였으나
이 것을 개인 연습으로 사용하다가 실제에 적용을 하였고 이리저리 일을 하다 보니 생각만 하지 제대로 정리를
하지 못해 범용적이지 못하고 직관적이지 못하게 만들어졌다.
XMLScript.h
// TODO: XML을 사용할 때의 주의점
/*
    1. XML 문서를 만든후 문법상으로 틀리지 않는지 체크를 위하여 인터넷 익스플러어로 체크해보기를 바란다.
    2. MS의 XML 파서를 사용 할 때는 다음과 같이 인코딩 옵션을 주어야 한글이 인식 가능하게 된다(tiny 파서는 필요 없음).
       
*/


#ifndef __XMLSCRIPT_H__
#define __XMLSCRIPT_H__

#include
#include any.hpp>
#include "XMLDefine.h"
#include "tinyxml.h"




class CXMLScript
{
public:
    CXMLScript(void);
    ~CXMLScript(void);



private:
    TiXmlDocument m_XmlDoc;                  // xml 도큐먼트 객체

    BOOL m_bInit;                            // 초기화 여부
    BOOL m_bLoadXml;                         // xml 파일 로딩 여부
   
        TiXmlNode*     m_pCurNode;            // xml 노드


public:
        TiXmlElement* m_pelement;             // xml 엘레먼트

        TiXmlAttribute* m_pattribut;          // xml 어트리뷰트
      
  
   
public:
        // xml 도큐먼트 객체를 반환
        TiXmlDocument* GetXmlDoc() { return &m_XmlDoc; }    

    //! XML 문서를 로딩한다.
    int LoadXml(const std::string& _strxmlDoc);

int SeperateNode( const std::string& _strNode, std::vector< std::string >& _vecString );

    // 원하는 위치의 퀋E躍?엘렌먼트로 얻는다.
    TiXmlElement* GetElement( const std::string& _strNode );

    //! 지정된 컨트롤 정보를 모두 얻는다.
    int GetAllControLayOut( int _iNodeDepthNum, std::string& _strNodeName, std::vector& _vecRect );
    //! 게임 방의 버튼의 정보를 얻는다.
    int GetRoomButtonInfo( int _iNodeDepthNum, std::string& _strNodeName, std::string& _strSubNodeName,
                           int _iFindIndex, ROOM_BUTTON& _RoomButton );

    //! 지정한 단계(깊이)의 노드에 있는 어트리뷰트 값을 읽어온다.
    int GetAttributeInt( int _iNodeDepthNum, std::string& _strNodeName, std::string& _strSubNodeName,
                         std::string& _strAttriName, int _iFindValue, std::vector& _vecAttribute,
                         int _iStart );
    
// 지정된 노드로 이동한다.
    int SetNodePosition( std::string& _strNodeName );

        // 현재 위치한 노드의 지정된 자식노드의 어트리뷰트 값을 읽어온다.
        int GetCurNodeChildIntAttribute( std::string& _strNodeName, int _iStart, std::vector& _vecAttribute );
        int GetCurNodeChildIntAttribute( std::string& _strNodeName, int& _iValue );
        int GetCurNodeChildIntAttribute( std::string& _strNodeName, int& _iValue1, int& _iValue2 );
        int GetCurNodeChildIntAttribute( std::string& _strNodeName, int& _iValue1, int& _iValue2, int& _iValue3, int& _iValue4 );
        int GetCurNodeChildStringAttribute( std::string& _strNodeName, std::string& _string );
        int GetCurNodeChildStringAttribute( std::string& _strNodeName, int _iStart, std::vector& _vecAttribute );
        int GetCurNodeChildRECTAttribute( std::string& _strNodeName, CRect& _rect );
       
        // 지정된 노드의 첫번째 어트리뷰트에 이동하도록 한다.( 함수 이름이 참 거시기 하다..-_-;;;)
       int GetCurNodeChildAttributeObj( std::string& _strNodeName );

public:
        // xml 도큐먼트를 비운다.
        void Clear() { m_XmlDoc.Clear(); }

        // 사용자가 지정한 데이터를 xml 파일을 만든다.
        void MakeXmlFile( const char* _pInitData );
};

#endif



또 이 랩퍼 클래스 이외에 직접 해당 게임에서 사용할 때 xml 데이터를 담은 후 사용하는 클래스가 있다면 좋으리라
생각한다. 이 클래스는 최대한 게임에 맞게 만들어 게임에서 쉽게 사용 할 수 있도록 하면 좋다. 철저하게 해당 게임에서
사용하는 데이트를 담을 필드로 구성하고 이 부분은 게임마다 다르게 만들어 사용하면 된다.
ScriptData.h
#ifndef _SCRIPTDATA_H__
#define _SCRIPTDATA_H__


struct LOBBY_LAYOUT_INFO
{
        int TitleTextPosX, TitleTextPosY;
       
        int MarqueePosX, MarqueePosY;
       
        int RoomListHeadRGB[3], RoomListBackRGB[3],  RoomListTextRGB[3];
       
        int UserListHeadRGB[3], UserListBackRGB[3], UserListTextRGB[3];
       
        int ChatOutL, ChatOutT, ChatOutR, ChatOutB,
               ChatOutBackRGB[3], ChatOutTextRGB[3];

        int GameStateInfoX, GameStateInfoY;
};


class CScriptData
{
public:
        CScriptData(void);
        ~CScriptData(void);

        BOOL Load_LobbyLayOutData( LOBBY_LAYOUT_INFO& _LobbyLayoutInfo );

        BOOL Load_RoomLayOutData();

        BOOL SetMarkPos();

        BOOL SetBaePaePos();

        BOOL SetMahJongPaePos();

        BOOL SetPositionXYArray4( std::string& _strNode, int* _pIntPos );

       


private:
        std::string m_strWork;
        std::vector m_vecIntAttributework;



public:
        int TurnMarkPosX[4], TurnMarkPosY[4];
        int MasterMarkPosX[4], MasterMarkPosY[4];
        int GigaMarkPosX[4], GigaMarkPosY[4];

        int BaePaeStartPosX[4], BaePaeStartPosY[4], BaePaeLastPos[4];
        int PlayerPaePosX[4], PlayerPaePosY[4];
        int ThrowPaePosX[4], ThrowPaePosY[4];
        int AddPae_PosX[4], AddPae_PosY[4];
        int RegistPae_PosX[4], RegistPae_PosY[4];
        int m_WanPai_PosX, m_WanPai_PosY;

        int ThrowPaeNumOf1Line;
        int iMahJongPanInfoPosX, iMahJongPanInfoPosY;

        int iSDirSprForAvatarPosX[4], iSDirSprForAvatarPosY[4], iLDirSprForAvatarPosX[4], iLDirSprForAvatarPosY[4];

        int iYakitoriPosXForAvatarPos[4], iYakitoriPosYForAvatarPos[4];

        int iRichiPosXForAvatarPos[4], iRichiPosYForAvatarPos[4];

        int iKukTextPosX, iKukTextPosY;

        int iBonNumTextPosX, iBonNumTextPosY, iReachNumTextPosX, iReachNumTextPosY, iCurPaiNumTextPosX, iCurPaiNumTextPosY;

        int iDice1PosX, iDice1PosY, iDice2PosX, iDice2PosY;

        int iTimeBarPosX, iTimeBarPosY;

        int iTimeBarLength, iTurnTimeBarLength;

        int iEffectRegistSprPosX[4], iEffectRegistSprPosY[4];

        int iSelectJji1PosX, iSelectJji1PosY;
        int iSelectJji1MouseOverX1, iSelectJji1MouseOverY1, iSelectJji1MouseOverX2, iSelectJji1MouseOverY2;
        int iSelectJji1PaePosX1, iSelectJji1PaePosY1, iSelectJji1PaePosX2, iSelectJji1PaePosY2;

        int iSelectJji2PosX, iSelectJji2PosY;
        int iSelectJji2MouseOverX1, iSelectJji2MouseOverY1, iSelectJji2MouseOverX2, iSelectJji2MouseOverY2,
               iSelectJji2MouseOverX3, iSelectJji2MouseOverY3;
        int iSelectJji2PaePosX1, iSelectJji2PaePosY1, iSelectJji2PaePosX2, iSelectJji2PaePosY2,
               iSelectJji2PaePosX3, iSelectJji2PaePosY3;

        int iSelectJjiAreaW, iSelectJjiAreaH, iSelectJjiPaeWidth;

        int iPlayingGamerInfoPosX[4], iPlayingGamerInfoPosY[4];

        int iGameReadyOKPosX[4], iGameReadyOKPosY[4];

        int iExitSubscriptPosX[4], iExitSubscriptPosY[4];

        CRect   m_GameDlgOutPutChatCtrRect;
        int m_GameDlgOutPutChatCtrRGB[3];
        
        CRect   m_GameDlgInputChatCtrRect;
        int m_GameDlgInputChatCtrRGB[3];
       
        CRect   m_GameDlgInfoOutPutCtrRect;
        int m_GameDlgInfoOutPutCtrRGB[3];

        BOOL SetGameDlgChatCtrInfo();

        void GetChatCtlImage( char* _strChatCtlName, char* _strUpBtnFile, char* _strDownBtnFile,
                                                        char* _strThumbBtnFile, char* _strBKFile );

        CRect   m_GameRoomCaptionBarRect;
       
        void GetObserveAvatarImgFile( char* _strChatCtlName, char* _str1, char* _str2, char* _str3, char* _str4 );

        bool GetRoomButtonInfo( char* _strBtnName, CString& _cstrFileName, CRect& _rect );

        CRect          m_FaceSelectDlgRect;
       
        int m_GameViewFaceIConPosX[4], m_GameViewFaceIConPosY[4];
       
        int m_ShortKeyPai_PosX[14], m_ShortKeyPai_PosY[14], m_ShortKeyBtn_PosX[14], m_ShortKeyBtn_PosY[14];
        bool SetShortKeySprPos();

int m_RuleOptionImgPosX[12], m_RuleOptionImgPosY[12];
        bool SetRuleOPtionImgPos();

        int m_RoomMaxIDSizeW;

        std::string    m_strGameBackImg1FileName[ MAX_BACKGROUNDIMAGE_NUM ];

        std::string    m_strPrevRallyWinnersNickName[ RALLY_PREV_WINNER_NUM] ;
        bool GetPrevRallyWinnersNickNames();
       
        void Reloaded_RoomInfo();
};

#endif



이렇게 구성되어 있는 파일을 프로그램에서는 다음과 같이 사용한다.





3. 사용

프로그램의 초기화 부분에서 파일을 로딩하여 데이터를 읽어온다.
…………….
if( SUCCESS_XMLSCRIPT != g_XmlScript.LoadXml( std::string(".\\\\Data\\\\MaJak_Para.xml") ) )
{
    AfxMessageBox(_T("xml 파일로딩 실패"));
}

// 방의 레이아웃 데이터를 로딩한다.
g_ScriptData.Load_RoomLayOutData();
………….


ScriptData 클래스에서 XMLScript 클래스(g_XmlScript)의 사용 예는 다음과 같다.
bool CScriptData::SetRuleOPtionImgPos()
{
        int i;
        std::string strTargetNode;
        std::vector vecIntAttribute;

       
        // 룰 옵션 표시를 보여줄 X 좌표의 위치 데이터의 노드
        strTargetNode = "RULE_OPTION_VIEW_POSX";
        g_XmlScript.GetCurNodeChildIntAttribute( strTargetNode, 0, vecIntAttribute );
        
        // 데이터의 수가 12개가 아니면 xml 파일에 데이터 잘못 들어가 있는 것이다.
        if( vecIntAttribute.size() != 12 )
               return false;

        // 읽어온 데이터를 변수에 담아 놓는다.
        for( i = 0; i < 12; ++i )
               m_RuleOptionImgPosX[ i ] = vecIntAttribute[i];


        strTargetNode = "RULE_OPTION_VIEW_POSY";
        g_XmlScript.GetCurNodeChildIntAttribute( strTargetNode, 0, vecIntAttribute );
       
        if( vecIntAttribute.size() != 12 )
               return false;

        for( i = 0; i < 12; ++i )
               m_RuleOptionImgPosY[ i ] = vecIntAttribute[i];


        return true;
}



프로그램에서 UI 배치 때의 사용 예는 다음과 같다.
……….
std::vector vecCtrInfo;
std::string strNode( "LOBBY_CONTROL" );      // 로비의 컨트롤 버튼의 레이아웃 정보 노드
if( SUCCESS_XMLSCRIPT == g_XmlScript.GetAllControLayOut( 1, strNode, vecCtrInfo ) )        // 레이아웃 데이터를 읽어서 담는다.
{
    int iControlNum = vecCtrInfo.size();
    for( i = 0; i < iControlNum; ++i )
    {
        // xml에서 읽어온 데이터를 기준으로 컨트롤을 배치 한다.
        GetDlgItem( vecCtrInfo[i].index )->MoveWindow( vecCtrInfo[i].x, vecCtrInfo[i].y, vecCtrInfo[i].w, vecCtrInfo[i].h );
    }
}
else
{
   AfxMessageBox(_T("로비의 컨트롤 버튼의 레이아웃 정보를 읽지 못했습니다."));
}
………….




대충 이런 식으로 사용하였다.
Xml 스크립트를 개인 프로젝트로 만들어 사용하다가 실제 프로젝트에 적용을 하게 되어서 실질적으로 클라이언트의 전 부분에 사용할 수 있는 곳에 사용하지는 않았지만 왠만한 부분은 다 적용했으면 적용을 하지 못한 부분은 다음에 그 부분의 수정 시에 적용하기로 생각하였다(그러나 그러지 못했다.-_-;;).

실제 처음에 XML스크립트를 사용했던 목적은 위에 적었듯이 공부의 목적이 있었지만 변경이 있을 UI 부분을
유연하게 하여 작업시간을 줄이고(실제 필자가 맡은 게임의 버전 2를 만들 때 UI쪽의 일이 상당히 경감되었다)
장래적으로 WinAmp의 스킨 기능과 같은 것을 게임 클라이언트에서 적용하고 싶어서 사용하였다.
아직(필자가 알기로) 온라인 보드 게임에서 유저가 직접 개입하여 스킨을 만들어 사용 할 수 있는 것은 없는
것으로 안다(특히 이 부분은 스킨 기능을 만들 때 사용 할 유틸리티 프로그램이 필요하다고 생각한다).
중도에 회사를 그만두는 바람에 스킨 기능까지 적용을 해보지 못해 아쉽지만 이 부분은 기획에 따라서는
유료 기능으로 사용 할 수도 있고, 유저는 게임 클라이언트에 애착을 가질 수 있으리라 생각한다.




처음 이야기 했듯이 별로 쓸만한 것은 없지만 혹시나 그래도 참고로 하고 싶은 사람은 링크된 파일을
받아서 보면 될 것이다. Script Lib
다시 한번 말하지만 소스가 지저분하고 너무 간단하니 이걸 사용한다면 고쳐야 될게 상당히 많으리라
생각한다. 그리고 쓸만하게 고치신 분은 꼭 필자에게도 보여줘서 견문을 넓혀주기를 바란다.^^

댓글 없음: