UDN
Search public documentation:

InstrumentingGameStatisticsKR
English Translation
日本語訳
中国翻译

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

게임 통계 수집하기

문서 요약: 게임 통계 수집을 성공적으로 조율하기 위해 필요한 구조, 함수성, 절차를 자세히 다루는 문서입니다.

문서 변경내역: 2009/05/11 초기 버전 Josh Markiewicz 작성; Jeff Wilson 업데이트; 홍성진 번역.

개요

엔진 내 통계 수집의 배경 개념은, 개임 개발 과정에서 플레이 세션 도중에 그러모은 유용한 데이터를 게임 디자이너에게 조달해 주고자 함입니다. 히스토리 비교 및 데이터 마이닝을 위해 유연하고 빠르며 철저해야 합니다. 마무리로써 버퍼 I/O 및 버전화 지원을 포함, 경량 데이터 구조를 사용하여 디스크로 스트림시키는 시스템을 사용합니다. 데이터가 새로 캡쳐되거나 포맷이 바뀌어도 예전 파일을 항상 읽을 수 있도록 하는 데 노력을 기울였습니다.

게임플레이 세션은 절대 똑같은 것이 두 번 반복되지 않기에, 어떤 순서의 데이터 스트리밍도 지원하는 파일 포맷이어야 합니다. 그 정의는 아래와 같습니다.

데이터를 어느 정도 모았으면, 게임 통계 비주얼라이저 참고서 로 넘어가 보시기 바랍니다.

이벤트

엔진이 기본적으로 지원하는 이벤트는 전부 GameStats.uci에 정의되어 있습니다. 이벤트 ID는 1000 이상으로 시작하는 게임 전용 식별자를 통해 논리 그룹으로 조직화되어 있습니다. 할당 사항은 원하는 대로 조직화하여 물론 그 고유성을 유지할 수 있습니다. 이 파일에 정의된 매크로와 이벤트를 사용하므로써 플레이 세션에 대한 정보 기록을 시작할 수 있습니다. 이미 정의된 지원 이벤트 유형 상세 정보는 GameStats.uci를 참고하십시오.

GameplayEventWriter

데이터 전부를 디스크에 쓰는 작업을 처리하는 클래스입니다. 파일 생성, 버퍼링, 닫기를 처리합니다. 플레이 세션에 대한 정보 기록용으로 정의된 다양한 로깅 함수를 사용하십시오. 물론 원하는 대로 함수성을 덮어쓸 수도 있습니다.

로그 세션 시작하기

StartLogging(optional float HeartbeatDelta)

이 함수는 녹화 세션 시작시 호출되며, 쓸 파일을 엽니다. 플랫폼 제한을 인식하여 확실히 고유한 파일명이 선택되고, 게임의 메인 디렉토리의 Stats 폴더에 생성됩니다. heartbeat 파라미터가 지정된 경우 시스템은 매 HeartbeatDelta 초마다 Poll() 로의 호출을 구하게 됩니다. 기본값으로 모든 활성 플레이어 콘트롤러 위치 및 방향은 이 시점에서 로그됩니다. 수집하는 정보는 게임에 관련있다 판단되는 정보는 무엇이든 캡처/업데이트하도록 확장 가능합니다.

게임 통계 로깅을 게임에 추가하려면, 커스텀 GameInfo 클래스가 이벤트 로깅을 담당하는 GameplayEventsWriter (또는 그 서브클래스)의 인스턴스를 새로 만들어야 합니다. 이는 GameInfo 서브클래스의 PostBeginPlay() 함수에서 수행될 겁니다.

function PostBeginPlay()
{
   local class<GameplayEventsWriter> GameplayEventsWriterClass;

    //코드 잘라냄...

   //옵션으로 게임플레이 이벤트 로거 셋업
   if (bLogGameplayEvents && GameplayEventsWriterClassName != "")
   {
      GameplayEventsWriterClass = class<GameplayEventsWriter>(FindObject(GameplayEventsWriterClassName, class'Class'));
      if ( GameplayEventsWriterClass != None )
      {
         `log("Recording game events with"@GameplayEventsWriterClass);
         GameplayEventsWriter = new(self) GameplayEventsWriterClass;
         //옵션으로 여기서 로깅 시작
         //GameplayEventsWriter.StartLogging(0.5f);
      }
      else
      {
         `log("Unable to record game events with"@GameplayEventsWriterClassName);
      }
   }
   else
   {
      `log("Gameplay events will not be recorded.");
   }

    //코드 잘라냄...
}

bLogGameplayEvents, GameplayEventsWriterClassName, GameplayEventsWriter 변수가 사용되는걸 보실 수 있을 겁니다. 모두 UTGame 클래스에 정의되어 있는 것들입니다. 게임이 UTGame에서 연장되지 않은 경우, 비슷한 식으로 변수를 정의해 줘야 합니다.

위의 코드는 커스텀 게임유형의 로깅이 켜진 경우 *Game.ini 파일에 지정된 클래스의 인스턴스를 만들어 냅니다. 옵션으로 로깅을 즉시 시작할 수 있는 부분을 주석 처리해 놔서, 주석을 없애면 게임이 통계 로깅을 바로 시작하게 할 수도 있습니다. StartLogging() 은 게임에 관계가 있는 곳이면 어디에서든 호출 가능합니다. 몇몇 게임유형에서는 통계 로깅 시작을 위해 StartMatch() 함수를 사용하는 경기에 대해 지연 시작을 사용하기도 합니다.

이벤트 로그하기

GameStats.uci에는 게임플레이 데이터 로그하기에 좋은 매크로가 이미 여럿 포함되어 있습니다. 적은 함수로도 많은 경우를 덮을 수 있도록 일반적으로 구현되어 있습니다. 전형적으로 특정 일반 데이터로 저장된 게임 이벤트 식별자는 고의로 캡처하기 충분할 겁니다. 플레이어 위치, 방향, 이벤트 도중 사용된 무기 및/또는 기타 정보 쪼가리들 로깅용 좀 더 복잡한 함수도 있습니다. 요점은 단일 ID와 일반 콘테이너는 그 자체로는 별 거 아닐지 모르겠지만, 나중에 데이터를 해석하기에 따라 완전 게임 전용, 맥락 전용, 유저에 특화되게 되는 겁니다.

데이터 파일 내 공간 절약용 다양한 메타데이터(팀, 플레이어, 무기 등)를 적절히 만들고 찾고 인덱싱하는 함수가 있습니다.

새 범주 GameStats가 통계 수집용 출력에 할당되어 있습니다. 로그 억제를 해제하면 기록되는 이벤트 출력을 실시간으로 확인할 수 있습니다.

이벤트를 로그하기 위해서는 GameplayEventWriter 의 적절한 함수에 필요한 정보를 전달하여 호출해야 합니다. GameStats.uci 파일에 미리 정의된 매크로를 사용하면 이 과정이 꽤나 수월해집니다. 예를 들어 각 플레이어 킬과 데쓰 이벤트를 로그하려면, GameInfo 서브클래스의 Killed() 함수 내에서 `RecordKillEvent 를 사용하면 됩니다.

// 킬 메시지 모니터링
function Killed( Controller Killer, Controller KilledPlayer, Pawn KilledPawn, class<DamageType> DamageType )
{
    //코드 잘라냄...

    if ( KilledPRI != None )
    {
        `RecordKillEvent(NORMAL, Killer, DamageType, KilledPlayer);

    //코드 잘라냄...
}

분명 완전 똑같은 게임은 둘 나오지 않기에, 필요한대로 다양한 변수를 죄다 접근해 버리면 미리지정된 매크로가 호출될 곳이 제한되게 됩니다. 사용가능한 데이터를 사용하여 자체 이벤트 로깅용 매크로를 자체적으로 정의해야 할 수도 있습니다.

위에 잘라낸 코드 쪼가리에서, NORMAL 은 GAMEEVENT_PLAYER_KILL_NORMAL 이벤트를, Killer 는 죽인 플레이어의 콘트롤러를, DamageType 은 죽게 만든 데이미즤 유형을 나타내는 클래스를, KilledPlayer 는 죽은 플레이어에 대한 콘트롤러를 나타냅니다. RecordKillEvent 는 이 정보를 잡아다가 데이터 스트림에 킬 이벤트를 기록하고, PlayerList 메타데이터 배열에다 필요한 대로 죽인자와 죽은자에 대한 항목을 추가하고 나서 스트림 자체 내에서 그 인덱스를 사용합니다.

로그 세션 끝내기

EndLogging()

세션 종료시에 호출되는 EndLogging() 는 나머지 꼬리말 데이터를 쓰고, 헤더를 확인하고, 최종적으로 기록 파일을 닫습니다. 이 함수가 호출되지 않으면 해당 파일은 사용불가능한 것으로 간주되어 파일 리더가 읽을 수 없습니다.

UTGame의 EndLogging() 함수는 통계 로깅 프로세스를 끝내는 기본 함수성이 포함되어 있습니다. 게임이 UTGame에서 연장되지 않은 경우, GameInfo 서브클래스에다 비슷한 함수성을 추가해야 합니다.

function EndLogging(string Reason)
{
   if (GameplayEventsWriter != None)
   {
      GameplayEventsWriter.EndLogging();
   }

   Super.EndLogging(Reason);
}

요약

코드 작업 방식을 어떻게 조율할 지에 대한 예제는 UTGame 소스 코드를 참고하십시오.

조율 점검사항:

  • 파생된 GameInfo 클래스에서의 게임플레이 통계 변수 선언
  • 게임플레이 세션 로깅의 시작 및 중지
    • 파생된 GameInfo 클래스의 PostBeginPlay() 도중의 쓰기 함수 인스턴스 생성
    • 게임의 디자인에 따라 PostBeginPlay() 또는 StartMatch() 에서 로깅 시작
    • 전형적으로 EndLogging() 함수에서 로깅 종료
  • 엔진에 포함되지 않은 커스텀 이벤트는 파생된 GameplayEventsWriter 클래스 내의 SupportedEvents 배열에 추가
  • 로깅에 사용된 각 .uc 파일에 GameStats.uci를 꼭 include
    • 파생된 GameInfo.uc 파일 안에다 파일을 include시킬 때, 다음과 같은 식으로 추가
               `define GAMEINFO(dummy)
               `include(UTGame\UTStats.uci);
               `undefine(GAMEINFO)
               
      이렇게 하면 include 파일이 최적화되어 WorldInfo에 접근할 필요가 없어집니다. 다른 파일에서 이벤트를 로그하려면, 그냥:
               `include(UTGame\UTStats.uci)
               
      define 없이 사용합니다. include directive 내의 매크로는 WorldInfo에 직접 접근할 수 있다 가정합니다. 아니면 로깅 함수는 직접 호출 가능합니다.
  • 무슨 클래스가 이벤트 로깅을 담당하는지, 로깅을 켜고/끄는지 알려주기 위해 게임의 DefaultGame.ini 속에다 항목을 추가합니다:
             [XXXGame.XXXGame]
             GameplayEventsWriterClassName=XXXGame.XXXGameplayEventsWriter
             bLogGameplayEvents=<true/false>
             
  • 어느 게임 통계 데이터베이스 클래스를 사용할지 지정하고 DB 업로드용 SQL 데이터베이스 연결 정보를 셋업하기 위해 게임의 DefaultEditor.ini 속에다 항목을 추가합니다. 에디터에서 데이터를 시각화해 보기 위해 필수입니다.
             [UnrealEd.GameStatsBrowser]
             GameStatsDBClassname=XXXEditor.XXXGameStatsDatabase
             RemoteConnectionIP="XXX.XXX.XXX.XXX"
             ConnectionString="Provider=sqloledb;Data Source=<database name>;Initial Catalog=GameStats;Trusted_Connection=Yes"
             RemoteConnectionStringOverride="Data Source=<database name>;Initial Catalog=GameStats;Integrated Security=True;Pooling=False;Asynchronous Processing=True;Network Library=dbmssocn"