반응형


반응형
반응형

http://www.gpgstudy.com/gpg3/gpg3s3-5draft.html


이 글은 조만간 출판될 Game Programming Gems 3 한국어판(류광 역, 정보문화사)의 일부입니다. 이 글에 대한 모든 권리는 정보문화사가 가지고 있으며, 사전 허락 없이는 웹 사이트 게시를 비롯한 어떠한 형태의 재배포도 금지되어 있습니다.

이 글은 최종 교정 전의 상태이므로 오타나 오역이 있을 수 있습니다. 또한 웹 페이지의 한계 상, 실제로 종이에 인쇄된 형태와는 다를 수 있습니다. 실제 책에서는 표나 수식이 좀 더 정확한 형태로 표시될 것이며 그림/도표 안의 영문도 적절히 한글화될 것입니다.

Game Programming Gem 3 한국어판에 대한 정보는 GPG 스터디(http://www.gpgstudy.com/)에서 얻을 수 있습니다.

3.5 AI 에이전트, 객체, 퀘스트를 위한 확장성있는 트리거 시스템

Steve Rabin, Nintendo of America, Inc.
steve_at_aiwisdom.com

게임의 1인용 버전에 식상해버린 플레이어는 웹에서 더 많은 레벨들이나 레벨 편집기를 찾게 된다. 그리고 게임이 성공하려면 그런 기대에도 부응할 수 있어야 한다. 확장성 있는 레벨들과 퀘스트들은 잘 설계된 게임에 대한 하나의 품질 보증서로 간주될 수 있으며, 게임의 일반적인 수명을 상당히 늘려줄 수 있다. 게임은 그것이 RTS이든 아니면 RPG나 액션이든, 플레이어가 어떠한 형태로든 레벨들을 커스텀화하거나 새로운 영역들을 추가하는 것이 가능해야 한다.

Baldur’s Gate, StarCraft, Dungeon Siege가 훌륭한 게임으로 평가되는 이유에는 플레이어가 새로운 퀘스트들을 만들거나 심지어는 AI를 수정하고 확장하는 것이 가능하다는 측면도 포함된다. 그러나 플레이어는 프로그래머가 아니므로 플레이어들에게 가상의 프로그래밍 언어를 배우도록 하거나 디버깅을 강제하는 것은 불가능하다. 평범한 플레이어도 스스로 레벨이나 퀘스트를 만들 수 있으려면 무엇보다도 단순함이 보장되어야 하는데, 그러한 단순함은 확장성있는 트리거 시스템을 통해서 제공할 수 있다.

트리거 시스템의 소개

트리거 시스템은 ‘조건을 평가하고 반응을 수행한다’라는 한 가지 목적을 가진 중앙 집중화된 코드이다. 일련의 조건들이 만족되면, 일련의 반응들이 수행된다. 이러한 간단한 시스템은 우아하고, 구현하기 쉬우며, 데이터 주도적[Rabin0]으로 만들기도 쉽다. 트리거 시스템은 다양한 범위의 문제들을 해결할 수 있으며, 특히 디자이너와 플레이어에 의해 수정될 수 있다는 점에서 바람직하다. 트리거 시스템의 매력은 무엇보다도 플레이어가 탐험할 흥미롭고 상호작용적이며 새로운 환경을 쉽게 만들 수 있도록 한다는 데 있다.

일단의 모험가들이 던전을 탐험하는 게임을 생각해보자. 모험대는 지하 묘지를 통과하는 도중 무너져 내린 기둥에 대장을 잃게 된다. 그리고 인상적인 석문에 도달했을 때에는 찬 바람이 불어 와 횃불도 꺼뜨린다. 마지막 횃불에 불을 붙인 후 석문을 보니 “무거운 심장이여 이 문을 통과하리니”라고 새겨진 문구가 눈에 띈다. 잠시 생각해본 후, 체중이 무거운 대원 하나를 심장 모양의 받침대에 올려놓았더니 문이 서서히 열린다.

간단한 조건-반응 패러다임을 이용하는 트리거 시스템으로 이러한 사건들을 지정하는 것이 가능하다. 대원이 특정 기둥 객체에 1미터 이내로 접근하면 바람 소리에 해당하는 음향 효과를 재생하고 그 주변의 횃불들을 꺼뜨린다던가, 플레이어의 파티 대원들 중 하나가 심장 모양의 받침대 위로 올라가면 문을 열고 돌이 갈리는 듯한 음향 효과를 재생하는 등은 모두 조건-반응 패러다임에 속한다.

트리거 시스템을 잘 구현한다면, 디자이너와 플레이어 모두가 독창적인 시나리오 및 퀘스트의 작성을 위해 여러 시간 동안 몰두하도록 만들 수 있는 장치들을 갖출 수 있다. StarCraft 맵 편집기는 참고할 만한 좋은 트리거 시스템의 예이다. 기존의 시스템을 연구하고 새로운 아이디어를 만들어 내는 것은 개발자의 기본적인 자세이다.

객체가 소유하는 트리거 시스템

트리거 시스템을 처음 만들 때에는 하나의 마스터 트리거 시스템([Orkin02]에 나온 것 같은)을 떠올리게 될 것이다. 그러나 좀 더 강력한 구조를 원한다면 어떠한 에이전트나 객체, 퀘스트도 소유할 수 있는 트리거 시스템 클래스를 생각해 볼 수 있다. 모든 객체가 트리거 시스템을 가져야 하는 것은 아니지만, 객체마다 자신의 트리거 시스템 인스턴스를 가질 수 있다면 객체 안에 데이터를 캡슐화하는 데에도 도움이 되며 시스템이 좀 더 유연하고 객체지향적이 될 수 있다. 또한 트리거라는 것 자체가 특정 객체에 대해 작동하게 되므로, 플레이어가 개념을 잡는 데에도 도움이 된다.

누군가가 다가오면 무너져 내리는 기둥을 생각해보자. 그러한 기둥을 레벨 편집기 안에서 정의하고 거기에 무너져 내리는 행동을 강조하는 하나의 트리거 정의를 부여한다. 그리고 디자이너나 플레이어가 게임의 여러 곳에 그러한 기둥들을 배치하면, 신통하게도 의도했던 대로의 모습을 볼 수 있게 된다. 이는 트리거 행동이 객체에 직접 부착되어 있기 때문에 가능한 것이다. 이런 방식에서는 어떠한 에이전트나 객체, 퀘스트도 전적으로 해당 개체만을 위해 존재하는 자신만의 고유한 트리거 시스템을 가질 수 있다.

조건의 정의

게임 안에서 수량화할 수 있는 이벤트나 상태라면 어떠한 것이라도 조건이 될 수 있다. 조건은 실행 파일 안에 고정되지만 인수들이나 레벨 편집기를 통해서 조절할 수 있는 여지도 매우 크다. 다음은 가능한 몇 가지 조건들이다.

  • 플레이어가 지점 (x, y, z)의 반경 R 이내에 존재
  • 플레이어가 위치 (x, y, z)의 사각형 영역 안에 존재
  • 플레이어와 적이 일정 거리 이상 근접함
  • 플레이어의 생명이 X% 이하
  • 플레이어의 인벤토리에 객체 X가 존재
  • 플레이어가 객체 X를 장착
  • 플레이어가 죽음
  • 플레이어가 적 X를 죽임
  • 플레이어가 메시지 X를 받음

부울 논리로 연결된 조건들

조건들을 AND, OR, NOT, XOR 같은 부울 연산자들로 결합할 수 있다면 조건들이 좀 더 유연해질 수 있다. 예를 들어 플레이어가 얼음검, 얼음방패, 얼음갑옷을 장착해야만 어떤 문이 열리게 되어 있다면 조건들은 AND로 결합되어야 한다. 또 어떤 문은 플레이어가 은열쇠나 해골열쇠 중 하나만 가지고 있어도 열릴 수 있다면 조건들이 OR로 결합되어야 한다. 그림 3.5.1과 3.5.2는 그러한 조건들을 트리 구조로 표현한 것이다.

                      AND        참이면 트리거 발동            문 열림

 얼음검 장착    얼음방패 장착    얼음갑옷 장착

그림 3.5.1 세 조건 모두 “참”이면 문이 열린다.

                     OR         참이면 트리거 발동              문 열림

 은열쇠를 가지고 있음    해골열쇠를 가지고 있음

그림 3.5.2 둘 중 하나라도 “참”이면 문이 열린다.

좀 더 복잡한 경우로, 플레이어가 얼음검, 얼음방패, 얼음갑옷을 장착하고 있으며 은열쇠 또는 해골열쇠를 가지고 있어야 문이 열리게 된다면 어떻게 될까? 그림 3.5.3이 그러한 조건들을 표현한 것이다.

이와 같은 그림들을 통한 시각화는 코드를 구조화하는 좋은 방법을 제시한다는 점에서 중요한 의의를 갖는다. 각 요소가 하나의 클래스인 경우 두 종류의 클래스가 필요한데, 하나는 Operator 클래스이고 또 하나는 Condition 클래스이다. Operator 클래스는 임의의 부울 연산자처럼 작동하도록 만들어야 할 것이다. 또한 여러 연산자, 피연산자들이 포함된 표현식을 나타내기 위해서는 다른 Operator 인스턴스들이나 Condition 인스턴스들로의 포인터들의 목록도 담아야 한다. Condition 클래스는 임의의 판정 가능한 조건들을 평가할 수 있어야 하며, 조건의 커스텀화를 위한 인수들을 담아야 한다.

              AND       참이면 트리거 발동               문 열림

 얼음검 장착  얼음방패 장착  얼음갑옷 장착              OR

                                     은열쇠를 가지고 있음   해골열쇠를 가지고 있음

그림 3.5.3 문을 좀 더 복잡한 조건들

반응의 정의

게임 안에서 변경하고자 하는 상태나 행동이라면 어떠한 것도 반응이 될 수 있다. 조건과 마찬가지로, 반응 자체는 실행 파일 안에 고정되지만 인수들 또는 레벨 편집기를 통해서 조절될 수 있다. 다음은 가능한 반응들의 목록이다.

  • 레벨/퀘스트 완수
  • 플레이어에게 X 포인트의 피해 또는 치료
  • 플레이어에게 경험치 X를 부여
  • 문을 연다/닫는다/잠근다/푼다
  • 종류 Y의 생물들을 X개 생성
  • 생물 X를 죽임
  • 음향 재생
  • 목록으로부터 임의의 음향을 재생
  • 플레이어/적 X를 중독
  • 플레이어/적 X를 마비
  • 플레이어/적 X를 보이지 않게 만든다
  • 플레이어/적 X를 무적으로 만든다
  • 트리거를 재설정
  • 플레이어에게 메시지 X를 보낸다

특정 조건들의 집합이 만족되면 그에 해당하는 반응이 수행된다. 이를 단 하나의 반응이 아니라 여러 개의 반응들의 집합으로 확장할 수도 있다. 그렇게 하면 하나의 트리거 발동이 여러 가지 것들에 동시에 영향을 미치게 할 수도 있고, 여러 반응들 중 임의의 것이 수행되도록 할 수도 있다.

트리거의 평가

트리거에 조건들이 정의되었다고 할 때, 다음으로 필요한 것은 한 트리거의 조건들을 평가해서 발동 여부를 판단하는 구조이다. 우선 고려할 것은, 특정 조건이 이벤트 주도적인지(트리거 시스템에 이벤트가 통지되기를 기다리는 것) 아니면 게임 세계를 주기적으로 점검해야 하는지를 결정하는 것이다. 두 방식 모두 지원하는 것이 유연성에 도움이 된다.

이벤트 주도적인 조건의 경우, 이벤트가 트리거 시스템에 통지될 수 있도록 하는 인터페이스가 필요하다. 가장 간단한 방식은 이벤트 메시지를 사용하는 것이다. 이벤트 메시지는 발생한 이벤트의 종류와 이벤트에 관련된 기타 데이터 등으로 구성된다. 이벤트 메시지에 대한 좀 더 자세한 내용은 [Rabin02]를 참고하기 바란다.

주기적 점검 방식의 조건이라면, 트리거 시스템 안에서 각각의 조건들을 점검하는 어떠한 갱신 함수를 호출하는 방식에 대해 생각해볼 수 있다. 그런 함수에서 이벤트 주도적 조건들에 대한 점검은 일어나지 않아야 한다.

이벤트 메시지나 주기적 점검 갱신이 트리거 시스템에 들어왔다면, 그것을 조건들에게 전파해야 한다. 그림 3.5.4는 이벤트 메시지와 주기적 점검 모두를 요구하는 하나의 조건 집합의 예이다. 왼쪽 조건은 충돌 이벤트를 기다리는 반면, 반면 오른쪽 조건은 주기적 점검 갱신이 들어왔을 때 조건을 점검한다.

이벤트 메시지나 주기적 점검 갱신이 트리거 시스템에 들어오면, 그것을 각 트리거의 루트 Operator 인스턴스에 전달한다. Operator는 그것을 자신의 자식들에게 넘겨주며, 자식들은 그에 대한 판단 결과를 “참” 또는 “거짓”으로 돌려준다. 각 자식 역시 그러한 요청을 자신의 자식들에 넘겨준다. 그러한 요청이 실제 조건에 도달하면 참/거짓 판정이 일어나게 되고, 그 결과들이 상위 Operator 인스턴스에 올라가서 부울 연산자에 의한 참/거짓 판정이 일어난다. 그런 식으로 참/거짓이 루트에게까지 올라가면 최종적인 결과가 만들어진다.

 이벤트 메시지 및 
 주기적 점검 갱신

                       AND                참이면 트리거 발동         쥐 10 마리 생성

 플레이어가 10 미터 이내     플레이어의 건강이 50% 이상

그림 3.5.4 하나의 트리거가 이벤트 주도적 조건(아래 왼쪽)과 주기적 점검 기반의 조건(아래 오른쪽)을 모두 가진 예

Operator 클래스는 자식들을 처리할 때 늦은 평가(lazy evaluation)를 사용해야 한다는 점을 명심하기 바란다. 어떠한 조건이 해당 연산자에 대해 만족되지 않은 경우, 트리거의 판정은 그 시점에서 끝난다. 예를 들어 그림 3.5.4에서 어떠한 이벤트 메시지가 왼쪽 조건에 전달되고 그것이 “거짓”으로 판정되었다면 오른쪽 조건으로는 그 이벤트 메시지가 전달되지 않도록 하는 것이다. 이렇게 하면 처리 시간을 줄이는 데 도움이 된다.

또 다른 중요한 점 하나는, 이벤트 주도적 조건의 경우 조건이 명시적으로 재설정되기 전까지는 이벤트들을 기억하고 있어야 한다는 점이다. 그림 3.5.4의 예에서는 플레이어가 10미터 이내로 접근하면 충돌 이벤트가 트리거에 전달되어야 한다. 왼쪽 조건은 그 이벤트를 기억해 두고(플레이어가 10 미터 바깥으로 나가기 전까지는), 이후의 이벤트 메시지나 주기적 점검 갱신에 대해 항상 “참”을 돌려줘야 한다.

어떠한 시점에서, 특정 트리거의 조건들이 모두 참을 돌려준다면 트리거가 발동된다. 트리거가 발동되면 발동되었다는 사실을 기억해서 연달아 다시 발동되는 일이 없도록 해야 한다.

단일 발동과 재설정 시간

모든 트리거들에는 추가적으로 두 개의 속성들이 더 필요하며, 이 속성들은 디자이너가 정의하게 된다.

 bool SingleShot;  // 트리거가 한 번만 발동되어야 하는지의 여부
 float ReloadTime; // 트리거가 여러 번 발동되어야 하는 경우,
                   // 재설정되기까지의 시간

이 두 속성들은 트리거가 한 번 이상 발동될 수 있도록 한다. SingleShot 속성은 트리거가 한 번만 발동되어야 하는지 아니면 여러 번 발동될 수 있는지의 여부를 가리킨다. SingleShot이 “거짓”일 때, ReloadTime은 트리거가 한 번 발동된 후 다시 발동될 수 있을 때까지, 즉 조건들을 초기화하고 다시 이벤트를 받게 될 때까지 걸리는 시간을 결정한다.

트리거를 플래그 및 카운터와 결합

트리거들이 함께 결합될 수 있으려면, 시스템 안의 모든 트리거들이 접근할 수 있으며 즉시 설정할 수 있는 상태들이 존재해야 한다. 따라서 트리거 시스템은 발동된 트리거들의 상태를 추적할 수 있는 일련의 플래그들과 카운터들을 갖출 필요가 있다. 이를 최대한 일반화하기 위해, 각각의 트리거가 문자열 이름으로 접근할 수 있는 임의의 플래그들을 만들 수 있도록 하자. 트리거 시스템은 플래그 이름이 참조될 때 플래그를 생성하거나, 생성되어 있다면 아니면 플래그를 설정한다. 생성된 플래그는 시스템이 종료될 때까지 유지된다.

다음과 같은 새로운 조건들을 생각해보자.

  • flag_name이 참(또는 거짓)인지?
  • flag_name이 짝수(또는 홀수)인지?
  • flag_name1 AND flag_name2가 참인지?
  • flag_name1 AND NOT flag_name2가 참인지?
  • flag_name1 OR flag_name2가 참인지?
  • flag_name1 XOR flag_name2가 참인지?
  • flag_name1 AND flag_name2 AND flag_name3이 참인지?
  • flag_name의 값이 X와 같은지?
  • flag_name의 값이 X보다 큰지?
  • flag_name의 값이 X보다 작은지?

이런 조건들이 추가된다면, 다음과 같은 새로운 반응들이 필요할 것이다.

  • flag_name의 값을 증가.
  • flag_name의 값을 감소.
  • flag_name의 값을 X로 설정.
  • flag_name2의 값을 flag_name1의 값으로 설정.
  • flag_name의 부울 값을 전환.
  • flag_name의 부울 값을 TRUE로 설정.
  • flag_name의 부울 값을 FALSE로 설정.

이러한 플래그들과 카운터들이 있으면 트리거 시스템은 특정 이벤트에 표시를 하거나 발동 횟수를 셀 수 있으며, 이를 통해서 예를 들면 플레이어가 특정 지역에 들린 횟수 등을 알 수 있다. 또한 이벤트들이 특정한 순서로 발생했을 때에만(예를 들면 세 개의 타일들을 특정한 순서로 밟았을 때 등) 트리거가 발동되도록 할 수도 있다. 이러한 플래그들과 카운터들은 상태 정보를 담으므로, 좀 더 많은 종류의 트리거들이 가능해진다.

그림 3.5.5는 플레이어가 어떤 문을 열지 못해서 특정 지역을 반복적으로 찾아오는 경우에 문 열기에 대한 단서를 제공하는 예이다. 세 개의 개별적인 트리거들이 “Visited”라는 이름의 카운터를 통해서 서로 협동적으로 작동한다는 점을 주목하기 바란다.

             AND           "Visited"를 증가             AND           "Visited"를 증가
                                                                                      
플레이어가        "Visited"가               플레이어가        "Visited"가              
지역 A 안에 있음  짝수                      지역 C 안에 있음  홀수                     

                             AND      문 D에 대한 
                                      단서를 떨어뜨림

                    "Visited"가   문 D가 
                    8과 같음      잠겼음

_그림 3.5.5 세 개의 트리거들이 “Visited"라는 이름의 카운터를 통해서 함께 작동하는 예. 플레이어가 문 D를 열지 못한 채로 영역 A와 C를 번갈아 8번 방문하면 단서가 제시된다.

플래그와 카운터가 추가되면, 트리거 시스템의 형태는 흑판 아키텍쳐[Isla02]와 매우 비슷해진다. 플래그들과 카운터들은 흑판이 되고 트리거들은 흑판의 내용을 조작하는 지식 원천(knowledge source)의 역할을 하게 되는 것이다. 그러나 트리거들은 대부분 흑판의 외부로부터 비롯된 데이터에 대해 작용하므로, 엄밀히 말해서 트리거 시스템이 곧 흑판 아키텍쳐인 것은 아니다.

트리거 시스템 대 스크립팅 언어

트리거 시스템의 기능성이 스크립팅 언어의 기능성과 비슷하다는 점을 눈치 챈 독자도 있을 것이다. 특히 상태 정보가 추가되면 더욱 비슷해진다. 양 쪽의 기능성이 겹치는 부분도 존재하나, 완전한 기능을 갖춘 스크립팅 언어에 비해 트리거 시스템은 다음과 같은 장점들을 가진다.

  • 트리거 시스템은 전적으로 GUI만으로도 설정, 조작이 가능하다. 이러한 종류의 단순화되고 안정적인 저작 환경의 제공에 중점을 둔 스크립팅 언어는 드물다. 트리거 시스템의 경우 트리거들이 조건과 반응에 따라 조직화되므로, 문법 오류 같은 문제를 걱정할 일이 없다.
  • 트리거 시스템은 사용자에게 좀 더 쉽게 다가갈 수 있다. 개념이 이해하기 쉬우므로 실제로 사용해 보고자 시도하는 사용자들의 수도 더 많을 수 있다.
  • 트리거 시스템은 안전하다. 트리거 시스템에서는 제한 조건들이 명시적으로 정해져 있으며 사용자는 오직 적은 수의, 집중된, 잘 테스트된 일련의 행동들만 수행할 수 있다. 따라서 사용자의 실수로 게임이 다운되는 경우는 별로 일어나지 않는다.
  • 트리거 시스템은 구현과 수정이 쉽다. 완전한 스크립팅 언어를 구현하는 데 소요되는 시간의 일부만으로도 트리거 시스템을 구현할 수 있다. 스크립팅의 경우 수개월에서 수년까지도 걸릴 수 있지만 트리거 시스템은 수 주면 충분하다[Tozour02], [Brockington02].
  • 트리거 시스템은 문서화가 쉽다. 트리거 시스템의 경우 문서 및 예제 작성이 상대적으로 간단하다. 문서나 예제는 사용자가 저작 시스템에 익숙해지기 위한 필수적인 요소이다.

한계

이 글에서 살펴 본 시스템의 주된 한계는 규모가변성(scalability)이 좋지 않다는 점이다. 그러나 이러한 문제는 무관한 트리거들을 걸러내는 추가적인 코드를 통해서 해결할 수 있다. 근접성을 통한 제외는 이미 그 효과가 상당함이 입증되었다.

또 다른 한계는 조건들과 반응들을 정의하기 위한 어휘가 실행파일 안에 고정되어 있다는 점이다. 사용자의 설정을 코드와 연결하는 부분은 프로그래머의 손을 거쳐야 한다. 이는 게임을 임의의 또는 불순한 의도의 조작으로부터 보호할 수 있다는 장점이 되기도 하다.

결론

부족한 개발 일정에 시달리는 게임 개발자들에게 있어 확장성 있는 트리거 시스템은 어찌 보면 사치스럽게 느껴질 수도 있을 것이다. 그러나 투자한 만큼의 가치와 깊이를 게임에 부여할 수 있다는 점은 잊지 말아야 할 것이다. 확장성있는 트리거 시스템은 또한 프로그래밍 능력을 갖추지 못한 레벨 디자이너도 로직을 구축할 수 있게 하는 효과적인 수단이다. 언뜻 보면 트리거 시스템이 너무 간단한 해결책으로 느껴질 수도 있겠지만, 목표는 디자이너와 플레이어에게 더 많은 능력을 부여하는 데 있음을 잊어서는 안 된다. 내용과 레벨 관련 로직을 정의하는 방식이 쉬우면 쉬울수록 게임은 더욱 확장되고 플레이어는 더 많은 재미를 얻을 수 있다.

참고자료

  • [Brockington02] Brockington, Mark, and Mark Darrah, “How Not To Implement a Basic Scripting Language,” AI Game Programming Wisdom, Charles River Media, Inc., 2002. 번역서는 “기본 스크립트 언어를 어떻게 구현하면 안 되는가”, AI Game Programming Wisdom, 정보문화사, 2003.
  • [Isla02] Isla, Damian, and Bruce Blumberg, “Blackboard Architectures,” AI Game Programming Wisdom, Charles River Media, Inc., 2002. 번역서는 “칠판 아키텍처”, AI Game Programming Wisdom, 정보문화사, 2003.
  • [Orkin02] Orkin, Jeff, “A General-Purpose Trigger System,” AI Game Programming Wisdom, Charles River Media, Inc., 2002. 번역서는 “다용도 트리거 시스템”, AI Game Programming Wisdom, 정보문화사, 2003.
  • [Poiker02] Poiker, Falco, “Creating Scripting Languages for Non-Programmers,” AI Game Programming Wisdom, Charles River Media, Inc., 2002. 번역서는 “비 프로그래머를 위한 스크립트 언어 만들기”, AI Game Programming Wisdom, 정보문화사, 2003.
  • [Rabin00] Rabin, Steve, “The Magic of Data-Driven Design,” Game Programming Gems, Charles River Media, Inc., 2000. 번역서는 “데이터 주도적 설계의 마법”, Game Programming Gems, 정보문화사, 2000.
  • [Rabin02] Rabin, Steve, “Enhancing a State Machine Language Through Messaging,” AI Game Programming Wisdom, Charles River Media, Inc., 2002. 번역서는 “메시지를 통한 상태기계 언어의 향상”, AI Game Programming Wisdom, 정보문화사, 2003.
  • [Tozour02] Tozour, Paul, “The Perils of AI Scripting,” AI Game Programming Wisdom, Charles River Media, Inc., 2002. 번역서는 “AI 스크립팅의 위험”, AI Game Programming Wisdom, 정보문화사, 2003. 

반응형
반응형





캐릭터

UE3를 활용하여 자신의 프로젝트에 맞는 캐릭터를 구현하기에 앞서 UE3에 이미 구현되어 있는 캐릭터 작동 방식을 분석하기로 한다. 그리하여 캐릭터 구현에 필요한 기능이 중복구현됨을 막고 효율적으로 UE3에 통합될 수 있도록 한다.

여기서 살펴볼 내용은 Core기능에 해당하는 AnimTree, AnimSet, Morph 등의 기능이 아니라, 로직에 해당하는 Pawn, PlayerController, PlayerReplicationInfo 등의 오버뷰와 이들이 엔진 내 어떤 포지션에 해당하는지를 중점으로 살펴본다.

캐릭터 Bone 구조

분석 요소

UE3에서 캐릭터를 구현하는데 필요한 요소들을 살펴본다.

Pawn

  • 특징
    • Actor→Pawn 상속
    • player 나 AI 가 컨트롤하기 위한 Base Actor 클래스에 해당한다.
    • mesh, collision, physics 등의 기능이 있고, 데미지를 주며 소리를 내고 무기나 다른 소지품을 들고 다니며, 월드상 다른 Pawn 과의 물리적 인터렉션 등을 담당한다.
  • 기능
    • 캐릭터가 돌아 다닐 수 있는 floor 에 대한 성격을 정의한다.
      • Pawn 이 서있는 지면의 Normal ( Floor, PHYS_Spider, PHYS_Walking 에서만 쓰인다. )
      • 올라갈 수 있는 floor 높이를 정의. ( MaxStepHeight, MaxJumpHeight )
      • 내려갈 수 있는 floor 높이를 정의. ( LedgeCheckThreshold )
      • 이동 가능한 floor 경사면 값 지정. ( WalkableFloorZ, 이는 floor 의 normal 벡터와 UpVector 를 내적한 스칼라 값 이며 Pawn은 기본적으로 0.7 (~= 45 degree) 로 초기화 되어 있다. )
      • 상기 3가지 사항은 캐릭터 이동 및 PathFinding 등에 활용된다.
    • 모든 Pawn을 링크드리스트로 관리. ( NextPawn, 월드 상 모든 Pawn 순회에 용이하다. )
    • Crouch 관련 정보 정의 가능
      • 웅크릴 수 있다. ( bCanCrouch )
      • 웅크리길 원한다. ( bWantsToCrouch )
      • 웅크린 상태이다. ( bIsCrouched )
      • 다시 일어서고 싶다. ( bTryToUncrouch )
      • Crouch 시, 실린더 정보 ( CrouchHeight, CrouchRadius )
      • 웅크리기 & 일어서기 이벤트 함수 제공.
        // APawn::performPhysics 로부터 호출
        void APawn::Crouch(INT bClientSimulation)
        {
            ...
        }
         
        void APawn::UnCrouch(INT bClientSimulation)
        {
            ...
        }
    • 이동 목적지에 다다르면 smooth하게 속도를 줄이면서 멈춘다. ( bReducedSpeed )
    • 여러 행위들에 대한 플래그
      • 점프기능이 있다. ( bJumpCapable )
      • 점프할 수 있다. ( bCanJump )
      • 데미지 입을 수 있다. ( bCanBeDamaged )
      • 웅크릴 수 있다. ( bCanCrouch )
      • 날 수 있다. ( bCanFly )
      • 수영할 수 있다. ( bCanSwim )
      • 텔레포트할 수 있다. ( bCanTeleport )
      • 걸을 수 있다. ( bCanWalk )
      • 사다리에 오를 수 있다. ( bCanClimbLadders )
      • 횡이동 할 수 있다. ( bCanStrafe )
      • 이 밖에 다양한 플래그 들 ( bAvoidLedges, bStopAtLedges, bAllowLedgeOverhang, bSimulatedGravity, bIgnoreForces, bCanWalkOffLedges, bCanBeBaseForPawns, bSimGravityDisabled, bDirectHitWall, bPushesRigidBodies, bForceFloorCheck, bForceKeepAnchor )
      • 곧 Pawn 에서 제거될 것 같은? 플래그 들 ( bCanMantle, bCanClimbUp, bCanClimbCeilings, bCanSwatTurn, bCanLeap, bCanCoverSlip )
    • AI 관련 변수
      • 성별 ( bIsFemale )
      • 아이템을 집을 수 있다. ( bCanPickupInventory )
      • AI가 날 무시하게 할 수 있다. ( bAmbientCreature )
      • 소리를 들을 수 있다. ( bLOSHearing )
      • 벽을 통해 들려오는 숨죽인 소리를 들을 수 있다. ( bMuffledHearing )
      • 게임 시작 시 controller 에 의해 소유되지 않도록 한다. vehicle 들은 게임 시작 시 controller 에 의해 선점되면 안되겠지.. ( bDontPossess )
      • 난 움직일 수 없다. ( bStationary )
      • 무기 사용할 수 없다. ( bNoWeaponFiring )
      • 뭔가를 사용할 수 있다. ( bCanUse )
      • 이 밖에 다양한 플래그 들 ( bModifyReachSpecCost, bModifyNavPointDest, bPathfindingsAsVehicle, )
      • Path Finding 관련
        • 내가 길을 찾는 방법 ( PathSearchType )
        • 기타 관련 변수 들 ( PathConstraintList, PathGoalList )
      • 들을 수 있는 거리 ( HearingThreshold )
      • 소리를 들을 수 있는 각성도 ( Alertness, -1 ~ 1 사이의 값으로써 높을 수록 소리를 더 잘 들을 수 있다. )
      • 시야 거리 ( SightRadius )
      • 시야 각 ( PeripheralVision, degree 의 cosine 값, default 로 -0.75 ~= 140 도 )
      • 물리적 업데이트할 모니터링 시간 ( AvgPhysicsTime, AI 가 목적지로 가기 위해 업데이트할 시간? 구불구불하게 가거나 똑바로 가게 하는 등의 처리를 위한 변수 )
    • 이동 관련 속성
      • 희망 무브먼트 속도 ( DesiredSpeed & MaxDesiredSpeed, GroundSpeed 에 곱해지므로 실질적으로 Pawn 의 전체적 무브먼트 스피드라고 보아도 무방 )
        // Pawn.uc
        defaultproperties
        {
            ...
            DesiredSpeed=+00001.00000
            ...
        }
         
        // UnController.cpp
        void AController::MoveTo( ... )
        {
            ...
            Pawn->DesiredSpeed = Pawn->MaxDesiredSpeed;
            ...
        }
         
        void AController::MoveToward( ... )
        {
            ...
            Pawn->DesiredSpeed = Pawn->MaxDesiredSpeed;
            ...
        }
         
        // UnPhysic.cpp
        FLOAT APawn::MaxSpeedModifier()
        {
            ...
            if ( !IsHumanControlled() )
            {
                Result *= DesiredSpeed;  // 사람이 조종하지 않는 녀석에 한해서만 적용
            }
            ...
        }
      • 가장 가까운 path ( Anchor )
      • nav mesh 인덱싱 ( AnchorItem, 헌데 사용되지는 않음 )
      • 최근에 도달한 가까운 path ( LastAnchor )
      • 마지막 path finding 실패 시기 ( FindAnchorFailedTime, FindPath() 함수 시도가 실패한 마지막 시간 )
      • 마지막 유효 path 발견 시기 ( LastValidAnchorTime )
      • 도착점 offset ( DestinationOffset )
      • 루트상 다음 지점의 반지름 ( NextPathRadius )
      • 구불구불 이동
        • 방향 ( SerpentineDir )
        • 거리 ( SerpentineDist )
        • 시간 ( SerpentineTime, 구불구불 이동 시 횡이동 시도하기 전까지 직진할 시간 )
    • 물리?
      • Pawn 의 질량 ( Mass )
        // Pawn.uc
        function CrushedBy( Pawn OtherPawn )
        {
            TakeDamage(
                ( 1 - OtherPawn.Velocity.Z / 400 ) * OtherPawn.Mass / Mass,   // 데미지, 위에서 내리누를 때 상대방과의 질량에 대비하여 데미지를 가감하는 용도.
                OtherPawn.Controller,                                         // 유발자 Controller
                Location,                                                     // HitLocation
                vect( 0, 0, 0 ),                                              // Momentum
                class'DmgType_Crushed' );                                     // 데미지 타입
        }
         
        event TakeDamage( ... )
        {
            ...
            momentum = momentum / Mass;     // 데미지가 가해질 때 전해진 충격량으로부터 움직일 속도를 구함. F=ma -> a=F/m
            ...
        }
      • 수영할 때 적용할 물 부양성 ( Buoyancy, 1=자연스러운 부양성 0=no부양성 )
        // UnPhysic.cpp
        void APawn::CalcVelocity( ... )
        {
            ...
            if ( bBuoyant )
            {
                Velocity.Z += GetGravityZ() * DeltaTime * ( 1.f - Buoyancy );  // Buoyancy 가 0 이면 중력을 그대로 적용한다.
            }
            ...
        }
      • 밀리어택 최대거리 ( MeleeRange, 일반적인 밀리어택이 아니라 이동하는 도중 목적지까지의 거리를 가늠하는데 사용하는 것 같음 )
    • 일반
      • 스폰 시간 ( SpawnTime, 스폰 후 일정시간동안 데미지 감소따위를 하는 데 사용. 관련변수 UTGame.SpawnProtectionTime )
      • view pitching 제한 ( MaxPitchLimit )
    • 무브먼트
      • controller 없이도 physics 돌려라~ ( bRunPhysicsWithNoController, acceleration 이 아닌 velocity 에 의해서만 움직이게 되겠다. )
      • 풀 악셀 ( bForceMaxAccel, 기존 acceleration 무시하고 풀악셀로 최대 velocity 를 이끌어 낸다. )
      • 최대 지형 이동 속도 ( GroundSpeed )
      • 최대 수영 이동 속도 ( WaterSpeed )
      • 최대 활강 이동 속도 ( AirSpeed )
      • 최대 등반 이동 속도 ( LadderSpeed )
      • 가속 비율 ( AccelRate, 이것이 곱해져 acceleration 을 구한다. )
      • 점프 속도 ( JumpZ, 수직 up 방향 속도 )
      • 수중 이탈 속도 ( OutofWaterZ, 점프로 물 밖으로 이탈할 때 z up 방향 속도. 물 근처 난간위로 올라가는 것을 보장하기 위해 설정하는 값인듯 )
        // Pawn.uc
        function JumpOutOfWater( vector jumpDir )
        {
            ...
            velocity.Z = OutofWaterZ;  // set here so physics uses this for remainder of tick
            ...
        }
      • 수중 이탈 가능 높이 ( MaxOutOfWaterStepHeight, 수영하며 수중이탈 가능한올라갈 수 있는 높이 )
      • 낙하 최대 가속도 제한할까? ( bLimitFallAccel )
      • 공중에서의 컨트롤 시간 factor ( AirControl, acceleration 을 이 시간만큼 적용한 후 테스트하는 용도 )
        // UnPhysic.cpp
        void APawn::physFalling( FLOAT deltaTime, INT Iterations )
        {
            ...
            if ( !bDoRootMotion && TickAirControl > 0.05f )
            {
                // 현재 velocity 에 TickAirControl 시간만큼 경과 후 delta velocity 까지 더한 후 이동거리를 체크한다.
                FVector TestWalk = ( TickAirControl * AccelRate * Acceleration.SafeNormal() + Velocity ) * deltaTime;
                TestWalk.Z = 0.f;
                ... // 이후는 현재 Location 으로부터 TestWalk 만큼 이동한 곳에 특정 world 오브젝트가 있는지 (지형 포함) 체크한다.
            }
            ...
        }
      • 걷기&웅크리기 속도 퍼센티지 ( WalkingPct & CrouchedPct, 기본 이동 속도에 곱하여 걷기속도 및 웅크리기속도를 구하는 방식에 사용 )
        // UnPhysic.cpp
        FLOAT APawn::MaxSpeedModifier()
        {
            ...
            if ( bIsCrouched )
            {
                Result *= CrouchedPct;
            }
            else if ( bIsWalking )
            {
                Result *= WalkingPct;   // 바로 위에서 Pawn 의 무브먼트 속도를 Result 에 누적하여 구하고 그것을 Walking 상태여부에 따라 곱하여 현재 무브먼트 속도를 구한다.
            }
            ...
        }
      • 데미지 없이 낙하 가능한 속도 ( MaxFallSpeed, velocity 와 비교된다. )
      • AI들은 이보다 적은 낙하속도가 가능한 길을 택할 것이다. ( AIMaxFallSpeedFactor, 바로 위 변수와 곱하여 AI를 위한 낙하 속도를 구함 MaxFallSpeed * AIMaxFallSpeedFactor )
    • Camera 관련
      • Pawn 카메라 높이 ( BaseEyeHeight )
        // UnPawn.cpp
        FVector APawn::GetPawnViewLocation()
        {
            return Location + FVector( 0.f, 0.f, 1.f ) * BaseEyeHeight;
        }
      • 계산된/조절된 Pawn 카메라 높이 ( EyeHeight )
    • 숨쉬기/HP
      • 숨을 쉬기 위한 머리 ( HeadVolume )
      • HP ( Health )
      • 최대 HP ( HealthMax )
      • HP 를 모든 클라에게 복제할까? ( bReplicateHealthToAll )
      • 숨쉬기 타이머 ( BreathTime, 물에 빠졌을 때 일정 시간마다 데미지를 주기 위한 주기 )
        // UnLevTic.cpp
        void APawn::TickSpecial( FLOAT DeltaSeconds )
        {
            // Authority 이고 BreathTime 중이라면
            if ( Role == ROLE_Authority && BreathTime > 0.f )
            {
                BreathTime -= DeltaSeconds;
         
                if ( BreathTime < 0.001f )
                {
                    // 때가 됐다면 BreathTimer 호출 (바로 아래)
                    BreathTime = 0.0f;
                    eventBreathTimer();
                }
            }
         
            ...
        }
         
        // Pawn.uc
        event BreathTimer()
        {
            if ( HeadVolume.bWaterVolume )
            {
                if ( Health < 0 || WorldInfo.NetMode == NM_Client || DrivenVehicle != None )
                    return;    // 죽었거나 클라이언트거나 무엇인가를 타고있다면 무시
         
                TakeDrowningDamage();    // 익사 피해
         
                if ( Health > 0 )
                    BreathTime = 2.0;    // 2초 후 다시 BreathTimer 호출
            }
            else
            {
                BreathTime = 0.0;        // 더 이상 피해는 없음
            }
        }
      • 얼마만큼 숨을 참을 수 있는가? ( UnderWaterTime, 최초 입수 시 BreathTime 에 대입되는 값. 이 시간이 지나면 위의 BreathTimer 함수에 나와있는대로 2초마다 데미지 )
        // UTPawn.uc
        event HeadVolumeChange( PhysicsVolume newHeadVolume )
        {
            ...
         
            else if ( ... )
            {
                BreathTime = UnderWaterTime;    // 입수 시 숨쉬기 타이머 발동
            }
        }
         
        defaultproperties
        {
            ...
            UnderWaterTime=+00020.000000    // 20초
            ...
        }
      • 마지막으로 피해를 입은 시간 ( LastPainTime, BOT 의 경우 피격받고 일정 시간동안 Aim 을 불안정하게 하기위한 용도로써 활용된다. )
        // Pawn.uc
        function PlayHit( ... )
        {
            ...
            LastPainTime = WorldInfo.TimeSeconds;
        }
    • 루트모션
      • 루트모션 속도 ( RMVelocity, 클라이언트에서 루트모션을 재현하기 위한 용도의 변수. 또는 Controller.bPreciseDestination정확한 이동 기능이 활성화일 때 사용되기도 함. )
      • 강제 루트모션 속도 사용 ( bForceRMVelocity, 이 값이 true 면 APawn::CalcVelocity() 에서 RMVelocity 값을 직접적으로 사용 )
        // UnPhysic.cpp
        void APawn::CalcVelocity( ... )
        {
            ...
            if ( bForceRMVelocity )
            {
                Velocity = RMVelocity;
                return;
            }
            ...
        }
      • 강제 일반적인 속도계산 ( bForceRegularVelocity, 이 값이 true 면 APawn::CalcVelocity() 에서 RMVelocity 값을 절대로 사용하지 않는다. )
    • 사운드와 노이즈
      • 여러 변수들 ( noise1spot, noise1time, noise1other, noise1loudness, noise2spot, noise2time, noise2other, noise2loudness )
      • 사운드 음 꺾기 ( SoundDampening )
    • 데미지
      • 데미지 증가 ( DamageScaling ) *

Controller

PlayerController

PlayerReplicationInfo

UTGame 관련

샘플로 제공되는 언리얼토너먼트3 (이하 UTGame) 의 캐릭터를 구현하는데 활용된 여러 요소들을 추가적으로 살펴본다.

UTFamilyInfo

UTPlayerReplicationInfo

이동

정확한 이동

엔진에서 목표지점destination까지 정확하게 이동시켜주는 로직이 존재한다.

대략적인 방법은 이렇다.

  1. 정확히 도달해야 하는 목표지점을 설정한다. (by controller)
  2. 매 프레임마다 도착했는지 여부를 검사하고 아직 도달하지 못했다면 적절한 velocity 를 계산한다.
  3. 목표지점destination에서의 허용 Offset 안에 도달하면 이벤트함수를 호출한다. ( Controller.ReachedPreciseDestination() )

관련 변수/함수는 아래와 같다.

  • 변수
    // Pawn.uc
    var float             DestinationOffset;          // 목표지점으로부터의 허용 Offset
     
    // Controller.uc
    var bool              bPreciseDestination;        // 목표지점에 맞는 velocity 를 강제할 것인지의 여부, 정확한 이동을 수행할 것인지 여부와 상통
    var BasedPosition     DestinationPosition;        // 목표지점
  • 함수
    // UnPhysic.cpp
    void Pawn::CalcVelocity( ... )
    {
        ...
        // RooMotion 일 경우를 제외하고 '정확한 이동' 처리를 수행한다.
        if ( !bDoRootMotionAccel && Controller && Controller->bPreciseDestination )
        {
            FVector Dest = controller->GetDestinationPosition();   // Controller.DestinationPosition 을 Vector 로 형변환하여 리턴
            if ( ReachedDestination( Location, Dest, NULL ) )
            {
                Controller->bPreciseDestination = FALSE;           // '정확한 이동'을 종료
                Controller->eventReachedPreciseDestination();      // 종료 이벤트 호출
     
                Velocity = FVector( 0.f );
                Acceleration = FVector( 0.f );
            }
            else if ( bForceMaxAccel )
            {
                const FVector Dir = (Dest - Location).SafeNormal();
                Acceleration      = Dir * MaxAccel;
                Velocity          = Dir * MaxSpeed;
            }
            else
            {
                Velocity = (Dest - Location) / DeltaTime;
            }
            ...
        }
        ...
    }
     
    // UnPawn.cpp
    UBOOL APawn::ReachedDestination( ... )
    {
        ...
     
        return ReachThresholdTest( ... );
    }
     
    UBOOL APawn::ReachThresholdTest( ... )
    {
        ...
        FLOAT Threshold = ThresholdAdjust + CylinerComponent->CollisionRadius + DestinationOffset;    // 도착으로 인정할 유효 반지름을 계산
        ...
        if ( Dir.SizeSquared() > Threshold * Threshold )
            return FALSE;
        ...
        // 적절하게 테스트하고
        return TRUE;        // 도착했다고 판정
    }

상기 APawn::ReachThresholdTest 함수의 동입부에 도착지점으로부터의 허용 반지름을 계산하는 부분이 있는데, 이 값에 음수를 주어 좀 더 정확한 목표지점에 도달하게 할 수 있다.

무브먼트

언리얼엔진3에 기본적으로 제공되는 UTGame 을 기반으로 한 분석내용이다.

웅크리기 Crouch

코드플로우

  • 발생
    1. DefaultInput.ini
      • .Bindings=(Name=“C”,Command=“GBA_Duck”)
      • .Bindings=(Name=“GBA_Duck”,Command=“Duck | onrelease UnDuck | Axis aUp Speed=-1.0 AbsoluteAxis=100”)
    2. simulated exec function Duck() (UTPlayerInput.uc)
      • bDuck = true;
  • 루프1
    1. state PlayerWalking::ProcessMove(…) (PlayerController.uc) ← state PlayerWalking::ProcessMove(…) (UTPlayerController.uc) ← state PlayerWalking::PlayerMove(float DeltaTime) (PlayerController.uc) ← state PlayerWalking::PlayerMove(float DeltaTime) (UTPlayerController.uc) ← event PlyaerTick(float DeltaTime) (PlayerController.uc) ← event PlayerTick(float DeltaTime) (UTPlayerController.uc)
      • CheckJumpOrDuck();
    2. function CheckJumpOrDuck() (UTPlayerController.uc)
      • 점프나 더블점프를 수행하는 함수를 호출
      • Pawn.ShouldCrouch(bDuck != 0);
    3. funciton SouldCrouch( bool bCrouch ) (Pawn.uc)
      • bWantsToCrouch = bCrouch;
  • 루프2
    1. AActor::TickAuthoritative ← AActor::Tick ← APawn::Tick ← TickActors<FDeferredTickList::FGlobalActorIterator>
      • performPhysics 호출
    2. APawn::performPhysics(float DeltaSeconds) (UnPhysic.cpp)
      • 적절한 조건을 체크한 후 (bWantsToCrouch, bIsCrouched 따위) Crouch() 나 UnCrouch() 를 호출
    3. APawn::Crouch(INT bClientSimulation) (UnPawn.cpp)
      • Collision Cylinder 의 반지름과 높이를 CrouchRadius 와 CrouchHeight 로 변경
      • Crouch Collision Cylinder 가 더 커졌다면 지면을 침범했는지 체크한다.
      • eventStartCrouch( 높이차이 ) 호출
    4. simulated event StartCrouch(float HeightAdjust) (UTPawn.uc)
      • Super.StartCrouch(HeightAdjust);
      • CrouchMeshZOffset = HeightAdjust;
    5. simulated event StartCrouch(float HeightAdjust) (Pawn.uc)
      • EyeHeight 조절
      • OldZ 조절
      • BaseEyeHeight 조절

특이사항

  • 웅크리기에 사용될 충돌영역 정보인 CrouchRadius, CrouchHeight 를 세팅해야 한다.
  • 이때 MaxStepHeight 가 CollisionHeight - CrouchHeight 보다는 커야 한다. MaxStepHeight 는 캐릭터가 자연스럽게 이동 가능한 최대 허용높이이다. 웅크리기가 발동되면 내부적으로 캐릭터 바운딩실린더 높이를 조절하는데 그 갭이 MaxStepHeight 보다 크면 캐릭터는 Falling 운동을 하게끔 처리된다. (엔진 내부적으로) 때문에 주의가 필요하다.

점프 Jump

코드 플로우

  1. 발동
    // DefaultInput.ini
    .Bindings=(Name="GBA_Jump",Command="Jump | Axis aUp Speed=+1.0 AbsoluteAxis=100")
  2. 플래그 체크
    // UTPlayerInput.uc
    exec function Jump()
    {
        ...
        Super.Jump();
        ...
    }
     
    // PlayerInput.uc
    exec function Jump()
    {
        ...
        bPressedJump = true;
    }
  3. 지연 점프
    // PlayerController.uc
    state PlayerWalking
    {
        ...
        function PlayerMove( float DeltaTime )
        {
            ...
            // 지금 점프할 수 없는 상황이면 지연시킨다.
            if ( bPressedJump && Pawn.CannotJumpNow() )
            {
                bSaveJump = true;
                bPressedJump = false;
            }
            ...
        }
        ...
    }
  4. 점프 시도여부 체크 (매 프레임)
    // UTPlayerController.uc
    function CheckJumpOrDuck()
    {
        ...
        else if ( bPressedJump )
        {
            Pawn.DoJump( bUpdateing );
        }
        ...
    }
     
    // PlayerController.uc
    function CheckJumpOrDuck()
    {
        if ( bPressedJump && (Pawn != None) )
        {
            Pawn.DoJump( bUpdating );
        }
    }
  5. 실제 점프로직 처리
    // UTPawn.uc
    function bool DoJump( bool bUpdating )
    {
        ...
        if ( Physics ==PHYS_Spider )
            Velocity = JumpZ * Floor;
        else if ( Physics == PHYS_Ladder )
            Velocity.Z = 0
        else if ( bIsWalking )
            Velocity.Z = Default.JumpZ;
        else
            // 보통 이부분에 걸린다.
            Velocity.Z = JumpZ;
        ...
    }

부가기능

TurnInPlace

Pawn 의 현재 Rotation 을 기준으로 좌우 일정 반경을 회전할 동안 발이 땅에 접지한 상태로 있는 기능을 지원한다. UnrealEngine3 에서는 이 기능을 Turn-In-Place 라고 지칭한다. 이를 수행하는 레이어는 UDKPawn 이다.

분석 포인트

  • AnimTree 노드 캐싱
    // UTPawn.uc
    simulated event PostInitAnimTree(SkeletalMeshComponent SkelComp)
    {
        ...
        // 허리쪽 Bone 을 제어하는 컨트롤 캐싱 (RootRot 이란 이름을 가진 SkelControlSingleBone 컨트롤)
        RootRotControl = SkelControlSingleBone( mesh.FindSkelControl( 'RootRot' ) );
        ...
        // 조준 노드 캐싱
        AimNode = AnimNodeAimOffset( mesh.FindAnimNode( 'AimNode' ) );
        ...
    }
  • 업데이트 로직
    // UDKPawn.cpp
    void AUDKPawn::TickSpecial( FLOAT DeltaSeconds )
    {
        ...
        // 현재 Aim pitch 와 yaw 를 얻는다.
        INT PawnAimPitch;
        if ( Controller )
        {
            // 컨트롤러의 Pitch를 얻는다.
            PawnAimPitch = Controller->Rotation.Pitch;
        }
        else
        {
            // Pawn 의 Pitch를 얻는다.
            PawnAimPitch = Rotation.Pitch;
     
            if ( PawnAimPitch == 0 )
            {
                PawnAimPitch = RemoteViewPitch << 8;
            }
        }
     
        // Pawn 의 최종 조준 Pitch
        PawnAimPitch = UnwindRot( PawnAimPitch );
     
        INT PawnAimYaw = UnwindRot( Rotation.Yaw );
        // Pawn 의 최종 조준 Yaw (가 될 값)
        INT AimYaw = 0;
     
        if ( Physics == PHYS_Walking && Velocity.Size() < KINDA_SMALL_NUMBER )
        {
            // PawnAimYaw 는 손이 향하는 방향, RootYaw 는 발이 향하는 방향이라 생각하면 이해가 쉽다.
            INT CurrentAimYaw = UnwindRot( PawnAimYaw - RootYaw );
            INT RootRot = 0;
     
            if ( CurrentAimYaw > MaxYawAim )
            {
                RootRot = ( CurrentAimYaw - MaxYawAim );
            }
            else if ( CurrentAimYaw < -MaxYawAim )
            {
                RootRot = ( CurrentAimYaw - (-MaxYawAim) );
            }
     
            RootYaw += RootRot;
            RootYawSpeed += ( (FLOAT)RootRot ) / DeltaSeconds;
     
            // 최종 손과 발의 offset. 이것이 곧 Aim 노드에 적용될 Yaw
            AimYaw = UnwindRot( PawnAimYaw - RootYaw );
        }
        else
        {
            RootYaw = Rotation.Yaw;
            RootYawSpeed = 0.f;
            AimYaw = 0;
        }
     
        // 좌우 90 도 회전을 각각 -1, 1 로 매핑시킨 값으로 변환
        if ( !bNoWeaponFiring )
        {
            CurrentSkelAim.X = Clamp<FLOAT>( ( (FLOAT)AimYaw / 16384.f ), -1.f, 1.f );
            CurrentSkelAim.Y = Clamp<FLOAT>( ( (FLOAT)PawnAimPitch / 16384.f ), -1.f, 1.f );
        }
     
        // 허리를 Aim 의 반대쪽으로 회전. 즉, 손의 방향이 Pawn Rotation 과 일치하고 발의 방향을 보정하는 방식
        if ( RootRotControl )
        {
            RootRotControl->BoneRotation.Yaw = -AimYaw;
        }
     
        // Aim 업데이트
        if ( AimNode )
        {
            AimNode->Aim = CurrentSkelAim;
        }
        ...
    }

분석

참고

반응형
반응형

스크립트에 의한 네이티브코드 생성

개요

UnrealScript 를 사용하다 보면 Native 레이어와 상호 통신을 해야할 필요가 생긴다. 이 때 Script 의 내용을 Native 가 인식할 수 있도록 글루코드를 생성할 필요가 있는데 여기서는 이것에 대해 다뤄보도록 한다.

UnrealScript

스크립트 컴파일

이어지는 설명을 이해하려면 스크립트 컴파일하는 법을 먼저 알아야 한다. 컴파일은 게임 실행파일을 이용한1) 콘솔명령어로 수행되며 거두절미하고 바로 명령어 때려본다.

// 이하 콘솔명령어이며, 실행파일은 UTGame.exe 라고 가정한다.
UTGame.exe make                            // 릴리즈로 빌드
UTGame.exe make -full                      // 릴리즈로 풀 빌드
UTGame.exe make -debug                     // 디버그로 빌드 (스크립트를 디버깅하기 위해)
UTGame.exe make -debug -full               // 디버그로 풀 빌드

Native 글루코드 생성

UnrealScript 에서 Native 코드를 생성하는 키워드는 아래와 같다.

// UTGame/Classes/UTWeapon.uc
class UTWeapon extends UDKWeapon
    native // 이 키워드가 붙으면 Native 글루코드가 생성된다.
    ...

:!: 글루코드가 생성되는 파일명도 지정할 수 있다.

위와 같이 native 키워드 뒤에 아무것도 없다면 [패키지명]+Classes.h 에 글루코드가 생성된다. (위 예제와 같이) UTGame 의 경우 UTGameClasses.h 파일에 AUTWeapon 클래스에 대한 선언이 되어있는 것을 확인할 수 있다.

만약 native( MyWeapon ) 와 같이 괄호 안에 특정 명칭을 넣는다면 [패키지명]+[괄호內명]+Classes.h 에 글루코드가 생성된다. 위의 경우 UTGameMyWeaponClasses.h 에 해당된다.2)

글루코드 등록 및 정의

위와같이 글루코드의 헤더가 생성되면 이제 정의(.cpp)를 하고 등록을 할 차례다.

글루코드 정의

스크립트 내에서 네이티브 함수를 지정하는 방법은 여럿 있지만 여기선 그 설명은 생략하고 클래스와 네이티브 함수를 정의하는 방법에 대해 설명하겠다. (위의 UTWeapon 을 예로들어 계속 설명)

먼저 적당한 .cpp 파일을 만든다. UTWeapon.cpp 라고 파일을 만든 후 그 안의 내용은 아래와 같아야 한다.

// UTWeapon.cpp
#include "UTGame.h"
#include "UTGameMyWeaponClasses.h"    // UTWeapon이 선언된 헤더
 
IMPLEMENT_CLASS(AUTWeapon);           // 클래스의 기본적인 기능들을 정의
 
UBOOL AUTWeapon::Tick( FLOAT DeltaTime, ELevelTick TickType )
{
    ...
}

여기서 핵심은 IMPLEMENT_CLASS 이다. 이것은 엔진 내에서 필요한 기본기능 및 글루코드에 필요한 기능들을 자동으로 정의해주는 매크로이다.

나머지 함수들은 알아서 정의한다.

글루코드 등록

이제 엔진이 알 수 있도록 글루코드를 등록할 차례이다. 복잡한 설명은 생략하고 바로 코드 들어간다.

// UTGame.cpp
 
...
 
#include "UTGameMyWeaponClasses.h"                  // 다른 헤더들을 보고 "여기다 싶은 곳" 에 모두 포함시킨다.
 
...
 
void AutoInitializeRegistrantsUTGame( INT& Lookup )
{
    AUTO_INITIALIZE_REGISTRANTS_UTGAME;
    AUTO_INITIALIZE_REGISTRANTS_UTGAME_MYWEAPON;    // 여기서 등록
    ...
}
 
void AutoGenerateNamesUTGame()
{
    ...
    #include "UTGameMyWeaponClasses.h"              // 여기서 이름 (Name) 등록
    ...
}
 
...
1) 언리얼엔진3로 개발된 게임의 실행파일에 스크립트 컴파일 모듈이 포함되어 있다.
2) 현재(20100909)확인해본 결과, 간혹 헤더파일이 생성되지 않는 경우가 있다. 아마 엔진버그같은데 이럴땐 헤더파일을 수동으로 생성해주고 스크립트 컴파일을 해주면 잘 반영된다.

반응형
반응형

 PackageNames.AddItem(TEXT("Sample02_LoadPackage"));

네이티브 패키지 목록에 패키지 추가하기

void appGetGameNativeScriptPackageNames(TArray<FString>& PackageNames, UBOOL bCanIncludeEditorOnlyPackages)
{
...
   PackageNames.AddItem(TEXT("AutoGenExample"));
}

반응형
반응형

http://donggas90.blog.me/100141378528



[UDK] 프로그래밍 초보를 위한 언리얼 스크립트 코드 10선

  

이번 포스트는 프로그래밍을 UDK로 처음 접하시는 분을 위해 적어 볼까 합니다.

 

 

먼저 UDK(언리얼 엔진)는 언리얼 스크립트라는 자신들만의 언어를 사용합니다.

 

 

언리얼 스크립트를 위한 비쥬얼 스튜디오 플러그인 엔프린지와

스크립팅을 시작하는 방법에 대해서는 아래 포스트에서 확인하세요.

http://donggas90.blog.me/100127258591

 

 

그리고 짠 언리얼 스크립트를 어떻게 게임과 에디터에 적용하는 지에 대한 정보는

아래 포스트를 확인하세요.

http://donggas90.blog.me/100134298424

 

 

위 두 포스트의 내용을 숙지하셨다는 가정 하에 시작하겠습니다.

 

 

1. 클래스 오버라이드 - extends

 

  class HUD extends Actor;

 HUD.uc의 일부입니다.

 

class는 앞으로 클래스를 선언할 것임을 나타 냅니다.

 

HUD는 이 클래스의 이름을 나타내며, uc파일과 이름이 같아야 합니다. 

 

extends는 이 클래스가 어느 클래스를 오버라이드하고 있는지 가르킵니다.

 

Actor는 extends가 가르키고 있는 클래스 입니다.

 

;는 여기까지 클래스 선언이 끝났음을 알리고 있습니다. 

 

 

오버라이드란 extends가 가르키는 클래스의 모든 내용을 전부 그대로 복사한다는 뜻입니다.

 

복사'당한' 클래스는 부모 클래스가 되고, 복사'받은' 클래스는 자식 클래스가 됩니다.

 

비록 그 내용이 여기 일일이 적혀 있지는 않지만 적혀져 있는 것으로 간주하는 것입니다.

 

 

그리고 또 다른 의미로 '덮어쓴다'는 의미가 있습니다.

 

부모 클래스에 있는 것을 다시 새로 쓸 때, 오버라이드했다고 합니다.

 

 

언리얼 스크립트 파일들을 하나하나 열어 보시면

 

모든 클래스들이 계속해서 다른 클래스를 오버라이드하고 있음을 알 수 있습니다.

 

그리고 그 가장 위에는 Object클래스가 있고 그 바로 밑에는 Actor가 있습니다.

 

즉, 모든 언리얼 스크립트의 시조는 Object입니다.

 

 

 

2. 변수 선언 - var

 

var string A;

var int B;

var(Variable) float C;

var bool D; 

var는 앞으로 변수를 선언할 것임을 나타냅니다. 

 

이것들은 이 변수가 어떤 타입인지 나타 냅니다. 

 

string이란 문자열을 말합니다. 

 

int란 정수를 말합니다. 

 

float이란 실수 말합니다.

 

bool이란 참, 거짓 논리자입니다. 

 

A는 이 변수의 이름이 A임을 나타냅니다.

 

;는 여기까지 변수의 선언이 끝났음을 나타냅니다. 

 

(Variable)란 언리얼 에디터에서 클래스 프로퍼티 창에

 

Variable이라는 카테고리를 만들고 이 변수를 표시하라는 의미이며 

 

반드시 필요하지는 않습니다. 

 

 

이런 방법으로 변수에 이름을 붙여 줄 수 있습니다. 

 

변수의 종류는 더 많지만 일단 이 정도만 알아도 대부분의 데이터 주무르기가 가능합니다.

 

 

변수에 값을 넣는 방법은 아래와 같습니다.

A = "string";

B = 123;

= 1.f;

이것들은 값을 넣어줄 대상입니다.

 

값을 받을 대상은 항상 왼쪽에 위치합니다.

 

=는 왼쪽에 있는 것에 오른쪽 것을 넣는다는 뜻입니다. 

 

일반적으로 사용하는 의미와는 다릅니다. 

 

넣을 대상과 넣어 줄것의 타입이 일치해야 함을 유의하세요. 

 

"string"은 문자열 string을 나타냅니다. " 가 양쪽으로 사용됐음에 유의하세요.

 

123는 정수 123(백이십삼)을 나타냅니다. 

 

별다른 기호를 사용하지 않아도 됩니다. 

 

1.f는 실수 1(일)을 나타냅니다. 

 

언리얼 스크립트에서는 정수와 실수를 구분하기 위해 

 

실수에는 특수한 표기 방법을 사용합니다. 

 

값의 끝에 f를 붙이는 것이 그것입니다.

 

만약 소수점 자리가 없다면 .(온점)과 함께 f를 붙입니다.

 

;는 여기까지 명령이 끝났음을 알립니다. 

 

 

* 심화

var array<int> A;

 

A[0] = 1;

A[100] = 2;

 

배열은 이름은 같은데 다른 번호를 가진 여러 값의 무리입니다.

 

 

 

 

3. 기본값 - DefaultProperties

 

DefaultProperties
{ 

 A = 123

} 

DefaultProperties{ 는 앞으로 기본값 영역이 시작될 것임을 의미합니다. 

 

A의 기본값이 123임을 의미합니다. 여기서 세미콜론(;)은 선택 사항입니다.

 

} 는 기본값 영역이 끝났음을 의미합니다. 

 

 

이렇게 하면 A는 UDK가 시작되면 항상 123일 것입니다. 

 

이런식으로 기본값이 필요할 경우 정의해 줄 수 있습니다.

 

 

* 심화

런타임이 초기화될 때마다 이 영역의 기본값이 로드됩니다.

 

 

4. 함수 - function

 

함수를 선언하는 방법은 아래와 같습니다.

 

function EXAMPLE()

{

A = 123;

}

function{}는 함수 영역을 나타냅니다. 

 

EXAMPLE는 함수의 이름을 나타냅니다.

 

()는 함수에 사용할 인수(파라미터)의 목록입니다. 

 

자세한 내용은 아래에... 

 

A = 123;는 함수에서 할 것입니다. 

 

 

이 함수가 집행되면  A = 123;을 할 것입니다.

 

하지만 재밌는 점은 이렇게 만들더라도 실질적으로 작동은 되지 않습니다.

 

왜냐면 언리얼 엔진이 이 함수를 호출하지 않기 때문입니다.

 

따라서 이 함수를 작동하게 하려면

 

언리얼 엔진이 호출하는 함수 안에서 이 함수를 호출해야 합니다.

 

simulated event Tick(float DeltaTime)
{
 

 EXAMPLE();

} 

대표적인 예로 Tick이라는 이벤트가 있습니다.

 

이 이벤트는 일정 시간 간격으로 계속 이 영역을 집행합니다.

 

그리고 그 시간 간격은 DeltaTime에 실수 타입으로 저장합니다.

 

 

이벤트는 함수와는 성격이 다른데, 이벤트는 모두 언리얼 엔진이 특정 상황이나 조건에서 호출하는 것입니다.

 

임의로 프로그래머가 호출할 수 없습니다.

 

 

 

function EXAMPLE( int B )

{  

A = 25;

A = B;

} 

 

EXAMPLE( 12 ); 

A는 12 

위 스크립트는 인수 사용의 대표적인 예입니다.

 

인수는 함수를 선언할 당시에는 타입만 지정돼 있습니다.

 

뭔가 값은 없습니다.

 

하지만 다른 곳에서 사용되었을 때 그 값을 지정해 주고

 

함수 내용을 집행하게 됩니다.

 

 

 

 

5. 함수의 변수화 - return

 

앞에서 함수에 대해 알아 봤습니다.

 

그런데 함수에서 뭔가 계산한 뒤 바로 무엇인가 얻고 싶을 경우가 생길 겁니다.

 

따로 변수의 도움 없이 말입니다.

 

function int EXAMPLE()

{

A = 123; 

return A; 

} 

 

B =  EXAMPLE();

B는 123 

int로 이 함수는 정수 결과를 출력할 것임을 나타냅니다. 

 

return A; 로 이 함수의 출력 값은 A값임을 알립니다.

 

이렇게 하면 함수 내에서 계산한 값을 또 다른 변수에 넣어주고

 

다시 받아서 B에 할당하는 번거로운 과정이 생략됩니다. 

 

* 심화

function Ex ( out int A )

{

A = B + C ;

return;

}

값을 직접 받지 않고 간접으로 받을 수도 있습니다.

 

 

6. 부모 함수 집행 - super

 

오버라이드는 덮어쓴다는 개념이 있다고 말씀 드렸습니다.

 

덮어쓰게 되면 이전의 것은 없어지고 새로운 것만 작성되게 됩니다.

 

함수 역시 오버라이드할 수 있습니다.

 

그런데 함수를 통째로 다시 쓰지 않고 나만의 새로운 코드만 추가하고 싶을 때가 있습니다.

 

이럴 때 쓰는 것이 super코드입니다.

function EXAMPLE()

{

super(Actor).Example(); 

A = 123;

}

super.Example();로 가장 가까운 부모에 있는 Example()함수를 집행하도록 합니다. 

 

 (Actor)는 선택 사항인데 어느 클래스에 있는 부모 함수를 집행할지 지정합니다.

 

 부모 엑터 클래스의 함수가 집행된 뒤 A=123; 집행되게 됩니다.

 

게임의 기본 틀을 구성하는 함수여서 마음데로 수정이 어렵거나

 

언리얼 엔진이 호출하는 이벤트에 내용을 덧붙이고 싶을 때 아주 유용합니다.

 

 

 

7. 조건부 - if

 

뭔가 조건을 제시하고 그게 맞다면 실행되게 하고 싶을 때가 많을 겁니다.

 

이럴 때 쓸 수 있는 것이 if 코드 입니다.

if ( A == 1 )

{

 B = 1;

} 

else if ( A == 2 )

{ 

B = 2; 

} 

else 

{ 

B = 3; 

} 

이것들은 한 묶음입니다. if와 else는 처음과 끝에 각각 하나만 올 수 있고 

 

else if 는 원하는 만큼 쓸 수 있습니다. 

 

이것이 참이면 그 아래 영역을 집행합니다. 

 

즉, A가 1이면 B는 1이될 겁니다. 

 

여기에 사용되는 논리 판단 기호에 유의하세요. 

 

==는 서로 같은지 비교합니다. 

 

!=는 서로 다른지 비교합니다.

 

이외에 <, >, <=, >=는 일반적인 수학에서 쓰는 그데로입니다. 

 

if는 조건 부분을 무조건 참인지 거짓인지 판단합니다.

else if 는 if의 조건이 거짓일 경우 참인지 거짓인지 판단합니다.

else 는 if 와 else if 의 조건 모두가 거짓이면 집행합니다.

 

 

위에서 적어드린데로 프로그램이 조건을 검사하게 됩니다.

 

조건을 검사하려면 CPU가 계산을 해야하고

 

그러면 리소스가 소모 됩니다.

 

게임을 최적화하려면 if를 남발하지 말고 적절히 else를 이용해야 합니다.

 

 

조건을 검사할 때, 꼭 한 가지가 아니라 여러 가지의 조건을 쓰는 경우가 있습니다.

 

이럴 때 수학에서는 AND, OR라는 표현을 쓰는데

 

프로그래밍에도 이런 개념이 존재합니다.

if ( A == 1 && B == 3 )

{

B = 1;

}

else if ( A == 2 || B == 1 )

{

B = 2;

}

&&(쉬프트 + 숫자 7)는 AND라는 의미입니다.

 

||(쉬프트 + \)는 OR라는 의미입니다.

 

 

덧붙여 이런 것도 가능합니다.

var bool A;

 

A = true;

B = 0;

 

if ( A )

{

B = 1;

} 

 

B는 1 

조건 부분이 참(true)인지 거짓(false)인지 검사하는 것이기 때문에

 

조건 그 자체가 참인 경우도 집행됩니다.

 

* 심화

switch(A) 

{ 

case(1) : 

//A가 1이면 집행합니다. 

break;//으로 집행의 끝을 알립니다. 

 

default: 

//A와 관계 없이 집행합니다.

break; 

} 

 

 

8. 변수의 무리 - struct

 

변수들을 여럿 선언하고 보면

 

이따금씩 서로 관련이 있는 변수인 경우가 있습니다.

 

예를 들면 게임 케릭터의 능력치입니다.

 

힘 변수를 선언하고 지능 변수를 각각 선언한다면

 

관리하기가 쉽지 않겠죠.

 

왜냐면 케릭터가 하나가 아닐테니까요.

 

그리고 새로운 능력치를 추가하려면 대부분의 코드들을 뜯어 고쳐야할 것입니다.

 

이럴 때 필요한 것이 바로 구조체, struct 코드입니다.

struct CharacterInfo

{

var int Strength;

var int Intelligence;

};

struct로 여기부터 구조체를 선언하겠다고 알립니다. 

 

CharacterInfo는 이 구조체의 이름입니다. 

 

var int Strength; 구조체에 포함된 변수입니다. 

 

; 구조체를 선언한 뒤 붙이는 것을 잊지 마세요. 

 

그런데 이걸 어떻게 사용하는 걸까요.

 

<strong></strong>

var CharacterInfo A;

구조체를 선언하면 마치 변수 타입인 것처럼 사용할 수 있습니다.

 

A라는 이름을 가진 CharacterInfo 구조체를 만들었습니다.

 

그렇다면 구조체에 포함된 변수는 어떻게 쓰는 걸까요.

 

A.Strength = 123;

A.Intelligence = 321;

구조체의 이름 뒤에 온점을 붙여 그 속에 포함된 변수를 부를 수 있습니다.

 

 

다른 이름의 같은 구조체가 있습니다.

 

하나의 값을 다른 하나에 넣어 주기 위해

 

포함된 변수를 하나하나씩 불러 할당하고 있습니다.

 

var CharacterInfo A; 

var CharacterInfo B; 

 

A.Strength = B.Strength;

A.Intelligence = B.Intelligence;

물론 이런 방법을 쓸 수도 있지만 

 

굳이 일일이 값들을 불러오지 않아도 됩니다.

 

var CharacterInfo A;

var CharacterInfo B;

 

A = B;

 

 

 

 

9. 메모 - /* **/

 

프로그래밍 언어는 우리가 쓰는 말과는 전혀 다릅니다.

 

그리고 복잡한 함수나 추상적인 이름의 변수를 사용해야 하는 경우가 자주 발생합니다.

 

이를 설명하기 위해 메모, 주석을 달게 됩니다.

/** 수 두 개를 더하는 함수 */

function int Add( int A, int B )

{

C = A + B;

return C;

}

/** */이 부호가 사용된 문자들은 모두 무시됩니다.

 

비쥬얼 스튜디오와 엔프린지를 사용하는 경우

 

Add에 마우스를 올리면 툴팁이 나오는데

 

두번째 줄에 주석의 내용이 표시됩니다.

 

 

* 심화

/** 수 두 개를 더하는 함수

* @param A 더할 첫번째 값

* @param B 더할 두번째 값 */

<strong></strong>

function int Add( int A, int B )

{

C = A + B;

return C;

} 

파라미터마다 개별적인 주석을 달 수도 있습니다. 

 

 

 

10. 디버깅 - `log 

 

버그는 예상치 못한 곳에서 주로 발생합니다. 

 

버그를 해결하는 작업을 디버깅이라 하는데 

 

디버깅을 위해서 사용되는 코드가 있습니다. 

 

바로 `log코드입니다. 

 

`는 1의 왼쪽에 있는 키입니다.

 

쉬프트를 누르면 ~이 되는 키죠.

`log("test");

이런 식으로 사용합니다.

 

괄호안의 내용은 문자열이므로 쌍따옴표를 사용해야 합니다.

 

그런데 이 글자는 어디서 확인하는 것일까요.

 

UDK에서 게임 태스트를 시작합니다.

 

그리고 커멘드라인에 SHOWLOG를 입력하면

 

윈도우 cmd같은 검은 창이 표시됩니다.

 

만약 로그가 정상적으로 집행됐다면

 

그곳에 하얀색 글씨로 test란 글자가 나타날 겁니다.

 

 

이런 단순 문자열 로그는 특정 함수나 조건부가 제대로 동작하는지

 

파악할 때 유용합니다.

 

 

만약 변수 값이 궁금할 때는 어떻게 할까요.

 

그럴 때는 이렇게 합니다.

var int A;

`log("variable is"@A);

@는 문자열을 공백 하나를 넣고 합칠 때 사용합니다.

 

만약 A가 123이면

 

로그창에는 이렇게 나타날 것입니다.

 

variable is 123

 

로그 코드는 디버깅에 효과적이기는 하지만

 

로그 또한 집행하려면 CPU가 계산을 해야 합니다.

 

따라서 디버깅이 끝나면 로그 코드는 지워 주세요.

 

반응형
반응형

퍼포스에서 UE3 코드 베이스 싱크를 마치고나면 퍼포스 디포 루트 폴더에 UnrealEngine3 라는 디렉토리가 생길 것입니다.

"UnrealEngine3" 폴더 안에는 다음 디렉토리가 있습니다:

  • Binaries - 이 파일에는 바이너리 오브젝트가 담깁니다. 엔진/게임의 컴파일된 버전, UE3 필수 DLL (DirectX, wx 런타임 등), 에디터 리소스 파일 (아이콘 이미지) 등입니다.
  • Development - 엔진의 모든 소스가 실제로 들어가는 곳으로, 이 폴더 안에는:
    • Build - 저희 CIS(연속 통합 서버) 상에서 여러가지 유형의 빌드를 발송하는 배치 파일을 저장하는 곳입니다. CIS 는 빌드를 검사하여 저희가 빌드 에러를 가급적 빠르게 잡아낼 수 있도록 해 줍니다.
    • Documentation - 문서 파일 저장용 폴더입니다. 저희의 자동 생성 윈도우 도움말 파일(UnrealEngine3.chm)이 여기 저장됩니다. 이 도움말 파일은 언리얼 소스를 빠르게 돌아보는 데 큰 도움이 되는 자료입니다.
    • External - 모든 에픽 외부 라이브러리 소스는 이 폴더에 저장됩니다. libPNG, wxWidgets, zlib, Cg 등입니다.
    • Intermediate - 모든 중간 빌드 파일은 여기 저장됩니다. .obj 파일, 미리컴파일된 헤더 등입니다.
    • Src - 소스 파일과 프로젝트 메이크파일용 폴더입니다. 주요 UnrealEngine3 솔루션은 이 폴더에 있습니다. 솔루션 안에는 각 프로젝트에 대한 폴더 역시 있습니다. 예제 프로젝트는 다음과 같이 나뉘어 있습니다:
      • Game 폴더
        • 프로젝트의 루트 폴더 안에는 프로젝트의 비주얼 스튜디오 프로젝트 파일이 있습니다.
        • Classes - .uc (UnrealScript) 파일을 담는 폴더입니다.
        • Inc - .h 파일을 담는 폴더입니다.
        • Src - .cpp 파일을 담는 폴더입니다.
    • Tools - 다양한 엔진 관련 툴에 대한 소스가 모두 저장된 폴더입니다. UDE 비주얼 스튜디오 매크로나 명령줄 툴같은 것들입니다.
  • Engine/Game 폴더 - Binaries 와 Development 폴더 다음에는 Engine 에 대한 폴더가, 그 이후 엔진을 사용하는 각 게임에 대한 폴더가 하나씩 있습니다.
    • Config - 해당 게임에 사용된 세팅을 저장하는 데 사용된 .ini 파일을 담습니다.
    • Content - 해당 게임용 콘텐츠 패키지를 담는 폴더입니다.
    • Localization - 해당 게임용 현지화 텍스트를 담는 폴더입니다.
    • Logs - 해당 게임 실행시 생성되는 로그 파일을 담는 폴더입니다.
    • Script - 해당 게임에 대한 컴파일된 UnrealScript 코드(.u 파일)를 담습니다.

프로젝트 구조


UE3 는 여러 프로젝트로 나뉘는데, 시스템을 분리하고 크로스-플랫폼 호환성을 쉽게 유지할 수 있도록 하기 위해서입니다.

기본 구조는 (최하위 레벨 코드에서 최상위 레벨까지) 다음과 같습니다:

  • Core
  • Engine
  • Editor
  • UnrealED

참고로 이 프로젝트 모두는 서로의 위에 빌드됩니다. 즉 Engine 은 Core 에 의존, Editor 는 Engine 에 의존, UnrealED 는 Editor 에 의존합니다.

  • Core: Core 는 최하위 레벨 클래스로, 문자열 클래스, 배열 클래스, 메모리 매니저와 같은 것들입니다. 말 그대로 핵심 스크립팅 및 로딩 코드가 있는 곳입니다.

  • Engine: Engine 은 대부분의 렌더링과 게임플레이 코드가 있는 곳입니다. 액터, 충돌 감지는 물론 여러가지 서브시스템에 관련된 코드도 포함되어 있습니다. 대부분의 '게임 인터페이스' 코드가 있는 곳입니다.

  • Editor: Editor 는 에디터가 실행중일 때만 실행되는 클래스와 루틴으로 구성됩니다. 사용자가 폴리곤, 브러시, 터레인을 조작 및 보통 게임내에서는 할 수 없는 다른 작업을 할 수 있도록 해 주는 클래스입니다.

  • UnrealED: UnrealED 는 에디터의 실제 UI 구현으로, 에디터 어플리케이션 코드 전부가 있는 곳입니다. 현재 저희가 선택한 UI 툴키트는 wxWindgets 입니다. 작성할 필요가 있는 wxWidgets 코드는 UnrealED 안에 작성됩니다. 'Editor' 프로젝트에는 편집 툴의 모든 함수성이 들어가고, 코드는 UnrealED 에 들어간다는 개념은 그저 해당 함수성에 대한 UI 감싸개(wrapper)일 뿐입니다.

다른 플랫폼 전용 프로젝트도 여럿 있는데, D3DDrv (렌더링의 Direct 3D 구현), WinDrv (뷰포트 생성, 이벤트 처리 등의 윈도우 구현) 등입니다. 한 프로젝트가 크로스-플랫폼 인터페이스의 특정 플랫폼 전용 구현이라면 보통 Drv 같은 접미사가 붙게 마련입니다.

반응형

+ Recent posts