UDN
Search public documentation:

InstrumentingGameStatistics
日本語訳
中国翻译
한국어

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

Collecting Game Statistics In Your Game

Document Summary: This document details the structures, functionality and procedures necessary to successful instrument your game with stats collection.

Document Changelog: Initial Version by Josh Markiewicz 05/11/2009. Updated by Jeff Wilson.

Overview

The idea behind statistics gathering in engine was to provide game designers with useful data captured during play sessions throughout game development. It must be flexible, fast, and exhaustive. Flexible to rapidly change with the needs of designers, fast in that it can capture lots of data in realtime without disrupting gameplay, and exhaustive for historical comparison and data mining. To that end, the system uses lightweight data structures streamed to disk with buffered I/O and versioning support. Effort was made to make sure that older files will always be readable even as the data captured or their format changes.

Since a gameplay session is never the same twice, the file format has to support the streaming of data in any order. This is defined below.

Once you've collected some data, head on over to the reference on using the Game Stats Visualizer.

Events

All events supported initially by the engine are defined in GameStats.uci. Event IDs have been organized into logical groupings, with game specific identifiers starting above 1000. You can keep your assignment organized however you wish, making sure they are unique of course. By using the macros and events defined in this file, you can begin to record information about your play sessions. See GameStats.uci for more information on the support event types already defined.

GameplayEventWriter

This class handles all the writing of the data to disk. It handles file creation, buffering, and closing. Use the various logging functions defined to record information about your playsessions. Of course you can override functionality as you wish.

Starting A Log Session

StartLogging(optional float HeartbeatDelta)

This function is called at the beginning of a recorded session and opens up the file for writing. A guaranteed unique and platform limitation aware filename is chosen and created in the Stats folder of your game's main directory. If the heartbeat parameter is specified, then the system will get a call to Poll() every HeartbeatDelta seconds. By default, all active player controllers positions and orientations are logged at this time. The information gathered can be extended to capture/update whatever information deemed relevant to your game.

To add game stats logging to your game, your custom GameInfo class must create a new insatnce of a GameplayEventsWriter (or subclass thereof) which is responsible for logging the events. This should be done in the PostBeginPlay() function of your GameInfo subclass.

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

    //Code snipped...

   //Optionally setup the gameplay event logger
   if (bLogGameplayEvents && GameplayEventsWriterClassName != "")
   {
      GameplayEventsWriterClass = class<GameplayEventsWriter>(FindObject(GameplayEventsWriterClassName, class'Class'));
      if ( GameplayEventsWriterClass != None )
      {
         `log("Recording game events with"@GameplayEventsWriterClass);
         GameplayEventsWriter = new(self) GameplayEventsWriterClass;
         //Optionally begin logging here
         //GameplayEventsWriter.StartLogging(0.5f);
      }
      else
      {
         `log("Unable to record game events with"@GameplayEventsWriterClassName);
      }
   }
   else
   {
      `log("Gameplay events will not be recorded.");
   }

    //Code snipped...
}

You'll notice the bLogGameplayEvents, GameplayEventsWriterClassName, and GameplayEventsWriter variable being used. Those are defined in the UTGame class. If your game does not extend from UTGame, you would need to define similar variables to be used.

The code above creates an instance of the class specified in the *Game.ini file if logging is enabled for your custom gametype. It also has a commented section that optionally starts logging immediately which could be uncommented if your game should begin logging stats immediately. StartLogging() can be called wherever relevant for the game. Some gametypes that use a delayed start for the match use the StartMatch() function to begin logging of stats.

Logging Events

GameStats.uci already contains numerous useful macros for logging gameplay data. They are implemented generically so that fewer functions can fill many needs. Typically a game event identifier stored as a particular generic data type should be enough to capture intent. There are more complicated logging functions that store player position, orientation, weapons used during the event and/or other pieces of information. The point is that while a single ID and a generic container isn't much by itself, it becomes game specific, context specific, and completely up to the user how the data is interpreted later.

Functions exist to properly create, find, and index various metadata (team, player, weapon, etc) for space savings within the data file.

A new category, GameStats, has been assigned to the output for stats collection. You can unsuppress it in the logs to see realtime output of recorded events.

In order to log an event, the appropriate function must be called in the GameplayEventWriter passing it the required information. Using the pre-defined macros in the GameStats.uci file, makes this process fairly painless. For example, to log each player kill and death event, `RecordKillEvent might be used inside the Killed() function in your GameInfo subclass.

// Monitor killed messages
function Killed( Controller Killer, Controller KilledPlayer, Pawn KilledPawn, class<DamageType> DamageType )
{
    //code snipped...

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

    //code snipped...
}

Obviously no two games are exactly the same, so access to various variables needed may limit where the predefined macros may be called. it may be necessary to define your own macros for logging your own events using the data available.

In the code snippet above, NORMAL represents the GAMEEVENT_PLAYER_KILL_NORMAL event, Killer is the controller of the player doing the killing, DamageType is the class representing the type of damage done to cause the death, and KilledPlayer is the controller for the player dying. RecordKillEvent will take this information and record a kill event in the data stream, adding appropriate entries into the PlayerList metadata array for the killer and killee as necessary then using those indices inside the stream itself.

Ending A Log Session

EndLogging()

Called at the end of the session, EndLogging() writes out the remaining footer data, validates the header, and finally closes the recording file. If this function is not called, then the file is considered invalid and cannot be read by the file readers.

The EndLogging() function in UTGame contains the basic functionality for ending the stat logging process. If your game does not extend from UTGame, then you would need to add similar functionality to your GameInfo subclass.

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

   Super.EndLogging(Reason);
}

Summary

Refer to the UTGame source code for examples of how instrumenting your code should work.

Instrumentation checklist:

  • Declaration of the gameplay stats variables in the derived GameInfo class
  • Starting and stopping logging of the gameplay session
    • Create an instance of a writer class during PostBeginPlay() of the derived GameInfo class
    • Start logging in PostBeginPlay() or in StartMatch(), depending on the game's design
    • End logging typically in the EndLogging() function
  • Any custom events not included in engine must be added to the SupportedEvents array inside the derived GameplayEventsWriter class
  • Be sure to include GameStats.uci in each .uc used for logging
    • When including the file inside of the derived GameInfo.uc file, add it in the following way
               `define GAMEINFO(dummy)
               `include(UTGame\UTStats.uci);
               `undefine(GAMEINFO)
               
      This allows the include file to make an optimization that doesn't require accessing the WorldInfo. In order to log events in other files, just use
               `include(UTGame\UTStats.uci)
               
      without the defines. The macros inside the include directive assume that WorldInfo is accessible directly. If not, the logging functions can be called directly.
  • Add an entry into the game's DefaultGame.ini to tell the game what class is responsible for logging events and to enable/disable logging
             [XXXGame.XXXGame]
             GameplayEventsWriterClassName=XXXGame.XXXGameplayEventsWriter
             bLogGameplayEvents=<true/false>
             
  • Add an entry into the game's DefaultEditor.ini to specify what game stats database class to use and to setup SQL database connection information for DB upload. This is necessary for visualization of the data in the editor.
             [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"