<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[[Unity]小游戏转换优化笔记]]></title><description><![CDATA[<p dir="auto">做Unity转换小游戏时的心情就像骑着公路自行车上高速：一会觉得它行、一会觉得它不行、还有时候觉得是自己不行。</p>
<h1>23.06.03</h1>
<p dir="auto">最近一直在做优化，有些感触，很多方面很重要但又很容易被忽略：</p>
<ul>
<li>小游戏与原生APP相比，就像是8051机之与PC机，内存和运算能力都有限，还不允许开线程，很多在编辑器、APP上能流畅运行的操作，到了小游戏上不一定能一样流畅，开发中不能轻信在编辑器中的运行效果</li>
<li>内存使用，官方推荐使用的内存不要超过1G，如果Unity预留托管堆内存超过768M，在有些机器上甚至都跑不起来，而纹理、音频、字体都是吃内存大户，需要注意压缩</li>
<li>游戏物体的实例化，在编辑器中只耗时几毫秒，实机运行则可能需要几十甚至上百毫秒，如果一帧内实例化大量物体，很容易造成长时间卡顿或触发单帧内存峰值</li>
<li>WebGL不支持Addressables的同步加载方式，只支持异步加载方式，开发初期就要考虑到，不然后面改起来遭老罪</li>
<li>资源的加载与卸载，用时加载，不用时及时卸载避免占用内存</li>
<li>资源包合理分组，避免出现某个包过大、牵扯到很多功能模块的情况、避免包与包之间出现重复引用</li>
</ul>
<h1>23.08.05</h1>
<p dir="auto">断断续续做了快两个月了，继续小总结一下，主要是加载方面。</p>
<p dir="auto">把<a href="https://github.com/wechat-miniprogram/minigame-unity-webgl-transform" rel="nofollow ugc">官方文档</a>中能做的都做了之后，接下来基本就是游戏自身的调整优化了，对于中轻度单机，影响性能的大概有以下几点（同时也是新手非常容易忽视的）：</p>
<ul>
<li>
<p dir="auto">资源加载与卸载</p>
</li>
<li>
<p dir="auto">复杂物体的实例化</p>
</li>
<li>
<p dir="auto">单帧内耗时/耗内存逻辑（特别是Awake Start）</p>
</li>
<li>
<p dir="auto">配置表的加载</p>
</li>
</ul>
<p dir="auto">这些对启动性能和游戏内动态加载一些物体时的影响较大。比起APP，小游戏对启动时间更为敏感，较长时间的加载很容易造成用户流失，如果设计上是先进入大厅的话加载的负担会少一些，而直接进入战斗的游戏对于加载优化的要求则更高。</p>
<p dir="auto">对于后者，等所有资源加载完黄花菜都凉了，一个思路是仅集中加载极少部分战斗必要资源（显示上转个菊花），进入战斗后在游玩的同时，陆续加载其他功能模块、UI等等。</p>
<p dir="auto">Unity转换微信小游戏必须有第一段Unity Loader的加载过程，根据项目情况（主要是C#代码的多少）耗时2~10s甚至更多，从用户体验上来说，第一段Unity Loader进度条走完后，不应该再展示游戏自身的加载进度条了。</p>
<p dir="auto">这个在设计上优化体验也有很多解决办法，这里说回程序方面。</p>
<h2>一招鲜吃遍天</h2>
<p dir="auto">分帧加载，分帧加载，还是™的分帧加载！配合UniTask食用更佳。</p>
<h2>资源加载与卸载</h2>
<p dir="auto">启动时加载的资源涉及到的资源包应该尽量少，可以通过Addressables的Event Viewer查看启动时加载了哪些bundle，用Analyze分析引用关系，然后做拆分。<br />
Addressables在加载较大资源时是有可能引起帧数波动的，严重时甚至会掉到10多帧，如果此时没有进度条的保护，而是处于游玩阶段，体验自然会非常糟糕。对于音频，可以考虑用流式传输，用平台提供的接口而不是Unity的接口来播放。<br />
资源的卸载算是基本功，但不知道为什么很多人都做不好（更恶劣的是会做但偷懒不做），做一个功能但不做资源卸载在早期测试中不容易发现问题，但后期甚至线上出现爆内存再来改就为时已晚了。<br />
卸载的原则也很简单就是当一个资源在未来不会用到，或者很长一段时间不会用到，并且有合适的卸载时机，则应该将其卸载以释放内存。卸载的时机可以参考物体销毁和主动GC的时机，比如出副本、切换关卡等等，尽量不要让玩家感知到卡顿。<br />
由于Addressables内部使用引用计数，加载和卸载应该成对出现，<code>LoadAssetAsync</code> 与 <code>Release</code> 成对，<code>InstantiateAsync</code> 与 <code>ReleaseInstance</code> 成对，引用计数为零时才会真正地卸载。<br />
下面是一个反面教材：</p>
<pre><code class="language-C#">private async void InstantiateSomeGo()  
{  
	var go = await Addressables.InstantiateAsync("SomePrefab").ToUniTask();  
	// 然后不管了
}
</code></pre>
<p dir="auto">一般来说会自己再封装一层，管理加载过的资源或实例化过的物体，需要时卸载。</p>
<h2>复杂物体的实例化</h2>
<p dir="auto">掉帧的元凶之一，物体过于复杂时，实例化耗时越久、牵扯到的资源也可能越多。例如实例化一个含有100个子物体的预制体，首先Addressables要加载它所有直接和间接依赖资源所在的bundle，然后从bundle中加载出这些资源，这是第一个耗时阶段；随后将在1帧内执行100多个物体的实例化，包括所有组件的Awake OnEnable Start，这一趟下来CPU和内存都被搞得叫苦不迭，如果机器也有神，写出这种代码的程序员恐怕是第一批被降下神罚的。<br />
解决办法就是分帧加载，将预制体下的子物体视情况拆分成其他预制体，例如少的话拆成2~3个，多则5~10个，在实例化完父物体后，分帧实例化子物体到父物体下，如果组件的Awake、Start中有耗时的初始化操作，也可将其挪到一个异步的实例化函数中，分帧调用。<br />
对于分帧加载，不太推荐使用协程，书写繁琐可读性差并且容易产生回调地狱，用UniTask来async/await是更加优雅的做法。<br />
一个示例：</p>
<pre><code class="language-C#">public class Test
{
    private ParentObj m_ParentObj;
    
    public async void LoadParentObj()
    {
        // 加载父物体
        var parentGo = await Addressables.InstantiateAsync("ParentObj").ToUniTask();
        m_ParentObj = parentGo.GetComponent&lt;ParentObj&gt;();
        // 延时1帧
        await UniTask.DelayFrame(1);
        // 初始化父物体
        await m_ParentObj.Init();
    }
    
    public void Release()
    {
        // 卸载物体
    }
}

public class ParentObj : MonoBehaviour
{
    private GameObject m_ChildA;
    private GameObject m_ChildB;
    private GameObject m_ChildC;
    private GameObject m_ChildD;
    private bool m_Initiated;
    
    // 避免写Awake Start
    
    public async UniTask Init()
    {
        // 分帧加载子物体，每加载完一个等待一帧
        m_ChildA = await Addressables.InstantiateAsync("ChildObjA", transform).ToUniTask();
        await UniTask.DelayFrame(1);
        m_ChildB = await Addressables.InstantiateAsync("ChildObjB", transform).ToUniTask();
        await UniTask.DelayFrame(1);
        m_ChildC = await Addressables.InstantiateAsync("ChildObjC", transform).ToUniTask();
        await UniTask.DelayFrame(1);
        m_ChildD = await Addressables.InstantiateAsync("ChildObjD", transform).ToUniTask();
        await m_ChildA.GetComponent&lt;SomeComponent&gt;().Init();
        await UniTask.DelayFrame(1);
        await m_ChildB.GetComponent&lt;SomeComponent&gt;().Init();
        m_Initiated = true;
    }
    public void Release()
    {
        // 卸载子物体
    }
}

public class SomeComponent : MonoBehaviour
{
    public UniTask Init()
    {
        //初始化操作
    }
}
</code></pre>
<p dir="auto">用这种方式加载UI时，可以得到比较好的效果，例如加载一个商店页面，可以做到背景-&gt;货架-&gt;货品这样一个顺序加载显示，而不是卡住一段时间后再全部显示。</p>
<h2>配置表的加载</h2>
<p dir="auto">因项目而异，不同项目有不同的表加载方式，这里只说下目前遇到的问题，之后再看看鲁班的加载是否会有更好的性能。<br />
目前的表加载是自己造的轮子，加载过程大致为：加载JSON文件到内存 -&gt; 转换为JSON对象 -&gt; 将每行数据存到数组中 -&gt; 构建 ID:下标 索引字典 -&gt; 释放资源。<br />
主要耗时在第四步构建索引，需要遍历一遍数据，构建一个Key为ID，Value为数组下标的字典，方便后续根据ID来检索表中某一行的数据。实际测试这一步在不同配置的机器上有很大的差异（应该跟CPU的计算能力有关），一个上万行的表，有些机器不到1s就能跑完，而有些机器则卡住10s之多。<br />
解决办法可以分帧，也可以考虑拆表精简表，之后有空看看鲁班加载这种表的性能如何。</p>
<p dir="auto">接下来是运行性能方面的优化了。</p>
]]></description><link>http://designhub.top/topic/51/unity-小游戏转换优化笔记</link><generator>RSS for Node</generator><lastBuildDate>Sat, 14 Mar 2026 23:11:21 GMT</lastBuildDate><atom:link href="http://designhub.top/topic/51.rss" rel="self" type="application/rss+xml"/><pubDate>Sat, 05 Aug 2023 03:54:44 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to [Unity]小游戏转换优化笔记 on Sat, 09 Sep 2023 12:03:51 GMT]]></title><description><![CDATA[<h1>23.09.08</h1>
<p dir="auto">已经...没有什么运行性能方面的优化了...</p>
<p dir="auto">如果在项目之初就已经确定目标平台是移动端或小游戏，在基础框架选型、造轮子、后续的开发中都注意了上面提到的各种问题，每个开发人员都有对性能优化的基础理解，那么只要客户端逻辑不是非常重度，相信性能都不会太差；</p>
<p dir="auto">但如果项目初期没有做这方面的考量，使用的基础框架未针对目标平台优化，后续开发时较少关注性能优化（或者没有认知），写完功能后缺乏真机环境下测试，连最核心的游戏逻辑单独拎出来都没法在云测试上跑到及格分，这种情况只能说是无力回天，还是多花点时间重构吧</p>
]]></description><link>http://designhub.top/post/87</link><guid isPermaLink="true">http://designhub.top/post/87</guid><dc:creator><![CDATA[Pamisu]]></dc:creator><pubDate>Sat, 09 Sep 2023 12:03:51 GMT</pubDate></item></channel></rss>