UDN
Search public documentation:

InstrumentingGameStatisticsCH
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

在您的游戏中收集游戏统计数据

文档概要:该文档详细描述了成功在您的游戏中装备统计数据收集必需的结构、功能和流程。

文档变更记录:初始版本由 Josh Markiewicz 于 2009 年 5 月 11 日创建,由 Jeff Wilson 进行更新。

概述

在引擎中收集统计数据的原理是为游戏设计师提供整个游戏开发的游戏会话过程中捕获的有效数据。它必须具有灵活性、快速性和全面性。灵活性体现在可以根据设计师的需求快速进行更改,快速是因为它可以在不破坏游戏性的情况下实时捕获大量数据,全面表现在历史记录比较和数据探勘。对另一端,系统可以使用通过缓冲 I/O 和版本控制支持动态载入到磁盘上的轻量级数据结构。尽量确保可以始终读取早期的文件,即使该数据已经被捕获或者它们的格式已经发生变化。

因为绝对不可能两次经历同样的游戏会话,所以文件格式必须支持以任何顺序动态载入的数据。定义如下。

您收集了一些数据后,可以访问有关使用游戏统计数据查看器的参考资料

事件

初始状态引擎支持的所有事件都定义在 GameStats.uci 中。将事件 ID 组成条理分明的群组,其中游戏指定标识符从 1000 以上计起。您可以按照您的意愿组织您的任务,当然要确保它们是唯一的。通过使用该文件中定义的宏指令和事件,您可以开始记录与您的游戏会话相关的信息。请参阅 GameStats.uci 了解更多有关已经定义的支持事件类型的信息。

GameplayEventWriter

这个类可以处理数据到磁盘的全部写入操作。它可以处理文件创建、缓冲和关闭。使用各种已经定义的记录函数记录有关您的游戏会话的信息。当然,只要您愿意,可以覆盖功能。

启动日志会话

StartLogging(optional float HeartbeatDelta)

在记录会话开头会调用这个函数,打开这个文件进行编写。选择保证是唯一并且对平台限制严格的文件名称,然后将它创建在您的游戏主目录 Stats 文件夹中。如果心跳参数已经指定,那么系统会在每 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.");
   }

    //省略代码...
}

您会注意到使用的是 bLogGameplayEventsGameplayEventsWriterClassNameGameplayEventsWriter 变量。在 UTGame 类中定义它们。如果您的游戏没有通过 UTGame 扩展,那么您需要定义类似的变量供使用。

如果您自定义的游戏类型启用了记录,那么上面的代码可以创建一个在 *Game.ini 文件中指定的类的实例。它还包含可以选择立即开始记录的注释部分,在您的游戏可以立即开始记录统计数据的情况下可以去除这部分注释。在与该游戏相关的地方可以调用 StartLogging()。某些使用匹配的延迟开始时间的游戏类型可以使用 StartMatch() 函数开始记录统计数据。

记录事件

GameStats.uci 已经包含大量可以用于记录游戏数据的宏指令。通常情况下会执行它们,这样可以满足很多需求的函数会越来越少。一般会将游戏事件标识符存储为应该足以达到目标的特殊通用数据类型。提供了更多复杂的记录函数,可以存储玩家在事件过程中的位置、方位以及用过的武器和/或其他方面信息。重点是一个单独的 ID 和通用容器本身大部分并不是取决于自己的,而是由指定游戏、指定关联及完全由用户决定稍后如何插入数据。

使用这些函数是为了正确地创建、查找和索引各种元数据(团队、玩家、武器等等),节省数据文件中的空间。

已经将一个新类别 GameStats 赋给统计数据集合的输出结果。您可以在日志中解压缩它,查看记录事件的实时输出结果。

为了记录事件,必须在 GameplayEventWriter 中调用相应的函数,同时向它传递需要的信息。在 GameStats.uci 文件中使用预定义宏指令,使这个过程可以十分轻松地完成。例如,为了记录每个玩家 PK 和死亡事件,可能要在您的 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 可以控制玩家 PK 过程, 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 类内部的 SupportedEvent 数组中
  • 确保在每个用于记录的 .uc 文件中都包含 GameStats.uci
    • 如果在衍生的 GameInfo.uc 文件内部包括这个文件,那么使用下面的方法添加这个文件
               `define GAMEINFO(dummy)
               `include(UTGame\UTStats.uci);
               `undefine(GAMEINFO)
               
      它允许 include 文件进行优化,不需要访问 WorldInfo。为了将事件记录在其他文件中,只需使用
               `include(UTGame\UTStats.uci)
               
      没有定义。在 include 指令内部的宏指令认为可以直接访问 WorldInfo。如果不可以,可以直接调用记录函数。
  • 将一个条目添加到游戏的 DefaultGame.ini 中,通知游戏负责记录事件以及启用/禁用记录的类
             [XXXGame.XXXGame]
             GameplayEventsWriterClassName=XXXGame.XXXGameplayEventsWriter
             bLogGameplayEvents=<true/false>
             
  • 将条目添加到游戏的 DefaultEditor.ini 中,指定 DB 上传可以使用和设置 SQL 数据库连接信息的游戏统计数据库类。它对于编辑器中的数据可视化来说是必需的。
             [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"