08-04-热更新-首包资源策略

热更新-首包资源策略

篇章 :08-核心技术-热更新系统 状态 :已完成 阅读时间:约 20 分钟


一、引言

1.1 本章在系列中的定位

首包资源策略是 YooAsset 热更新架构设计中最关键的一环。它决定了玩家第一次下载游戏时,游戏包有多大、进入游戏需要等多长时间、后续更新要下载多少内容。

如果说版本管理是"物流系统的单号系统",那么首包资源策略就是"仓库的初始库存":放太多会撑爆仓库(包体积超大),放太少又会缺货(玩家进入游戏就要等下载)。

1.2 本章要解决的核心问题

  • 首包应该放什么资源?什么资源应该走热更?
  • 三种主流策略(零资源、全量、混合)如何选择?
  • 资源分包如何规划?按场景?按功能?
  • 首包大小如何控制?业内标准是什么?

1.3 阅读前置要求

  • 了解 YooAsset 的构建流程(参考 04-源码深度-编辑器模块)
  • 了解 YooAsset 的运行模式(EditorPlayMode、HostPlayMode)
  • 了解 Unity 出包流程(Android / iOS 包体限制)

二、零资源安装包

2.1 零资源安装包的概念

零资源安装包 (也称"裸包"、"超小包")是指游戏的安装包内只包含最核心的启动资源,其他所有资源都通过热更下载。

python 复制代码
零资源包内容:
├── 启动代码(C# DLL)
├── YooAsset Runtime
├── 启动场景(最小化)
├── 启动资源(Logo、Loading、配置)
└── 初始清单(PackageManifest_1.0.0.bytes)

2.2 零资源安装包的实现

2.2.1 核心资源识别

首包中必须包含的资源:

类别 内容 必要性
启动代码 YooAsset Runtime、Hotfix DLL 必须
启动场景 Splash、Loading 必须
启动 UI Logo、Loading 界面 必须
核心配置 版本配置、玩家配置 必须
必要资源 字体、默认 UI、主城极简资源 必须
业务资源 关卡、角色、UI 等 不必
2.2.2 YooAsset 的"零资源"配置

在 YooAsset 的 IBuildPipeline 中,可以通过 PackRule 控制资源的归属:

csharp 复制代码
public class ZeroResourceBuildPipeline : IBuildPipeline
{
    public void CreateBuild()
    {
        // 1. 启动 Bundle 放首包
        var startupRule = new PackRule
        {
            Name = "StartupPack",
            Filter = "Assets/Startup/**/*",
            PackTo = EBundlePackTarget.InstallPackage  // 打包到首包
        };
        
        // 2. 其他所有资源都走热更
        var normalRule = new PackRule
        {
            Name = "NormalPack",
            Filter = "Assets/**/*",
            PackTo = EBundlePackTarget.HotUpdatePackage  // 打包到热更包
        };
    }
}
2.2.3 启动流程
csharp 复制代码
public class ZeroResourceStartup
{
    public async Task StartupAsync()
    {
        // 1. 加载首包中的启动资源
        var logoOp = YooAssets.LoadAssetAsync<Sprite>("Assets/Startup/UI/Logo.png");
        await logoOp.Task;
        ShowLogo(logoOp.Result);
        
        // 2. 初始化 YooAsset(连接 CDN)
        await YooAssets.InitializeAsync();
        
        // 3. 检查版本
        var checkOp = YooAssets.RequestCheckVersionAsync();
        await checkOp.Task;
        
        if (checkOp.Result.IsNewVersion)
        {
            // 4. 下载热更资源
            var downloadOp = YooAssets.DownloadUpdateAsync();
            ShowDownloadProgress(downloadOp);
            await downloadOp.Task;
        }
        
        // 5. 进入主城
        var mainCityOp = YooAssets.LoadSceneAsync("Assets/Scenes/MainCity.unity");
        await mainCityOp.Task;
    }
}

2.3 零资源安装包的挑战

2.3.1 启动等待
玩家网络 启动资源大小 预计下载时间
4G 200MB 2-3 分钟
WiFi 200MB 30-60 秒
弱网 200MB 5-10 分钟

启动等待是零资源包最大的痛点,必须有良好的 Loading 体验:

  • 进度可视化:明确告诉玩家"正在下载什么"
  • 动画过渡:用精致的动画掩盖等待感
  • 后台下载:在 Loading 期间同时下载多个资源
  • 可取消:允许玩家取消(不推荐强引导下载)
2.3.2 网络依赖

零资源包强依赖网络,离线状态下游戏完全无法启动。应对方案:

csharp 复制代码
public class OfflineFallback
{
    public void OnStartupFailed()
    {
        // 如果热更下载失败
        if (!NetworkManager.IsAvailable())
        {
            // 检查本地是否有任何可用清单
            if (HasLocalManifest())
            {
                // 使用本地清单进入游戏(资源可能不完整)
                YooAssets.ForceChangeVersion(GetLocalVersion());
                EnterGame();
            }
            else
            {
                // 完全没有资源,提示玩家联网
                ShowOfflineHint();
            }
        }
    }
}
2.3.3 用户体验挑战
  • 首次启动慢:玩家期望"秒开",零资源包无法满足
  • 心理预期管理:在应用商店描述、买量素材中说明游戏大小
  • 玩家流失风险:启动等待期间的流失率明显高于全量包

三、全量包

3.1 全量包的概念

全量包 (也称"完整包"、"大包")是指游戏的安装包内包含所有资源,玩家下载安装后无需热更即可完整体验游戏。

复制代码
全量包内容:
├── 启动代码
├── YooAsset Runtime
├── 启动场景
├── 所有 UI
├── 所有关卡
├── 所有角色
├── 所有音频
└── 完整初始清单

3.2 全量包的优势

3.2.1 体验最佳
维度 全量包 零资源包
启动时间 秒开 几分钟
离线游玩 完全支持 不支持
玩家预期 与商店描述一致 需额外说明
留存率 高 略低
3.2.2 适用场景
  • 休闲单机游戏:如消除、卡牌、单机 RPG
  • 小游戏(微信、抖音):对包体极度敏感,资源总量小
  • 离线优先游戏:玩家可能在飞行模式下玩
  • Demo 版本:让玩家完整体验后再决定是否深入
3.2.3 YooAsset 全量包配置
csharp 复制代码
public class FullResourceBuildPipeline : IBuildPipeline
{
    public void CreateBuild()
    {
        // 所有资源都打包到首包
        var fullRule = new PackRule
        {
            Name = "FullPack",
            Filter = "Assets/**/*",
            PackTo = EBundlePackTarget.InstallPackage
        };
        
        // 仍可保留热更通道(用于后续更新)
        var hotUpdateRule = new PackRule
        {
            Name = "HotUpdatePack",
            Filter = "Assets/HotUpdate/**/*",
            PackTo = EBundlePackTarget.HotUpdatePackage
        };
    }
}

3.3 全量包的挑战

3.3.1 包体积过大
平台 包体限制 后果
iOS App Store 4GB(蜂窝下载限制 200MB) 超过 200MB 必须 WiFi 下载
Google Play 150MB(APK) 超过需用 AAB + 扩展包
Android(国内) 渠道差异较大 小米 100MB、华为 200MB
微信小游戏 20MB(首包) 资源必须全部走 CDN
抖音小游戏 10MB(首包) 资源必须全部走 CDN
3.3.2 更新成本高

每次发版需要重新下载整个包:

  • 1.0.5 → 1.0.6 改动 50MB 资源
  • 全量包:必须下载完整新包(可能是几 GB)
  • 增量包:只下载 50MB 差异
3.3.3 存储占用大

玩家设备上需要保留所有资源,对于存储空间紧张的低端机不友好。


四、混合策略

4.1 混合策略的概念

混合策略是商业项目最常用的方案:核心资源放首包,扩展资源走热更。

markdown 复制代码
混合包内容:
├── 启动代码
├── 核心资源(必须)
│   ├── Logo、Loading
│   ├── 主城基础场景
│   ├── 玩家创建角色资源
│   ├── 主线关卡 1-5
│   └── 字体、默认 UI
│
└── 扩展资源(热更)
    ├── 支线关卡 6+
    ├── 活动资源
    ├── 商城皮肤
    └── 远期 DLC

4.2 混合策略的实现

4.2.1 资源分类原则

判断一个资源是否应该放首包:

原则 含义 示例
启动必需 没有它游戏无法启动 Logo、Loading、字体
早期必须 玩家前 10 分钟会用到 主城、初始角色、新手关
网络无法访问 玩家可能完全没网 关键剧情资源
体积关键 资源较小 小 UI、配置表
大型可选 资源大但不急需 高清材质、远期皮肤
4.2.2 首包大小控制

业内参考标准:

平台 推荐首包大小 极限首包大小
iOS < 200MB < 1GB
Android < 150MB < 500MB
小游戏 < 10MB < 20MB
海外 < 100MB < 300MB
4.2.3 YooAsset 的混合配置
csharp 复制代码
public class HybridBuildPipeline : IBuildPipeline
{
    public List<PackRule> GetPackRules()
    {
        return new List<PackRule>
        {
            // 1. 启动必备:打包到首包
            new PackRule
            {
                Name = "StartupPack",
                Filter = "Assets/Startup/**/*",
                PackTo = EBundlePackTarget.InstallPackage
            },
            
            // 2. 主线关卡:打包到首包
            new PackRule
            {
                Name = "MainLevelPack",
                Filter = "Assets/Levels/Main/**/*.unity",
                PackTo = EBundlePackTarget.InstallPackage
            },
            
            // 3. 主城基础:打包到首包
            new PackRule
            {
                Name = "MainCityPack",
                Filter = "Assets/City/MainCity/**/*",
                PackTo = EBundlePackTarget.InstallPackage
            },
            
            // 4. 其他资源:打包到热更包
            new PackRule
            {
                Name = "HotUpdatePack",
                Filter = "Assets/**/*",
                PackTo = EBundlePackTarget.HotUpdatePackage,
                ExcludeFilters = new[] { "Assets/Startup/**/*", "Assets/Levels/Main/**/*", "Assets/City/MainCity/**/*" }
            }
        };
    }
}

4.3 混合策略的优化

4.3.1 核心资源识别

精确识别"核心资源"是混合策略的关键。常见方法:

csharp 复制代码
public class CoreResourceAnalyzer
{
    /// <summary>
    /// 通过玩家行为日志识别核心资源
    /// </summary>
    public List<string> IdentifyCoreResources()
    {
        var stats = LoadPlayerBehaviorStats();
        
        var coreResources = stats.Resources
            .Where(r => r.FirstSessionUsageRate > 0.8)  // 80% 玩家首次会话就用到
            .Where(r => r.First10MinUsageRate > 0.5)   // 50% 玩家在 10 分钟内用到
            .Select(r => r.ResourcePath)
            .ToList();
        
        return coreResources;
    }
}
4.3.2 首包大小控制技巧
技巧 说明 效果
资源压缩 LZ4 / LZMA 减少 30-50%
图片压缩 Crunch、ASTC 减少 50%+
音频压缩 Vorbis、OPus 减少 70%+
网格简化 LOD、减面 减少 50%+
剔除冗余 资源去重 减少 10-30%
拆分初始 启动最简,其他延迟 减少 50%+
4.3.3 渐进式加载
csharp 复制代码
public class ProgressiveLoad
{
    public async Task ProgressiveLoadMainCity()
    {
        // 1. 先加载最核心的(主城框架 + 玩家自己)
        await LoadBundles(new[] { "maincity_framework", "player_self" });
        
        // 2. 显示主城,玩家可以开始移动
        EnterMainCity();
        
        // 3. 后台加载其他玩家、NPC、装饰
        await LoadBundlesBackground(new[] { "maincity_others", "maincity_npc", "maincity_decoration" });
    }
}

五、分包策略

5.1 分包策略的概念

分包策略 是指将热更资源按某种规则拆分为多个包,按需下载。它的本质是"更细粒度的混合策略"。

css 复制代码
分包示例:
├── 首包(200MB)
│   ├── 启动资源
│   └── 主线前 5 关
│
├── 热更包 A(活动包,200MB)
│   ├── 春节活动
│   ├── 圣诞活动
│   └── 节日 UI
│
├── 热更包 B(DLC 包,500MB)
│   ├── 资料片 1
│   ├── 资料片 2
│   └── 新角色
│
└── 热更包 C(语音包,300MB)
    ├── 中文语音
    ├── 英文语音
    └── 日文语音

5.2 分包策略的实现

5.2.1 按功能分包
csharp 复制代码
public class FunctionalPackRule
{
    public List<PackRule> GetRules()
    {
        return new List<PackRule>
        {
            new PackRule
            {
                Name = "ActivityPack",
                Filter = "Assets/Activities/**/*",
                PackTo = EBundlePackTarget.HotUpdatePackage,
                Tag = "activity"  // 标签:活动包
            },
            new PackRule
            {
                Name = "DLCPack",
                Filter = "Assets/DLC/**/*",
                PackTo = EBundlePackTarget.HotUpdatePackage,
                Tag = "dlc"  // 标签:DLC 包
            },
            new PackRule
            {
                Name = "LanguagePack",
                Filter = "Assets/Audio/Localized/**/*",
                PackTo = EBundlePackTarget.HotUpdatePackage,
                Tag = "language"  // 标签:语言包
            }
        };
    }
}
5.2.2 按场景分包
csharp 复制代码
public class ScenePackRule
{
    public List<PackRule> GetRules()
    {
        var rules = new List<PackRule>();
        var scenes = GetAllScenes();
        
        foreach (var scene in scenes)
        {
            rules.Add(new PackRule
            {
                Name = $"{scene.Name}Pack",
                Filter = $"Assets/Scenes/{scene.Name}/**/*",
                PackTo = EBundlePackTarget.HotUpdatePackage,
                Tag = $"scene_{scene.Name}"
            });
        }
        
        return rules;
    }
}
5.2.3 按类型分包
csharp 复制代码
public class TypePackRule
{
    public List<PackRule> GetRules()
    {
        return new List<PackRule>
        {
            new PackRule
            {
                Name = "TexturePack",
                Filter = "Assets/Textures/**/*",
                Tag = "texture"
            },
            new PackRule
            {
                Name = "AudioPack",
                Filter = "Assets/Audio/**/*",
                Tag = "audio"
            },
            new PackRule
            {
                Name = "PrefabPack",
                Filter = "Assets/Prefabs/**/*",
                Tag = "prefab"
            }
        };
    }
}

5.3 分包策略的挑战

5.3.1 包管理复杂

分包越多,包管理越复杂。YooAsset 通过 Package 概念管理多个包:

csharp 复制代码
// 在 YooAssets.InitializeAsync 时配置多个包
public async Task InitializeMultiplePackages()
{
    // 1. 初始化主包
    await YooAssets.InitializeAsync();
    
    // 2. 按需加载其他包
    var activityOp = YooAssets.LoadPackageAsync("ActivityPackage");
    await activityOp.Task;
    
    var dlcOp = YooAssets.LoadPackageAsync("DLCPackage");
    await dlcOp.Task;
}
5.3.2 依赖关系管理

包之间存在依赖关系,需要精心设计:

css 复制代码
包 A(活动) 依赖 包 X(公共 UI)
包 B(DLC)  依赖 包 X(公共 UI)

如果只下载包 A 而不下载包 X,会导致 UI 缺失
csharp 复制代码
public class PackageDependency
{
    public List<string> GetDependencies(string packageName)
    {
        return packageName switch
        {
            "ActivityPackage" => new[] { "CommonUIPackage" },
            "DLCPackage" => new[] { "CommonUIPackage" },
            "LanguagePackage" => new[] { "CommonAudioPackage" },
            _ => new List<string>()
        };
    }
    
    public async Task LoadPackageWithDependencies(string packageName)
    {
        var deps = GetDependencies(packageName);
        foreach (var dep in deps)
        {
            await YooAssets.LoadPackageAsync(dep);
        }
        await YooAssets.LoadPackageAsync(packageName);
    }
}
5.3.3 下载调度

多包场景下,需要一个统一的下载调度器:

csharp 复制代码
public class MultiPackageDownloadScheduler
{
    public async Task DownloadAll(List<PackageInfo> packages)
    {
        // 1. 构建全局下载队列
        var allBundles = new List<BundleInfo>();
        foreach (var pkg in packages)
        {
            allBundles.AddRange(pkg.PendingBundles);
        }
        
        // 2. 去重(多个包可能依赖同一个 Bundle)
        allBundles = allBundles.Distinct().ToList();
        
        // 3. 排序(共享资源优先)
        allBundles = allBundles
            .OrderByDescending(b => b.IsShared)
            .ThenBy(b => b.Priority)
            .ToList();
        
        // 4. 执行下载
        var op = YooAssets.DownloadUpdateAsync(bundles: allBundles);
        await op.Task;
    }
}

六、首包规划的实战建议

6.1 首包资源清单模板

markdown 复制代码
首包资源清单 (项目: MyGame)
========================

[启动核心] 5MB
- Logo (3MB)
- Loading UI (0.5MB)
- 启动场景 (1.5MB)

[字体] 8MB
- 默认字体 (5MB)
- 多语言字体 (3MB)

[主城] 30MB
- 主城场景 (15MB)
- 主城 UI (5MB)
- 主城 NPC (5MB)
- 主城特效 (5MB)

[新手关卡] 50MB
- 关卡 1-1 (15MB)
- 关卡 1-2 (15MB)
- 关卡 1-3 (15MB)
- 新手教学 UI (5MB)

[通用配置] 2MB
- 角色配置表
- 关卡配置表
- 道具配置表

[音频核心] 10MB
- 背景音乐 1 首
- 通用音效 100 个

首包总计: ~105MB

6.2 决策流程图

css 复制代码
[开始]
  │
  ▼
[游戏是单机还是联机?]
  │
  ├── 单机 → [是否需要离线游玩?]
  │            │
  │            ├── 是 → [资源总量是否 < 200MB?]
  │            │         │
  │            │         ├── 是 → 全量包
  │            │         └── 否 → 混合策略
  │            │
  │            └── 否 → 零资源包
  │
  └── 联机 → [玩家流失敏感度?]
              │
              ├── 高 → 混合策略
              │
              └── 低 → 零资源包

6.3 实践经验

  1. 首包要"刚好够用":放太多浪费玩家流量,放太少启动等待长
  2. 首包决策要做 AB 测试:同样游戏,不同首包大小,看哪个留存高
  3. 热更资源要分批上线:新版本分多次热更,避免一次下载量太大
  4. 下载要可恢复:网络中断后下次启动继续,不从头开始
  5. 监控先行:上线后必须监控首包下载成功率、平均时长

七、总结

6.1 本章要点回顾

  • 零资源包:包体最小,但启动慢、强依赖网络
  • 全量包:体验最好,但包体大、更新成本高
  • 混合策略:商业项目首选,平衡包体与体验
  • 分包策略:混合策略的延伸,按需拆分资源

6.2 与前后章节的关联

  • 前章:08-03 边玩边下载解决"运行时下载"
  • 本章:解决"什么应该预下载、什么应该运行时下载"
  • 后章:08-05 完整热更新流程实战串联所有知识点

6.3 实践建议

  1. 首包大小控制在 100-200MB:兼顾玩家体验与平台限制
  2. 核心资源识别要数据驱动:用玩家行为日志辅助决策
  3. 混合策略是商业项目首选,零资源/全量包只在特殊场景使用
  4. 分包要按"业务可独立"原则:每个分包都应该有明确的业务边界
  5. 首包决策要做 AB 测试:不同首包大小对留存的影响显著

下一篇 :完整热更新流程实战

相关推荐
SmalBox1 天前
08-03-热更新-边玩边下载
unity3d·游戏开发
西风逍遥游2 天前
从 EveScript 到 B站 Toy:一个 WebGPU 游戏发布插件怎样设计
游戏开发
西风逍遥游2 天前
500 单位 RTS 为什么还没到 60 FPS:一次 WebGPU 游戏性能诊断
游戏开发
甲维斯2 天前
Codex,Zcode,KimiCode电脑操作能力对比!
人工智能·游戏开发
SmalBox2 天前
08-02-热更新-差量更新与断点续传
unity3d·游戏开发
UWA2 天前
多人战斗中Lua持续高耗时,是瞬时峰值还是高频调用在作祟
性能优化·lua·游戏开发
ZzT3 天前
vibe coding 做小游戏·问题排查篇:解决包体积、引擎适配与 iPhone 机型适配问题
ai编程·游戏开发
SmalBox3 天前
08-01-热更新-版本管理机制
unity3d·游戏开发
Aman19843 天前
Claude Code做Unity项目移植(二): 自动执行只读操作,告别频繁的
unity·游戏开发·vibe coding·claude code