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.

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.

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.

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.

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.

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.