∇ 스크립트 강좌

 ▼ 스크립트의 구현

C

소스無


그렇다면 스크립트는 어떻게 구현해야 하는가가 문제가 된다. 스크립트라는 것은 게임에 도움을 주기 위한 하나의 수단이지만 그 자체가 게임 프로그래밍의 목적은 아니다. 따라서 스크립트의 중요성에 따라서 스크립트 엔진의 구현 정도는 판이하게 달라진다. 위에서 예를 든 가장 간단한 형태의 롤플레잉 게임의 스크립트를 다시 보자.

1. 스크립트 구현 에제


    01:  #이름: "우민1"
    02:  #초기좌표: (10,10)
    03:  #생김새: 10
    04:  #대화: "마을 남쪽의 동굴에 용이 살고 있다는 전설이 있어"
     

    우선 이것은 텍스트 형태로 저장되어 있다. 따라서 우리는 각각의 라인 별로 데이터를 가져 올 수 있다. 그렇다면 그 다음에는 문장을 분리해야 한다. 간단한 형태의 문장이지만 변수가 있고 그 변수에 들어가는 초기 값이 있다. 그리고 그것들을 구분에 해주는 구분 기호가 있다.

    가장 첫 줄을 보자. 여기서 변수는 ‘이름’이고 그 변수의 초기 값은 ‘”우민1”이다. 사람의 입장에서 보면 눈으로 그 두 가지를 분리해서 name이라는 변수에 “우민1”을 대입하면 되는 것이지만 컴퓨터의 경우에는 파싱의 과정을 거쳐야 비로소 그것을 분리할 수 있다. 보면 알겠지만 변수는 예약된 기호에 둘러 싸여 있다.  “#이름:”과 같이 사용자가 약속한 기호로 시작해서 사용자가 약속한 기호로 끝난다. 따라서 ‘#’이 시작하는 다음부터 ‘:”으로 끝나기 전까지가 변수가 된다. (그리고 그 변수의 앞 뒤에 공백 문자가 있다면 그것을 제거해주는 것까지가 실제 과정이 될 것이다.) 그리고 변수를 읽었으면 그 위의 모든 데이터는 변수의 값이라고 볼 수 있다. 특히 여기서 ‘”’으로 둘러 싸인 곳은 문자열이라고 약속하였으므로 문자열로 취급되어 저장된다.  두 번째 줄의 경우에는 괄호에 둘러 싸인 부분은 좌표를 나타낸다고 약속하였으므로 그렇게 읽으면 된다.

    아래의 코드는 위와 같은 문장을 풀어내기 위해 간략하게 구현해본 것이다.

    먼저 필요한 기본 함수들이 필요하다. 물론 C의 스탠다드 라이브러리만으로도 스크립트를 읽는 데는 문제가 없긴 하지만 아무래도 자주 쓰이는 몇몇 기능에 대해서는 미리 함수로 만들어 두는 것이 좋다. 먼저 자료형을 하나 선언해야 한다.


    01:  typedef char STRING[256];
     

    이것은 단순하게 캐릭터형 배열을 STRING이라는 이름의 타입으로 선언한 것이며 이후의 모든 스크립트 텍스트의 라인은 이 형태로 읽게 된다.


    01:  void ReplaceChar(STRING s, char srcChar, char dstChar)
    02:  {
    03:     int i, len = strlen(s);
    04:     for (i = 0; i < len; i++)
    05:        if (s[i] == srcChar)
    06:           s[i] = dstChar;
    07:  }
     

    이 함수는 s라는 스트링에 있는 srcChar라는 하나의 문자를 dstChar라는 문자로 바꾸는 기능을 한다. 텍스트 파일에는 문자 이외에 사용자가 제어 문자를 삽입할 수 있기 때문에 그것들을 제거 하거나 대치하는데 사용된다. 주로 탭 문자 같이 공백 기능을 하는 문자들을 공백 문자로 대치하여 사용하는데 사용된다.


    01:  #define COPY_STRING(dest, sour, offset, length)    \
    02:     do                                              \
    03:     {                                               \
    04:        int len = (length);                          \
    05:        strncpy(dest, sour + (offset), len);         \
    06:        dest[len] = 0;                               \
    07:     } while(0);
     

    이것은 매크로이며 C의 표준 라이브러리에 있는 strcpy()의 용법을 약간 수정한 것이다. sour라는 스트링의 offset번째부터 length 만큼의 문자열을 dest에 복사하는 기능을 한다. 이것은 앞으로 나올 함수의 기능을 간략하게 보이게 하기 위해 사용되는 매크로이다.


    01:  void FetchWord(STRING fetchStr, STRING srcStr, char separater)
    02:  {
    03:     int   divider;
    04:     char  subStr[2] = {separater, 0};
    05:     char *tempStr;
    06:  
    07:     if (tempStr = strstr(srcStr, subStr))
    08:     {
    09:        divider = (tempStr - srcStr) + 1;
    10:  
    11:        COPY_STRING(fetchStr, srcStr, 0, divider - 1)
    12:        COPY_STRING(srcStr, srcStr, divider, strlen(srcStr)-divider)
    13:     }
    14:     else
    15:     {
    16:        strcpy(fetchStr, srcStr);
    17:        strcpy(srcStr, "");
    18:     }
    19:  }
     

    이것은 스크립트에서 명령어를 분리하거나 파라미터를 분리하기 위한 기본 함수이다. srcStr이라는 스트링에서 separater라는 단어를 기준으로 앞쪽의 문자열을 fetchStr에 집어 넣고 뒤쪽의 문자열만 srcStr에 되돌려 준다. 예를 들어, SetValue(10,10); 이라는 문장이 있을 때 ‘(‘를 기준으로 ‘SetValue’라는 명령어와 나머지 부분인 “10,10);”을 분리하는 역할을 한다.


    01:  typedef enum tagSHURINKMODE
    02:  {
    03:     SHURINKMODE_FORWARD,
    04:     SHURINKMODE_BACKWARD,
    05:     SHURINKMODE_ALLDIRECTION,
    06:     SHURINKMODE_ALLCHARACTER
    07:  } SHURINKMODE;
    08:  
    09:  void ShurinkString(STRING s, char ch, SHURINKMODE mode)
    10:  {
    11:     int    i, j, len;
    12:     STRING aux;
    13:  
    14:     len = (int)strlen(s);
    15:  
    16:     switch (mode)
    17:     {
    18:     case SHURINKMODE_FORWARD:
    19:        for (i = 0; (i < len) && (s[i] == ch); i++);
    20:        COPY_STRING(aux, s, i, strlen(s) - i + 1)
    21:        break;
    22:  
    23:     case SHURINKMODE_BACKWARD:
    24:        for (i = len-1; (i > 0) && (s[i] == ch); i--);
    25:        COPY_STRING(aux, s, 0, i + 1)
    26:        break;
    27:  
    28:     case SHURINKMODE_ALLDIRECTION:
    29:        for (i = 0; (i < len) && (s[i] == ch); i++);
    30:        COPY_STRING(aux, s, i, strlen(s) - i + 1)
    31:        for (i = strlen(aux)-1; (i > 0) && (aux[i] == ch); i--);
    32:        COPY_STRING(aux, aux, 0, i + 1)
    33:        break;
    34:  
    35:     case SHURINKMODE_ALLCHARACTER:
    36:        int cantSkip = 0x01;
    37:  
    38:        // " 를 변경할 때는 무조건 변경
    39:        if (ch == '\"')
    40:           cantSkip |= 0x02;
    41:        
    42:        for (i = 0, j = 0; i < len; i++)
    43:        {
    44:           if (s[i] == '\"')
    45:              cantSkip ^= 0x01;
    46:  
    47:           if ((cantSkip == 0) || (s[i] != ch))
    48:              aux[j++] = s[i];
    49:        }
    50:        aux[j] = 0;
    51:        break;
    52:     }
    53:  
    54:     strcpy(s, aux);
    55:  }
     

    함수 코드가 설명하기에는 좀 긴 느낌이 있다. 기능은 s라는 스트링에서 ch라는 문자를 mode의 방법에 따라 제어하는 기능을 한다.

    mode의 값

    기능

    SHURINKMODE_FORWARD

    문자열의 앞쪽의 ch를 제거한다.

    SHURINKMODE_BACKWARD

    문자열의 뒤쪽의 ch를 제거한다.

    SHURINKMODE_ALLDIRECTION

    문자열의 양쪽의 ch를 제거한다.

    SHURINKMODE_ALLCHARACTER

    문자열 전체에서 ch를 제거한다.

    스크립트를 만드는 사람과 프로그래머가 같은 사람이라면 약속에 의해 정확하게 스크립트 코드를 작성할 수 있을지도 모르겠지만, 일반적으로는 그렇지가 않는 경우가 많다. 따라서 스크립트 작성하는 사람의 코드 작성 취향이나 습관같이 예상하기 어려운 상황들이 스크립트 코드에 들어 갈 수가 있는데 이런 것들을 제거하기 위해서 사용하는 것이 바로 위의 함수이다. 특히 스페이스의 남용이나 간단한 문법의 실수들을 눈감아 주기 위해서도 사용된다. (물론 개발 과정에서는 문법에서 실수가 있을 때 정확하게 잡아 주는 것이 필요하겠지만, 최종 릴리즈 버전에서는 스크립트 에러 때문에 게임이 멈추는 일은 없어야 하므로 문법 실수에 대해서 가장 예측하기 쉬운 쪽으로 스크립트를 해석하는 방법론이 필요하다.)

    위의 모듈을 이용해서 가장 처음에 제시한 스크립트를 읽는 프로그램은 단행본을 구입하면 부록 CD에 있다. (물론 단행본은 나오지 않을 것이다.)