有一类游戏缺陷通过用户报告是很难跟踪的。这种类型的缺陷一般涉及到复杂的步骤,可能涉及到AI要根据当前的游戏状态做出决定。 当用户报告一个类似这种情形的问题时,仅能在可以观察得到的结果上报告。 这使得追踪游戏状态相关的问题变得非常难。 虚幻引擎4有一个工具可以在这方面予以辅助;我们称之为Visual Logger(可视化记录器)。 其核心同您以前看过的很多日记记录系统类似,可以捕获当前正在被记录的actor的状态,然后当那个事实完成之后在游戏或编辑器中可视化地显示该状态。 这是一个强大的工具,允许您在出现问题时查看游戏状态,同时还提供用户报告。 通过查看那些数据,您可以将您期望发生的事同数据及底层代码比较。
要想查看正在运作中的Visual Logger(可视化记录器),请使用“窗口| 开发者工具 | Visual Logger(可视化记录器)”菜单。 在4.7版本之前,请使用控制台命令"VisLog"。 这里是StrategyGame中的一个会话。 第一个图片显示了该可视化记录器,第二张图片是编辑器中的视口。 注意,有一条代表选中AI的路径的紫色线,时间轴上选中点处还有一个红色的位置标识。
正在运作中的可视化记录器。
编辑器中的关卡视口,显示了呈现信息的可视化记录器.
下面图片中的突出呈现区域显示了Actor列表,这些Actor的信息在记录过程中被记录到了可视化记录器中。 还有一个搜索条,用于通过名称快速地查找Actor的日志信息。
Actor列表和搜索选项。
下一张图片突出显示了时间轴视图。 这张图片中的拖拽块位于23.53秒标记处。 StrategyAIController_1被选中,所以那个时刻其他区域的任何信息都属于该Actor。 带颜色的水平条是记录的事件。 时间条可以前后拖动来通过特定快照即时地更新其他区域。
时间轴区域
在下面图片的左下角区域的高亮显示部分中,可视化记录器显示了在时间轴上的指定时间处可以捕获的针对那个Actor的任何快照数据。 该数据在每个给定帧中被捕获一次,该Actor通过使用UE_VLOG()宏请求一个可视化日志项。 同一帧中的多个可视化记录调用会重用那帧的相同数据。 作为快照一部分捕获的数据可以进行分类,并针对你的游戏进行自定义(以下提供了一个代码示例)。
展开了自定义类别的Actor快照区域
下图中突出显示的区域包围了可视化记录器的记录区域。 它显示了该日志所写入的类别及日志信息本身。 如果每帧有多个日志信息,那么它们将在那个区域显示为列表。
显示信息的日志区域
既然现在您知道了可视化记录器的主要区域的作用,现在让我们看下如何在您的游戏中添加针对它的支持吧。 在下图中,使用第一人称模板创建了一个新项目,命名为GDC,用于在游戏开发者大会上进行展示。 向Actor的捕获状态信息中添加了一个函数,并添加了一个UE_VLOG()宏调用来触发日志记录器的捕获。
带有示例数据的可视化记录器
为了填充该工具的快照区域,您需要重载一个虚拟函数GrabDebugSnapshot()。 该函数是作为Actor的一部分实现的,所以如果您不想提供自定义信息,您可以跳过此步。 可视化记录器可以根据您的编译设置进行编译,所以您必须使用适当的头文件封装该函数。 以下是需要添加到GDCCharacter.h文件中的代码,以添加快照支持:
#if ENABLE_VISUAL_LOG
/** Appends information about this actor to the visual logger */
virtual void GrabDebugSnapshot(FVisualLogEntry* Snapshot) const override;
#endif
该方法的实现请参照下面,添加了一个类别并在那个类别中添加了一项。 再次说明,该方法封装在#ifdef中,以防版本设置编译可视化记录器支持。 该代码添加到GDCCharacter.cpp中。
#if ENABLE_VISUAL_LOG
void AGDCCharacter::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const
{
Super::GrabDebugSnapshot(Snapshot);
const int32 CatIndex = Snapshot->Status.AddZeroed();
FVisualLogStatusCategory& PlaceableCategory = Snapshot->Status[CatIndex];
PlaceableCategory.Category = TEXT("GDC Sample");
PlaceableCategory.Add(TEXT("Projectile Class"), ProjectileClass != nullptr ? ProjectileClass->GetName() : TEXT("None"));
}
#endif
该函数仅当Actor向可视化记录器记录日志时调用。 出于示范的目的,记录了一个射弹的发射情形,以便可以轻松地触发它并添加到第一人称模板代码中。 在下面的OnFire()函数中,请注意UE_VLOG()宏的应用。 这是您告诉可视化记录器您想如何捕获Actor的数据的方式。 正如前面所述的,在首次使用那个宏时,可视化记录器将调用 GrabDebugSnapshot()来收集在快照面板中显示所需的数据。 该宏像其他UE_LOG()宏一样进行编译,所以不需要在 #ifdef中显式地封装它。
void AGDCCharacter::OnFire()
{
// try and fire a projectile
if (ProjectileClass != NULL)
{
const FRotator SpawnRotation = GetControlRotation();
// MuzzleOffset is in camera space, so transform it to world space before offsetting from the character location to find the final muzzle position
const FVector SpawnLocation = GetActorLocation() + SpawnRotation.RotateVector(GunOffset);
UWorld* const World = GetWorld();
if (World != NULL)
{
// spawn the projectile at the muzzle
World->SpawnActor<AGDCProjectile>(ProjectileClass, SpawnLocation, SpawnRotation);
UE_VLOG(this, LogFPChar, Verbose, TEXT("Fired projectile (%s) from location (%s) with rotation (%s)"),
*ProjectileClass->GetName(),
*SpawnLocation.ToString(),
*SpawnRotation.ToString());
}
}
// try and play the sound if specified
if (FireSound != NULL)
{
UGameplayStatics::PlaySoundAtLocation(this, FireSound, GetActorLocation());
}
// try and play a firing animation if specified
if(FireAnimation != NULL)
{
// Get the animation object for the arms mesh
UAnimInstance* AnimInstance = Mesh1P->GetAnimInstance();
if(AnimInstance != NULL)
{
AnimInstance->Montage_Play(FireAnimation, 1.f);
}
}
}
您不仅可以记录文本信息,您还可以记录像您之前在StrategyGame关卡视口中看到的可视化的图形信息。 这是一个强大的功能,因为它可以帮助您可视化游戏中当前的运行状态。 以下图片显示了您可以可视化记录的不同形状类型。
路径信息、圆柱体、椎体、胶囊体、盒体式形状。
以下宏提供了记录形状的支持。
- UE_VLOG_SEGMENT
- UE_VLOG_LOCATION
- UE_VLOG_BOX (axis aligned box)
- UE_VLOG_OBOX (oriented box)
- UE_VLOG_CONE
- UE_VLOG_CYLINDER
- UE_VLOG_CAPSULE
每个宏需要不同的参数来记录该形状信息。 您可以在VisualLogger.h查找这些宏来看一下每个宏要想进行记录需要哪些信息。
最后一个要提及的关于可视化日志记录器的事情是您可以将这些文件保存到磁盘中,以便调试时使用。 如果您有测试团队一直记录这些会话,那么他们可以将这些文件附加到他们所创建的任何缺陷跟踪项上。 然后您可以加载相应关卡及可视化日志文件来查看发生了什么。 该工具应该可以大大地帮助您加速调试过程。