UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技
UWA博客，UWA旗下官方博客。针对游戏及VR应用性能优化等内容记录、交流，以及案例分享。博客主要分为：厚积薄发、万象更新、博观约取、充电一刻、博物纳新等几个模块。
【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ 总览 UE5的GC，相对UE4做了一些改动，但本质没变，依然是基于UObject的标记清扫算法，许多改动都属于优化。 先看GC的各个阶段： 遍历所有Object，标记为不可达 改动较少，增加了RefCount引用方式；更改了Unreachable Flag；增加了单独的RootArray作为根集。 遍历引用关系做可达性分析 改动较多，Batch模式的引用收集；UObject对ClassPrivate和Outer的引用从Token中移除；增加了引用收集的Debug模式。 清理不可达Object 差别不大，依然是分帧Purge，对每个UObject依次执行BeginDestroy, Destroy, FinishDestroy。 除此之外，还有实验性的Increment可达性分析模式，想完全避免可达性分析造成的一帧卡顿，算是一个最大的不同。 对于GC Cluster，变化不大。 另外还有一些其他Tips，比如IsPendingKill接口改成了IsGarbage；ObjectFlags增加了Reachable0，Reachable1，Reachable2等等。 文章中所使用的为UE5.7。 前篇：《UE4垃圾回收》 一、加入引用计数机制 1. RefCounted Flag & RefCount 观察InternalObjectFlags，发现多了一个叫RefCounted的Flag，说明UE的GC在标记清扫的基础上，又加入了引用计数机制，算是一个不小的改动。 引用计数是另一种GC方式，Python中就使用了，简单概括为每个Object记录自己被多少个其他Object引用了，当发现引用计数为0时，就判定自己为垃圾。至于垃圾的清理，可以选择为0时立即清理，也可以选择之后一起清理，反正垃圾是不会再被重新引用到的。 enum class EInternalObjectFlags : int32 { None = 0, //... RefCounted UE_DEPRECATED(5.7, "Use GetRefCount() to determine if a refcount exists instead.") = 1 << 29, ///< Object currently has ref-counts associated with it. //... }; 但有了Flag还不够，得给每个Object再定义一个RefCount属性，记录被引用的次数。UE5.7之前，在FUObjectItem中有一个专门的int32 RefCount属性，但UE5.7开始把RefCount和Flags一起打包成了int64。Flag存在高32位里，RefCount存在低32位里。 这样有个好处，RefCount通常用不了int32这么大，因此可以把更多位数给EInternalObjectFlags用，比如添加一些我们自定义的Flag，或者UE引擎自己扩展。 2. 操作接口 添加RefCount： void UObjectBase::AddRef() const { FUObjectItem* ObjectItem = GUObjectArray.ObjectToObjectItem(this); ObjectItem->AddRef(); } 减去RefCount： void UObjectBase::ReleaseRef() const { FUObjectItem* ObjectItem = GUObjectArray.ObjectToObjectItem(this); ObjectItem->ReleaseRef(); } 获取RefCount： 在引擎内部代码，比如GC过程中，才有获取RefCount的需求，因此UObject上没有获取RefCount的接口，只有FUObjectItem上有。 FORCEINLINE int32 GetRefCount() const { return RefCount; } 哪些地方会给UObject加上引用计数？ 引用计数管理UObject显然更容易出错，万一AddRef/ReleaseRef不成对，就会造成UObject泄漏，引擎中目前也只有StrongObjectPtr使用了。而且是用了RAII模式，在构造和析构中操作RefCount，防止泄漏。StrongObjectPtr可以对一个UObject添加强引用，防止被GC，和AddToRoot类似。下面是StrongObjectPtr中操作的代码： FORCEINLINE_DEBUGGABLE void Reset(ObjectType* InNewObject) { if (InNewObject) { if (Object == InNewObject) { return; } if (Object) { // UObject type is forward declared, ReleaseRef() is not known. // So move the implementation to the cpp file instead. UEStrongObjectPtr_Private::ReleaseUObject(Object); } InNewObject->AddRef(); Object = InNewObject; } else { Reset(); } } 3. 为什么要使用引用计数管理UObject 一个原因可能是能减少可达性分析时间。 可达性分析阶段需要遍历Object间的所有引用关系，耗时正比于引用数量。 比如下图，4个UObject相互引用，总共要遍历10个引用关系。但假如都用引用计数管理，可以省略遍历开销，直接判定四个对象都可达。 但是理论上讲，引用计数方式只是把遍历开销分摊到了运行时维护引用计数的加减上。而且注意到上面例子其实是环形引用的情况，没有外部引用情况下，四个UObject都该被判定为垃圾才对。因此一个完善的垃圾回收实现，不会只用引用计数，比如Python的FullGC就用的标记-清扫算法，作为辅助。UE依然把标记-清扫作为主GC流程，引用计数只是小小的辅助，但使用是绝对要注意。 另一个原因是单独针对StrongObjectPtr的优化。 StrongObjectPtr之前的实现是把包起来的UObject单独放到一个大数组里，然后这个数组被一个FGCObject添加引用。这样有个问题，当数组元素很多时，添加需要遍历全数组，判断是否已添加过，而删除时同样需要遍历全数组，找到元素并删除，遍历的时间很可观。 4. RefCount如何影响可达性分析 其实RefCount等同于Root，观察AddRef调用链，最终会执行AddToRoot操作。 在第一步标记所有UObject不可达后，会遍历GRoots容器，把其中的UObject变成可达，那么RefCounted的Object也变成可达了。 二、可达性分析阶段的优化 1. FPrefetchingObjectIterator 首先介绍一个挺细节的ObjectIterator，基于经验做了Prefetch，尽量避免访问Object的Outer和ClassPrivate造成的CacheMiss。整个可达性分析阶段都充分应用了Prefetch技术。 首先观察下面代码，会遍历一组UObject，然后分别访问它们的Class和Outer： FORCEINLINE_DEBUGGABLE void ProcessObjects(DispatcherType& Dispatcher, TConstArrayView<UObject*> CurrentObjects) { for (FPrefetchingObjectIterator It(CurrentObjects); It.HasMore(); It.Advance()) { UObject* CurrentObject = It.GetCurrentObject(); UClass* Class = CurrentObject->GetClass(); UObject* Outer = CurrentObject->GetOuter(); //... } } 这样必然会导致读Object内存操作，相比读CPU Cache肯定是更慢的。粗略估计，访问内存的延迟是60ns，而访问CPU L1 Cache的延迟是1ns，当中相差不少。 既然已经知道要访问这么多Object的ClassPrivate和Outer属性，那么能不能提前把它们读取到CPU Cache中，加快访问速度？这就是FPrefetchingObjectIterator做的事情，使用了操作系统的Prefetch接口，异步的提出一个预读请求，不需要当场等待内容返回到CPU Cache，CPU可以继续执行后面指令。 FPrefetchingObjectIterator迭代器在++时，会Prefetch后面第六个的Outer和ClassPrivate的Schema，以及后面第16个的Class。这些数字都是经验值，既需要保证访问到这块内存时，其已经被加载到了CPU Cache，又不能Prefetch太多，从而挤占CPU Cache。 示意图如下： 2. 基于Batch的Object引用收集 Batch UE4怎么做的： 给定一个待分析可达性的Objects数组，会不断遍历其引用，做广度优先搜索，最终遍历完所有可达的UObject，完成任务。其中碰到StructArray结构时，还会形成类似递归的情况，处理代码更复杂些。整个过程由一个大的ProcessObjectArray函数实现，总共有600+行。 UE5的Batch： UE5使用了Batch的遍历方式，首先把ProcessObjectArray逻辑拆成多个函数，函数逻辑精简，最长不超过90行，对CPU指令Cache更友好，然后多个函数形成执行流；大概500个Object作为一个Batch，依次经过这些执行流，更少的数据量对应更少的内存使用量，让CPU Cache足够应对；另外每段处理函数逻辑变简单后，可以更容易地使用上面提到的Prefetch技术做Cache预取，进一步提升效率。 Schema 首先，UE5加入了Schema的概念，其实就是UE4里的TokenStream，记录Object带引用的各成员地址偏移，以及成员类型。 ProcessObjectArray 下面是ProcessObjectsArray的主要流程，首先执行ProcessObjects，收集引用到Dispatcher，Dispatcher的具体类型为BatchedDispatcher。CurrentObjects处理完后，再尝试从全部的ObjectsToSerialize数组中取出BlockSize数量的UObject，继续执行ProcessObjects做引用收集。如果ObjectsToSerialize也空了，就执行FlushWork，把引用到的UObject都添加到ObjectsToSerialize中继续。 这样就能把标记阶段分成多个Pass，每个Pass处理一点Object，流程示意图如下： ProcessObjects ProcessObjects用于收集CurrentObjects的所有引用，FPrefetchingObjectIterator已在上面提过。注意到这里把Class和Outer的处理单独提出来了，UE4还是在正常的Schema里处理它们。可能是考虑到Class和Outer是每个UObject必备的引用，提出来统一处理能减小Schema大小。 VisitMembers函数是真正的处理Schema里定义的各种引用，包括UPROPERTY引用单个Object，TArray数组引用等等，这里只给出单个Object引用和StructArray引用两个常见例子。 VisitStructArray只会把StructArray缓存到对应容器中，后面Flush时再处理： FORCEINLINE_DEBUGGABLE void QueueStructArray(FSchemaView Schema, uint8* Data, int32 Num) { StructBatcher.PushStructArray(Schema, Data, Num); } ImmutableReference & KillableReference 注意到对于单个UObject的引用，分了ImmutableReference和KillableReference(FMutableReference)，其实只有UObject对Class和Outer的引用属于Immutable，其他都属于Killable。这对UE4而言并没有提出新的内容，只是老内容包装成了新概念。 比如有如下代码，想强制删除B，但B又是A的属性，被引用了 A->Prop = B; B->MarkAsGarbage(); 对于Class和Outer引用，非常强力，强到能阻止对B的强删，让B继续以Garbage的形式存在。 对于普通的KillableReference，无法阻止，同时A->Prop属性会变成nullptr。 因此查看它们的初始化方式，FMutableReference是存储了指向属性的指针。 // Retains address of reference to allow killing it struct FMutableReference { UObject** Object; }; struct FImmutableReference { UObject* Object; }; FlushWork 依次处理StructArray和其他Object引用，主要工作就是遍历各种Array。 FORCEINLINE_DEBUGGABLE void FlushWork(DispatcherType& Dispatcher) { if constexpr (DispatcherType::bBatching) { if (Dispatcher.FlushToStructBlocks()) { ProcessStructs(Dispatcher); } Dispatcher.FlushQueuedReferences(); } } 最终通过HandleValidReference函数，把UObject标记为可达，并插入到ObjectsToSerialize数组中，以待后续遍历。 三、配套Debug功能的完善 1. gc.history协助找泄露 其中一个比较实用的功能是可以记录下上次GC过程中，每个对象被谁引用了，方便排查UObject泄露问题。 看个例子，假如有如下代码： FActorSpawnParameters SpawnParameters; SpawnParameters.Name = TEXT("TestObject123"); APawn* TestPawn = GetWorld()->SpawnActor<APawn>(SpawnParameters); USceneComponent* TempObject = NewObject<USceneComponent>(TestPawn, USceneComponent::StaticClass()); TempObject->RegisterComponent(); { TStrongObjectPtr<UObject> StrongPtr(TempObject); TestPawn->Destroy(); CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true); TempObject->MarkAsGarbage(); } GEngine->Exec(GetWorld(), TEXT("OBJ Refs Name=TestObject123")); 在有StrongObjectPtr的情况下，即使MarkAsGarbage，这次GC也是删不掉TempObject和TestPawn的，在严格的逻辑下，算是UObject泄露。Obj Refs的输出也印证了： LogReferenceChain: (Garbage) Pawn /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123 is not currently reachable. Try using GC history to debug transient leaks with 'gc.historysize 1' 输出中只能看到UObject泄露了，但打印Obj Refs时，引用现场已经没了，不知道为什么泄露。过去，要调试这个泄露，得先记下Object地址，再去GC下数据断点，再对Flags下断点等等操作，非常麻烦。 现在UE5加了HistorySize的Debug功能，会对上次GC存储Snapshot，之后再输出。 首先通过如下控制台指令开启： gc.ForceEnableGCProcessor gc.Historysize 1 然后把Exec代码改成： FReferenceChainSearch::FindAndPrintStaleReferencesToObject(TestPawn, EPrintStaleReferencesOptions::Log); 接着就能从GC History中获取到历史GC引用了，快速定位到泄露原因，非常方便： LogLoad: Old Pawn /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123 not cleaned up by GC! Garbage object SceneComponent /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123.SceneComponent_0 was previously being referenced by NULL: (refcounted<1>) (Garbage) SceneComponent /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123.SceneComponent_0 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^ This reference is preventing the old Pawn from being GC'd ^ -> UObject* UObject::Outer = (Garbage) Pawn /Game/UEDPIE_0_Zoo.Zoo:PersistentLevel.TestObject123 2. 实现原理 UE5中，新增了用于Debug的TDebugReachabilityProcessor，在可达性分析阶段，当第一次遍历到一个Object时，把当前路径记录下来即可。 四、增量式可达性分析 1. 为什么要做增量式可达性分析 增量式的垃圾回收，目前还是Experimental阶段，个人感觉有点意义不明。 Documentation | Epic Developer Community 一个典型例子是Lua虚拟机实现，采用了黑白灰三色标记，UE的实现也比较类似。 开启方式： DefaultEngine.ini加入下面配置： [ConsoleVariables] gc.AllowIncrementalReachability=1 ; enables Incremental Reachability Analysis gc.AllowIncrementalGather=1 ; enables Incremental Gather Unreachable Objects gc.IncrementalReachabilityTimeLimit=0.002 ; sets the soft time limit to 2ms 为什么有必要把可达性分析阶段改成增量式的？ GC耗时可以分成三个部分：所有UObject先标记不可达，可达性分析，销毁垃圾。其中，销毁垃圾本身是分帧的，不会造成卡顿。前两个部分过去不能分帧，而可达性分析阶段是耗时主要部分，通常超过第一部分十倍，如果游戏希望维持60帧或更高帧率运行，这绝对会是一个阻碍。为了缓解这个卡顿，UE很早就提出了Cluster优化方案，现在又进一步提出了增量GC方案。 为什么之前可达性分析不能分帧执行？ 考虑这样的场景，假如可达性分析要多帧完成，在中间执行了A.XX = B语句，使A引用到B，但A已经完成了可达性分析，UE识别不到这个新加的引用。那么B就有可能被错误当成垃圾回收掉。 可是这是官方文档的解释，但个人感觉不太符合正常使用场景，还没想通。不妨把B分成下面两种情况： B在这次GC开始前已创建，如果此时B没有其他引用了，那么代码里又是如何获取到B的？存裸指针当然可以，但本身也是不规范的做法。 B在这次GC开始后才创建，但新创建UObject默认是Reachable的，不会被这次GC删除。这点和Lua是有区别的，因为Lua里Object创建后默认不可达。 这个也许和增量回收还处于Experimental有关，逻辑还不成熟，不妨顺着UE思路继续往下看实现。 2. Write Barrier 解决这种问题还是要靠Write Barrier。得益于UE5中把裸指针替换为TObjectPtr，使得A.XX = B这种语句能被捕获到了。 TObjectPtr会在Operator =函数里做检查，如果发现正在GC，就会立即把B作为可达对象，加到全局GReachableObjects或GReachableClusters链表中。 之后以GReachableObjects为例，在可达性分析的PerformReachabilityAnalysisPass函数开头，会首先把GReachableObjects容器中的Object作为InitialObjects，当成初始可达对象，然后继续做可达性分析遍历。 这只是TObjectPtr的处理，我们也会定义些自定义逻辑，然后使用AddReferenceObjects函数添加引用，这就不太好处理了。 3. 如何实现分帧 回顾之前的ProcessObjectsArray函数，已经实现了Batch形式的Object遍历，每趟处理500个Object，那么分帧也是比较方便实现的。这一段TimeLimit逻辑控制了每帧可以花费多少时间做可达性分析，超过了就给下一帧继续执行，会从CollectGarbage入口进来。 既然分帧，就要保留当前GC进度，要知道还有哪些Object需要遍历。这些信息就存储在Context.InitialObjects数组里。因为可达性分析阶段是多线程执行的，因此每个线程都有自己的Context。 其实不仅可达性分析阶段做了增量式处理，后面的收集不可达对象阶段也支持了增量执行。只是耗时本身不高，逻辑也更简单，进度保留在Context中即可。 开启增量GC后的Trace如下，可以看到实现了分帧，但还不能做到完美的Timelimit分帧： 五、其他Tips 1. ReachabilityFlag0, ReachabilityFlag1 UE5在标记所有Object不可达时，不再遍历所有Object并设置UnReachable标记，而是交替使用两种ReachabilityFlag，同时依然有UnReachableFlag，用来表示真正的不可达Object： ReachabilityFlag0 = 1 << 14, ///< One of the flags used by Garbage Collector to determine UObject's reachability state ReachabilityFlag1 = 1 << 15, ///< One of the flags used by Garbage Collector to determine UObject's reachability state ReachabilityFlag2 = 1 << 16, ///< One of the flags used by Garbage Collector to determine UObject's reachability state Unreachable = 1 << 28, ///< Object is not reachable on the object graph. 交替两个ReachabilityFlag代码如下： FORCEINLINE static void SwapReachableAndMaybeUnreachable() { // It's important to lock the global UObjectArray so that the flag swap doesn't occur while a new object is being created // as we set the GReachableObjectFlag on all newly created objects GUObjectArray.LockInternalArray(); Swap(ReachableObjectFlag, MaybeUnreachableObjectFlag); // Maintain the old flag variables for backwards compatibility PRAGMA_DISABLE_DEPRECATION_WARNINGS UE::GC::GReachableObjectFlag = ReachableObjectFlag; UE::GC::GMaybeUnreachableObjectFlag = MaybeUnreachableObjectFlag; PRAGMA_ENABLE_DEPRECATION_WARNINGS GUObjectArray.UnlockInternalArray(); } 如此能实现这一帧Flag0表示可达，Flag1表示可能不可达，下一帧反过来，下下一帧再反过来。 然后一次GC结束后，PostCollectGarbageImpl，GatherUnreachableObjects会遍历所有UObject，把依然有MaybeUnreachableObjectFlag的Object视为垃圾，标记上Unreachable Flag。 遍历完成后，插入到专门储存待清理对象的GUnreachableObjects数组中。 2. MarkAsGarbage取代MarkAsPendingKill UE4对于强删的Object，比如Actor，会MarkAsPendingKill，UE5把接口改成了MarkAsGarbage： inline void MarkAsGarbage() { check(!IsRooted()); AtomicallySetFlags(RF_MirroredGarbage); GUObjectArray.IndexToObject(InternalIndex)->SetGarbage(); // If we explicitly marked the object as garbage, remove the async flag so it's visible to the GC AtomicallyClearInternalFlags(EInternalObjectFlags::Async); } 对应的IsPendingKill判断也改成了IsValid： inline bool UKismetSystemLibrary::IsValid(const UObject* Object) { return ::IsValid(Object); } 这是侑虎科技第1958篇文章，感谢作者南京周润发供稿。欢迎转发分享，未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859） 作者主页：https://www.zhihu.com/people/xu-chen-71-65 再次感谢南京周润发的分享，如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859）
1）如何降低Animator的调用次数 2）为什么UI相关堆内存对象和纹理资源都占用了较大内存没有释放 这是第467篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From 问答社区 Q：现在动画的Culling Mode是Cull Update Transforms，不过从Scene窗口来看，在屏幕外的动画还是在动，这个还需要做其他的什么设置吗，还是我理解的不对？ 然后，之前Camera没有勾选Occlusion Culling，这个有关系吗？ 为什么报告里Animator的调用次数那么多呢？ A：首先，Scene里面确实会影响的，要准确的话，需要关闭Scene窗口。 然后，Camera没有勾选Occlusion Culling，这个没关系的。 最后，在Profiler里面选择那个Animator.ApplyOnAnimatorMove函数，然后打开Related Data，是可以看到具体是哪些对象的： 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69b7a2f392894f1c4f0c2354 From UWA AI 问答 Q1：项目中发现一些UI图片被大量UI对象引用，但这些对象应该已经在代码中调用销毁了，但看起来RUUA和GC都没起到作用，这最终导致我项目中很多UI相关堆内存对象和纹理资源都占用了较大内存没有释放。可能是什么原因？ A：此问题通常由隐性引用、资源标志位或原生资源未释放导致。 排查代码引用链：Lua或C# 中的静态变量、事件监听、闭包可能持有UI对象引用，导致GC无法回收。查看GOT Online报告的Mono引用对象统，若“Destroyed对象数量”持续上涨，说明存在Lua端未解引用，需及时释放引用链。 检查资源标志位：若纹理或对象被标记为 HideFlags.DontUnloadUnusedAsset，Resources.UnloadUnusedAssets将无法卸载它们。需检查代码是否误设该标志，必要时使用DestroyImmediate手动清除。 确认AssetBundle与图集依赖：若引用图集的AssetBundle未完全卸载，图集会驻留。确保AssetBundle.Unload(true)调用正确，或尝试Resources.UnloadAsset。 排查原生资源：RenderTexture不会自动垃圾回收，使用后必须调用Release释放硬件资源，否则会造成显存与内存泄漏。 考虑内存碎片：堆内存高未必是对象未卸载，可能是碎片过多。排查byte[]、MemoryStream等频繁分配对象，使用对象池优化。 建议结合内存快照分析具体占用对象，定位引用源，必要时手动管理资源生命周期，避免过度依赖自动卸载机制。 Q2：我看了UWA报告中的Mono引用对象统计页面，Destroyed对象数确实较高且有上涨，这个具体是什么含义？ A：Destroyed对象数较高且持续上涨是Mono堆内存泄漏的重要预警信号，具体含义如下： 定义说明 Destroyed对象指在C# 端已调用Destroy销毁，但Lua端仍持有引用的Mono层对象。这些对象虽在场景中已被移除，但因Lua端未解引用，仍占用堆内存无法被GC回收。 产生原因 Lua访问Unity对象时，会通过Wrapper代码将对象引用存入容器，以确保C# 端GC不会提前回收。若Lua端未及时释放这些引用，C# 端的Destroy操作无法彻底清理内存，导致对象驻留。 持续上涨的风险 若Destroyed对象数量持续上涨，说明Lua端引用链未正确清理，Mono堆内存会不断累积，最终导致： 内存占用持续增长 GC频率升高，引发卡顿 极端情况下导致内存溢出闪退 排查建议 查看GOT Online报告中Mono对象引用柱状图，确认Destroyed对象趋势 对比不同采样帧，定位对象数量激增的时间点 结合Lua堆内存分配堆栈，查找高分配函数 检查Lua代码中的事件监听、闭包、静态变量是否持有UI对象引用 解决方法 在Lua端及时释放不再使用的对象引用 移除不必要的静态引用和事件监听 使用弱引用管理长期持有的对象 定期调用GC强制回收 若Destroyed对象数稳定在某一水平后不再上涨，通常风险可控；若持续上涨，需优先排查Lua引用链，避免内存泄漏恶化。 分享链接： https://www.uwa4d.com/main/uwa-shared.html?shareId=1d682f3a-b8eb-49d9-a00b-07f0813dddc0 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
很多开发者都有过这样的经历： 性能报告几千行数据，不知道先看哪 遇到瓶颈，反复Google查文档，效率低下 团队讨论时，QA和开发对数据理解不一致 项目紧急，但等待人工分析需要时间 UWA GOT Online AI问答，就是为解决这些痛点而生。 顺势而为，AI 技术融入性能优化 在AI技术快速发展的今天，UWA始终站在行业前列，将前沿技术落地到实际开发场景。这一次，我们不只是加了一个按钮，而是为开发者配备了一位24小时在线的优化专家。无论你在何时、何地，打开GOT Online的报告页面，就能立即与AI助手对话，快速定位性能瓶颈、获取优化建议。 更重要的是，这不是通用大模型，而是基于UWA近10年积累的性能分析数据训练的垂直专家。通用AI读不懂的复杂性能指标，它懂。它懂游戏的性能规律，也更懂每个项目的独特场景。 报告读得更快、问题找得更准、协作变得更顺 无需学习复杂工具，直接用自然语言提问，AI助手能快速识别报告中的关键指标、帮你过滤无关信息，直击性能瓶颈、替代平均2小时的人工排查时间，80%常见问题可在30秒内得到答案。制作人5分钟内即可掌握项目性能状况。 真实用户怎么说 “我们团队现在看GOT Online报告，第一件事就是先问AI，80%的问题不用再翻文档了。新人第一天就能上手，省去了大量的培训成本。” —— 某头部游戏工作室技术总监 “以前QA提交性能问题，开发还要花半小时去理解数据，现在直接问AI，3分钟出答案，沟通效率提升了不止一半。” —— 某中型手游制作人 全模式智能分析支持 Mono模式报告 - 深入分析Mono堆内存、GC问题 GPU模式报告 - 全面评估渲染性能、DrawCall瓶颈 更多AI交互模式持续接入中，满足不同项目需求 典型使用场景 场景1：新人快速上手 第1天加入的测试人员，不需要培训，直接问「哪些资源导致内存占用过高」，AI立即给出Top 5资源列表，10分钟看懂关键问题，自学也能快速上手。 场景2：临时会商，当场决策 开会时突然被问到「上周那帧为何卡顿」，现场打开报告问AI，3分钟出答案，而人工分析需要等待2小时。配合截图和数据，当场给出优化方向。 场景3：多团队协同效率提升 QA提交性能问题时，直接附上AI分析总结，开发人员省去翻阅报告的时间，直接看结论，把更多时间用在分析根因上。 无论你是开发人员，需快速定位性能问题，获取优化方向； 亦或是制作人，需了解项目性能状况，辅助决策判断； 还是QA测试，需准确理解测试数据，提高测试价值； 亦或者是技术负责人，需统筹性能优化工作，降低沟通成本。 打开GOT Online，问AI ，就懂了。 限时福利：即刻免费体验 即日起至4月30日，新用户可直接体验AI问答全功能，无需等待审批。 马上体验，1分钟上手。 UWA，一直致力于让游戏性能优化更简单。这次AI问答功能的升级，是我们向"技术支持更高效"迈出的又一步。未来，我们将持续投入AI技术在性能分析领域的应用，为开发者带来更多惊喜。 关于UWA UWA是一家创业十年的高新技术企业，作为游戏行业的深耕者，UWA始终专注于为使用Unity、Unreal引擎的开发者提供丰富的优化产品，帮助开发者高效解决开发问题、定位性能瓶颈、提供解决方案，已支持超过一万款游戏项目。
引擎Quality设置里的Texture Quality和Global Mipmap Limit有什么区别
1）引擎Quality设置里的Texture Quality和Global Mipmap Limit有什么区别 2）小游戏上在动画状态机中替换动作会产生较大消耗怎么办 这是第466篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From 问答社区 Q：引擎Quality设置里的Texture Quality好像被删掉了？这个新的叫Global Mipmap Limit的设置项看上去是替代？有什么区别吗？ A：Global Mipmap Limit确实替换了Texture Quality，或者说是该功能的升级版，除了仍然是按照设置从Mipmap第0层开始移除外，额外增加了使纹理按照所在Mipmap Limit Group剔除Mipmap层级的设置；并且需要针对具体纹理开启Use Mipmap Limits并设置Group。使用上远比之前Texture Quality的一刀切做法更实用，但相比Texture Streaming的灵活自动处理还是需要投入一定人工成本针对分级进行设置。 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69ae6e7eabed2e338a7dac66 From 问答社区 Q：我们在小游戏项目中使用Animator Override Controller替换动作会产生较大的开销，有什么优化方案吗？ A：替换动画时会对所有的动画重新计算绑定，因此在动画数量本身就很多的情况下，开销也会相应偏高；建议尽可能降低动画的数量，从源头减少合并运算的数据量，并适当削减动画精度，优化动画曲线数据。 这个是大体思路，不过还是比较笼统的，也看看大家有没有什么更好的建议可以取取经。 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69ae3139abed2e338a7dac65 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ 一、前言与简述 1. 前言 “最终效果可见文尾”。 此篇文章主要是记录开发过程中的一些细节与踩坑，基础动画框架是复刻的ALS，技术方案来自刺客信条基于预测的FootIK。 自己实现过程中也参考了其他大佬的文章（链接放末尾），实现的方法可能存在很多问题，写法也不是很优雅。文章中一些步骤为制作过程记录，也有补录，可能存在顺序或制作内容和正常顺序有差别，有一些细节或者坑可能忘记了，还请见谅。 2. 简述 基于预测的FootIK通过预测角色的落点来计算即将行走的路径，从而通过IK更改脚步和盆骨来适配地形，相较于传统IK会更加灵活（当然也更复杂）。 总体步骤为： 准备脚步数据 预测脚步落点 计算Pelvis数据 计算脚步路径 计算脚步数据 应用数据 二、准备数据与预测 1. 数据准备 在此阶段我们需要在动作中添加一个曲线，通过这个曲线上的时间来告诉我们脚步还有多久会落下，但正如育碧所言：“Absolutely must be automated”，所以我使用的是UE的AnimationModifier来自动的生成曲线，在其中通过对动画信息进行采样，获取当前帧信息。 之后通过获取到的Foot高度（可以更加细致，比如加上脚尖，甚至旋转等）来判断是否为关键点，如果是就记录下来。这里我还开放了FootName和Height的阈值作为参数。 这里获取脚步位置没有直接的函数，需要GetAnimPosAtTime之后去GetBonePose获取，然后根据获取到的脚步落下与脚步抬起关键点来连成曲线。 这里找到落点其实很简单，但生成曲线还比较麻烦，要注意开始点和结束点的连贯，以及曲线应该改成线性。最后就可以得到自动生成的曲线。 2. 脚步预测 在开始之前我创了一个结构体，往里加入很多参数避免之后修改一次就要改函数输入，又要加一堆参数。 在PreDictFootLocation中，思路是通过我们之前生成的脚步落地曲线以及角色的移动速度来获取落点（除去黑框的内容）。 而其中黑框主要是来解决脚步相对位置的问题。 如果你只是一个动作，可以直接写死相对位置，但如果要应用到游戏中的各个动作，它的脚步相对位置会有差距，这个时候我最先想的是：那我实时计算脚步的位置不就好了？ 但脚步会有在后面和在前面，比如落脚的时候不会跟随角色移动，如果用实时位置，那么预测的位置必定很不准确。所以我采取的做法是在落地时记录相对位置，然后用黑框中进行计算。 而这其中的脚步落下是通过一个参数记录上一帧脚步是否落下，如果上一帧落下则更新，同理也可以得到脚步抬起时刻来获取信息。 例如在脚落下时记录脚相对位置，脚步开始位置设为当前脚步位置，上一次的结束位置等。这就是脚步开始位置，而结束位置是通过我们的BoxTrace实时得到的。 这里其实还有个小问题，就是预测的落点会有误差。在移动的时候会逐渐非常缓慢向角色靠近（可看下方视频慢放时）。这个虽然影响不大，但在脚步离台子非常近的时候会有抖动，目前这个问题找了动作、曲线、角色都没找到原因。就先不管了，最后画一画DebugShape就可以得到如下效果： 三、计算Pelvis数据 1. Pelvis起始点与终点 来到Pelvis的计算，首先要获取两脚间的起始点位置（绿线）作为Pelvis计算的起始点和终止点： 这个开始点和结束点的切换是在脚步交错时进行，所以我就直接根据脚步的Y值谁更靠前来决定，当和上一帧不同时则为切换，这个时候更新双脚位置。 注意结束点要在脚落地时一直更新，因为我们之前提到了会有误差，那个落点会一点点向角色靠近，如果只取那一帧的会对不上，所以在脚落地时Pelvis的落点也要一直更新。 这里有些动作会在短时间内交叉两次（比如ALS的冲刺）导致错误，所以还需要对切换加上一个很小的冷却时间，这个时候我们就可以得到下面视频的绿色曲线： 可以看到，我们在上下坡时Pelvis会随着碰撞体一直运动，但我们的预期是在上坡落脚时上升，和在下坡时边下坡边下降，所以首先要固定住Pelvis，之后再去加位移。 2. 上下坡 这里固定Pelvis我之前尝试过使用PelvisOffset‌去做，但是不知道是不是因为更新时机或顺序的问题，会延迟一帧，导致强烈抖动。 所以我最后采取的做法是直接Set角色保持Mesh的高度一直在StartPos处（黑框后面会说）。 此时就可以得到如下效果，可以看到我们角色是一直和开始点保持同样高度： 之后就是处理我们的上坡和下坡的逻辑（平地可视为上坡下坡）来计算Pelvis Addtive Offset Target。 上坡是要落地后采样，并且在结束后Mesh会抬升到新的开始点，所以Addtive要变为0，不然会在原有基础再高一截。而且下坡则是要在没有落地时采样落地，因为脚步落地后，脚步可能还未交叉，开始点还未更新，所以要保持Addtive未最后一个点，不然在交叉腿时会抖。 现在我们知道了怎么计算，但是如何采样呢？这里提前计算了StartFootRate和EndFootRate（Rate这里我代指比例）。 之前尝试过用曲线值来采样，但是一直不准确，还有些其他问题就没用曲线。 先说EndFoot，它是用在下坡时采样。在脚步交错时，EndFoot其实已经抬起后运动了一段距离（下图左脚），通过记录交错时脚步到交错时脚步位置和到最后点位置（XY方向上的长度）的比例来设置。 对于StartFootRate则比较简单，因为交错时StartFoot是落地的，所以直接看到StartPos的距离和End的距离即可。 最后将Mesh偏移哪里的Addtive加上去，就有下面的效果： 四、计算Foot路径数据 1. FootPath 计算过程很简单，就是从一直脚的开始和结束点作为初始点。 从开始到结束和结束到开始分别打射线，在检测到碰撞后再从顶上向下打BoxTrace找到碰撞点，这个时候把两个新的点作为下一次的开始点和结束点，直接没有发生碰撞，或者达到迭代次数上限，最后将所有点数依次组合起来 从开始点到结束点放进数组中。 得到这些点后，我们还需要准备一个数据，用于之后采样。 我们还需要知道脚步到何时需要这个点，此处曾尝试过预测曲线，但不精准有问题，最后采用的还是和上面Pelvis采样相同方法。 通过记录每个点到开始点的距离比例，记录下来，这样开始点是0，结束点是1，中间点介于0-1，最后就得到了FootPath记录点位世界坐标的数组和一个Foot Curve Vars的数组。 2. FootPath采样 计算完上述之后，我们需要知道角色当前取哪个点。 首先要确定角色的脚步位于哪两个路径点之间，就需要根据角色脚步到这一段路径的起始点和终点的距离比例来插值获取位置信息。 可以看到下图中白色小球就是我们采样的位置： 五、应用Foot与更正IK问题 1. Foot 计对于Foot我们仍然要从脚当前位置向上和向下去打一条射线，通过碰撞点得到Trace的脚步位置。 将这个Trace的位置和我们的当前路径采样位置做比较，取更高的那一个，这样就能保证脚步不穿墙。 得到IK位置之后就需要计算偏移，这里要和地面的距离来算而不是脚步位置，否则脚步的动画运动细节会被IK覆盖掉。 最后对位置和旋转平滑一下即可，这个时候如果你运行起来，可以看到平地是基本正常，但上下坡你会发现很奇怪： 所以我们需要在之后进行Pelvis的更正。 2. Pelvis更正 上坡时脚被压着很难受，是因为我们的Pelvis目前严格跟随我们的预测落点，所以在上坡时脚开始抬升前，它的高度并不会动（甚至坡度很抖会穿进去）。 所以我们需要给它一些额外的高度。 我采取的策略是让其不低于最低脚的偏移，这样上坡时就会正常。但是，如果当运动速度比较快，前脚抬的比较高也会有诡异情况，所以我们的Pelvis还要根据前脚的角度来进行一定的偏移，但是如果根据更高的脚来进行，对平地和下坡有一定影响，所以要进行一些处理。 3. 其他脚步问题修复 你以为这样就完美了吗？NO！NO！NO！ 当你运行起来，你会看到如下现象： 这Twist怎么又坏掉了？ 点开骨骼一看： 原来是我们之前将Mesh下拉，Root也下来了，身体部分随着Pelvis恢复正常，但Root并不会动，导致Knee出现问题，所以此时要对Knee也额外加上脚步的偏移。 这个时候基本就已经完成： 六、融合其他运动状态 1. 不同状态的融合 这个PredictIK在奔跑走路时表现良好，但是如果你加上跳跃、停止等就会出现不可预料的问题。同时在角色刚刚起步时预测IK有些数据没有完全更新，其实是有些问题的，所以我选择在停步时候选择常规IK，而在空中取消IK。 此时你如果尝试着跑一跑，你就会发现下列问题： 2. 奇怪的抖动 这里的抖动除了视频中跳跃之后的抖动，也有刚刚Play移动时候的抖动。 对于空中的抖动是因为在空中落地后是普通IK，没有更新Predict的FootLocation，导致它刚刚进入PredictFootIK时候脚步点还在起跳的地方，就会抖。 所以我的做法是在普通IK的时候也计算更新Foot的Locaton（就是文章开始的第二部分中的预测脚步），同时，如果是在上坡时起跳，会出现人物掉到了墙里，这是因为我们位移了Mesh，所以在切回去的时候没有Offset了，但Mesh位置没还原就会这样，所以在Reset要还原一下Mesh位置，但需要来个插值，不然会瞬移。 同时为了解决开始的抖动，还要Reset一下Pelvis的前后脚位置到Actor的位置（其实不准确，但用下来感觉没影响）。 3. 无法站立 在走到悬崖边缘时，可能出现两个脚的预测都没打到点，或者单脚会出现问题： 对于这个首先要在PredictFoot的时候，如果没预测到，就不要执行PredictFootIK，切回普通IK。但直接切回去会有些问题，还要重置一下IKOffsets。 4. 细节补充 相机我是把Z轴Lag调慢，抖动好很多。 七、待解决问题和最终效果 1. 最终效果 虽然还存在很多问题，但最终效果还是不错的： 更新：在下楼梯时由于射线检测的高度没有设置够，会有时检测不到从而不触发PredictIK（演示视频中存在这个问题），可以通过增高范围解决，但是不宜过大。 2. 待改进问题 A. 对于上不去的坡，Pelvis虽然是落地才抬升，但是脚步会有一个最低的高度（我们预测的路径） 就会有奇怪现象。 这个问题想了一下要解决又要多出很多情况，是否考虑游戏中用一个自动跨上或者攀爬来解决？（有待商榷） B. 在运动过程中也有很小的概率会脚乱飞，累了不想修了，毕竟小概率。 C. 预测脚步的落点有误差（前面提到过），所以脚步落在边缘是因为如果由于误差缩下来或者缩上去了会有一些抖动。 D. 在上下坡时，脚步切换过程中，起始点会变，所以Mesh高度会变。为了避免跳变我们是有个平滑，但是这个平滑会造成脚步有轻微的浮空，不慢放基本看不出来，就不管了。 参考文章： Fitting the World: A Biomechanical Approach to Foot IK 《刺客信条》基于预测的FootIK方案分析与UE4实现 UE4实现基于预测的FootIK 这是侑虎科技第1952篇文章，感谢作者Shadow供稿。欢迎转发分享，未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859） 作者主页：https://www.zhihu.com/people/shadow-21-71-4 再次感谢Shadow的分享，如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859）
GPU带宽分析中GPU Non-Base Level Textures过低是什么意思
1）GPU带宽分析中GPU Non-Base Level Textures过低是什么意思 2）为什么不建议用LoadFromMemory接口 这是第465篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From UWA AI 问答 Q：GPU带宽分析中GPU Non-Base Level Textures过低是什么意思？ A：GPU Non-Base Level Textures指标过低解析： 在GPU带宽分析中，GPU Non-Base Level Textures指标统计的是GPU采样非Mipmap第0层（即经过缩小处理的层级）纹理的次数。该指标“过低”通常意味着绝大多数纹理采样都强制使用了最高分辨率的第0层（Base Level）。 这并非性能优异的标志，反而往往暗示了潜在的带宽浪费和缓存命中率低的风险，具体分析与优化策略如下： 1. 核心含义与风险 Mipmap未生效或使用不当：正常情况下，随着物体远离摄像机，GPU应自动采样更低分辨率的Mipmap层级。若该指标过低，说明场景中的纹理即便在远处也被强制以最高精度采样。 带宽压力剧增：采样第0层意味着每次像素着色都需要读取更多数据。对于远距离或小面积物体，这不仅浪费了宝贵的显存带宽，还增加了GPU Memory Bus的负载，直接导致功耗上升和设备发热。 Cache Miss率升高：过大的纹理数据难以完全放入L1/L2缓存，导致频繁的Cache Miss，进一步引发GPU Stall（读写停滞），降低帧率。 2. 常见成因排查 未开启Mipmap：检查关键纹理资源是否在导入设置中关闭了"Generate Mip Maps"选项。 各向异性过滤（Anisotropic Filtering）强制开启：若在Quality Settings中将各向异性过滤设为"Forced On"，或在特定纹理上设置了过高等级，GPU为了修正视角倾斜带来的拉伸，会倾向于采样更高层级甚至混合多层，但在某些统计口径下，过度追求清晰度可能导致非基础层采样逻辑异常或实际仍大量依赖基础层数据。 Shader编写问题：检查是否有Shader代码强制指定了LOD层级（如tex2Dlod固定取0），或使用了不支持Mipmap采样的特殊算法。 UI纹理误用：UI元素通常不需要Mipmap，但如果3D场景中的大量贴图被错误地当作UI处理而未开启Mipmap，也会导致此现象。 3. 优化建议 合理开启Mipmap：对3D场景中的大部分纹理开启Mipmap生成。这能显著降低远处物体的采样带宽。 分级设置各向异性过滤：避免全局强制开启。建议在Project Settings中设为"Per Texture"，仅对地面、墙面等需要高质量斜视效果的纹理单独开启，并尽量使用较低等级（如2x或4x）。 控制纹理分辨率：结合渲染距离，对远景物体使用低分辨率纹理。若高分辨率纹理的Mipmap 0层采样率极低（如小于5%），说明其高分辨率部分从未被使用，应直接缩小源图尺寸。 检查RenderTexture：确认动态生成的RenderTexture是否必要开启了Mipmap，避免不必要的内存与带宽占用。 通过提升非基础层纹理的采样比例，可有效降低单次采样的数据量，从而在保证视觉质量的前提下，显著优化GPU带宽与能耗表现。 From UWA AI 问答 Q：为什么不建议用LoadFromMemory接口？ A：不建议大规模使用LoadFromMemory（含同步/异步版本）主要基于以下三方面性能缺陷，已在真实移动端项目中验证： 一、显著更高的CPU与内存开销 同步阻塞主线程：LoadFromMemory是完全同步调用，强制在主线程解压并构建AssetBundle对象，导致帧率卡顿，尤其在加载较大包（如含10张1024×1024纹理）时，耗时可达数十毫秒（对比LoadFromFile的零点几毫秒）。 双倍内存驻留：需先将完整AssetBundle字节数组（如几十MB）全部载入内存，再解压/解析；而LoadFromFile仅加载头部（KB级），资源按需流式读取，内存占用极低。 二、丧失底层IO优化能力 LoadFromFile在真机上利用系统文件句柄缓存、预读机制及OS磁盘缓存，对LZ4压缩包可直接映射解压，避免全量解压。 LoadFromMemory绕过文件系统，强制走内存拷贝+解压路径，无法享受任何OS级IO优化，且破坏Unity内部PreloadManager的加载队列与速率控制。 三、加密需求存在更优替代方案 若为防破解而加密AssetBundle，LoadFromMemory会引入额外解密内存副本（如解密后字节数组+AssetBundle对象），加剧内存压力。 更佳实践： ✅ 对关键配置/脚本单独加密，资源文件（纹理、模型等）免加密（实际防护效果有限）； ✅ 使用LoadFromFile+自定义偏移加载（offset参数）实现轻量混淆； ✅ WebGL等平台可先解密至内存再调用LoadFromMemoryAsync，但需严格复用byte数组避免内存膨胀。 结论：除非必须动态解密或跨平台兼容性受限，否则应优先选用LoadFromFile（LZ4压缩+合理分包策略，兼顾加载速度、内存效率与热更新可行性。 分享链接： https://www.uwa4d.com/main/uwa-shared.html?shareId=116ff3ca-ba5e-429a-976d-422b78dfc77a 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
2 月刊｜GPM 2.0错误日志分析上线，PC端监测能力全维度突破
GPM 2.0聚焦研发问题精准归因与PC端监测需求，全新上线「错误分析」功能，实现Error&Exception异常日志全量捕获与深度拆解；同时完成PC端画面截帧、专属性能指标的双重突破，并新增版本与设备筛选优化功能。从异常分析、多端监测、数据提纯三大维度升级性能管控能力。 错误分析 补全异常监测闭环，精准定位程序错误根源 新增的「错误分析」功能，针对游戏运行中的Error、Exception两类核心程序异常实现精准区分与全量捕获，填补了非崩溃类程序错误的监测空白。系统会自动对各类错误日志归类统计，清晰展示影响范围、发生次数等核心信息，支持开发者快速查看错误具体堆栈详情。该功能与崩溃、ANR 分析模块联动，自动关联设备硬件、系统版本等基础信息，在异常报告中可同步查看错误触发的场景、画面截帧等关键内容，结合堆栈与消息信息，助力研发人员快速定位错误发生位置与深层诱因，针对性制定解决方案。 错误分析列表 单条错误分析详情 PC端玩家画面截帧 可视化还原操作轨迹，高效定位异常诱因 全新上线的「PC端玩家画面截帧」功能，将移动端成熟的截帧监测能力延伸至PC端，实现玩家操作轨迹与异常场景的可视化还原。当PC端出现性能卡顿、程序错误等问题时，可在异常报告中查看问题触发前后的画面截帧，并联动性能参数曲线、自定义事件，清晰还原玩家行为路径与操作细节。支持按玩家、问题类型、时间节点精准筛选截帧内容，直观捕捉技能释放、场景切换、多窗口交互等操作中的异常诱因，快速定位机型、GPU适配的渲染问题或玩家操作引发的功能异常。可搭配「玩家行为洞察」的协作笔记功能，对关键时间戳添加注释，实现团队协同排查，大幅降低PC端问题复现与分析成本。 PC平台数据展示图例 PC端专属性能指标 大维度精准监测，捕捉全链路性能短板 针对PC端游戏的性能监测特性，新增七大专属性能指标，覆盖内存、CPU、GPU、IO四大核心维度，实现PC端性能状态的全方位、精细化捕捉，填补此前PC端关键性能数据的采集空白，让性能优化更具针对性。 内存维度：PrivateBytes私有内存占用，精准监测游戏进程独立内存消耗； 网络维度：网络延迟指标，快速判断网络环境对游戏体验的影响； 温度维度：GPU温度监测，及时预警显卡过热引发的性能降频问题； CPU维度：CPU占用率+句柄数，全面掌握CPU资源分配，定位过载、句柄泄露问题； GPU维度：GPU利用率，实时跟踪显卡负载，定位高画质、复杂场景的渲染瓶颈； IO维度：Read/WriteCount读写次数、Read/WriteBytes读写字节数，排查存储设备引发的加载卡顿、画面掉帧问题。 版本黑名单 & 仅真机模式 数据提纯双功能，提升分析效率 针对测试阶段数据干扰、筛选操作繁琐的痛点，新增两大数据筛选优化功能，实现监测数据的精准提纯，让问题分析更高效；并可在“筛选器设置”中快速配置，操作更便捷。 「版本黑名单」功能支持屏蔽指定测试版本的数据，避免多版本并行带来的无效数据干扰性能大盘，减少查询与分析的资源占用，保障数据统计与问题归因的准确性。 「仅真机模式」开启后，系统将自动筛选排除模拟器设备数据，仅保留真机监测数据，从源头减少模拟环境带来的数据污染，省去每次手动筛选的繁琐操作，若需临时查看模拟器数据，删除筛选条件即可快速恢复。 未来，GPM 2.0将持续迭代升级，落地更多游戏研发运营实用场景，深挖数据价值、提供多元数据线索，全方位助力游戏产品体验与性能双提升。 立即体验！ 专属通道限时开放 ➡ 预约技术专家1V1部署指导！我们将全程支持服务搭建、数据分析与反馈，确保您能够充分体验GPM 2.0带来的价值。 邮件：sales@uwa4d.com 微信号：17502188376 了解GPM 2.0全功能，可以点击《GPM 2.0 核心功能全景指南｜重塑游戏性能主动管控能力》查看详情。 关于GPM 2.0 UWA GPM 2.0是专为上线及测试阶段游戏项目打造的高性能监测平台。它不仅能深度捕捉宏观性能数据，更创新采用性能无损截帧技术，在不影响玩家体验的前提下，助力开发者全面掌握玩家端运行关键细节，从多维度优化游戏性能，实现从“玩家投诉后救火” 到 “问题发生前预警” 的核心转变。 GPM 2.0全景指南 《UWA GPM 2.0 核心功能全景指南｜重塑游戏性能主动管控能力》 《GPM 2.0 功能更新月报：新增导出表格、崩溃反混淆等实用功能，体验再升级！》 《1 月刊｜日报周报一键获取+AI问答，玩家行为精准分析》 《12月刊｜上新！全局筛选+设备标记+序列帧回溯，GPM 2.0性能分析效率拉满》 GPM 2.0新功能 集锦 《GPM 2.0全新功能发布｜GPU精准监测 + 精细化运营，重构游戏性能管控新范式》 《GPM 2.0 SDK 热更新｜不打扰玩家的“隐形升级术”！》 《GPM 2.0功能尝新｜AI识图精准捕获材质丢失，全链路护航游戏品质！》 《UWA GPM 2.0 崩溃上报功能全面升级｜全场景覆盖+免费开放，让崩溃排查不再卡壳》 《大R流失、问题难复现？GPM 2.0「指标 - Session直连」功能，让异常定位效率翻倍！》 《从 “被动救火” 到 “主动掌控”：GPM 2.0 如何重塑游戏性能竞争力？》 《GPM 2.0 告警功能：游戏性能的智能守护者》 《无限接近真相｜GPM 2.0 功能上新》 《崩溃现场全回溯｜GPM 2.0「崩溃上报分析」上线，终结“无法复现”的噩梦》 《GPM 2.0 全新发布「卡顿帧堆栈」功能｜精准高效定位游戏玩家端性能》 《深入解析「卡顿帧堆栈」》 《解锁GPM 2.0「卡顿帧堆栈」｜代码示例与实战分析》 《如何精准打点解决卡牌、SLG、开放大世界、放置类游戏卡顿难题》 《不妥协每一毫秒的性能 不浪费每一字节的流量》 《GPM 2.0 重新定义性能监测与优化｜从未知到已知，从被动到主动》 GPM 2.0案例精讲 集锦 《我们的目标是不耗电、不发烫、不闪退》 《冒险闯关类玩家关注的四大体验》 《玩家端性能数据 一手掌握》 《造成设备功耗高的罪魁祸首》 《海外市场设备适配性优化实战》 《5000人测试背后的秘密》 GPM 2.0实操篇 集锦 《GPM 2.0 按游戏类型定制性能监控策略｜「告警」功能实战指南》 《GPM 2.0 让每一帧的流畅兑换真金白银》 《GPM 2.0 实操之Session篇之如果闪退是因为内存崩了》 《GPM 2.0 应用场景实例之解决让人头疼的问题》 《GPM 2.0 助力设备监测、行为分析与性能优化｜玩家共测新体验》 《GPM 2.0 API 功能助力定制你的性能优化》 《GPM 2.0 驱动优化与投放决策｜硬件性能一目了然》 《GPM 2.0 多维数据与实时截帧完美结合》
1）堆内存对象的Managed Size具体是如何计算的 2）微信小游戏项目，跑图过程场景加载比较慢如何优化 这是第464篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 From 问答社区 Q：在Memory Profiler观察到一个长度为9的字符串对象Managed Size为40B，显然不止每个字符2字节。请问这个应该是怎么算的？ A：堆内存对象的Managed Size可以认为是以下四个部分： 类型对象指针； 同步块索引； 专属属性（比如数组和String就有一个int32 length 4B）； 实例字段。 针对每种对象这四个部分可能都有不同，最好的办法就是直接看下源码里这个类的构造。比如String： 它就是8 (*monotor) + 8(union) + 4(length) + 2( N+1(\0) )，特殊之处就是多了个\0，相当于长度要算N+1。所以总共是22+2*N字节。 其中为什么每个对象都要额外分配类型对象指针和同步块索引，导致一定的基础占用，原理上可以参考一些官方文档或者社区文章。 《.NET 框架内部结构：CLR 如何创建运行时对象 |Microsoft Learn》 欢迎大家转至社区交流： https://answer.uwa4d.com/question/69858fdcabed2e338a7dac5e From 问答社区 Q1：目前有个点比较陌生，就是小游戏的每秒资源后台最大同时下载数量，和下载文件大小的指标比较缺失， 这块会影响玩家跑图的过程中场景加载比较慢的问题。 请问有没有官方大数据指标？ A：官方说法是小游戏资源下载并发数为10，超过时底层自动排队；单个请求文件最大不超过100MB（理论最大值，但考虑到带宽，建议单文件2~5MB以内）。 Q2：由于我们是横屏地图，真机上持续控制角色移动10秒以上，停下后，附近场景资源下载大概要等10秒才加载完整。 大概200个资源，请问有什么解决的方法？ A：这种情况看起来是没有用WXAssetBundle的接口进行AssetBundle加载，一般更推荐用这个，而不是原生的AssetBundle加载接口。WXAssetBundle的加载接口会把网上下载的东西缓存到本地，下次直接从本地加载，而且这个接口会有自动卸载的功能，总体内存占用也会更小一些。其他的优化方法就是资源设置以及打包策略的问题了。 欢迎大家转至社区交流： https://answer.uwa4d.com/question/6985999eabed2e338a7dac60 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。本期分享分别来自UWA AI问答和UWA问答社区，希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ Lyra项目，被誉为UE5的官方教学示例，实际上却远超初学者的难度。官方定义为初学者游戏，但真正掌握它，却需要较深的技术背景和至少200小时的UE开发经验。 视频地址： 【Inside Lyra】之我要学UE 引言 Lyra项目，是伴随着UE5一起发布的官方教学的示例项目，全称是Lyra Starter Game。官方定义为初学者游戏。 但学过的人都知道，这个示例项目远远超出了初学者的难度。简单来说，如果自学Lyra的话，你至少需要掌握以下条件： 1.熟练掌握UE特化C++编程。这就表示，你需要在熟练掌握普通C++的基础上，额外掌握Unreal为C++做的特性化封装，比如：以UHT（Unreal Header Tool）为代表的反射系统，以UObject为代表的UE对象系统，以TSharedPtr等为代表的智能指针和垃圾回收系统。 2.熟练掌握UE编辑器与核心框架。这表示，你需要能够熟练使用UE的编辑器，至少能知道绝大多数功能的入口，以及常用功能的位置和使用方法。在展示某个窗口的时候，你应该知道它是如何打开的，以及它包含哪些操作和信息。掌握核心框架则代表，你了解UE的模块化构建和依赖规则，了解事件委托机制和用法，了解UE的资产序列化与引用，了解常用的UE容器，以及核心的Gameplay框架等等。 除此之外，还希望你拥有至少2年的编程经验，或者至少1年左右的游戏开发经验。这表示，你应该能秒懂某些约定俗成的知识点，比如单例和工厂，广播和分发，会话和鉴权，缓存与热修等等。 低于上面这个门槛的同学，自学Lyra就如同在啃天书一样。并且从经验上来说，200小时的UE开发经验是达到高效学习Lyra的临界值，低于此时长的开发者自学Lyra的放弃率高达 90%。 但可以这么说，如果你能基本看懂并理解Lyra的所有代码，那么月薪2W是简简单单的。如果你能熟练掌握和使用所有的Lyra代码，做个主程也是随随便便的。 本文就是帮大家剖析一下Lyra这个项目，减少自学的挫败感，希望能够尽快掌握这个“工业级”入门游戏，提高自己的游戏开发水平。所以，我将这个系列定义为Inside Lyra。 在开始之前，再次强调一下，本文不是0基础，不会讲解诸如编辑器有几个面板，蓝图有几种类型，用TArray等容器写一个用于排序的Lamba表达式之类的基础知识。而是，尽量为大家深入浅出的去剖析，整个Lyra项目的运作流程，以及各个工业级框架和组件之间的协同关系等。 本文的目标是，能正常打开UE编辑器并运行Lyra就成功。 一、为什么是UE？ 由于本文的目标非常简单，在开始之前我们还是先说明几个前置话题。 我拥有10多年Unity开发经验，过去也一直是在做Unity引擎相关的教程，那么这次为什么选择Unreal的项目做教程呢？从程序的角度来分析一下，主要有以下几点： 1. 引擎源码开放。 这一点对技术开发来说非常重要，非常非常重要。虽然引擎源码会大幅增加项目开发时的复杂度，从而导致学习和开发效率的锐减，但一旦你迈过这个门槛，从学习者变为熟练使用者之后，你才知道引擎源码对于开发者来说是多么提高效率的事情。你无需再盲猜每个功能背后的实现思路，你可以随意修改引擎源码完成特殊功能的适配，你可以随时断点来调试某个功能的调用顺序，你可以自己修复引擎版本自带的BUG，你甚至可以直接Copy引擎模块代码来实现一个比较接近的功能，等等。虽然我自己也能接触到其他引擎的源码，但绝大部分的开发同学是没有这个条件的。因此选择一个提供源码的引擎，对于开发者而言，会得到前所未有的控制力与安全感。 2. 预置Gameplay框架。 从这一点上来说，我个人觉得是有利有弊的。如果大家接触过其他引擎就会知道，大部分引擎在开发项目的时候，是可以自由地定制项目的启动入口的，并且启动的流程也可以完全自定义。但是UE不是，它提供一套自己的Gameplay框架，开发者必须完全理解这套机制，并且在这个基础上穿插去做自己的定制流程。很难说这两种方式，谁更好。自由度高意味着，复用性低。而自由度低则意味着，可以举一反三。 也可以这么认为，当你理解了一个UE项目的启动流程之后，你就可以快速上手任何一个UE项目。但相比于可自由定制的项目来说，这增加了初学者的学习门槛，也是学习UE会卡关的主要问题。在其他引擎上，你花20个小时可能已经做出一个有点意思的小游戏了，而在UE上，20个小时可能连Gameplay框架还没学完。但总结来说： 用短期学习成本换取长期团队协作效率与项目可维护性的指数级提升，是非常有必要的，因为你不可能一直是一个人在战斗。 3. 强大完整的服务器。 UE采用的是服务器同构设计，同构设计代表的是服务器逻辑和客户端逻辑是同一套代码。也就是说服务器可以完全复用所有为该项目实现的框架和代码。以基于Actor级别的同步能力，和可靠的同步机制，让开发者完全不需要关心通信。使绝大多数的异常可锁定在游戏逻辑层，而无需反复排查底层同步机制，减少开发者在定位问题上的尝试，提高开发和调试效率。同时，也可以任意部署在本地或者直接用编辑器充当服务器来调试相关逻辑。但缺点也是有的，因为自己又当客户端，又当服务器，难免会在系统在开发的时候发生“精神分裂”，因为你要时刻切换自己的身份，思考某句代码是执行在权威服务器上，还是在本地客户端上。 4. 完整的无缝大世界方案。 无缝大世界，可以说是次世代开放世界游戏的标配了。在其他引擎中完成这个方案的代价非常高，而在UE中，只需在创建地图时，简单勾选几个选项，即可激活基础框架，配合Data Layers可快速实现昼夜/环境破坏等动态效果。这可以让开发者的精力从实现无缝大世界本身解放出来，全力投入内容创作与玩法设计上。 5. 影视级的动画系统。 内置Control Rig+Motion Matching等工业流水线，搭配IK/FK重定向的工具链，让中小团队也能产出3A级动画的丝滑表现。 6. 完善的编程规范和命名规范。 UE为所有代码和资产提供了一套命名规则，所有开发人员需要共同遵守，甚至部分规则如果不遵守还会强制报编译错误，因为代码可读性、可维护性是一个团队高效协作的利器。 7. 蓝图。 是一把威力与风险并存的双刃剑。无需代码，策划/美术也能快速搭原型、配置数据，对UE新手友好。但从开发效率上和逻辑可读性上来说，远远落后于C++。当哪天你突然意识到蓝图实现某个功能很麻烦的时候，那么恭喜你，你现在是个程序员了。 8. 多线程安全。 在UE中，你可以通过Task Graph和AsyncTask提供高层抽象，在规范使用下大幅降低死锁/竞态风险。傻瓜式的封装，可以让你随意指定线程池（游戏/渲染/后台）来干活，完成后用委托（Delegate）再回调到Game线程去处理（非线程安全操作必须回主线程！）。 以上并不是全部的UE优势，UE最大的优势其实是他们每个版本都提供最前沿的技术，供开发者学习和了解，并且非常慷慨地给出所有资源和示例代码。甚至官网的文档都远远落后于代码的更新速度。 所以综合而言，选择UE，其实是选择一艘拥有核动力引擎的工业巨轮。启航虽慢（学习曲线陡），转向笨拙（框架约束），但其源码级的掌控力、成熟的Gameplay框架、高效的同构Server、革命性的大世界/动画方案、规范的代码环境、灵活的代码/蓝图模式、强悍的并发异步工具链等能力，让你在开发3A级巨制或大型多人游戏的惊涛骇浪中，拥有无与伦比的稳定性、生产力与技术上限。它逼你成长为“正规军”，过程虽让人头秃，但学成之日，你便是战场上的“王牌舰长”！ 所以，就你了，Unreal，启动！ 二、为什么是Lyra？ Lyra一直以来被当做UE官方的“初学者示例”，其实是存在认知错位的。它本质是Epic用来展示UE5工业级开发范式的技术演示，而非真正的零基础教程。它是一个完整的项目，包含各种顶级的框架设计和UE5最新的引擎特性。比如： 1.它有约15万行的代码和200+个蓝图，规模远超独立小游戏的标准。 2.多层抽象的基础框架，GameFeature+Experience+HotFix，再加客户端和服务器一体化的设计，复杂度非常高，需先熟练掌握插件系统/模块化设计。 3.核心机制代码分散，学习难度高。被拆分为20+个独立的插件，对于能力复用和组合有较好的支持度。 4.使用数据驱动设计，一个简单的角色属性的修改，可能就要跨越数个配置文件。而非简单修改一个值。 一个典型的场景就是： 设计一个简单的复活队友的技能可能就要穿透至少5层：LyraHeroComponent → AbilitySystem → GameplayCue → 客户端预测 → 服务器RPC验证。 那么为什么Epic仍坚持推广Lyra呢？说些个人不负责任的猜想： 1. 技术展示。 强制开发者接触UE5前沿特性（如World Partition/MetaSounds, 等优秀能力）。 2. 生态绑定。 使用Lyra等于深度依赖Epic后端服务（如EOS好友，排名，成就等系统）。 3. 开发者筛选。 用高复杂度来过滤非目标用户（3A/多人游戏团队）。 一个不负责任的数据显示，某个国外课程的后台数据表明，仅12%的UE自学者在Lyra项目上坚持超2周。 那么，最后回答一下，我们为什么要学习Lyra呢？我的答案是： 1.从技术提升角度来看，Lyra涵盖了UE5众多前沿且复杂的技术特性和架构设计。学习Lyra能够帮助开发者深入理解UE5的高级功能和工业级开发范式，掌握插件系统、模块化设计、数据驱动开发等关键技术，这些技术在大型游戏开发和复杂项目中具有极高的实用价值。例如，掌握了Lyra的架构设计思路，开发者在面对其他大型项目时，能够更高效地进行系统架构和模块划分。 2.从职业发展角度考虑，由于Lyra的学习难度较大，成功掌握它意味着开发者具备较强的技术学习能力和解决复杂问题的能力。正如前面调查数据所示，掌握Lyra的开发者在求职大公司和大型项目工作室时具有显著优势，能够获得更多的职业机会和更好的职业发展前景。 3.从行业趋势来看，随着游戏行业的发展，对游戏的品质和规模要求越来越高，UE5的工业级开发范式将逐渐成为行业主流。学习Lyra可以让开发者提前适应行业发展趋势，紧跟技术前沿，在未来的职业竞争中占据有利地位。 其实，大家也可从B站或者知乎上搜索Lyra相关的关键词，可以看到对一个子模块的讲解都可以作为一个技术大会的分享主题，也就证明了Lyra的含金量。再一个是，就目前来看，绝大部分UE5的项目，不管是什么类型的，都会或多或少参考Lyra的框架设计。比如，Lyra中完整实现了编辑器扩展，客户端和服务器交互和分离，构建多平台等能力，这些是无论做什么类型的游戏都有参考价值的。 三、教程形式 传统教程像在组装乐高说明书 —— 先逼你认遍所有“零件”，比如从UObject到Actor再到Component等，按照UE的复杂度来看，可能20小时后还在学基础概念。而那种技术大会分享更不用说，简直就是“量子速读现场” —— 满屏架构图咻咻乱飞，一张PPT涉及的知识点可能比他的头发还多。每章的知识点过的还非常快，来不及截图不说，截了也看不懂。 对于Lyra这种工业级的架构和业务来说，这两种就更不合适了，讲个GameState要先科普一个小时的UObject，这谁受得了呢？有可能你讲完Gameplay框架，UE都更新两个版本了。 而如果分模块击破也不靠谱，Lyra的核心代码分散在20多个核心的Plugin中，如果要按模块讲解，要就要把UE的插件-模块化的结构先讲清楚，再讲明白Lyra拆分插件的思路和优劣势，这也是耗时且性价比不高的事情。 所以真要按照传统线性的教学方式来的话，挫败感会很强，弃坑率也绝对会非常高。 而我这次的思路，会偏向于先上路再慢慢修正。也就是直接从Lyra运行的核心代码开始，按照顺序，代码执行到哪我们就拆解到哪。这样的好处就是，每一个介绍的代码你都知道它在当前运行时候是有用的。虽然坏处是会将知识点分散到多节课中，但每节课学到的就是项目当前在用的，知识点不浮空。 最后呢，本文还是以大家能看懂为主，在能看懂的基础上再去局部修改和替换小模块来验证自己的理解，以此为迭代才能真正的掌握。比如一个UI界面，当你看懂之后，可以先尝试修改上面的一个文字，然后添加或者修改一个自己导入的图片，或者直接加载一个自己写的UI等等。循序渐进地理解才能真正掌握。 四、环境准备 本次使用UE最新的版本，也就是5.6版本进行。请严格按照下面的步骤配置你的开发环境。 UE 5.6的下载和安装 1. 安装Epic Games启动器。 https://www.unrealengine.com/zh-CN/download 下载完成之后，像普通程序一样，双击安装。由于要额外下载内容，所以需要等待一会儿，也可以通过观看右边官方提供的安装视频进行安装。 UE引擎官方版本的安装只有这一个办法，当然当你有一定经验之后，可以到GitHub上下载引擎源码自己编译生成引擎也是可以的。 如果你有时间和话，也可以阅读官方提供的安装文档进行引擎安装： https://dev.epicgames.com/documentation/zh-cn/unreal-engine/install-unreal-engine 如果你打开网页是英文的话，可以在这里切换语言： 启动器下载完成之后，需要登录才能正常使用。未登录状态下，运行启动器的界面是这样的，需要自行注册Epic账号进行登录，这一步就留给大家自行完成。 当你登录之后，看到的界面是这样的。 其中右边有三个页签，商城、库和虚幻引擎。前两个就跟Steam平台一样，展示的是作为游戏平台的内容，库则是你已经拥有的游戏。友情提示，Epic平台每周可以免费领取1~2款游戏，重大活动时候免费送大型游戏，可以保持关注。 然后我们要看的其实是第三个页签，也就是关于虚幻引擎的部分，点开页签后长这样。 这个页签下会发布很多UE引擎的动态和新闻。顶上的一排子标签的作用分别是： News：热点资讯。 示例：UE官方发布的各种技术演示工程，基本上都是完整可以运行的部分。 Fab：是EPIC多个资产商城的合集，可以下载大量的三方插件、代码和资产之类的。不过它需要在单独的页面打开才能看，启动器里只是一个跳转入口。你甚至可以在这里下载Unity的插件和资产。 库：这个页签是引擎相关的管理页签。在这里可以下载新的引擎版本，和管理已经下载的引擎版本，比如我这里就已经下载好了5.6版本的引擎。 当你有已经安装好的引擎的时候，在右上角就会提示可以直接启动引擎，这是引擎启动的方式之一。当然如果安装了多个版本的话，右上角也可以切换版本。 如果没有引擎或者想要下载引擎，点击左边的“+”号，可以选择要下载的引擎版本。 选好引擎版本之后，点击安装就会弹出安装的一些选项，包括位置和选项。选项这里可以选择额外支持的部署平台，一般我们加上安卓和iOS就可以了。还有一个是调试符号，一般是用来调试引擎的，新手的话可以不管，这个要多增加几十G的空间。 都选好之后，点击安装，然后等待安装完成即可。 若要删除引擎，只要把鼠标移入标签卡里，就会出现卸载按钮，点击即可卸载。 VS的下载和安装 当引擎安装完毕之后，桌面上就会出现图标，同时右上角也会出现启动引擎的按钮。无论点击哪个都会打开引擎的预启动界面。第一次启动的时候，时间会很久，因为它要编译引擎启动所需要的材质，界面上会有进度和数量显示，需要耐心等待。 打开之后，这个界面长这样，主要是跟随引擎一起的模板、示例这些。然后可以管理和新建项目。 比如我要新建一个第三人称的示例项目就可以通过点击右侧Games标签来完成。 到这里就需要注意了，如果你是新手还不懂代码，只想用蓝图的话，现在就已经可以开工了。但是如果蓝图不能满足或者未来不能满足你的要求，就必须切换成 C++的版本。由于C++是代码，就必须要安装对应的编译环境才可以工作。这是UE附加的必要条件，只要你用代码，就必须要安装。一般我们是安装Visual Studio 2022的社区版本。因为它是免费的。 也可以手动去这里下载安装： https://visualstudio.microsoft.com/zh-hans/vs/community/ 另外当你切换任意一个示例到C++版本的时候，如果本地没有代码环境，那么会有一个黄字提示需要安装VS 2022，点击也会自动帮你下载。 安装VS的技巧可以参考官网，一些必要的组件需要勾选： https://dev.epicgames.com/documentation/zh-cn/unreal-engine/setting-up-visual-studio-development-environment-for-cplusplus-projects-in-unreal-engine 一些SDK版本，尽量选最新的。当然选多个也没问题，就是多占点空间而已。 当VS安装完成之后，UE开发必要的组件就已经准备好了。接下来我们就可以运行项目了。 Lyra示例工程的下载和安装 Lyra示例工程需要到Fab上先订阅，再在Epic启动器里下载。 Fab上直接搜索Lyra，找到图示的这个项目，点开之后加入到库里。点击在启动器查看，或者直接在启动器里搜索Lyra。 注意，这个库是引擎标签的库，不是最外层的游戏库。找到之后，点击创建，设置好目录，等待下载完成就可以了。 到这里Lyra的工程也就准备完成了。到设定的安装目录下就可以看到工程信息了。 由于Lyra是一个C++工程，在运行之前，必须要先编译才行。右键uproject文件，执行代码工程的生成，完成之后会有一个.sln的文件出现，代表生成成功。 这个时候双击Lyra，就可以打开Lyra工程了。当然，由于Lyra工程引入了大量新的材质，所以第一次启动也是会编译较长时间，请耐心等待，编译后会自动打开编辑器界面。 到这里，本文的目标就已经达成了。 后面这个点是我个人推荐的开发经验，并且我之后也都是会使用这个软件进行代码更新的。推荐大家继续看完，否则可能会影响后续课程的一些理解。因为不同IDE软件的操作和显示是不一样的。 Rider的下载和安装 Visual Studio是基础，但Rider专为UE深度优化，提供更智能的代码补全（理解UCLASS, UFUNCTION等）、更便捷的蓝图/C++互跳、更强大的反射信息查看、更直观的调试体验，以及更友好的项目结构管理。它能显著提升阅读和理解引擎和Lyra庞大代码库的效率。 访问以下地址下载Rider for Unreal Engine安装包。运行安装程序，按提示完成安装即可： https://www.jetbrains.com/lp/rider-unreal/ Rider是一个付费软件，但个人非商业许可是免费的。第一次启动Rider的时候，会提示你选择许可。关联许可之后就可以正常使用Rider了。 在打开Lyra项目之前，推荐先下载一下这个插件，可以更好地关联UE： 不手动安装其实也行，Rider在检查项目完成之后，会提示你是否安装，他会有两个选择，一个是安装在引擎目录下，一个是安装在项目目录下。建议大家安装在引擎目录下，这样在创建其他项目的时候就不用再次安装了。 前面我们用双击uproject的方式打开编辑器，但作为程序员，你应该用IDE的方式打开编辑器。也很简单，用Rider打开它即可。 打开后，界面长这样，右上角两个按钮一个是正常启动，一个是调试模式。选择任何一个都可以。点击启动之后会先检查代码有没有编译，没有的话会先编译。编译好之后就会直接启动编辑器，效果跟双击uproject打开是一样的，但不一样的是调试模式就可以断点调试相关代码和逻辑。 那么到此，本文的内容就全部结束了。 这是侑虎科技第1946篇文章，感谢作者放牛的星星供稿。欢迎转发分享，未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859） 作者主页：https://www.zhihu.com/people/niuxingxing 再次感谢放牛的星星的分享，如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859）
1 月刊｜日报周报一键获取+AI问答，玩家行为精准分析
GPM 2.0聚焦游戏研发与运营的核心需求，2026年1月全新上线三大功能 —— AI问答、Insights、玩家行为洞察，从玩家操作可视化分析、问题维度一键定位、智能数据解读三个层面，构建更高效的性能/运营优化闭环，助力团队精准匹配玩家体验需求。 GPM AI问答 内置智能顾问，上手零门槛 全新上线的GPM AI问答助手，作为内置专属智能顾问，解决你“操作遇阻、问题难查、分析耗时”的核心痛点。在“AI助手”入口，直接输入问题（如“如何忽略加载阶段卡顿异常”、“分析新版本性能波动原因”），即可快速获取精准解答，覆盖功能操作、报告解读、性能分析等全场景需求。从新手快速上手到资深用户高效分析，实现性能管控效率全面提升。 GPM Insights 三维健康度总览，一键锁定性能问题维度 针对海量报告层层筛查的低效痛点，GPM Insights功能打造“版本-场景-机型”三维性能健康度总览面板，省去人工逐个筛选的繁琐流程，实现问题维度“一键可见”。 面板直观呈现各维度严重问题数量，例如，标注特定版本的高风险异常项、核心场景的性能短板机型，帮助团队快速聚焦优化优先级；同时整合平均FPS、卡顿率、内存占用等核心指标，搭配环比变化趋势，让性能波动与优化效果一目了然。对于崩溃、ANR等P0级异常，还能精准标注关联设备与场景，大幅缩短问题定位周期。从此你的日报和周报将一键获取，省时省力。 玩家行为洞察 助力运营决策的全新视角 新增的「玩家行为洞察」功能，为运营与研发人员打造了直观的玩家行为追踪方式。可直接筛选目标玩家查看其游戏内完整操作轨迹（场景切换、UI交互、任务推进等），并同步关联性能数据标红节点与操作日志，精准分析不同行为场景下的性能表现和玩家行为。 例如，统计玩家进入高负载场景时的卡顿率、内存占用、设备温度等指标，快速定位“玩家行为-性能短板”的对应关系。针对高频异常操作节点，还可通过“协作笔记”添加时间戳注释，方便团队成员快速对齐问题，让优化策略更贴合玩家实际体验。 GPM 2.0始终以游戏研发团队的实际需求为核心，围绕更多实用场景推进功能升级，即将上线的自定义区间、资源加载模块、更全面的崩溃上报信息等功能相信也会得到您的青睐。 立即体验！ 专属通道限时开放 ➡ 预约技术专家1V1部署指导！我们将全程支持服务搭建、数据分析与反馈，确保您能够充分体验GPM 2.0带来的价值。 邮件：sales@uwa4d.com 微信号：17502188376 了解GPM 2.0全功能，可以点击《GPM 2.0 核心功能全景指南｜重塑游戏性能主动管控能力》查看详情。 关于GPM 2.0 UWA GPM 2.0是专为上线及测试阶段游戏项目打造的高性能监测平台。它不仅能深度捕捉宏观性能数据，更创新采用性能无损截帧技术，在不影响玩家体验的前提下，助力开发者全面掌握玩家端运行关键细节，从多维度优化游戏性能，实现从“玩家投诉后救火” 到 “问题发生前预警” 的核心转变。 GPM 2.0全景指南 《UWA GPM 2.0 核心功能全景指南｜重塑游戏性能主动管控能力》 《GPM 2.0 功能更新月报：新增导出表格、崩溃反混淆等实用功能，体验再升级！》 《2 月刊｜GPM 2.0错误日志分析上线，PC端监测能力全维度突破》 《12月刊｜上新！全局筛选+设备标记+序列帧回溯，GPM 2.0性能分析效率拉满》 GPM 2.0新功能 集锦 《GPM 2.0全新功能发布｜GPU精准监测 + 精细化运营，重构游戏性能管控新范式》 《GPM 2.0 SDK 热更新｜不打扰玩家的“隐形升级术”！》 《GPM 2.0功能尝新｜AI识图精准捕获材质丢失，全链路护航游戏品质！》 《UWA GPM 2.0 崩溃上报功能全面升级｜全场景覆盖+免费开放，让崩溃排查不再卡壳》 《大R流失、问题难复现？GPM 2.0「指标 - Session直连」功能，让异常定位效率翻倍！》 《从 “被动救火” 到 “主动掌控”：GPM 2.0 如何重塑游戏性能竞争力？》 《GPM 2.0 告警功能：游戏性能的智能守护者》 《无限接近真相｜GPM 2.0 功能上新》 《崩溃现场全回溯｜GPM 2.0「崩溃上报分析」上线，终结“无法复现”的噩梦》 《GPM 2.0 全新发布「卡顿帧堆栈」功能｜精准高效定位游戏玩家端性能》 《深入解析「卡顿帧堆栈」》 《解锁GPM 2.0「卡顿帧堆栈」｜代码示例与实战分析》 《如何精准打点解决卡牌、SLG、开放大世界、放置类游戏卡顿难题》 《不妥协每一毫秒的性能 不浪费每一字节的流量》 《GPM 2.0 重新定义性能监测与优化｜从未知到已知，从被动到主动》 GPM 2.0案例精讲 集锦 《我们的目标是不耗电、不发烫、不闪退》 《冒险闯关类玩家关注的四大体验》 《玩家端性能数据 一手掌握》 《造成设备功耗高的罪魁祸首》 《海外市场设备适配性优化实战》 《5000人测试背后的秘密》 GPM 2.0实操篇 集锦 《GPM 2.0 按游戏类型定制性能监控策略｜「告警」功能实战指南》 《GPM 2.0 让每一帧的流畅兑换真金白银》 《GPM 2.0 实操之Session篇之如果闪退是因为内存崩了》 《GPM 2.0 应用场景实例之解决让人头疼的问题》 《GPM 2.0 助力设备监测、行为分析与性能优化｜玩家共测新体验》 《GPM 2.0 API 功能助力定制你的性能优化》 《GPM 2.0 驱动优化与投放决策｜硬件性能一目了然》 《GPM 2.0 多维数据与实时截帧完美结合》