로그인

회원가입 | ID/PW 찾기

연재

게임 개발에 사용되는 도구들 ①

게임 프로그래머 문기영의 ‘게임 프로그래머 이야기’ 11화

ProgC 2015-01-19 11:51:12

이제 이번화를 비롯해 2~3회에 걸쳐 게임 개발에 사용되는 도구(툴)에 대한 이야기를 할 것이다. 우리가 무언가를 만들 때는 도구가 필요하다. 이전에도 설명했지만, 컴퓨터 소프트웨어를 작성하기 위해 컴퓨터 프로그래밍 언어가 필요했고 수학이라는 도구를 이용해 논리를 표현했다. 거기서 한층 더 나아가 게임 개발용 도구를 이용하면 더 쉽게 게임을 제작할 수 있다.

 

대부분의 게임 개발사는 이미 다양한 도구들을 사용하고 있다. 프로그래밍 언어뿐만이 아니라 캐릭터를 그리기 위해 포토샵(Photoshop)과 같은 도구를 사용하고, 캐릭터의 3D 모델링을 만들기 위해 3D Max나 Maya를 사용한다.

 

이러한 도구들이 기본에 속한다면, 이제 필자가 설명할 부분은 실제 게임 개발에 사용하는 도구에 대한 이야기다. 현재 게임 업계에 종사하는 사람들이 어떤 도구를 사용하는지 알아볼 것이다.

 

 

스프라이트(Sprite)


스프라이트는 2D 이미지들을 연속적으로 화면에 나타날 때 애니메이션이 되는데, 이러한 애니메이션이 될 객체를 스프라이트라고 한다. 가령, 다음과 같은 여자 캐릭터가 있다고 치자.

 

 

iOS, Android 게임 <질풍 행성>(Flurry Planet)의 여자 캐릭터

 

현재 화면에 보이는 이미지는 하나의 이미지다. 이 캐릭터가 달리는 것을 표현하기 위해 디자이너가 여러 장의 그림을 그리면 다음과 같이 하나의 캐릭터로 표현할 수 있다.

 

4개의 프레임으로 이루어진 여자 캐릭터

 

4개의 프레임을 연속으로 번갈아가면서 화면에 표시하면 다음과 같이 달리는 캐릭터가 완성된다.

 

애니메이션시킨 모습

 

스프라이트는 기본적으로 이렇게 구성된다. 이제 이러한 캐릭터를 프로그래머가 움직이는 로직을 작성해 움직여주면 캐릭터가 왼쪽으로 달리는 것처럼 보이는 것이다.

 

 

애니메이션 툴


앞서 4장의 스프라이트 이미지를 이용해 달리는 애니메이션을 화면에 나타내었다. 이제 중요한 것은 이러한 각각의 이미지들을 얼마만큼의 시간 간격으로 화면에 나타내는지에 따라 애니메이션의 속도가 결정된다.

 

예를 들어, 위의 이미지들을 차례대로 A, B, C, D라고 두고, A가 화면에 나타나서 지속하는 시간이 1초라고 생각하자. B, C, D도 각각 모두 1초씩 화면에 나타나서 지속한다고 생각하면 우리가 4장의 그림을 모두 보려면 4초가 걸린다.

 

이러한 이미지들이 화면에 지속되는 시간을 조정해서 애니메이션을 부드럽게 한다거나, 타격감을 느끼게 할 수 있다. 이미지가 4개 있고, 텍스트 파일에

 

A, 1

B, 1

C, 1

D, 1

 

위와 같이 저장한 후, 이것을 컴퓨터 프로그램에서 애니메이션 데이터로 사용할 수 있다. 쉼표(,)를 기준으로 이미지 파일, 그리고 지속되는 시간을 표현한 것이다. 만일 C라는 이미지에서 경직된 이미지를 표현하고 싶다면 뒤에 있는 숫자를 수정하면 된다.

 

A, 1

B, 1

C, 3

D, 1

 

애니메이션을 시키면 C라는 이미지가 3초간 화면에 나타나기 때문에 정지한 느낌을 받을 것이다. 이제 새로운 이미지 E가 추가될 경우에는 어떻게 될까? 아마도 다음과 같이 애니메이션 데이터를 구성할 것이다.

 

A, 1

B, 1

C, 3

D, 1

E, 1

 

애니메이션 데이터의 의미는 E라는 이미지를 1초간 화면에 지속시킨다는 뜻이다. 이렇게 애니메이션이 단순하게 구성되어 있다면 이미지, 숫자 값을 읽어 프로그램에서 간단히 사용할 수 있다.

 

하지만 보통 게임을 개발할 때는 애니메이션 툴을 사용하게 된다. 그 이유는 게임에서 사용하는 애니메이션들은 간단하지 않기 때문이다. 캐릭터가 달려가는 모션만 해도 보통은 4프레임 (이미지 4장) 이상이고, 각각의 프레임마다 지속시간이 항상 일정하지 않아 단순히 텍스트 파일을 이용하기에는 쉽지 않다.

 

필자가 예전에 만들었던 Dead Animation이라는 도구를 이용해 이 개념을 이해해보도록 하자.

 

Dead Animation 도구의 초기화면

 

스프라이트 등록하는 과정은 게임 개발도구마다 사용방법이 다르므로 그 부분은 생략하기로 하고 스프라이트 이미지 4개가 이미 등록되어 있다고 가정하자.

 


4개의 이미지가 하나의 이미지에 담겨있다. 이런 것을 스프라이트 시트(Sprite sheet)혹은 아틀라스(Atlas)라고 한다.

 

이미지를 하나의 스프라이트 시트에 넣어두는 이유는 관리하기도 편하고, 프로그램 내부적으로 속도가 더 빠르기 때문이다. 필자가 만들었던 게임 <Flurry Planet>이라는 게임에서 사용하는 스프라이트 시트의 한 예는 다음과 같다.

 

 

게임 <Flurry Planet>의 스프라이트 시트.

 

필자가 만든 애니메이션 툴은 스프라이트 시트를 기반으로 애니메이션을 하게 구성되어 있기 때문에 반드시 스프라이트 시트를 이용해야 한다. 아무튼, 이렇게 스프라이트 시트를 구성한 후 플레이어가 뛰는 동작을 만들기 위해서 애니메이션을 추가한다.

 

 

RunAnimation을 추가한다.

 

RunAnimation이라는 애니메이션을 추가하면 아래와 같은 타임라인(Timeline)이 등장한다. 이것은 플래시의 타임라인 기능과 매우 유사한데, 그 이유는 필자가 비슷하게 만들었기 때문이다.

 

RunAnimation은 타임라인 기반으로 동작한다.

  

앞서 여자 캐릭터가 달려가는 4개의 이미지를 이용해 뛰어가는 애니메이션으로 만들기 위해서는 4장의 이미지를 몇 초간 화면에 나타나게 하는지에 대한 정보가 있어야 하는데, 이것을 위해 Add Actor를 눌러서 Actor를 추가해 주어야 한다. 

 

※ 노트: 이러한 내용은 게임 도구에 따라 다르지만, 기본적인 사용방법은 비슷하다. 필자가 여기서 도구 사용법을 설명하는 이유는 이러한 도구를 직접 만들 때 프로그래머들에게 도움이 될 것으로 생각하기 때문이다.

Actor는 게임내에 등장해 움직일 수 있는 오브젝트들을 말한다. 이것을 추가하고 나면 다음과 같이 채널이라는 형태로 타임라인에 추가된다.

 

Actor를 하나 추가한 모습

 

현재 채널의 이름은 TempActor로, 아무런 의미가 없다. 이 채널은 이후에 캐릭터의 모습을 반영하므로 이름을 수정해 Player로 설정한다.

 

 

채널의 이름을 Player로 설정.

 

이제 이 플레이어가 화면에 나타나야 하는데, 현재는 화면에 아무것도 나타나지 않는다. 왜냐면 이미지를 표시할 프레임을 지정하는 ‘키 프레임’을 추가하지 않았기 때문이다. 키 프레임이라는 것을 추가해서 어떠한 이미지를 몇 초간 화면에 나타낼 것인지 추가해 주어야 한다.

 

※ 노트: 프레임(frame)이란 정지 화면의 단위를 나타낸다. 초당 30프레임, 60프레임이라는 말을 들어봤을 텐데, 이는 1초에 30개의 정지 화면이 지나간다는 뜻이다. 초당 30프레임이 기준일 경우, 3프레임은 3/30초, 즉 0.1초 사이에 지나간다.

 

 

 

첫 번째 키 프레임(1프레임)에 마우스 오른쪽 버튼을 눌러 키 프레임을 추가한다.

 

첫 번째 키 프레임에 키를 추가한 모습

 

키를 추가해도 화면에는 아무런 이미지가 표시되지 않는다. 그 이유는 현재 키 프레임에 어떠한 이미지를 화면에 표시해야 할 지 프로그램은 모르기 때문이다. 이러한 정보를 설정하기 위해 스프라이트 윈도우에서 이미지를 하나 선택해 드래그 앤 드랍해서 키 프레임 위에 올려 놓으면 이미지가 설정된다. 

 

스프라이트 윈도우에서 이미지를 하나 선택해 새로 추가한 키 프레임에 드래그 앤 드랍해서 이미지를 설정한다.

 

그러면 화면에 다음과 같이 여자 캐릭터 이미지가 나타난다.

 

 

드디어 여자 캐릭터가 화면에 나타난다.

 

현재 달리기 모션은 1프레임만 존재하므로, 달리는 모습을 표현하기 위해 나머지 3개의 키 프레임들도 등록해 주어야 한다.

 

4개의 키 프레임들을 추가한 모습

 

키 프레임을 위와 같이 설정하면 A라는 이미지가 1프레임에서 화면에 나오고, 3프레임 이후 B라는 이미지가 나온다. 그 이후에도 3프레임 이후에 지속적으로 다른 이미지로 교체되면서 애니메이션이 된다. 현재는 각각의 이미지마다 지속시간이 일정하지만, 키 프레임을 이동해서 다른 지속시간을 가질 수 있다.

 

도구를 이용했을 때 장점은 바로 수정이 쉽다는 점이다. (물론, 도구가 그 기능을 제공했을 때)만일, 키 프레임 B를 3레임이 아니라 6레임 뒤로 옮기려면 어떻게 해야 할까? 단순하게 키 프레임을 잡고 움직여서 배치하면 된다.

 

키 프레임 B의 위치를 변경한 모습

 

만일 B뿐 아니라 전체를 다 움직여야 한다면? 전체를 선택하고 이동하면 된다.

 

전체를 선택한 후,

 

원하는 위치에 드래그 앤 드랍해서 위치를 변경한다.

 

선택된 키 프레임들이 원하는 위치로 변경되었다.

 

이제 애니메이션 툴에서 가장 핵심적인 기능인 이동, 회전, 스케일에 대해 알아보자.

 

 

이동

 

키 프레임에 스프라이트를 설정하고 애니메이션을 시켜 여자 캐릭터가 달리는 것을 어떻게 구현하는지 알아보았다. 만일, 캐릭터가 왼쪽으로 이동하거나 위아래로 높이가 조절된다면 키 프레임에 이러한 위치 정보를 설정해서 구현할 수 있다.

 

키 프레임 A에는 위칫값을 기본값으로 설정하고, 키 프레임 B를 선택해 스프라이트를 왼쪽으로 이동시켜보자.

 

키 프레임 A에는 위칫값을 기본값으로 설정한다.

 

이제 키 프레임 B를 선택한 후 (두 번째 것) 스프라이트의 위치를 왼쪽으로 움직여보자.

 

스프라이트를 이동

 

애니메이션을 재생하면 여자 캐릭터가 왼쪽으로 이동하는 것을 예상할 수 있다.

 

 

회전

 

회전 역시 애니메이션에서 자주 사용된다. 2D의 경우 회전축이 하나 존재하는데, 바로 Z축 회전이다. 회전을 이용해 애니메이션을 구현하면 사용되는 이미지의 용량을 줄일 수 있다.

 

회전이 없는 상태

 

예를 들어, 달리기 모션을 취하고 있는 여자 캐릭터 한 장이 있고 90도 꺾은 모습을 표현하고 싶을 때 회전 기능이 없으면 스프라이트 이미지에 90도 꺾은 모습을 한 스프라이트를 하나 더 추가해 주어야 한다. 

 

회전 기능이 없다면 회전한 스프라이트 이미지를 미리 준비해 두어야 한다.

 

만일 여자 캐릭터의 죽는 모션이 360도로 회전이 된다고 하면 회전 각도에 따라 모든 스프라이트를 준비해 두어야 한다. 이것은 명백하게 메모리를 더 많이 사용하게 된다. 요즘 컴퓨터는 메모리가 매우 크기 때문에 이 정도는 문제가 되지 않으리라 생각할 수 있지만, 회전각도 1도로 360도를 표현하려면 360장의 이미지가 필요해서 보통은 회전 기능을 엔진에 추가하거나 회전 방향의 개수를 줄여 메모리 사용량을 줄인다.

 

게임 엔진에서 회전 기능을 제공하면 이미지를 미리 준비해두지 않아도 되기 때문에 메모리 사용량을 줄일 수 있다. 하지만 내부적으로 이미지가 화면에 그려질 때 실시간으로 회전해야 해서 CPU/GPU에 부담이 될 수 있다. 이제 필자가 만든 엔진에서 어떻게 회전을 구현했는지 알아보도록 하자.

 

첫 번째 키 프레임에 달리기 모션을 취하고 있는 캐릭터를 추가

 

첫 번째 키에 들어간 회전 정보는 현재 0이다.

 

첫 번째 키의 회전 값은 0으로 설정

 

이제 두 번째 키를 추가하고 Rotation 값을 360으로 설정한다.

 

 

두 번째 키의 회전 값은 360으로 설정

 

연결할 키들을 선택한 후 마우스 오른쪽 버튼을 눌러 모션을 추가한다.

 

첫 번째 키와 두 번째 키를 전체 선택한 후, 마우스 오른쪽 버튼을 눌러 ‘Create a motion’을 선택한다.

 

모션타입은 Linear Motion으로선택하고 Create를 누른다.

 

모션 타입을 선형모션으로 선택하고 모션을 만들면 키 중간에 직선이 그려지며 화살표가 그려진다.

 

첫 번째 키와 두 번째 키 프레임 사이에 모션이 생겼다.

 

화살표의 의미는 첫 번째 키 프레임에서 다음 키 프레임으로 넘어갈 때 움직임(모션)이 존재한다는 의미다. 이제 시간이 흐르게 되면 첫 번째 키 프레임에서 두 번째 키 프레임으로 이동, 회전, 스케일 값이 선형으로 변화하게 한다.

 

키 프레임 중간의 회전 값이 실시간으로 계산되고 있다.

 

시간 값이 5일 때, 첫 번째 키 프레임에서 두 번째 키 프레임의 값으로 회전 값이 보간되어 적용되고 있다.

 

 

스케일

 

스케일은 확대/축소를 의미한다. 확대/축소의 경우에도 본래는 스프라이트 이미지를 다 준비해 두어야 하지만, 하드웨어가 발전함에 따라 최근에는 실시간으로 확대/축소를 해서 이미지를 표현한다. 확대/축소의 경우, 결과가 너무 명백해서 설명은 생략하도록 하겠다.

 

 

태그

 

지금까지 애니메이션 툴의 동작 방식과 핵심적인 기능인 이동, 회전, 스케일에 대해 알아보았다. 이러한 기능 외에도 게임을 개발하면서 지속적으로 새로운 기능들을 추가하게 되는데, 가장 중요한 컨셉은 지정된 시간에 무언가를 할 수 있도록 해주는 기능이다. 필자가 만든 애니메이션 툴에서도 이러한 기능을 제공한다.

 

예를 들면, 지정된 시간에 사운드를 플레이 한다거나 이펙트를 화면에 보여줄 수 있다. 혹은 지정된 시간 동안만 충돌처리를 한다거나 하는 기능을 추가할 수 있다. 여러 기능이 있지만, 핵심은 지정된 시간무언가를 한다는 점이다. 이러한 기능을 보통 태깅(Tagging)한다고 한다.

 

 

유니티 애니메이션에서 태그 기능을 이용한 모습. 0.2, 0.3, 0.5초에 어떠한 이벤트를 발생시킬 수 있도록 설정되어 있다.

 

※ 노트: 현재 게임 개발에 사용되는 엔진, 도구들은 대부분 이러한 기능을 제공한다. 유니티, 언리얼 엔진과 같은 엔진에서 제공하는 기능들을 살펴보면 더 많은 도움이 될 것이다.

 

상태(State), 유한 상태 머신(Finite State Machine, FSM)


컴퓨터 용어 중 많이 사용되는 것 중 하나가 상태(State)라는 것이다. 화면에 보이는 캐릭터는 스프라이트 이미지 혹은 3D 모델링으로 표현되지만, 내부적으로 어떠한 상태를 가질 수 있다. 

 

앞서 달리는 여자 캐릭터를 생각해보자. 캐릭터가 달리고 있다면 현재 캐릭터의 상태는 달리기이다. 캐릭터는 여러 개의 상태가 필요한데, 예를 들어 캐릭터가 죽는 경우가 있다면 죽음 상태가 존재할 것이고, 적 캐릭터로부터 맞아서 데미지를 입은 상태가 있다면 히트 상태가 있을 것이다. 만일, 캐릭터가 아무것도 하지 않고 가만히 서 있게 된다면 빈둥거리는(Idle) 상태가 존재할 것이다.

 

이러한 상태들은 캐릭터의 인공지능 혹은 게이머의 입력에 따라서 바뀔 수 있다. 예를 들어, 게이머가 아무런 입력이 없을 때 캐릭터가 빈둥거리다가, 게이머가 왼쪽으로 입력 버튼을 눌렀을 때 달리기 상태로 바뀔 수 있다.

 

이것을 단순히 표현하면 다음과 같다.

 

 

캐릭터를 위한 두 가지 상태

 

여러 개의 상태가 서로 연결되어 있고, 어떠한 특정 조건이 되면 하나의 상태에서 다른 상태로 전이된다. 이러한 상태들의 모음을 상태 기계(State Machine)라고 한다. 이러한 상태 기계를 프로그래머가 코드로 관리 할 수 있는데, 코드로 관리한다는 의미는 어떤 것일까?

 

 

 public enum PlayerMoveState

{

    idle,

    move,

    attack,

    stun,

    jump,

    guard,

    dead,

    air_attack,

}

 

 

코드를 설명하자면, 캐릭터의 이동상태에는 idle, move, attack, stun, jump, guard, dead, air_attack 상태가 있다는 말이다. 그리고 각각의 상태마다 어떠한 행동을 한다면 다음과 같이 매우 지저분한 모양을 가지게 된다.

 

 

 switch (mMoveState)

{

case PlayerMoveState.idle:

    {   

        if ( leftButton )

        {

            Move(GameTypes.Direction.LEFT);

        }                    

        else if (rightButton)

        {

            Move(GameTypes.Direction.RIGHT);

        }

 

        if (guardButton)

        {

            if (mMoveState != PlayerMoveState.jump && mMoveState != PlayerMoveState.stun && mMoveState != PlayerMoveState.guard )

            {

                Guard();

            }

        }

        else if ( jumpButton )

        {

            if (mMoveState != PlayerMoveState.jump)

            {

       Jump();

jumpButton = false;

            }

        }

    }

    break;

case PlayerMoveState.move:

    {

        if (leftButton)

        {

            Move(GameTypes.Direction.LEFT);

        }

        else if (rightButton)

        {

            Move(GameTypes.Direction.RIGHT);

        }

        else

        {

mMoveState = PlayerMoveState.idle;

mCurrentArmor.PlayAnim("idle");

mCurrentShovel.PlayAnim("idle");

        }

 

        if (guardButton)

        {

            if (mMoveState != PlayerMoveState.jump&&mMoveState != PlayerMoveState.stun&&mMoveState != PlayerMoveState.guard)

            {

                Guard();

            }

        }

        else if ( jumpButton )

        {

            // jump

            if (mMoveState != PlayerMoveState.jump)

            {

                Jump();

jumpButton = false;

            }

        }

    }

    break;

 

case PlayerMoveState.jump:

JumpUpdate();

    break;

 

case PlayerMoveState.dead:                

    break;

 

case PlayerMoveState.guard:

    if (!guardButton)

    {

mMoveState = PlayerMoveState.idle;

mCurrentArmor.PlayAnim("idle");

mCurrentShovel.PlayAnim("idle");

    }

    break;

case PlayerMoveState.air_attack:

AirAttack();

    break;

}

 

 

상태를 코드에서 관리하면서 어려운 점은 상태의 전이 조건이 바뀌었을 때 코드로 모든 것을 해결해야 한다는 점이다. (이것이 장점이 되는 경우도 있긴 하다.) 이러한 문제들을 해결하는 도구가 상태 기계 도구인데, 필자가 만들었던 PAIS가 그 한 예이다.

 

게임 <샤이아>에서 사용된 보스급 몬스터의 AI 구조

 

하나의 상태에서 다른 상태로 전이할 때 조건이 있을 수 있는데, 이러한 조건들이 변경되거나 전이할 상태의 대상이 바뀌었을 때 이것들을 코드로 관리하면 매번 코드를 수정해 주어야 한다.

 

보통 인공지능 캐릭터를 디자인하게 되면 조건들이 바뀌거나 새로운 상태가 지속적으로 추가되면서 지능의 수준이 높아지는데, 이럴 때 계속해서 코드를 수정하게 되면 게임 개발속도를 지연시키는 원인이 되기도 한다.

 

예를 들어 고블린이 있고, 현재 체력이 최대 체력의 30% 이하가 되면 전투를 하지 않고 도망가게 인공지능을 만들었다고 치자. 코드로 표현하면 다음과 같다.

 

 

 if ( curLife <= maxLife * 0.3f )

{

    mCurState = GoblinState.RunAway;

}

 

 

여기서 curLife, maxLife는 각각 현재 체력과 최대 체력을 의미하고, 0.3을 곱한 것은 최대 체력의 30%를 얻기 위함이다. 만일, 현재 체력이 최대 체력의 30%보다 작거나 같다면 현재 상태를 RunAway로 설정하겠다는 뜻이다. 이렇게 디자인한 인공지능 캐릭터를 그 이후로 수정하지 않는다면 모르겠지만, 보통은 게임을 개발하면서 계속해서 이러한 수치 데이터는 수정하기 마련이다. 만일 기획자(프로듀서)가

 

“음… 30%는 너무 큰 거 같고… 10% 이하가 되면 도망가게 만들어 주세요”

 

라고 프로그래머에게 요청하면 프로그래머는 위의 코드를

 

 

if ( curLife <= maxLife * 0.1f )

{

    mCurState = GoblinState.RunAway;

} 

 

 

로 수정한다. 코드만 수정하면 끝이 아니라 컴파일도 해야 하고, 모바일 게임의 경우 실행파일을 iOS, Android 폰에 올려야 해서 배치(Deploy)하는 시간도 걸린다. 숫자 하나만 고치게 되더라도 작업하는 환경에 따라서 요구되는 시간이 다르다.

 

이제 개발이 끝난 것 같지만, 기획자가 만일 다음과 같은 요청을 한다고 생각해보자.

 

“음… 10% 이하가 되면 도망가지 말고, 주변에 있는 적들을 불렀으면 해요. 그런데, 매번 부르면 안되고 랜덤하게 불러주되 한 번 부를 때 주변에 있는 적들 중 최대 3마리만 불러줬으면 좋겠어요. 그런데 이전에 적을 이미 불렀다면 현재 이 고블린이 부른 적들이 최대 3마리를 넘으면 안되게 해주세요. 그러니까 현재 고블린이 싸우고 있다가 체력이 10% 이하가 되어서 3마리를 불렀는데, 게이머가 3 마리 중에 1 마리를 없애면 다음에 고블린이 친구를 부를 때는 남은 2마리에서 부족한 1 마리만 불러서 최대 3마리가 되게 해주세요.”

 

불행하게도 실제로 게임 기획자가 프로그래머에게 위와 같은 사항을 요청할 때는 이런 식으로 깔끔하게 설명해주지 않는다. 보통은 기획자가 아이디어 수준에서 프로그래머에게 원하는 결과물을 설명하고, 프로그래머가 그것을 구현하기 위해서 필요한 부분을 하나하나 따져가면서 필요한 사항들을 정리해 하나의 문장이 만들어진다.

 

예를 들어, 위의 사항을 기획자가 요청하면 필자라면 다음과 같은 질문을 한다.

 

“주변에 있는 적들을 3마리 불러주길 원하는데, 주변이라는 뜻이 어떻게 되나요? 현재 도움을 요청한 고블린의 위치로부터 반지름 10미터 안에 있는 다른 고블린들을 불러야 하나요?”

 

“반지름 10미터에서 10이라는 숫자는 바뀔 수 있는 건가요?”

 

“적들 3마리를 부를 때 같은 종류의 적인가요? 예를 들어, 고블린은 고블린만 부를 수 있나요? 아니면 다른 오크나 오우거도 부를 수 있나요?”

 

“적들은 3마리로 고정인가요? 아니면 바뀔 수 있는 건가요?”

 

이러한 질문들은 게임을 만들면서 기획자를 곤경에 빠뜨리려고 하는 질문들이 아니라 프로그래머가 게임을 만들 때 최대한 변경할 수 있는 부분들을 변수로 빼내서 로직을 하드코딩하지 않고 변경에 유연하게 대처하기 위함이다.

 

예를 들어, 반지름 10미터라고 했을 때 10미터가 아닌 15미터로 변경할 때 다음과 같은 로직으로 작성하면 기획자가 이러한 요구사항을 바꿀 때마다 코드를 다시 작성해야 한다.

 

몬스터가 친구를 부를 수 있는 영역이 10미터 보다 작다면

 

 

if ( distance <= 10 )

{

} 

 

 

같은 코드가 된다. 이 부분을 기획자가 15미터로 변경하길 원한다면 다음처럼 코드를 수정해야 한다.

 

 

if ( distance <= 15 )

{

} 

 

 

만일, 이러한 부분이 얼마 되지 않는다면 문제없겠지만, 실제로 동작하는 상업용 게임은 코드가 엄청나게 많아서 이런 부분을 일일이 찾아서 수정하기란 보통 작업이 아니다. 그래서 프로그래머들은 이러한 수치 데이터를 외부로 뽑아내는 기법을 도입하게 되었고, 그렇게 함으로써 코드를 다시 컴파일하지 않고 관리도 더 편하게 할 수 있었다.

 

그러면 코드는 다음과 같다.

 

 

if ( distance <= Attrib_Distance )

{

} 

 

 

여기서 Attrib_Distance는 앞서 설명한 외부 데이터로 인해 바뀔 수 있는 수치 데이터다.

 

이제 수치 데이터를 외부로 뽑아내는 기법을 이용해 바뀔 수 있는 수치 데이터 문제는 해결했지만, 여전히 로직은 고정되어 있다. 대부분 로직이 바뀌는 경우는 프로그래머가 코드를 다시 작성해주어야 한다.

 

고블린의 예를 다시 들어보자.

 

 

if ( curLife <= maxLife * 0.1f )

{

    mCurState = GoblinState.RunAway;

} 

 

 

고블린의 체력이 10% 이하가 되었을 때 도망가는 로직에 하나를 더 추가해보자. 가령, 주변에 같은 팀이 있다면 랜덤하게 구조를 요청해보자.

 

 

if ( rand <= 0.2f )

{

    CallFriends();

}

 

if ( curLife <= maxLife * 0.1f )

{

    mCurState = GoblinState.RunAway;

} 

 

 

20%의 확률로 주변에 친구들을 부르는 로직을 추가했다. 이처럼 로직이 변경되면 코드가 수정되어야 하는데, 수치 데이터를 외부로 뽑아내는 방법과 마찬가지로 로직도 외부로 빼내어 프로그래머가 다시 코드를 작성하지 않도록 할 수 있다.

 

 

※ 노트: 로직을 외부로 빼내기 위해 임베디드, 스크립트 언어를 이용한다. 대표적으로는 파이썬, 루아와 같은 언어가 있다.

필자가 만들었던 PAIS라는 도구, 시스템은 상태들을 관리하고 이러한 상태들 사이의 관계를 간단한 코드로 표현할 수 있게 해서 로직을 외부로 빼낼 수 있었다. 앞서 살펴본 고블린의 예가 PAIS에서 어떻게 표현되는지 그림을 통해 알아보자.

 

 

추가 메뉴를 통해 행위자를 추가할 수 있다.

 

GoblinExample이라는 행위자를 추가한다.

 

행위자를 추가한 후, 마우스 오른쪽 버튼을 눌러 상태들을 추가할 수 있다.

 

상태 추가

 

현재 고블린 예제에서는 Idle, RunAway, Help가 필요하니 이 3개를 등록해준다.

 

 

고블린 예제를 위한 3개의 상태가 추가된 모습

 

기본적으로 고블린은 Idle상태에 있으며, 고블린이 싸울 경우에 Battle이라는 상태가 있어야 한다. 하지만 현재 예제에서는 고블린이 친구를 부르거나 도망치는 로직만을 작성할 것이기 때문에 Battle은 제외했다.

 

이제 Idle에서 RunAway혹은 Help로 가는 조건이 있어야 하는데, 이미 앞서 이러한 조건들을 살펴보았다. 현재 에너지가 20%이하가 될 경우에 RunAway로 도망가는 조건은 다음과 같이 작성할 수 있다.

 

 

Idle에서 마우스 오른쪽 버튼을 누른 후 전이 추가를 선택

 

Idle에서 RunAway로 갈 것이기 때문에 Output State는 RunAway로 설정한다.

 

※ 노트: 실제 <샤이아>를 개발할 때는 이렇게 상태 전이를 추가하는 작업이 번거롭다는 의견이 있어서 컨트롤 키를 누른 상태에서 드래그 앤 드랍을 하면 전이를 할 수 있게 수정했다.

 

 

Idle상태에서 RunAway로 가는 전이가 추가되었다.

 

Idle에서 친구를 부르는 전이도 추가해야 하는데, 이전 과정과 똑같다. 최종적으로는 다음과 같이 된다.

 

Idle상태에서 RunAway, Help로 갈 수 있다.

 

이제 실제로 전이시키려면 조건을 추가해야 한다. 현재 고블린의 에너지가 20% 이하일 때 RunAway로 가고, 50% 이하가 된다면 친구를 부르도록 하자.

 

전이에서 마우스 오른쪽 버튼을 눌러 조건을 설정한다.

 

 

추가 버튼을 눌러 조건을 추가한다.

 

조건식은 한 줄로만 작성하며 함수 호출이 가능하다.

 

GetEnergyPercent는 현재 몬스터의 에너지를 0~1사이 값으로 반환하며, 이 값이 0.2(20%)보다 작을 경우에 전이가 활성화된다.

 

전이에 조건이 생기면 그림과 같이 조건의 개수가 표시된다.

 

이와 같은 방식으로 코드를 직접 작성하지 않고도 게임 기획자가 몬스터의 전체적인 의사결정을 만들어 낼 수 있다. PAIS는 필자가 만든 도구지만, 이러한 방식으로 동작하는 기본 컨셉은 다른 게임 엔진(언리얼, 유니티)에서 사용하는 방식과 거의 같다. 전문적인 프로그래머가 아니더라도 숫자를 바꾸는 수준이 아닌, 전체적인 의사결정을 만들어 낼 수 있다는 것이 이러한 도구를 사용하는 장점이라고 볼 수 있다.

 

최근 게임엔진에서 제공하는 이러한 기능들은 필자가 만든 PAIS의 연장선에 있다. 이러한 기능들의 더 좋은 예는 유니티의 에셋 중 하나인 Play Maker, 언리얼 엔진 4의 블루 프린트를 살펴보면 될 것이다.

 

다음 화에서는 레벨 에디터(맵툴), 소스 비교/병합 도구들에 대해 알아보도록 하겠다.

  • 게임은 혼자 만드는 것이 아니다

  • 샤이아에 적용된 망토 시뮬레이션은 어떻게 구현했을까?

  • 게임 개발에 사용되는 도구들 ①

  • 게임 개발에 사용되는 도구들 ②

  • 게임 개발에 사용되는 도구들 ③