UWA问答 | 博客 | 游戏及VR应用性能优化记录分享 | 侑虎科技
UWA博客，UWA旗下官方博客。针对游戏及VR应用性能优化等内容记录、交流，以及案例分享。博客主要分为：厚积薄发、万象更新、博观约取、充电一刻、博物纳新等几个模块。
卡顿帧自己划，Shader看编译，Overdraw自动采：这次把决定权交还给项目
项目快收口时，经常会碰到这种要求：“这一轮标准再收紧一点，原来不看的那批帧也拉出来。” 问题也随之而来：项目已经换线了，工具还守着原来的默认阈值。那批需要继续排查的帧，在报告里看着却“挺正常”，测试同学只能自己再筛一遍。 默认规则当然有用，但真查进项目里，很快就会发现，同一把尺子很难从头用到尾。前期抓大放小，后期标准越压越紧，一些关键战斗、固定玩法和主城场景也可能有自己的性能目标。哪些帧要继续往下查，最终得看项目自己的要求。 卡顿帧自己划：把“刻度尺”交还给项目 这次，GOT Online把卡顿阈值开放出来了。阈值调整后，卡顿曲线、卡顿点和相关统计会按照新的标准重新计算。哪些帧进入本轮重点排查范围，由项目自己决定。 在同一浏览器标签页和同一份数据里继续分析时，自定义阈值和降噪状态会保留；刷新页面或切换其他数据后，再恢复默认。看起来只是改了一条线，但真正做性能分析时，哪批帧会先被看见，本身就会影响后面的排查起点。 非主线程打点：把“暗中吃时间”的嫌疑人拉出来 有些性能问题不会爆出明显尖峰。比如Lua GC、资源加载，或者拆到后台执行的逻辑，可能每帧只稳定占掉几毫秒，但整段流程跑下来，一直在吃CPU预算。 现在可以通过SDK API给关心的代码段打点，单独观察峰值和全流程平均耗时，判断它是偶发抬高，还是长期处在高位。 想盯Lua GC，就给Lua GC打点；怀疑资源加载，就把对应代码段标出来。先把怀疑对象拉出来，比在一堆线程里找重点更直接。 Shader日志直达：打破“只给函数名”的排查黑盒 报告里出现Shader.CreateGPUProgram大尖峰，只能说明这里发生了Shader编译，但还不知道涉及哪些Shader和编译记录。 以前分析到这里，经常得再去找设备日志和工程配置。现在可以直接采集并下载对应的Shader编译日志，目前仅支持Android包。 拿到日志后，可以结合编译记录继续确认当时涉及哪些Shader，为后续检查预热、变体管理和运行时触发路径提供线索。 Overdraw自动采集：不再靠测试同学“拼手速” 以前抓相机Overdraw，需要手动点击Capture Overdraw。碰到快速跑图、战斗或镜头快速变化，想看的画面很容易错过，有时只能重跑流程。现在可以提前配置相机Overdraw自动采集。 测试流程照常跑，系统按配置完成采集，减少对人工点击时机的依赖，也更不容易错过目标画面。 区间框选与有效均值：少进几次Excel 资源管理页面板新增资源总值，方便直接对比版本间的资源规模。有效帧均值只统计存在有效数据的帧，更接近资源实际出现期间的平均水平。 自定义面板支持直接框选时间区间，函数、资源和相关统计会按照选中区间重新计算。 以前可能要导CSV、进Excel、筛区间，现在很多时候框一下就能直接看结果。 让工具跟上项目自己的排查思路 真实的性能优化，本来就不是照着固定路线往下走。项目阶段变了、关注对象变了，手里的“尺子”也得跟着变。 卡顿帧自己划，线程自己标，Shader顺着日志追，Overdraw交给系统采，区间框到哪里，数据就算到哪里。工具少卡一下，排查思路就能更顺地接下去。 最后说两句： 我们深知，在项目期引入或尝试一个新工具是有决策成本的。 工具的价值，不仅在于它能算得多快，更在于它能不能真的融入团队的日常流转中，帮大家准时下班。 但如果它能每天帮你省下半小时的查Bug时间，这笔账就绝对划算。为了让大家能毫无负担地验证这波“AI+性能优化”的疗效，我们为大家准备了专属福利： 关于UWA UWA是一家创业十年的高新技术企业，作为游戏行业的深耕者，UWA始终专注于为使用Unity、Unreal引擎的开发者提供丰富的优化产品，帮助开发者高效解决开发问题、定位性能瓶颈、提供解决方案，已支持超过一万款游戏项目。还打造了技术博客、问答、学堂等社区产品，为开发者提供便利和高效的支持。线上培训和线下教育的新业务，满足行业对人才培育的需求。
1）预留Heap为何还会扩容 2）闲置Camera为何还有Culling 这是第491篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：小游戏Total Heap预留近500MB，为什么还会继续扩容？ A：Total Heap只是预留空间，真正决定是否扩容的，是Dynamic Heap的实际使用量。 从UWA GOT Online报告中可以看到，Total Heap初始约为496MB，运行后很快触及上限，随后扩到600多MB，后面又进一步扩到800多MB。说明并不是预留没有生效，而是Dynamic Heap已经超过了最初的预留范围。 这里还要特别关注扩容瞬间的内存压力。小游戏Heap扩容过程中，原有内存可能会先被复制，短时间同时存在新旧两部分内存。例如原本已经接近500MB，扩容阶段的瞬时占用可能接近1GB，对内存限制更严格的iOS设备风险更高。报告后期Dynamic Heap已经达到700多MB，继续拆分后大致包括： Native Heap：400多MB Mono：约150MB Lua或其他第三方插件相关内存：100多MB 所以这里不能只靠提高Total Heap初始值解决。一方面要根据完整运行周期的实际水位设置合理的预留空间，另一方面还要继续降低Dynamic Heap本身的占用。 Native Heap可以继续结合引擎及资源内存排查；Mono重点关注是否持续上涨；第三方插件则可以通过开关插件做对照测试，确认具体的内存贡献。同时，Total Heap也不是设得越大越好。初始值过高，同样会增加低端设备启动阶段的内存压力。 实战案例 Q：Camera没有实际参与画面渲染，为什么Culling耗时还在？ A：只要Camera仍处于启用状态，即使当前画面并不需要它，Culling等渲染准备工作仍可能继续产生CPU开销。 UWA GOT Online报告中Camera.Render的调用次数持续在10次左右。检查发现，场景虽然存在多个Camera，但部分当前不需要使用的Camera并没有Disable；与此同时，Culling在渲染模块中的耗时占比约为40%。 Culling需要根据Camera视角筛选后续可能参与渲染的对象，所以画面里没有看到这个Camera的内容，不等于这个Camera已经没有开销。角色预览、礼包展示、3D UI以及RenderTexture等功能都可能创建额外Camera。如果界面关闭后只是隐藏内容，却没有同步关闭Camera，这部分开销仍可能保留下来。 小游戏报告中的情况更加明显，部分区间Camera数量甚至达到40个左右，对应区间的渲染耗时也有明显上升。 排查时重点做三件事： 对比Camera.Render调用次数和当前画面真正需要的Camera数量； 检查UI、角色预览、RenderTexture等临时Camera是否在使用结束后及时Disable； 对必须保留的Camera，再检查Culling Mask是否包含了不需要处理的Layer。 处理后重新观察Camera.Render次数和Culling耗时是否下降，就能判断额外Camera是否确实贡献了这部分开销。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
GPM 2.0 WebGL 监测能力扩容｜覆盖 Unity/Cocos/LayaAir 多引擎微信 & 抖音小游戏
小游戏赛道蓬勃发展，除Unity之外，大量商业小游戏项目基于Cocos、LayaAir进行开发。 WebGL环境受浏览器虚拟机、单线程内存模型约束，线上版本普遍会遇到：偶发卡顿、内存持续上涨、GC抖动、低端机型闪退，大量问题线下模拟器难以复现，只能依靠玩家反馈被动救火。 GPM 2.0此前已经为Unity引擎开发的小游戏/WebGL上线了完整的专属线上监测体系，打通小游戏线上性能黑盒。本次迭代，扩展了SDK引擎适配范围，将这套线上性能监测能力，完整开放给Cocos、LayaAir引擎项目。至此GPM 2.0 WebGL监测可同时覆盖Unity、Cocos、LayaAir三大主流引擎的微信、抖音小游戏/WebGL项目，让更多小游戏团队可以享用同一套线上真实玩家性能分析能力。 ▘▖小游戏共性痛点：本地测试表现良好，线上却频出问题 无论使用Unity、Cocos还是LayaAir开发WebGL小游戏，都会面临共通的技术困境： 1.本地环境不等于真实玩家手机环境 编辑器、PC浏览器测试流畅，但移动端浏览器内核、内存限制、iOS非JIT机制会放大各类性能缺陷，很多问题只在线上真实设备触发。 2.内存问题隐蔽难复现 Cocos节点销毁不彻底、LayaAir资源缓存残留、JS对象频繁创建，会带来渐进式内存上涨。不会直接抛出崩溃报错，随着游戏时长累积逐步卡顿、被系统杀进程，线下很难复现完整长会话。 3.卡顿根因难以区分 脚本逻辑阻塞、GC抖动、渲染过载、资源加载混杂在一起，玩家只反馈“玩起来卡”，缺少数据定位到底哪一部分导致问题。 4.偶发BUG复现成本极高 部分卡顿、闪退仅出现在特定机型、特定操作序列，QA很难复现，问题长期悬置，持续带来玩家流失。 5.过去Cocos、LayaAir小游戏缺少专业线上监测工具，大多依靠打印日志与客服工单，数据碎片化，很难做版本回归、机型分层分析。 现在，GPM 2.0的整套WebGL线上分析能力，平台分析指标、回放逻辑、告警逻辑等功能扩展支持Cocos、LayaAir引擎项目。 ▘▖GPM 2.0 WebGL监测能力总览 整套能力为WebGL浏览器环境定制，不是通用性能工具简单兼容；SDK采集开销可控，画面截帧功能默认关闭，满足微信、抖音小游戏平台审核上线规范。 1.全维度线上性能时序指标采集 持续采集真实玩家会话FPS、帧耗时、帧率抖动、网络延迟、加载耗时，生成时序趋势曲线。 可以捕捉本地测试容易忽略的瞬时性能尖峰、间歇帧率抖动；支持按版本、渠道、机型、游戏场景聚合筛选，看清不同群体玩家真实线上体验，不被开发机的良好表现误导。 包含性能数据&截帧的异常对话报告 2.内存趋势追踪，定位内存泄漏问题 在运行环境支持的情况下，Unity、Cocos和LayaAir的WebGL项目均可采集WASM Heap、JS Heap以及二者组合得到的近似WebGL Heap指标。其中，JS Heap是否可用取决于浏览器或小游戏运行环境是否开放相关能力。 通过内存时序曲线，研发可以直观观察进入哪类场景、执行哪一类交互之后内存持续走高，快速定位资源残留、对象频繁创建等泄漏根源，排查渐进式闪退问题。 小游戏项目内存数据及趋势图 3.卡顿帧堆栈溯源+版本性能基线对比 发生帧率暴跌或卡顿事件时，捕获对应调用堆栈信息，定位耗时代码逻辑、循环执行或资源加载等问题。 不用反复复现玩家故障，优化需求直接落地对应业务模块。同时支持新旧版本、活动前后横向基线对比，识别迭代引入的帧率下滑、内存上涨等性能劣化，明确调优优先级。 单个玩家一次对话中发生的卡顿帧堆栈信息 玩家全局卡顿汇总批量分析 新旧版本性能数据对比 4.玩家故障现场回溯，解决线上难以复现难题 a.异常瞬间自动截帧：卡顿、帧率异常发生时留存游戏画面，直观看到故障时刻场景、UI、特效状态，不再只依赖玩家文字描述。 ⚠️ 提示：小游戏平台会受性能开销约束，截帧功能默认关闭，项目需要结合自身性能实测手动开启。 b.全链路玩家操作轨迹回放：记录玩家点击、滑动、场景跳转等交互行为，与性能时序曲线联动，复现完整会话，闭环偶发线上客诉。 在线玩家数据实时看板 玩家行为&性能数据&自定义事件一览 c.多维度定向筛选：支持渠道、机型档次、游戏场景、付费玩家分组过滤，可单独锁定低配机型、特定渠道独有的性能缺陷，针对性完成适配与资源轻量化。 5.覆盖小游戏完整研发运营生命周期 能力贯穿项目：测试验收 → 版本上线 → 活动迭代 → 长线运营。 ✔ 事前测试验收：批量收集多机型、浏览器环境真实性能，提前识别负载超标、内存风险，上线前完成优化。 ✔ 事中版本上线监控：全渠道感知性能波动，识别不同渠道、机型玩家体验差异。 ✔ 活动迭代保障：监控新副本、新特效上线瞬时负载，定位活动专属性能BUG。 ✔ 事后复盘沉淀：永久留存性能、故障会话回放，复盘疑难线上问题，支撑持续迭代。 以上整套能力，现在Unity、Cocos、LayaAir三类引擎开发的微信、抖音小游戏/WebGL项目均可接入使用。 ▘▖接入与约束说明 1.支持平台：微信小游戏、抖音小游戏、WebGL应用； 2.截帧功能默认关闭，项目评估性能开销后手动开启，符合小游戏平台审核要求； 3.部署选项：支持GPM SaaS、私有部署两套模式。 ▘▖适合接入的项目 ✔ 基于Cocos/LayaAir开发的微信、抖音商业小游戏，希望拿到真实玩家线上性能数据； ✔ Unity WebGL小游戏项目，需要完整WebGL线上监测能力； ✔ 线上频繁收到卡顿、闪退反馈，线下难以稳定复现； ✔ 需要做版本性能回归、机型分层评估、大版本/活动上线性能护航； ✔ 希望从小问题发生后被动救火，转向提前感知线上性能风险。 三大主流引擎小游戏现在可以使用同一套GPM 2.0平台，完成内存泄漏定位、卡顿堆栈溯源、玩家故障会话回溯、版本基线对比，帮助更多小游戏团队以数据驱动性能优化，降低卡顿带来的玩家流失。 如需体验WebGL小游戏专属性能监测能力，可前往官网查阅完整接入文档： 官网地址：https://gpm.uwa4d.com 技术咨询：gpm-support@uwa4d.com GPM 2.0 可以提供私有云和GPM SaaS平台的两套部署方式供选择。 关于GPM 2.0 UWA GPM 2.0是专为上线及测试阶段游戏项目打造的高性能监测平台。它不仅能深度捕捉宏观性能数据，更创新采用性能无损截帧技术，在不影响玩家体验的前提下，助力开发者全面掌握玩家端运行关键细节，从多维度优化游戏性能，实现从“玩家投诉后救火” 到 “问题发生前预警” 的核心转变。 GPM 2.0全景指南 《UWA GPM 2.0 核心功能全景指南｜重塑游戏性能主动管控能力》 《5 月刊｜GPM 2.0实现全场景可视化溯源、多维度数据解析与根因精准定位》 《4 月刊｜GPM 2.0核心玩家守护功能上线》 《3 月刊｜GPM 2.0性能数据探索 & 单玩家追踪升级，精细化性能分析再突破》 《2 月刊｜GPM 2.0错误日志分析上线，PC端监测能力全维度突破》 《1 月刊｜日报周报一键获取+AI问答，玩家行为精准分析》 《12月刊｜上新！全局筛选+设备标记+序列帧回溯，GPM 2.0性能分析效率拉满》 《GPM 2.0 功能更新月报：新增导出表格、崩溃反混淆等实用功能，体验再升级！》 GPM 2.0新功能 集锦 《卡顿和发热，未必是性能不够，也可能是分档配错了画质》 《大R玩家从不抱怨，他们只是消失了》 《想优化哪段就量哪段｜GPM自定义区间精准观测任意游戏流程》 《我以前只能告诉你“哪个函数卡”，现在我能带你去看“玩家在哪卡”》 《大家好，我是UWA GPM「小Q」，今天是我加入项目的第一天》 《GPM 2.0 全新上线WebGL专属监测，全面适配微信、抖音小游戏》 《无需自建机房运维｜UWA GPM 2.0 SaaS正式上线，让游戏线上质量监控轻量化落地》 《数字助理已到岗！GPM AI 智能体发布，游戏性能分析进入全自动时代》 《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 重新定义性能监测与优化｜从未知到已知，从被动到主动》 《UWA GPM 2.0 重新定义性能监测与优化｜如何高效预警和定位玩家端的性能问题》 GPM 2.0案例精讲 集锦 《携手护航亿级体验｜《迷你世界》× UWA 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）小游戏RT内存为何偏高 2）Physics耗时为何居高不下 这是第490篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：同一套项目资源，APP侧RenderTexture占用只有30MB左右，但小游戏侧却达到130MB左右，为什么会出现这么大的差异？ A：排查后定位到两个明显的配置问题：小游戏侧3D渲染分辨率没有按预期缩小，同时Depth Buffer仍然使用32位格式。这两项都会增加RenderTexture相关的内存压力。 小游戏和APP端虽然运行的是同一套项目，但不同平台实际创建出来的RenderTexture配置可能并不一致。尤其是渲染分辨率、Depth格式以及MSAA配置，一旦平台侧实际生效情况不同，最终内存占用就会产生明显差异。排查时可以重点关注： 3D渲染分辨率没有正确缩小。 小游戏侧实际渲染分辨率较大，说明原有的降分辨率策略没有按预期生效。RenderTexture内存和宽高直接相关，如果3D场景仍然按照设备较高的原始分辨率渲染，Color和Depth相关RT都会随之增大。 APP端原本可以通过分辨率设置控制渲染尺寸，但到了小游戏环境，需要重新检查对应接口是否生效。对于URP项目，可以通过renderScale控制3D场景实际渲染分辨率，最终再放大到屏幕，而UI仍保持全分辨率显示。 Depth Buffer仍然使用32位格式。 小游戏侧当前使用的是32位Depth，而移动端原有配置使用24位。需要检查平台相关设置，将小游戏/WebGL纳入对应的Depth格式配置，减少这部分额外的内存占用。 确认MSAA配置。 当前RenderTexture配置中可以看到MSAA为2。MSAA会增加Color Buffer和Depth Buffer的存储开销，尤其是在RT分辨率较高时，需要保存更多采样数据，会进一步放大内存占用。排查RenderTexture内存差异时，需要确认小游戏侧最终生效的MSAA等级，避免平台配置差异导致额外的RT开销。 实战案例 Q：项目已经针对物理碰撞制作了简化模型，但低端机上Physics耗时仍然达到二三十毫秒。碰撞模型已经简化，为什么物理压力还是很高？ A：碰撞模型复杂度只是影响Physics耗时的一部分。如果Collider已经进行了简化，但Physics耗时仍然较高，还需要继续关注场景中同时参与模拟的动态对象数量，以及这些对象之间产生的碰撞关系。 从GOT Online报告中数据变化来看，随着场景中的物件减少，Physics耗时也同步下降，同时Overlaps数量明显降低。当前场景中的Overlaps已经达到几千级，说明物理系统需要维护大量潜在碰撞关系。排查时可以重点关注以下几个方向： 确认Physics耗时是否和碰撞关系规模相关。 可以减少部分动态物体参与碰撞，再观察Overlaps数量和Physics耗时是否同步下降。如果两者变化趋势一致，说明主要压力可能来自大量碰撞关系，而不是单个Collider复杂度。 检查动态Rigidbody和Mesh Collider组合。 场景中存在大量带Rigidbody的动态对象，其中部分使用Mesh Collider。对于动态非Kinematic Rigidbody，Mesh Collider需要使用Convex。形状允许的情况下，可以继续对比Box、Sphere、Capsule或者Compound Collider的实际效果。不过碰撞体替换最终仍需要结合项目实际测试结果，不能简单认为基础Collider一定更优。 减少无效碰撞检测。 如果部分对象之间没有实际碰撞需求，可以通过Layer Collision Matrix关闭对应Layer之间的Collision，减少不必要的碰撞组合。调整后可以继续观察Overlaps数量和Physics耗时变化。 检查物理模拟频率。 Unity默认按照Fixed Timestep执行物理模拟。当低端设备出现掉帧时，一个渲染帧内可能需要补做多次物理模拟，从而进一步增加Physics耗时。可以尝试调整Fixed Timestep，例如从0.02调整到0.03，对比Physics耗时以及实际物理表现。 Unity 2022以后，也可以测试不同Physics Simulation Mode带来的影响，例如切换到Update驱动，观察减少单帧多次物理模拟后能带来多少收益。不过这种方式会受到实际帧率波动影响，需要同时确认碰撞稳定性和运动表现是否满足需求。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
卡顿和发热，未必是性能不够，也可能是分档配错了画质
今天小Q聊聊GPM的「设备体验雷达」。把“设备分档”这件老大难，从拍脑袋变成看得见。 读完本文你可以：看懂你的项目真实设备画像、定位画质‑设备错配问题、科学调校设备分档、开启小Q自动巡检。 小Q先从一个容易被忽略的地方说起：在“设备”这件事上，平均帧率是个和平主义者 —— 它把所有人的痛苦平摊，然后告诉你大家都还行。 你的大盘均帧看着挺体面。可那是旗舰机、中端机和一批老机器加权平均出来的。那批在二十几帧上挣扎的玩家不会出现在这个数字里，他们只会出现在退款和差评里。 但这篇文章真正想说的，不是“有人卡”。是他们里面有相当一部分，本来可以不卡。 一件每个团队都在做、但基本靠猜的事 你的游戏里一定有一套画质分档 —— 低、中、高，或者更细：开不开后处理、要不要动态阴影、渲染分辨率给多少、锁不锁帧。这套档位怎么发到玩家手上？大多数团队的答案是：一份机型白名单，加一套按GPU型号或公开跑分写死的规则，可能还是两年前定的，改一次要排期。 这套规则一旦和真实玩家对不上，后果不是某张报表不好看，是具体的人在具体的机器上卡顿、发烫、掉电、闪退：老机器被判高了，默认给了它扛不住的画质，于是它烫、它掉帧、它闪退；好机器被判低了，玩家花钱买的性能，你没让他用上。 这是设备这件事上最贵的一类问题，也是最容易漏掉的一类 —— 因为它不报错。崩溃有堆栈，Error有日志，画质配错了不抛任何异常，只留下一条慢慢往下走的留存曲线。等你从工单里看出苗头，往往已经过了两个版本。 过去做设备适配，我们往往缺少三样关键信息： 不知道自己项目玩家真实的硬件构成，不是行业公开市场份额，而是属于这款产品的真实设备分布； 无法验证自研分档规则，在自家游戏下，各档位实际体验是否拉开合理差距； 看不清画质档位下发之后，每一档设备真实运行的画质，以及错配机型的实际体验。 这三样，过去只能靠抽样机测、靠客服工单、靠论坛里那句“我这手机烫得能煎蛋”。 GPM的「设备体验雷达」就是来补这三样的。它不需要你埋点，也不需要额外配置 —— 你现在的上报数据直接就能看。 下面我按你实际会撞上问题的顺序讲，不是按菜单顺序 ，文中截图都取自产品内置的演示数据、不是真实上报。 平均帧率是个和平主义者：它把所有人的痛苦平摊，然后告诉你大家都还行。 第一件事：你的玩家，实际在用什么设备？ 你大概能说出行业里最热门的十款机型。但那和你的玩家是两回事 —— 一个二次元游戏和一个SLG，玩家手里的机器可以完全不是一批。 雷达的第一页叫「设备结构与风险概览」，它上半屏就是「设备结构概览」。这一屏先不给你任何结论，只给你一张户口本：多少台设备、多少次会话，你的分档规则把这些人切成了什么样，以及机型、SoC、GPU三张热度榜。如果你在项目里配了自定义分档，它会把系统默认分档和你自己那套并排摆着 —— 同一批玩家，两把尺子量出来什么样，一眼可比。 设备结构概览 这里你只需要盯住一个格子：Unknown。它装的是GPM目前认不出来的设备。 为什么会有认不出来的呢？主要是三种： 太新：刚上市的机器，设备库还没来得及收录； 太偏：确实小众的硬件，Android学习机、冷门平板，甚至Android手表，这类东西本来就不在主流收录范围里； 假的：模拟器改过的硬件信息，报上来的那颗“GPU”很可能压根不存在。 这三种情况该做的事完全不同：第一种等收录就行，第二种你得决定要不要为它花适配成本，第三种你根本不该把它算进玩家设备分布里。所以我们既不猜，也不硬塞进某一档，而是如实标成Unknown，再给你一个能查清楚的地方 —— 「设备信息订正」页会逐台列出这些到底是什么设备、属于上面哪一类，能补的你当场补，补完全站的分档口径跟着一起对。 为什么值得你专门看这一格：很多人瞄一眼占比不高就跳过了，但同一页往下滚到「体验差异与风险对比」你会发现，这批“没归类”的机器，实测帧率往往比你最低那一档还低。它们不是无关紧要的一小撮，只是没进到任何一档里，所以在任何一档的均值里你都看不见它们。这个数字一高，后面所有分档结论都要打折 —— 发红了请先去订正，别急着看诊断。 分不清的设备，分不出的结论。 除此之外，这一屏只是个起点：它告诉你人是怎么分布的，但分布本身说明不了你分得对不对、配得对不对。这两个答案，一个就在同一页往下滚的「体验差异与风险对比」里，一个在「分档诊断」页。我先讲更要紧的那个。 第二件事，也是最要紧的一件：你配的画质，配到对的人手上了吗 这是整套雷达里我最想让你先打开的一块 —— 「分档诊断」页上的「设备分档 × 画质匹配」。它最直接对应开头那个问题：为什么玩家会卡、会烫，以及那批本来不该卡的人在哪儿。 它做的事说穿了很简单：把每台设备属于哪一档和这台设备实际跑着哪个画质这两件事叉在一起看，然后回答三个问题 —— 配对了多少、配高了多少、配低了多少。 注意是两个方向。大多数团队聊画质匹配，只聊“别让老机器跑高画质”。但反过来那一半同样是钱：一批性能有余量的机器在跑低画质，玩家的手机能给的体验，你没给他。前者伤留存，后者伤口碑，而你在任何一张常规报表上都看不出这两件事。 设备档 x 画质 交叉匹配矩阵 页面给你一个匹配度评分、两个方向的错配占比，再往下是一张设备档 × 画质的交叉矩阵：每一格给“这一档里有多少会话在跑这个画质”和“跑这个画质时的实测帧率”。这张表是这一节的核心，因为它把链条摆到你眼前了 —— 在低端那一行，画质每往上一档，实测帧率就掉一截；在高端那一行正相反，跑低画质的那批帧率反而最高，说明性能是真有富余。 矩阵下面是结论和处方，两个方向各一条，都带典型机型：低端那条建议你给低/中端档设更保守的默认画质，或者干脆限制可选上限；高端那条建议你给高端档提供或默认更高的画质位，别让性能白放着。每条后面挂着“查看全部N台建议”，点进去是可以直接交给客户端同学的名单。 这一块里有三个细节，我觉得比数字本身更能说明这套东西的性格： 第一，它主动说破了自己的口径陷阱。 上面三个比例看的是“占全部会话”，下面表格每行看的是“各档自己内部”的构成 —— 两处的100%不是同一批会话，不能对照着算。这种话通常要等客户发现了才补，它写在了矩阵正上方最显眼的位置。 第二，它把脏数据挑出来单独交代。 演示数据里混进了两个明显不可信的画质上报值（一个负的九位数，一个整数溢出后的天文数字）。它没有默默扔掉，也没有算进去污染结论，而是明说：排除了多少会话、为什么排（“通常来自客户端未初始化字段、哨兵值或整数溢出”）、建议你回去查上报链路、以及判定上限在哪个设置里改。这是在帮你修一个Bug，连你自己都还不知道的。 第三，它不满足于“你有错配”，还告诉你错配堆在哪儿。 可以按版本、按场景两个维度下钻，也可以在“按错配量”和“按错配率”之间切 —— 而这两个口径经常指向不同的版本：量最大的那个版本人多，率最高的那个版本人少但密度高。页面把两个都摆出来，还补了一句“量不大也建议优先治理”，没有替你二选一，但把选择的依据给全了。 “你有两成会话画质错配”是个焦虑；“哪个版本贡献了多少、哪个版本最不健康”是个任务。 第三件事：画质对上了，那分档本身站得住吗？ 画质是“配错了人”，分档是“人分错了组”。前者好修，后者是根。 答案就在刚才那张户口本下面 —— 「体验差异与风险对比」和设备结构概览同在第一页，往下滚一屏就是。它按档位摆开核心指标，默认给你四列 —— 帧率、卡顿、内存、CPU温度，另有十余项可选指标自己勾。先看档间：三档的曲线拉不拉得开？如果高中低三档的实测体验挤在一起，说明你这套分档规则在你的项目里其实没分出东西来；如果出现“高档反而比低档差”，那就是体验倒挂，你的分档规则和真实表现是反的。 体验差异与风险对比 但真正给结论的是最右边那列风险标签。它把每一档的问题写成短句直接挂出来，而且分成性质完全不同的两类： 一类是“这一档相对全局差多少”：低端档卡顿高于全局、帧率低于全局之类。这类标签说的是“这一档确实弱”，多数时候你早就知道。 另一类是“这一档内部，机型之间差多少”：标签上直接写着「档内离散过大」（同一个档位里面机型实际性能差距巨大，不该放到同一档），后面跟着是哪个指标、变异系数多大、牵扯几台机型。这一类才是在告诉你：这个分档本身有问题。 第二类是这一屏最值钱的东西，也最容易被跳过 —— 因为它不在均值列里，它藏在均值下面那行小字：P10和P90。P10代表最差10%玩家，P90代表最优10%玩家，避开被平均值欺骗。 举个这份演示数据里的例子。高端档均值57帧，低端档41帧，差了16帧，听起来就是“一档人比另一档惨”而已。可把区间摊开：高端档里最好和最差的机型差4帧，低端档里差了33帧。低端档里跑58帧的那批机器，过得比高端档的平均水平还好。 这一档根本不该是一档。 均值骗人就骗在这儿：一档里三款机型都跑45帧，和一档里一款58帧、一款25帧平均下来也是45帧，在均值列上长得几乎一样，对你却是完全不同的两件事 —— 前者是“这一档就这个水平，认了”，后者是“你把两拨过着不同日子的人塞进了同一个盒子，然后给他们发了同一套画质”。 而这还只是帧率。同一档的卡顿区间摊开来更夸张：有人几乎不卡，有人卡到根本没法玩，而他们被分在同一档、拿着同一套画质。所以这一档被挂上了「档内离散过大」的标签 —— 不是某台机器坏了，是这一档的边界画错了。 这就是我说“某个指标离散过大，说明这个分档确实有问题”的意思。它和“这一档比较弱”是两回事：弱只是事实，你可以接受；不齐是缺陷，说明你的分档规则没能把体验相近的人放到一起，而分档的全部意义就是把体验相近的人放到一起。 所以离散标签后面直接跟了个「定位机型并给建议」的入口，档位名旁边也挂着红字提醒。你不用自己横着比四列数字才发现这一档有问题 —— 有问题的档会自己举手。 第四件事：那我到底该动哪里？ 前面几块都在回答“哪儿不对”。而「分档诊断」页 —— 刚才那张画质匹配矩阵也在这一页上 —— 还负责回答“那该怎么办”，而且是按你做起来有多贵来回答。 进页第一屏是待办行动清单：把全页命中的问题按严重度排好队，每条后面挂一个可点的去处。每条问题给的不是形容词，是下一个页面。 最该做的几件事 再往下是分档健康度 —— 一个分数、一句判定、有效分档数和未识别占比。分数不是玄学：旁边写着覆盖了几项规则，每一项扣了多少分都能展开看，严重问题扣多少、关注级扣多少、多少分算健康，全部明写。 分档合理性诊断 然后是决策树，它分两节问两个不同的问题。 1.结构检查：档位的“人数与梯度”合理吗？ 查的是分档骨架 —— 未识别是不是太多、有没有某一档挤了绝大多数人、有没有空档形同虚设、相邻档拉不拉得开、有没有体验倒挂。这份演示数据的结论是一枚绿色的「通过」，外加一句“3档梯度清晰，无空档/倒挂/过度集中”。它没有为了显得勤奋而挤出几条“建议关注”，而且把“凭什么算通过”的依据一起给了你。 2.一致性检查：同一档里，大家过的是同一种日子吗？ 这一节命中了刚才那个撕裂的低端档。而它给出的不是一句“该档离散过大，请处理”，是三步递进 —— 我认为这是整个雷达里设计得最讲道理的地方： 第1步，先问画质是不是统一。 如果这一档里的人跑着五花八门的画质，那“机器不一样”的结论根本还没轮到你下。页面直接给出这一档的主导画质占比和偏离比例，并挂上“逐机型画质建议”的入口。 机型画质建议 第2步，再问差异是不是按GPU分群。 画质统一了还散，那就看是不是同一档里混进了性能不在一个水平的GPU —— 点击「分析档内GPU分簇」。 差异是否按GPU分群 第3步，最后才逐台定位机型。 前两步都解释不了的特殊机型，才轮到逐台归因，而且给的是散点图作为离群判定的证据。 机型实测分布与离群归因 注意这个顺序：它把“换机型的锅”放在最后。 一个诊断工具最容易讨好你的方式，就是一上来甩给你一张“问题机型清单” —— 看起来具体、可执行、有活干。可如果这批机器只是画质给高了，你按机型清单挨个折腾，忙完一圈离散还在那儿。先排掉便宜的解释，再动贵的手术，这个顺序本身就是结论的一部分。 最后是全页结论汇总，它把上面所有区块的结论合成一份清单，按处置代价从重到轻排。 先是要改分档边界的（一次动全项目），再是只能整档处理的，最后才是逐台能调的。这个排序本身就是态度 —— 代价最大的动作排在最前面让你先决策，而不是把最容易点的按钮放在最上面。 结论汇总 第五件事：你的玩家手里，真实的天梯长什么样？ “这台机器到底算什么水平” —— 这个问题你以前只能去查跑分网站。可跑分是在标准测试场景下跑出来的，它测不出你的游戏。同一台手机在两个游戏里的排位可以完全不同，取决于你的场景负载、渲染管线、系统版本，甚至机身温度。 「设备性能天梯」给的是你项目里真实上报过的机型，按参考性能排成一列，每台带默认档、自定义档、实测帧率、活跃占比、卡顿和崩溃率，风险机型直接挂红标。 页面顶部第一句话是它的免责声明，我原文抄给你：“参考天梯：按默认分档与实测FPS排列，用于理解分档、快速定位低端与高风险机型，非绝对排名。” 这句话不是谦虚，是准确。任何声称给出手机绝对性能排名的东西，都在替你做一个它做不了的判断。所以这一页同时摆着“参考序列”和“你项目的实测”，让你自己看两者对不对得上 —— 而它们经常对不上，这恰恰是这一页最有用的时候：参考序列把未识别机型排在最前，实测却告诉你真正最惨的在后面一点。你要是只信其中一栏，两次都会挑错人。 样本不够的行一律标出来，也可以一键隐藏。它宁可让列表看起来空一点，也不让你拿几次会话的数据去给一款机型定性。 真实玩家设备天梯图 任何声称给出手机绝对性能排名的东西，都在替你做一个它做不了的判断。 想改分档，可以。但先让你看见改了会怎样？ 看完诊断，你大概已经想动手了。「机型分档规则」就是给你动手的地方。 默认分档那一栏是只读的，展示系统按GPU性能等级切出的低/中/高三档和各档示例机型，你可以在“标准示例”和“本项目实测”之间切，看这套通用规则落到你项目里是什么样。 自定义分档才是你的操场：性能等级这条轴上，边界随你拖，档数随你加减，每一档还能自己起名 —— 起了名之后全站（概览、风险对比、诊断、天梯、趋势、影响预览）都跟着叫这个名字。 有件事我要说三遍，因为它最容易被忽略：分档改动只对新上传的数据生效，历史会话不重算。你今天改完边界，明天的分布图不会立刻变成新的样子，得等新数据攒起来。这不是Bug —— 档位是在每条会话入库那一刻就写死进去的。页面在顶部、在保存前的预览里、在标定页的横幅上，三处都写了这句话。 而在你按下保存之前，它会先给你看一份影响预览：这一改，多少机型会换档、各档人数怎么变。分档是个爆炸半径极大的操作，一改，全项目所有跟分档相关的口径都跟着变。看不到影响面就能保存的按钮，不该存在。 标定订正影响预览 数据不对的时候，先修数据 前面所有分析都有一个前提：这台设备被认出来了。 认不出来的，它会明说 —— 就是第一页那个Unknown格子，还附带告诉你为什么。所以雷达里有两页专门用来修数据。它们不产出分析，但没有它们，分析不成立。 设备信息订正 「设备信息订正」修的是“这是什么机器”：机型名、SoC、GPU、价格、上市季度。它的生效方式很关键 —— 查询时生效、含历史，且会同步影响价格档和上市季度筛选，但不改动原始数据。你今天补录，昨天的报表马上就对；也不会出现“列表里显示3000块，按价格筛却筛不到它”这种割裂。它明确不改性能档，改档是另一页的事。 这一页我最想让你看的是「未识别原因」那一列 —— 那个Unknown格子，到这儿被逐台拆开了。它不笼统写一句“未识别”，而是标明每一台属于哪一类，对应的动作也完全不同： GPU平台库还没收录：真正的待办 —— 等收录，或者你等不及，先在本地补上； 客户端压根没上报GPU：这不怪设备，回去查上报链路； 这压根是台模拟器：别让它进你的玩家设备分布。 剩下两类根本不用你动：GPU其实早就标定过了（只是这条历史数据入库当时还没有）和你自己已经补过档了（待办早就闭环） —— 新数据会自己归位，它标出来只是让你别白跑一趟。 所以它给你的不是一个待办数量，是几类性质完全不同的事 —— 其中还有几类压根不用办。 标定订正管理 「标定订正管理」修的是“这台机器有多强”，也就是决定档位的那个性能档。这页是所有订正资产的集中管理处，也是唯一的删除与回退入口，分三类：机型描述订正、GPU性能订正（按芯片，影响所有上报这颗GPU的机型）、机型性能订正（只订正单台，优先级高于GPU级）。 每条订正后面都跟着依据、生效状态和是否同步到平台设备库。最后这一列有条界线写得非常直白：机型级订正只在本项目内生效，不会同步给平台设备库 —— 页面还专门解释了一句“这是产品设定，不是权限不足，也不用等审批”。只有GPU级订正才能申请收录进平台库，因为那是要影响所有客户的事。 页面顶部还有一条橙色横幅交代连带效应：任何性能订正都会同时改变机型在低/中/高档里的位置，各档机型构成一变，指标均值也会在订正当天出现跳变。它提前告诉你“你的曲线明天会跳一下，那是你自己改的，不是数据坏了”。 而当你真的点开“设定性能档”，弹出来的是这套系统里最谨慎的一屏：先问你订正粒度（按芯片还是仅这一台），把影响范围直接写在旁边；然后摆出定档依据 —— 实测帧率、会话数、活跃占比，样本够不够你自己看得见；底部三条口径明写“仅对新数据生效”、“低/中/高档由性能档推导不可直接指定”，以及一句我很想单独拎出来的话： “订正是例外不是主体，受活跃用户占比5%的覆盖面预算约束。” 一个允许你改数据的功能，在它自己的弹窗里劝你少改，还给你设了个预算上限。因为标定订正一旦泛滥，你看到的就不再是你玩家的真实设备分布，而是你自己想象中的分布。 最后那个按钮不叫“保存”，叫「下一步：影响预览」 —— 你看不到这次订正的影响面，就点不到保存。 一个允许你改数据的功能，在自己的弹窗里劝你少改。 改完到底有没有用？我给你留了标记线 改完分档、调完画质，最难受的是接下来那几天：曲线动了一下，你不知道是你改的，还是新版本带的，还是这周正好来了一批新玩家。 事前有影响预览，事后有调整效果趋势。诊断页顶部四张按自然日画的卡：分档健康度、画质匹配度、档内离散、设备结构占比。每张给末值、相对区间起点的变化和区间均值 —— 一个数配一个方向，比单看今天这个数有用得多。离散那张还专门标了“越低越好”，免得你看见绿色下降箭头反而以为是坏事。 而这四张卡最有用的东西是曲线上的虚线标记：紫色是版本上线，鼠标放上去告诉你是哪个版本、哪一天；做了标定订正的项目还会多一条绿线，注明“该日起新数据换档，历史不重算”。 你改了分档、改了标定、发了版本 —— 哪条线是你自己划的，图上标着。不然你看见曲线跳一下，第一反应永远是怀疑数据。 这套页面，现在也可以直接问我 上面这一路走下来，你大概也发现了：这套东西的门槛不在看不懂，而在你得先知道有它、先点进来、先找到该看的那一页。分档诊断算得再准，你不打开它，它就不存在。 所以现在多了一个入口 —— 你可以在聊天框里直接问我。 能问什么：我的分档合不合理（我把诊断结论取回来讲给你：健康分多少、有哪几个问题、每个多严重）、我的设备结构什么样（就是第一页那张户口本）、画质和设备档匹配吗（匹配度、错配率，以及错配偏向哪个方向）、帮我建议一下分档边界（我按你项目的实测分布算一组建议值，然后把你送到分档规则页去确认）。 不用你专门开口的两件事：一是带你去页面，说“打开分档诊断”“我想看设备信息订正”，我直接把你送过去；二是替你盯着 —— 现在有个叫「设备分档守护」的巡检目标，你说一句“帮我盯着设备分档”就能建起来。它默认每周跑一次（嫌慢可以改成每天），下面挂两个子任务：分档合理性巡检和画质匹配巡检，正好对应前面第三节和第二节。你还能选它用哪套口径、回看多长的窗口。分档退化了、画质错配变多了，我推给你，并告诉你该去哪一页处理。 这件事的意义不在“省了你几次点击”，在于开头那个问题 —— 画质配错了不报错。没有人会每周主动打开一个没报警的页面。所以它得自己找上门。 小Q巡检 接下来是更要紧的部分：我不能做什么。 我不能替你改分档边界，不能替你提交设备订正，也不能替你做设备库审核。前两件技术上我做得到，是我们决定不做的：分档边界一改，全项目所有跟分档相关的口径都跟着变，这个爆炸半径不该由聊天框里的一句话触发。所以那个能力的名字就叫“建议” —— 边界我替你算，保存那一下必须你自己按。 还有一件是能力边界，不是选择：Harmony、Windows、WebGL这三个平台没有设备分档维度，整个巡检模板在这些项目上是置灰的。没有数据源的地方，我不会假装我能守。 有几句实话，照例说在前面 第一，算这些数的不是我。 设备体验雷达是一套确定性的统计与规则工具 —— 所有数字由代码算出，所有判定由你能看见、能改的阈值决定，这部分全程没有大模型参与。我上一节讲的那些，本质是给它加了一个对话入口和一个定期提醒，不是给它换了个大脑。 第二，分档不重算历史，这件事你必须先接受。 分档在会话入库那一刻写死，改规则、改标定都只影响之后的新数据。你需要的是耐心，不是刷新按钮。 第三，画质错配是线索，不是因果。 “两成会话画质配高了”说的是一个相关的、值得查的现象，不是“这两成会话的卡顿都是画质造成的”。给你的是线索和优先级，定论仍然归你。 第四，它会犯错。 阈值是我们给的默认值，不可能对每个品类都合适；机型识别会漏，标定会偏。碰上了请改阈值、提订正 —— 判据全部开放本身就是承认“默认值可能不适合你”。 结语 回到开头那件事。分档 × 画质配不上，是个不报错的问题 —— 它不进崩溃列表，不触发告警，只是让一批玩家在你不知道的地方，用着他们本不该受的体验，然后安静地走掉。 这套东西不承诺替你解决它，它承诺的是让它看得见：你的玩家真实在什么机器上、你那套分档在你的项目里切没切对、每一档实际跑着什么画质、跑错的那批人堆在哪个版本里。看得见之后，改不改、怎么改、什么时候改，仍然是你的判断。 最省事的上手路径是这样：先看概览的Unknown占比，高了先去订正；再看画质匹配 —— 两个方向的错配率，通常这里的收益最直接；然后看风险对比里的档内离散，别只看均值列，把P10/P90那行小字扫一眼；要动分档边界，去分档规则页，保存前把影响预览看完；过几天回诊断页看趋势卡上的标记线，确认你那一改到底有没有用。 这条路径你要是懒得记，也可以直接问我“我的分档合不合理”，我按同样的顺序讲给你听，再把你送到该去的那一页。要动的那一下，还是你自己按。 你的游戏不是在“一个平均帧率”上运行的，它同时在几千款不同的机器上运行，每一款都在过自己的日子。你不可能让所有人都满意，但你至少应该知道，你正在让谁不满意 —— 以及其中有多少人，本来是可以满意的。 极简行动清单（5 步快速上手设备体验雷达） ① 查看概览页Unknown未识别占比，高优先做设备信息订正； ② 打开分档诊断，查看画质双向错配情况； ③ 关注风险标签：重点看「档内离散过大」； ④ 修改分档前务必查看影响预览； ⑤ 开启小Q「设备分档守护」自动定期巡检。 立即体验！ 专属通道限时开放 ➡ 预约技术专家1V1部署指导！我们将全程支持服务搭建、数据分析与反馈，确保您能够充分体验GPM 2.0带来的价值。 技术支持：gpm-support@uwa4d.com 微信：18683824 关于GPM 2.0 UWA GPM 2.0是专为上线及测试阶段游戏项目打造的高性能监测平台。它不仅能深度捕捉宏观性能数据，更创新采用性能无损截帧技术，在不影响玩家体验的前提下，助力开发者全面掌握玩家端运行关键细节，从多维度优化游戏性能，实现从“玩家投诉后救火” 到 “问题发生前预警” 的核心转变。 GPM 2.0全景指南 《UWA GPM 2.0 核心功能全景指南｜重塑游戏性能主动管控能力》 《5 月刊｜GPM 2.0实现全场景可视化溯源、多维度数据解析与根因精准定位》 《4 月刊｜GPM 2.0核心玩家守护功能上线》 《3 月刊｜GPM 2.0性能数据探索 & 单玩家追踪升级，精细化性能分析再突破》 《2 月刊｜GPM 2.0错误日志分析上线，PC端监测能力全维度突破》 《1 月刊｜日报周报一键获取+AI问答，玩家行为精准分析》 《12月刊｜上新！全局筛选+设备标记+序列帧回溯，GPM 2.0性能分析效率拉满》 《GPM 2.0 功能更新月报：新增导出表格、崩溃反混淆等实用功能，体验再升级！》 GPM 2.0新功能 集锦 《GPM 2.0 WebGL监测能力扩容｜覆盖Unity/Cocos/LayaAir多引擎微信&抖音小游戏》 《大R玩家从不抱怨，他们只是消失了》 《想优化哪段就量哪段｜GPM自定义区间精准观测任意游戏流程》 《我以前只能告诉你“哪个函数卡”，现在我能带你去看“玩家在哪卡”》 《大家好，我是UWA GPM「小Q」，今天是我加入项目的第一天》 《GPM 2.0 全新上线WebGL专属监测，全面适配微信、抖音小游戏》 《无需自建机房运维｜UWA GPM 2.0 SaaS正式上线，让游戏线上质量监控轻量化落地》 《数字助理已到岗！GPM AI 智能体发布，游戏性能分析进入全自动时代》 《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 重新定义性能监测与优化｜从未知到已知，从被动到主动》 《UWA GPM 2.0 重新定义性能监测与优化｜如何高效预警和定位玩家端的性能问题》 GPM 2.0案例精讲 集锦 《携手护航亿级体验｜《迷你世界》× UWA 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 多维数据与实时截帧完美结合》
性能数据跟着问题走：2026年，你的AI也该懂怎么查Unity性能瓶颈了
用AI查性能，第一问一般过得去。报告刚跑完，丢进Cursor问“这版为什么掉帧”，它多少能说两句。 下一句就费劲了。 你想知道卡在哪几帧、哪个函数，这些数常常不在对话里。于是停下来，切回报告翻曲线、搜函数、把帧号抄出来，再切回Cursor接着聊。换Codex或Claude Code，也是同一套动作。 来回几趟，刚才想的那点东西就散了。工作量不大，就是烦。 于是，我们把UWA已经开放的查询接进了MCP。你问到哪，它去报告里取哪一段，少切几次。 前两天小编手头有一份现成的GOT Online报告。测试同学在群里说：中间有一段卡得厉害。正好编辑器还开着，小编我也懒得对曲线，直接在Cursor里问了。 先问：主线程有没有明显慢帧，慢在哪几帧。 Cursor回过来是帧号：第103帧，主线程大约1538ms....以前这会儿就得切回去对时间轴，把帧号记在备忘录里，现在数字已经在对话里了。 以前问到这，我多半会说「等一下，我去把103的堆栈翻出来」。这回堆栈就在下一句。 我又问：其他慢帧呢，像不像同一类？ 对比之下，很多人是把报告截图，或者把概述贴给AI。第一句够用，再问下去，下一张图不在上下文里，它只能猜，或者等你再翻。接上MCP之后，问慢帧就去取帧号，问某一帧就去取这一帧的堆栈，觉得像个模式，再把旁边几帧拉过来。还是那份GOT Online数据，中间少了几次自己动手。 CI、看板、准出卡继续走 OpenAPI，没必要重做。这次Overview补了函数曲线、每帧耗时，也能取指定帧的堆栈，往下挖方便一点。MCP把这些查询接到对话里，给那种下一步还没想好的排查用。 已经在Cursor、Claude Code或Codex里看代码的，用上周那份GOT Online报告就能试。可以先问主线程有没有明显慢帧，帧号是多少；再问最慢那一帧时间花在哪、同帧还叠了什么。如果还像同一类，让它把其他慢帧也拉出来对照。 刚改完战斗或者UI，提测前不踏实，问一句也行：我刚动过的这几个函数，这次测试里耗时有没有变？它手头没有，会自己再取。 固定流程用OpenAPI 动态排查更适合MCP 对于已经跑稳的工业化管线（如固定拉指标、跑CI、维护性能看板），场景、CPU/GPU、逐帧数据等指标，直接通过UWA OpenAPI接进原有流程即可，稳定高效。 而MCP真正发力的，是那些“下一步还没定”的疑难杂症动态排查。 先看FPS，发现集中在某块地图，再拿场景数据；GPU没变化，注意力立刻转给CPU；范围缩小到异常帧，再深挖函数调用。数据还是UWA的数据，只是中间彻底省去了你接接口、翻报告、整理完再丢回AI的低效体力活。 最后说两句： 我们深知，在项目期引入或尝试一个新工具是有决策成本的。 工具的价值，不仅在于它能算得多快，更在于它能不能真的融入团队的日常流转中，帮大家准时下班。 但如果它能每天帮你省下半小时的查Bug时间，这笔账就绝对划算。为了让大家能毫无负担地验证这波“AI+性能优化”的疗效，我们为大家准备了专属福利： 关于UWA UWA是一家创业十年的高新技术企业，作为游戏行业的深耕者，UWA始终专注于为使用Unity、Unreal引擎的开发者提供丰富的优化产品，帮助开发者高效解决开发问题、定位性能瓶颈、提供解决方案，已支持超过一万款游戏项目。还打造了技术博客、问答、学堂等社区产品，为开发者提供便利和高效的支持。线上培训和线下教育的新业务，满足行业对人才培育的需求。
对象销毁了，为什么材质还留在内存里
1）Material Instance为什么持续增长 2）动态生成Mesh后，为什么Mono内存变高了 这是第489篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q：项目运行一段时间后，Material Instance数量持续增长，从开始的100多个增加到2000多个，这是什么原因？应该如何排查？ A：这种情况可以先排查运行过程中是否不断实例化了新的Material。报告中可以看到，某类技能材质（Instance）从最开始的100多个逐渐增加到2000多个，而且持续出现新的实例。这类现象通常需要结合技能、角色、特效对象的生命周期一起看，确认运行过程中是否不断产生新的Material Instance。 Unity中访问Renderer.material时，如果当前Renderer使用的是共享材质，Unity会为它实例化一份独立的Material。这里不只是调用SetColor、SetFloat等修改操作需要注意，仅仅访问Renderer.material本身，就可能触发实例化。如果只是读取或继续使用共享材质，可以检查是否应该使用Renderer.sharedMaterial。 真正容易被忽略的是材质和GameObject的生命周期并不是完全绑定的。通过Renderer.material等方式在运行时实例化出来的Material，不会因为对应GameObject被Destroy就自动完成资源释放，需要对这类动态材质单独管理。 比如技能释放时创建了临时特效对象，同时实例化了一份材质。技能结束后GameObject已经销毁，但这份Material没有被显式销毁，下一次释放技能又生成新的实例，长期运行后就可能看到Material Instance数量从几百一路涨到几千。未被使用的材质也可能在Resources.UnloadUnusedAssets()等资源清理过程中被回收，但不应该把它当成高频动态材质的主要生命周期管理方式。 排查时可以从持续增长的材质名称入手，再对应技能释放、角色生成、特效播放等操作做复现。如果每执行一次操作，某类Material Instance数量都会增加，而对应对象销毁后实例仍然保留，就需要继续检查这条材质创建和销毁链路。 优化时可以重点关注两个方向： 减少不必要的Material Instance创建。 检查是否存在只为了读取材质却访问Renderer.material的情况；如果只是修改颜色、Vector等每个Renderer不同的属性，也可以评估MaterialPropertyBlock是否适用，减少独立Material实例。需要切换Shader或Shader Keyword时，则不能简单用MPB替代。 显式管理动态材质生命周期。 如果确实需要实例化Material，需要保留对应引用，并在对象生命周期结束时显式Destroy。对于高频创建的技能、角色和特效，也可以进一步检查是否能够复用材质，而不是不断创建新的实例。 实战案例 Q：项目地图区域原本直接使用MeshRenderer，但动态加载、卸载时会有卡顿，后来改为在脚本中保存顶点等数据，运行时再动态生成Mesh。UWA GOT Online报告显示Mono内存占用较高，如何判断是否和这部分数据有关？ A：可以先通过Memory Profiler确认Mono堆中的主要占用。如果其中存在大量顶点、矩阵等Mesh相关数据，再结合引用关系查看这些数据是否由地图区域相关脚本对象持有，就能进一步判断Mono占用是否和这套动态Mesh实现有关。 这里需要关注的并不是“动态生成Mesh”本身，而是为了生成Mesh，脚本侧长期保存了多少网格数据。例如C#侧保存了大量Vector3[]、索引数组或矩阵数据，这些内容会占用Managed Heap，也就是这里看到的Mono内存。随后再把这些顶点、索引数据提交给Mesh，Mesh自身还需要在Native侧维护对应的网格数据。 因此，如果Mesh已经生成，而C#侧的原始顶点、索引等数据仍然长期保留，就可能出现同一批网格信息同时占用Managed和Native两侧内存的情况。这里不能简单理解为固定的“双倍内存”，但确实需要同时关注两边的数据占用，而不能只看Mono。 排查时可以重点看： 确认Mono中的Mesh相关数据占了多少。 查看顶点数组、索引、矩阵等数据的数量和实际占用，再通过引用关系确认它们是否由地图区域相关脚本长期持有。 检查Mesh生成后，托管侧数据是否仍然需要保留。 如果网格生成完成后不再需要频繁修改，原始Vector3[]、List等数据就要确认是否还有长期保存的必要。如果没有其他引用，可以解除引用，让GC后续回收，而不是一直和Mesh同时保留。 检查是否存在多份数据。 如果原始地图区域数据、转换后的顶点数据、运行时Mesh和其他缓存同时存在，同一份区域信息就可能以不同形式占用多份内存。 此外还要关注动态Mesh生成过程中的临时分配。如果频繁创建新的Vector3[]、List等托管对象，会持续产生GC Alloc，Managed Heap扩容后也不一定会马上回落，因此会出现Mono内存长期维持在较高水位的情况。 Mono堆规模变大、需要管理的托管对象增多后，GC扫描和标记的成本也可能随之增加，因此这部分问题不仅体现在内存占用上，也可能进一步带来GC耗时。 如果项目使用的Unity版本和现有架构允许，还可以继续评估NativeArray、Mesh.SetVertices(NativeArray)，以及MeshDataArray、Mesh.ApplyAndDisposeWritableMeshData等方式，减少动态网格构建过程中对Managed Heap的依赖。不过这属于进一步的实现优化，排查时仍然应该先确认：现在到底是哪一批数据占用了Mono，它们为什么还被脚本长期持有。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
流式加载后，为什么GPU仍在裁剪大量三角形
1）流式加载后，为什么GPU仍在裁剪大量三角形 2）开启级联阴影后，三角形数量为什么明显增加 这是第488篇UWA技术知识分享的推送，精选了UWA社区的热门话题，涵盖了UWA问答、社区帖子等技术知识点，助力大家更全面地掌握和学习。 UWA社区主页：community.uwa4d.com UWA QQ群：793972859 本次推送的实战案例来自于使用UWA服务的项目的真实且典型的问题。UWA将关键线索、定位路径与处理建议整理成了可复用的案例笔记，便于大家快速对照、排查自身项目中的同类问题。 实战案例 Q1：大地图已经做了流式加载，只加载角色附近区域，但GPU里仍有大量三角形被裁剪。为什么还会出现这种情况？ A：可以先看UWA GOT Online报告中GPU Input Primitive的数据，继续拆分Culled Primitives的具体构成。 以报告中第830帧为例，Culled Primitives约95.8万，其中Facing & XY Plane Culling Primitives约43.2万，Z Plane Culling Primitives约21万，Coverage Culling Primitives约31.6万。 Facing & XY Plane Culling里包含背面剔除，这本身属于正常的渲染流程，而且目前无法继续区分其中有多少来自背面剔除、多少来自XY方向超出范围，所以不能直接把这部分都当成无效开销。 Z Plane Culling可以优先检查场景里是否有覆盖范围比较大的Mesh。比如一块Mesh横跨较大的空间，其中一部分几何已经超出摄像机视锥的前后范围，但整块模型仍然被提交给GPU，这种情况下可以评估大块Mesh是否需要适当拆分。 Coverage Culling则可以重点检查LOD。如果模型已经离相机很远，但仍保持近距离时的高面数，三角形投射到屏幕后会非常小，其中一部分甚至覆盖不到有效采样点，最后被GPU裁掉。可以检查远距离LOD是否降得足够。 Q2：项目里的角色支持换装，如果给不同衣服都做LOD成本比较高，这种情况下角色LOD还需要做吗？ A：可以先确认角色对当前三角形数量的影响有多大，再决定是否值得做。比较简单的方式是在相同场景、相同视角下做一组开关测试，一组正常显示角色，一组把角色关闭，再对比Primitive相关数据。 角色关闭后三角形数量和裁剪数据明显下降，说明角色对当前几何压力的贡献较大，可以进一步评估角色LOD；数据变化不明显，则优先把精力放在场景模型上，继续检查大块Mesh和场景LOD。 实战案例 Q：高画质开启级联阴影后，三角形数量从几十万涨到100万以上，这部分增长和级联阴影有关吗？ A：有关系。级联阴影会让部分物体重复参与阴影绘制，同一个物体除了正常渲染，还可能需要参与额外的阴影绘制，所以GPU需要处理的几何量也会增加。 高画质下Triangle数量 中画质下Triangle数量 从GOT Online报告数据来看，两档画质下的几何量差异比较明显。中画质下大部分场景维持在几十万，峰值约62.1万；高画质下则多次接近或超过100万，峰值约130.6万。 不过，高画质下可能还同时调整了其他渲染配置，所以不能把这部分增长全部归到级联阴影上。可以固定场景和摄像机视角，只调整级联阴影相关配置，再对比Triangle或Input Primitive的变化。 中画质GPU Fragment Shaded 除了三角形数量，也可以结合GPU Fragment Shaded看GPU侧的压力。从报告中可以看到，Fragment shaded均值约332.2万，峰值约966.1万，说明除了几何量以外，还可以继续关注片元侧的处理量。 高画质下GPU压力增加时，不建议只看三角形数量。可以结合Triangle、Input Primitive和Fragment Shaded等数据一起判断。 无论是社区里开发者们的互助讨论，还是AI基于知识沉淀的快速反馈，核心都是为了让每一个技术难题都有解、每一次踩坑都有回响。希望这些从真实开发场景中提炼的经验，能直接帮你解决当下的技术卡点，也让你在遇到同类问题时，能更高效地找到破局方向。 封面图来源于网络 今天的分享就到这里。生有涯而知无涯，在漫漫的开发周期中，我们遇到的问题只是冰山一角，UWA社区愿伴你同行，一起探索分享。欢迎更多的开发者加入UWA社区。 UWA官网：www.uwa4d.com UWA社区：community.uwa4d.com UWA学堂：edu.uwa4d.com 官方技术QQ群：793972859
内存数据不再逐页翻！GOT Online开启“精准降本增效”模式
对很多项目组来说，“内存”永远是性能优化里避不开的硬骨头。但每次分析报告时，寻找各种内存指标、整理不同岗位的内存数据，往往耗费了大量的无用时间。GOT Online此次更新，主打的就是把核心内存数据集中呈现，并全面增强了对内存细分资源的定位能力。 除了针对内存分析的重磅升级，本次还同步上线了函数Top10耗时点和函数组数据一键导出等实用功能。 聚合常用指标：按需定制专属面板 做性能排查时，程序、美术、测试打开的是同一份报告，但关注点完全不同。哪怕都在美术团队，场景、角色、特效需要盯着的数据也差了十万八千里。 结果就是每次打开报告，都得重新翻页、找指标、做对照。数据明明都在，时间却花在了“去哪里找”上。好的性能工具，从不强求全员看同一份大表，而是让每个人打开报告的第一眼，都能直奔自己的“主战场”。 GOT Online本来就支持自定义模板，这次进一步把内存分析模块中的指标也放进了面板。以后经常看的内存数据，直接集中搬到自定义面板里，大家按自己的工作内容建立模板，搞场景的看场景，搞特效的看特效。关注的数据一眼就能拿到，不用再在大大小小的页签里绕圈子。 结合同步更新的场景性能表，还能进一步看清各个场景区间的细分指标。像 DrawCall 均值/峰值、不透明与半透明 DrawCall 分布等数据一目了然，甚至超标项还会直接高亮预警。看到哪个场景区间有问题，点击对应行的“查看场景数据”就能秒级跳转深挖，排查效率直接拉满。 深挖Unreal资源：三步锁定内存大户 自定义面板解决了“内存数据怎么看更快”的问题。但一旦发现内存异常飙升，接下来还是得回答那个最现实的问题：到底是哪类资源占得多？最后又落到了哪个具体的资产上？性能排查最怕凭空盲猜，缩短排查路径的本质，就是尽可能减少这种无效猜想的过程。 GOT Online之前已经支持Unity项目的资源内存分析，这次把这项能力也补齐给了Unreal项目。现在Unreal项目也可以直观查看资源内存趋势、数量变化和不同资源的占用分布。纹理、网格、动画、音频、材质、Shader、RenderTexture和粒子系统等，都能分类查。 以前看到内存涨了，大家第一反应只能是猜：“是不是纹理爆了？还是RenderTexture没释放？”现在可以先看类型分布，纹理占用明显抬高，就先查纹理；RenderTexture数量不对，就继续盯RenderTexture。 看清类型后，点进具体资源列表，资源名称、内存占用峰值、数量、生命周期直接展现。如果是纹理，还能结合尺寸、格式、Mipmap和Streaming等属性继续判断。这样排查路径就彻底顺了：先看内存怎么变 ➔ 再看是哪一类 ➔ 最后追到具体资源。报告不会替项目直接下结论，但至少不用对着一条上涨曲线干猜了。 标出耗时Top10：快速定位最慢帧 一条耗时曲线上可能有成千上万个数据点，真正值得关注的，通常只是少数几个卡顿峰值。以前排查偶发问题，沿着曲线来回翻，大部分时间看到的都是正常数据。但性能优化的黄金法则，永远是先干掉最显著的瓶颈。别把有限的精力浪费在那些平稳正常的帧上。 现在，只需点击按钮，就会直接在曲线上标出Top10最慢的点。哪些位置拖了后腿，一眼就能挑出来，再结合对应帧、线程和上下文去深挖。当然，Top10只是耗时最高的位置，不代表它一定就是卡顿根因。但它至少把范围缩小了。与其从头到尾逐帧找，不如先盯住这10个最可疑的位置，排查效率显然高得多。 导出一键完成：告别手动搬运数据 很多团队都会按照项目特点建立“函数组”，把长期关注的函数整理在一起，用来做版本复测、设备对比或问题回归。麻烦的是，报告一多，这些函数组和对应耗时数据也会散在不同地方。后面想做对比，或者交给其他同事继续分析，往往还得手动重新整理一遍。 性能优化本身就足够消耗脑力了，完全没必要再把时间消耗在重复的数据搬运上。GOT Online现在支持一键导出所有函数组以及对应耗时数据。导出结果既有整体汇总（总耗时均值、自身耗时均值、耗时占比等），也有组内每个函数的详细耗时和调用数据。 同时，这项数据也已经同步支持OpenAPI拉取。不仅可以手动导出Excel，还能直接接进团队内部的自动化平台或对比工具。版本复测时，把不同报告的导出结果拿来一比，或者用接口自动跑对比脚本就行，不用再傻傻地逐个函数组去抄数据了。 场景横向对比：一眼找出表现异常 长时间运行的测试报告里，往往穿插了多个场景。真正开始深挖之前，第一步通常不是直接查某个具体函数，而是先找出“哪个场景表现不太对”。 GOT Online针对Unity项目重做了场景概览页，把不同场景的关键指标（FPS、CPU、GPU、内存、耗电和温度）拉到一起横向对比。哪个场景FPS突然掉底，哪个场景内存数据异常，通过图表和列表一眼就能看出来。先把差异摆出来，告诉你哪里值得继续看，至少打开报告后不用对着一堆指标发愣。 更多实用补充：卡顿堆栈与温耗均值 OpenAPI同步补充卡顿数据：新增卡顿帧列表、卡顿堆栈树和场景卡顿堆栈。单份报告可以直接在页面分析；报告数量多了，可以通过接口接入内部自动化平台，批量筛选异常报告。 新增温度和功耗“平均值”：峰值高只能说明设备某个瞬间很忙，均值高才代表持续高负载。现在结合“峰值”和“均值”一起看，更能判断设备是不是持续发热或降频。 性能报告里的数据越来越多当然是好事，但如果每次分析都要在不同页面之间来回翻、重新找指标、手动整理表格，再完整的报告也会让人看得很累。 这次GOT Online的更新，做的事情其实很纯粹：把大家最关心的内存和常用数据放得更近一点，把值得关注的异常提前挑出来。真正高效的研发体验，从来不是一味地堆砌数据量，而是让最关键的信息在你需要的时候触手可及。排查顺手了，优化效率自然就上去了。 最后说两句： 工具的价值，不仅在于它能算得多快，更在于它能不能真的融入团队的日常流转中，帮大家准时下班。 我们深知，在项目期引入或者尝试一个新工具是有成本的。为了让大家能毫无负担地验证这波更新的疗效，我们特别开放了「两周 GOT Online 免费试用通道」，并且会提供专属的性能专家诊断名额。 不管你们现在卡在什么性能瓶颈上，联系我们的商务同学，UWA 的技术专家会一对一陪着你们跑通测试，直击问题。 关于UWA UWA是一家创业十年的高新技术企业，作为游戏行业的深耕者，UWA始终专注于为使用Unity、Unreal引擎的开发者提供丰富的优化产品，帮助开发者高效解决开发问题、定位性能瓶颈、提供解决方案，已支持超过一万款游戏项目。还打造了技术博客、问答、学堂等社区产品，为开发者提供便利和高效的支持。线上培训和线下教育的新业务，满足行业对人才培育的需求。
【USparkle专栏】如果你深怀绝技，爱“搞点研究”，乐于分享也博采众长，我们期待你的加入，让智慧的火花碰撞交织，让知识的传递生生不息！ 前言 UE：5.7.1 GASP：5.7 本文是以Mover版本角色来进行浅析GASP中旋转的实现，包括Strafe移动，AimOffset，TurnInPlace。 同时也会结合分析Lyra和ALS的一些实现方案。 1. Debug与基础RotationMode 1.1 Debug 为了更方便查看旋转相关的Debug信息，我们需要打开Widget中的Draw Shapes，就会看到下图这样很多的箭头，其中有意义的部分为： 圈内黑色大箭头：Root根骨骼朝向 圈外黑色小箭头：角色（碰撞体）朝向 亮粉色箭头：TargetOrientation旋转偏移前的目标朝向 暗粉色箭头：TargetOrientation+RotationOffset旋转偏移后的目标朝向 橙色箭头：MovementIntent，角色的期望运动方向 绿色弧线：当前MovementDirection移动朝向的范围 1.2 RotationMode 在GASP中，旋转模式分为OrientToMovement、Strafe和Aim，如果你是用过ALS的，那么它们是与VelocityDirection、LookingDirection和Aiming一一对应的。 OrientToMovement：角色将旋转至面向移动方向 Strafe：角色在移动时将大体面向瞄准方向旋转 Aim：角色将始终以最小偏移量精确面向控制器旋转方向，也算是Strafe模式 RotationMode主要是由玩家的按键输入以及游戏模式决定，在GASP中可以通过鼠标中间切换Strafe和Movement两种模式，并且可以按住右键进入Aiming模式。在常见的开放世界游戏中大多都是默认使用OrientToMovement模式，而当玩家拿起弓箭或者枪械瞄准时则会进入Strafe之中。 其中OrientToMovement比较简单，角色朝哪边移动就转向哪边即可，就不过多介绍。 2. Strafe基础 2.1 BlendSpace做法存在的问题 记得我最早接触Strafe移动还是在跟着一些UE4新手教程做战斗系统的时候，当做到了弓箭瞄准，或者类似只狼锁定目标时，就需要出现玩家始终面朝某个方向，却向着其他方向移动的情况。但是新手教程中的做法往往很简单，例如下图这种，用一个Direction来表示离面朝方向偏差多少角度，然后一个1D BlendTree混合。也有稍微好一点点的会使用2D BlendTree，使用LR/FB来进行混合。 1D因为不是环形的，在遇到从左后切换到右后时，需要经过-180度到180度把中间状态全都走一遍的情况，表现十分奇怪。而2D无论是四向在融合的地方都可能出现预料之外的表现。对于一些融合表现不好的地方可以选择补全八向动画。 若使用RootMotion动画，混合后会出现移速衰减问题，例如前后、左右单方向移动速度均为100，在往左前走的时候速度就只有50 √2 ，但是我们的期望肯定是斜向移动也是100，最简单的做法通过调整动画播放速度，来调整移动速度，但是会比较鬼畜。也可以考虑用程序补偿，缩放一下，或者直接用程序位移。 2.2 程序化多方向动画 二维的BlendTree也并不是要固定四个方向的动画，可以根据需要灵活调整为8向，10向都是OK的，GASP则是用到了四个动画F\B\LL\RL，但GASP并不是通过BlendTree混合出来的，使用BlendTree融合在没有动画的角度融合的角色往往会不尽人意，其使用的是OrientationWarping，同样是程序化修改在小范围内效果还是可以的。Lyra更是暴力地只用一个动画就OrientationWarping了360°出来。 OrientationWarping能使脚根据运动方向向不同方向移动。让下半身运动匹配移动方向，而上半身保持不变，以此实现向不同角度进行Strafe。他的实现逻辑是先旋转Root，再反向依次旋转Spine，例如下图中就是使用向正右方移动，但是被Warp了45度到了右前方。 2.3 腿部穿插 无论是BlendSpace还是OrientationWarping这些方法都无法解决脚步交叉的问题。而脚步打绕常出现于交叉脚的时候去往另一个Pose融合，但是切换过去的脚前后相反，直接融合过去就会穿插，比如下图本来是向右走的左脚在前，交叉时切回向后的右脚在前，腿就穿了。 为了解决这个问题，一个比较常见的做法就是分扇区，将前向和后向分别作为不同的BlendTree，在每个扇区中都让同一只脚保持在前方，同时保证同样播放进度时脚都是同一的前后位置，这样在扇区内Blend的时候就不会出现前腿要放到后方的穿插情况。同时为了避免在边缘时扇区频繁切换还会加入DeadZone，则让每个扇区的范围扩大一些，有一些重叠区域。 当融合发生时，融合前后的Pose一只脚在前，另一个Pose脚在后就会出现融合时穿插的问题，但其实要修复这个也非常简单，只需要在合适的时机进行融合也可以解决，要么相同脚在前，要么处于非交叉状态，正如下面视频展示。 我们可以选取当他到了什么时间节点我才允许切换过渡，但是这样会影响手感，更好的做法是使用SyncGroup让切换过去的时机对应。 除此之外还可以通过衔接动画方式来解决，通过过渡动画替代动画直接融合，来规避脚部穿插的情况，这样还可以改善在切换过程中仍然是两个直接动画融合可能让表现不符合预期的问题。 3. GASP的Strafe 3.1 MovementDirection分扇区 在GASP中，为了避免脚步打绕穿插的问题，同样也用到了分扇区的做法。 在最新的工程中他们还开放了一个实验性功能，可以针对于Aim和Strafe分别选择不同的扇区风格，可以是分前后左右四个扇区，也可以只有两个或者一个。 但是如果你打开枚举类型会发现，并不是只有F\B\L\R这四个，而是F\B\LL\LR\RR\RL这六个。如果你看过ALS你就会感觉非常的熟悉，和ALS的RF\RB类似，RF代表角色向右运动但是胸口朝右，RB表示角色向右移动但是胸口朝左（背部朝右）。而GASP中也是一样，第一个字母L/R表示向哪边移动，第二个L/R表示胸口的朝向，例如RL就表示朝右移动，背部朝左（胸口朝右）如下图所示： 对于MovementDirection的选取也很简单，根据当前运动朝向处于哪个范围内就选择哪个，但是并不是左右胸口朝向不同的都上了，在当前Mover版本的实现中，采用了LL作为L，RL作为R，组成了F\B\L\R四个部分。（不过看动画也预留了RR和LR，后续可能会扩展成ALS那样六向。） 通过FL\FR\BL\BR四个角度值，就可以区分出四个扇区，如下图所示，同时在每个扇区时都会稍微扩大当前扇区范围以此来形成一个DeadZone，避免在边缘疯狂跳转。 在Debug模式中的绿圈就是表示当前扇区的范围，根据我们选择的Strafe Style不同，扇区覆盖的区域就会有所变化，就实现了控制扇区数量。例如在F\B模式中，FL和BL是相同的值，所以就不存在进入L扇区的情况。 3.2 Rotation offset 在操作Mover角色移动时，如果你打开Debug会经常发现有一个暗紫色的箭头，并且角色的朝向会跟随他。让我们偏移我们实际想要的朝向（亮紫色箭头）。这就是RotationOffset，和ALS的Yaw Offset是一个东西。 这东西的计算很简单，就是根据现在的移动方向和期望朝向的夹角来采样，比如上图的情况，亮紫色的期望朝向TargetOrientation和橙色的运动方向夹角为+135°，并且此时处于B的MovementDirection ，就会采样Curve_RotationOffset_B得到-45的RotationOffset. 关闭RotationOffset后，在同一扇区内动画表现差异不大，但在切换扇区时可能出现衔接问题，因为角色的朝向一直向前会导致选择到错误的衔接动画，这个后面会说。 （这里还有个小细节是GASP的OrientationWarp有个AngleThreshold开的135，为了测试这个我们需要扩大一些，这个值本质是为了防止Orientation过渡，不然在超过135度时会反转成向背面Warp45度造成错误。） RotationOffset的作用主要还是让角色的动画更加自然，下图中一个是带Offset一个不带的，虽然没有截到同一帧动画，但是可以看出动画还是略有差异的。在有RotationOffset的时候，角色移动方向和朝向差距180°，正好和原动画一样，就不需要OrientationOffset来程序化的修改动画，毕竟动画师K好的动画大概率是比融合或者程序化修改后的表现好一些。而在ALS中YawOffset就是为了避免多方向的融合，尽量使用原动画。两者都是为了尽量使用更加自然的原动画。 但是如果是这样的话为什么不一直保持这个RotationOffset呢？这样就不需要BlendTree的融合或者程序化的修改了，能够一直使用原始动画。在下面图片中可以看到GASP在右侧时，移动方向为右下时有45°的偏移，但是朝向右上的时候偏移却为0而非我们预期的-45°。左侧也同理。 如果强行改一下让他的左右扇区都有RotationOffset的偏移，可以看到下面视频的效果，因为有AimOffset的存在，虽然脚步正常，但是此时胸口和头是朝向正前方，然后此时角色朝向又朝斜向移动，表现就略显奇怪。同时因为此时是处于小角度45°，OrientationWarp也不会太过扭曲，所以这个时候GASP就选择使用OrientationWarp来处理这个情况而非Offset。 3.3 衔接动画资源 对于扇区之间的切换，GASP采用了过渡动画的解法，依靠于MotionMatching和Chooser制作了大量的衔接动画。 在GASP中有着大量的这种衔接动画，以Run为例就有密密麻麻的上图这么多，做的非常细节，不仅区分了左右脚还有各种不同的类型。这些Box Spin等貌似是舞蹈的步伐专业术语，但是我们可以从轨迹和作用来区分。 Spin：在运动方向不变情况下，将角色朝向进行改变 Box：在保持朝向不变情况下，运动方向发生改变90度，轨迹为L Pivot：在保持朝向步不变情况下，运动方向发生改变180度 Turn：朝向和运动同时进行改变，并且移动方向和角色朝向始终一致 Switch：运动方向与朝向都不变，但是胸部朝向左右切换 下图是每个类型举了一个例子： 3.4 衔接动画选取 这么多资源，还都有着不同的功能，那么在GASP中是如何选取使用的呢？ 在GASP中是通过Chooser选取一系列合适的动画，再进行MotionMatch选择。 其中选择的条件中需要StateMachineState为Transition to Locomotion才会被选中，而这个状态的进入条件主要就两类：一种是当前状态发生变化，比如最关键的就是movementdirection；另一种就是正在播，但是又快速有了调整导致当前动画不符合，就会再次进入再次触发选择合适的动画。 但StateMachineState并不能作为我们区分这么多动画的依据，主要还是靠的后面三个：FutureFacingDelta、IsPivoting和MovementDirectionRecent。其中FutureFacingDelta、IsPivoting都是根据未来轨迹预测的角度来计算。 FutureFacingDelta是由当前Root朝向和期望朝向的差值计算得到，用来计算角色的朝向在未来发生变化的角度，也就是Debug模式下的暗粉色箭头的变化。通过这个参数就可以区分角色朝向发生变化的Spin，Turn与未变化的Switch，Box，Pivot。同时根据这个参数还可以区分Turn的角度，转向的左右，以及使用哪个Spin动画，例如我在LL情况转回F则是向左转90度，而表中这个动画的值范围是-110到10度，正好满足同时多了+-20的容差。 IsPivoting用来判断运动方向有没有发生变化，它是通过未来轨迹和当前运动方向计算一个方向夹角，如果大于Ground是75度并有一定的移动速度就为True，使用这个就可以将Spin、Switch这类运动方向不变的动画与其他区分开。 在最开始我们介绍过MovementDirection，但它和这个带Recent的参数还不太一样，当我们比如切换到F方向时，是先使用MovementDirection选择当前方向，然后再在这里面选择从哪个方向转过来，除了刚刚的FutureFacingDelta描述转多少度之外，还有MovementDirectionRecent用来表示上一个稳定的状态是什么，这个参数是只有在当前MovementDirection保持0.1s以上才会储存更新。 这样在一些快速切换的时候，角色朝向和运动朝向其实都没有发生变化，Recent这个参数就会让没有动画被选择从而跳过衔接动画直接回到本来的Loop，避免播放了衔接动画但实际上并不需要从那衔接过来的情况。同时也可以为Switch动画提供选择依据，因为方向都没变化只有通过Recent来选择。 所以总结下来，对于之前提到的各个动画类型的示意图来说，FutureFacingDelta决定角色朝向变化，也就是紫色箭头的信息。IsPivoting决定运动方向是否发生变化，也就是橙色箭头的信息。MovementDirectionRecent决定当两者都不变化时，如何选择，也就是区分胸口朝向，绿色箭头信息。三个参数共同作用就解决了动画DataBase选择的问题。 最后再根据MotionMatching来匹配L/R Foot，以及以未来轨迹决定运动轨迹来完成最后从动画库中的选择。 4. AimOffset AimOffset还是比较常规的做法，用了一大堆的Pose组成一个BlendSpaces，用Yaw和Pitch来采样。最后通过MeshSpaceAdditive叠加上去，至于为什么是MeshSpace可以看官方文档的解释： https://dev.epicgames.com/documentation/unreal-engine/aim-offset-in-unreal-engine 但是AO有个细节还是可以关注下，GASP的AO也有平滑，但是它并不是对最后计算完毕的Yaw和Pitch进行平滑，因为这样会导致如下面视频一样（红色箭头代表Head朝向）的情况，因为AO是Root和Aiming方向的插值，Root转动比较快而AO再慢慢平滑的话就会出现偏离目标，头在转身过程中多歪了一些再回正。 解决也很简单，就是对Aiming方向进行平滑，但是计算Root和Aiming的差值不平滑，这样就能够跟上Root避免平滑导致的错误偏移。 5. 原地转身 5.1 ALS与Lyra原地转身回顾 在没有Turn的时候，角色旋转要么一直跟随相机像下面视频一样直接整体旋转却非常不符合正常生物的表现。而如果一直不旋转，只靠着AO去看向目标，在当我们的目标点在身后时，人是没办法把头旋转180°看到后方的，而且一般AO极限也就90°左右了。想一下现实中当我们想要看向另一个方向到达一定角度时，我们就会动一动脚转一下身子，这就是TurnInPlace。 在看GASP怎么做的之前，我们可以回归一下ALS与Lyra的流程。 ALS的角色旋转朝向没有使用CMC自带的ControllerDesiredRotation或者RotationToMovement。所以在移动时候能够根据期望朝向去旋转，而原地时如果没有特殊逻辑则会保持Actor不旋转。 当ALS处于Strafe或者Aiming模式下时，会计算Control和Actor的朝向夹角，当超过一定角度后（Strafe还会要求并且保持一定时间）就会根据方向和角度选择合适的动画播放。每一帧的旋转量会被存在动画曲线中，根据实际旋转角度调整动画播放速度和缩放旋转曲线值以实现一个动画对应多个不同的角度，并每一帧应用这个旋转曲线值就实现了原地旋转。 但是这样会存在一个问题，就是如果游戏帧率很低，那就会跳过一些曲线点导致旋转被应用的不够，ALS是直接强行差了几倍时间就缩放几倍，这样会导致最后有所偏差。 Lyra则比较简单，首先因为Lyra的Actor旋转是始终跟随ControlRotation的，所以Mesh也会跟着动，而为了让Mesh保持不动，Lyra在ABP中会计算一个差值然后反向应用给Root让其保持不动。 在旋转动画Lyra并未考虑转身动画适配不同角度，而且是暴力的直接播放转90度动画，在转身后使用AO去对齐目标。 5.2 DistanceMatching For Turn In Place (修正：这里提到的AdvanceTime和UE的节点不太一样，UE节点是输入Delta，然后根据Dleta进行前进，并不会追逐剩余时间，而我这里直接把AdvanceTime当做了输入Remain，更像是ToTarget带RateLimit。) 对于这个方案是UE官方在Paragon Feature Examples和Bringing a Hero from Paragon to Life with UE4的分享中提到（链接在末尾）。虽然DistanceMatching在熟知的Lyra中被用在停步的距离匹配。这个不只可以匹配移动距离也可以用来做旋转，当使用DistanceMatching做旋转时，我们可以根据想要旋转的角度，以及旋转曲线来匹配合适的时间点。而非基于时间的线性播放来驱动。 Lyra在起步停步动画的时候，用到了两个叫DistanceMatching的节点， 虽然DistanceMatching本质是调整动画播放的时间点，如果使用DistanceMatchToTarget则会直接跳到合适的位置播放，比如停步的时候就可能会是前面的动画不播放而从后面直接开始，另一个AdvanceTime如果去掉PlayRateClamp，其实和ToTarget是一样的，但是因为有PlayRateClamp的存在，也可以理解为是缩放了动画播放速度。但是相比于简单的缩放PlayRate，可能实际运动的节奏和动画的节奏不一定能对上，可以看下图。如果只是缩放播放速度，虽然最终时间对上了，但是过程中也和实际位移对不上就会滑步，就如下图所示。当动画位移和实际位移是一致时候，缩放PlayRate和使用DistanceMatching。在动画位移过大就会减慢速度等一等，当动画过慢就会加速播放追上去。 但是实际在使用过程中，并不会出现实际位移和动画位移相同的情况，在下图中左图是动画位移大于实际位移，右图是动画位移小于实际位移。因为有PlayRateClamp的限制，所以会导致最终动画位移和实际位移有差距从而滑步。 在动画位移较小，使用ToTarget会丢失前面动画，而AdvanceTime会先播放较慢速度，让实际剩余位移追上动画剩余位移。 在动画位移大的时候，使用ToTarget会丢失前面动画，AdvanceTime则会先加速动画让动画的剩余位移减少，追上实际剩余位移。 虽然上面都是在说距离，但是把距离换成旋转角度来理解也是同样的。可以看到AdvanceTime的轨迹总有一段是与实际的位移无法对上，会产生滑步问题。而ToTarget虽然可以比较好的匹配，但是代价是会丢失部分动画。 所以如果用到了Turn上时，就和起步动画类似，我们希望他从头开始播放，并且能够对应角度的旋转，我们会使用AdvanceTime。但是由于避免动画太过鬼畜有PlayRate的限制，还是会在某些时刻与实际旋转产生偏差从而滑步，这就需要配合FootLock来解决。所以DistanceMatching只是解决了如果在PlayRate的范围内，转身结束的时候动画也刚好结束。让动画来匹配逻辑旋转。 5.3 TurnInPlace In GASP 说了ALS、Lyra还有Paragon的方案，回来GASP来看。 首先GASP只有在Aiming状态下才会有TurnInPlace的表现。在Mover组件下，角色的朝向是由输入的OrientationIntent决定。在Aiming原地时，是超过60°时才更新OrientationIntent为AimingRotation的方向。之后通过一个FacingSmoothingTime的参数平滑过去。如果把这个角度限制去掉，那么就和CMC的UseControllerDesiredRotation一样，角色旋转会一直平滑转向控制器方向。 之后通过Root和期望朝向的差值计算得到前面说过的FutureFacingDelta，当这个值超过50°时就会触发shouldTurnInPlace，切换DataBase为原地Turn动画触发Turn。 这里在Aiming模式下60°才会修改角色期望朝向，而一旦改变为60°>50°就会触发TurnInPlace，但是如果我们把触发TurnInPlace的角度扩大呢？让它变为90°就可以看到下面视频中的情况（关闭了OffsetRoot）。还没有触发TurnInPlace但是角色转过去了。除非像最后甩鼠标一下转超过90°不然就永远触发不了Turn，直接滑过去。 但是这并不正确，在没有到达触发Turn角度的时候，我们正确应该是保持在原地不旋转，但是因为我们胶囊体已经转了，想要Mesh不转我们就需要将Root给反算回去，这就是Lyra的做法。而GASP采用了一个新节点来实现，避免了还需要在蓝图各种计算最后再使用ModifierBone节点修改Root。 在OffsetRootBone节点上，RotationMode是除了播放Montage时使用Release模式，其他情况都是Accumulate。在这个模式下，节点会保持Root节点在误差范围内保持不变，不会因为胶囊体作为父级旋转而让Mesh的Root也跟着旋转，在GASP中MaxRotationError值为-1表示无限大。当我们在刚刚那个视频同样条件下开启OffsetRootBone，再旋转60°时候，可以看到胶囊体已经旋转过去，但是我们的Root仍然保持不变。 解决了Root的问题，我们就可以接着看转身动画的逻辑，在shouldTurnInPlace触发后，状态会跳转进入Transition to Idle状态，触发Chooser根据FutureFacingDelta选择合适动画，这里要注意的是UseMM=False，所以会直接正常播放而不会对这个动画进行MotionMatching选择合适的位置。 首先在下图看到，如果直接播放这个动画，我们的角色大概会转80多度的样子，没有符合动画的90°，这是因为在Idle向Turn动画切换的时候有0.4s的融合时间，在融合阶段Turn动画的RootRotation并未全部应用，所以会有一些偏差，关掉之后就是正常的90°。（在ALS之中RotationAmount曲线也同样有这个问题。） 这个时候问题就来了，我们只是一个90°的动画，怎么让他在有融合干扰的情况下还能刚好转到60°的位置或者更多其他不同的角度呢？同时上面我们还说过有OffsetRoot的Accumulate模式，那我在移动时怎么才能正确的转向呢？ 这些问题GASP都使用了Steering节点来解决，这个节点的作用是缩放调整动画的旋转，让Root转向TargetOrientation方向。 因为需要缩放动画，就需要知道未来一段时间动画的位移，所以会有AnimAsset并且会传入AnimatedTargetTime表示向后预测多长时间，如果预测时间不够长则会导致动画缩放不均匀，破坏动画本身的旋转节奏。 因为在Steering生效期间玩家还是可以操作，上一秒希望转30°下一秒可能是135°。所以Steer会动态去追随。 Steering内部的做法和ALS的原理很类似，通过预测实际需要旋转角度和动画的旋转角度进行缩放，比如动画是90°但是转60°，则把每次的旋转量只应用66.7%。但同时他也有DistanceMatching的思想，但并不是用在修改时间，而且修改Root。 例如我需要转60°，有一个动画3s转90°，前两秒他会转20°，最后一秒转70°，动画节奏为先慢后快，但本文预测时长仅设置2s。 所以在开始的时候，预测到了动画有20°，实际有60°。理论应该缩放3倍，但是因为他也有Max/Min ScaleRate的配置，会导致只缩放1.5倍。虽然前2s转了30°，后1s因为有缩放限制最大缩放到0.5倍，转了35°。最终导致实际旋转为65°而非期望的60°，和DistanceMatching的AdvanceTime非常类似。 可能是因为预测时间不足，也可能是因为旋转过程中Target产生了变化导致Root在缩放范围内无法满足，所以除了缩放Root旋转之外，Steering还有校正补偿的部分。同时像RunFLoop这种没有根旋转动画也能正常转向也是因为校正补偿在起作用。 他的实现就是通过弹簧平滑补偿，而ProceduralTargetTime就决定了平滑的误差半衰期，越小就会平滑的越快。 在GASP中，TargetOrientation并不是使用当前的期望朝向，而是根据轨迹预测的。这使得转向节点能够朝向未来的方向旋转，而不总是朝着当前角色的旋转方向旋转，否则可能会导致他落后得太久。 理论上这一个节点应该可以解决原地转身和动画朝向的问题，但是GASP目前做了两个Steering节点，在原地转身的时候第二个也会生效。他们的主要差别在于RootMotionAngleThreshold从-1改为1和Disable Steering Below Speed从10改为-1。 第二个Steering的Disable Steering Below Speed可以避免在原地不动时Steering不生效，RootMotionAngleThreshold是当动画的RootMotion小于这个角度就不会执行RootScale的过程，但看起来对于第二个Steering幅度下应该没什么作用。 5.4 FootLock 无论是上面的哪种方法，在调整旋转量后都会出现滑步的问题。对此的解决方法就是FootLock。在脚步放在地上时将其锁住，让他不会因为角色整体旋转对不上而滑动。 在GASP中叫做FootPlacement，其做法也很常规，就是通过给动画打曲线标记记录哪个时刻脚步是落下的，之后在对应时刻将脚锁住即可。 他的具体实现是放在ControlRig中，在那里面GASP还做了很多小细节来修正一些表现，到这里篇幅已经有点大了，后面的文章再来探究ControlRig中的一些东西。 6. 后记 本文分析的都只是GASP中旋转的主要思路，同时也对比了GASP之外的其他实现方案，如ALS、Lyra等。 但是其中还有大量细节处理逻辑，还有一些比如处于连续转身的情况下避免选择Pivot动画，以及许许多多没有提及到的。 参考 Unreal AimOffset 官方文档 Paragon Feature Examples官方分享 Bringing a Hero from Paragon to Life with UE4 这是侑虎科技第2016篇文章，感谢作者Shadow供稿。欢迎转发分享，未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859） 作者主页：https://www.zhihu.com/people/shadow-21-71-4 再次感谢Shadow的分享，如果您有任何独到的见解或者发现也欢迎联系我们，一起探讨。（QQ群：793972859）