前言
本文基于4.27版本
UE本身提供了很多STAT命令来帮助实时查看游戏运行时的一些数据:
【Stat命令表】
在游戏运行时通过控制台输入对应指令即可
不过直接使用命令,只会在屏幕上实时看到数据,如果使用Insights或者Frondend等工具,则会同时获取到大量的信息,不方便对数据的整理
比如,需要对游戏内所有的蒙太奇资源进行检查,来排查某些蒙太奇是否在某个性能指标上存在问题,希望可以快速的自动化测试,记录数据方便快速地进行筛选并反馈给美术,就需要自己稍微组织一下逻辑,并且直接借一下STATS的统计数据
STATS统计数据
这里使用Cascade Particle来举例
按照官方文档所示【统计数据系统概述】
比如在Engine/Source/Runtime/Engine/Public/ParticleHelper.h
这个文件中定义到的很多STATS:
1 2 DECLARE_STATS_GROUP (TEXT ("ParticlesOverview" ), STATGROUP_ParticlesOverview, STATCAT_Advanced);
STATGROUP_ParticlesOverview
和STATGROUP_Particles
是GroupId,直接在控制台上使用的时候是直接输入stat ParticlesOverview
或者stat Particles
即可
1 DECLARE_DWORD_COUNTER_STAT_EXTERN (TEXT ("Sprite Particles" ),STAT_SpriteParticles,STATGROUP_Particles, );
其中STAT_SpriteParticles
则是一个计数器,声明一个DWORD类型的计数器统计数据。由上面的声明可知,该计数器的名字为Sprite Particles
,StatID为STAT_SpriteParticles,GroupId为STATGROUP_Particles
,
全局搜索这个StatID可以找到:
在Engine/Source/Runtime/Engine/Private/Particles/ParticleEmitterInstances.cpp
中有:
1 INC_DWORD_STAT_BY (STAT_SpriteParticles, ActiveParticles);
给该计数器增加了指定值,指定值为ActiveParticles
同样在这个文件的另一个位置:
1 DEC_DWORD_STAT_BY (STAT_SpriteParticles, ActiveParticles);
给该计数器减少了指定值,指定值为ActiveParticles
如果开启了STAT,这两个宏就会在对应的逻辑位置被执行,从而改变了计数器的值
通过C++直接获取STATS数据
在Engine/Source/Developer/FunctionalTesting/Public/AutomationBlueprintFunctionLibrary.h
提供了一些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 UFUNCTION (BlueprintCallable, Category = "Automation" , meta = (HidePin = "WorldContextObject" , DefaultToSelf = "WorldContextObject" ))static void EnableStatGroup (UObject* WorldContextObject, FName GroupName) ;UFUNCTION (BlueprintCallable, Category = "Automation" , meta = (HidePin = "WorldContextObject" , DefaultToSelf = "WorldContextObject" ))static void DisableStatGroup (UObject* WorldContextObject, FName GroupName) ;UFUNCTION (BlueprintCallable, Category = "Automation" )static float GetStatIncAverage (FName StatName) ;UFUNCTION (BlueprintCallable, Category = "Automation" )static float GetStatIncMax (FName StatName) ;UFUNCTION (BlueprintCallable, Category = "Automation" )static float GetStatExcAverage (FName StatName) ;UFUNCTION (BlueprintCallable, Category = "Automation" )static float GetStatExcMax (FName StatName) ;UFUNCTION (BlueprintCallable, Category = "Automation" )static float GetStatCallCount (FName StatName) ;
按照前文的例子,这里函数中的GroupName
指的就是STATGROUP_Particles
,StatName
指的就是STAT_SpriteParticles
但是看一下函数的具体实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void UAutomationBlueprintFunctionLibrary::EnableStatGroup (UObject* WorldContextObject, FName GroupName) {#if STATS if (FGameThreadStatsData* StatsData = FLatestGameThreadStatsData::Get ().Latest) { const FString GroupNameString = FString (TEXT ("STATGROUP_" )) + GroupName.ToString (); const FName GroupNameFull = FName (*GroupNameString, EFindName::FNAME_Find); if (StatsData->GroupNames.Contains (GroupNameFull)) { return ; } } if (APlayerController* TargetPC = UGameplayStatics::GetPlayerController (WorldContextObject, 0 )) { TargetPC->ConsoleCommand ( FString (TEXT ("stat " )) + GroupName.ToString () + FString (TEXT (" -nodisplay" )), false ); }#endif }
这里能看到其实就是执行了一下命令行,并且实际要传进来的GroupName应该是去掉了STATGROUP_
的
而几个获取数据的函数,最后实际上是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #if STATS template <EComplexStatField::Type ValueType, bool bCallCount = false >float HelperGetStat (FName StatName) { if (FGameThreadStatsData* StatsData = FLatestGameThreadStatsData::Get ().Latest) { if (const FComplexStatMessage* StatMessage = StatsData->GetStatData (StatName)) { if (bCallCount) { return StatMessage->GetValue_CallCount (ValueType); } else { return FPlatformTime::ToMilliseconds (StatMessage->GetValue_Duration (ValueType)); } } }#if WITH_EDITOR FText WarningOut = FText::Format (LOCTEXT ("StatNotFound" , "Could not find stat data for {0}, did you call ToggleStatGroup with enough time to capture data?" ), FText::FromName (StatName)); FMessageLog ("PIE" ).Warning (WarningOut); UE_LOG (AutomationFunctionLibrary, Warning, TEXT ("%s" ), *WarningOut.ToString ());#endif return 0.f ; }#endif
这里要传递进来的StatName应该是STAT_SpriteParticles
这样的完整名字
但是在这里发现一个问题,比如,在ParticleHelper.h
中定义了非常多的计数器,其中有一部分是没有办法通过这种方式获取到的
经过调试有发现:
这里的数量确实没有所有的该Group下的计数器,也就是说并不是所有被定义过的计数器都可通过该接口来获取。
其中额外发现对于类似于STAT_SpriteParticles
这种COUNT
类型的计数器,实机的位置是在StatsData->ActiveStatGroups
的CountersAggregate
如果要获取这一类的计数器,可以通过这种方式来获取,下面的代码是获取STAT_SpriteParticles
的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #if STATS if (FGameThreadStatsData* StatsData = FLatestGameThreadStatsData::Get ().Latest) { for (FActiveStatGroupInfo ActiveStatGroup : StatsData->ActiveStatGroups) { for (FComplexStatMessage CountersAggregate : ActiveStatGroup.CountersAggregate) { const double IncAveValueDouble = CountersAggregate.GetValue_double (EComplexStatField::IncAve); const double IncMaxValueDouble = CountersAggregate.GetValue_double (EComplexStatField::IncMax); FName Name = CountersAggregate.GetShortName (); if (Name == TEXT ("STAT_SpriteParticles" )) { PeakCascadeSpriteParticleCount = FMath::Max (PeakCascadeSpriteParticleCount, static_cast <int >(IncMaxValueDouble)); AverageCascadeSpriteParticleCount = static_cast <int >(IncAveValueDouble); } } } }#endif