YooAsset运行机制
·
Unity 游戏启动流程(Boot脚本)
功能概述:
- 初始化游戏运行环境(帧率、后台运行等)
- 初始化核心系统(事件系统、资源系统)
- 加载并显示补丁更新界面
- 启动补丁更新流程(异步)
- 更新完成后切换到主场景
public class Boot : MonoBehaviour
{
/// <summary>
/// 资源系统运行模式
/// </summary>
public EPlayMode PlayMode = EPlayMode.EditorSimulateMode;
void Awake()
{
Debug.Log($"资源系统运行模式:{PlayMode}");
Application.targetFrameRate = 60;
Application.runInBackground = true;
DontDestroyOnLoad(this.gameObject);
}
IEnumerator Start()
{
// 游戏管理器
GameManager.Instance.Behaviour = this;
// 初始化事件系统
UniEvent.Initalize();
// 初始化资源系统
YooAssets.Initialize();
// 加载补丁更新页面
var go = Resources.Load<GameObject>("PatchWindow");
GameObject.Instantiate(go);
// 开始补丁更新流程
var operation = new PatchOperation("DefaultPackage", PlayMode);
YooAssets.StartOperation(operation);
yield return operation;
// 设置默认的资源包
var gamePackage = YooAssets.GetPackage("DefaultPackage");
YooAssets.SetDefaultPackage(gamePackage);
// 切换到主页面场景
SceneEventDefine.ChangeToHomeScene.SendEventMessage();
}
}
如果你希望支持纯离线模式(比如单机游戏不更新),通常会在 Boot.Start() 中判断 PlayMode,跳过 PatchOperation 直接进入游戏。
补丁更新操作(异步状态机)PatchOperation
使用
UniFramework.Machine.StateMachine构建一个状态机,包含多个状态节点(FsmXXX):
状态节点 功能 FsmInitializePackage初始化资源包 FsmRequestPackageVersion请求远程版本信息 FsmUpdatePackageManifest更新清单文件 FsmCreateDownloader创建下载器 FsmDownloadPackageFiles下载资源文件 FsmDownloadPackageOver下载完成处理 FsmClearCacheBundle清理缓存 FsmStartGame准备进入游戏
using UnityEngine;
using UniFramework.Machine;
using UniFramework.Event;
using YooAsset;
public class PatchOperation : GameAsyncOperation
{
private enum ESteps
{
None,
Update,
Done,
}
private readonly EventGroup _eventGroup = new EventGroup();
private readonly StateMachine _machine;
private readonly string _packageName;
private ESteps _steps = ESteps.None;
public PatchOperation(string packageName, EPlayMode playMode)
{
_packageName = packageName;
// 注册监听事件
_eventGroup.AddListener<UserEventDefine.UserTryInitialize>(OnHandleEventMessage);
_eventGroup.AddListener<UserEventDefine.UserBeginDownloadWebFiles>(OnHandleEventMessage);
_eventGroup.AddListener<UserEventDefine.UserTryRequestPackageVersion>(OnHandleEventMessage);
_eventGroup.AddListener<UserEventDefine.UserTryUpdatePackageManifest>(OnHandleEventMessage);
_eventGroup.AddListener<UserEventDefine.UserTryDownloadWebFiles>(OnHandleEventMessage);
// 创建状态机
_machine = new StateMachine(this);
_machine.AddNode<FsmInitializePackage>();
_machine.AddNode<FsmRequestPackageVersion>();
_machine.AddNode<FsmUpdatePackageManifest>();
_machine.AddNode<FsmCreateDownloader>();
_machine.AddNode<FsmDownloadPackageFiles>();
_machine.AddNode<FsmDownloadPackageOver>();
_machine.AddNode<FsmClearCacheBundle>();
_machine.AddNode<FsmStartGame>();
_machine.SetBlackboardValue("PackageName", packageName);
_machine.SetBlackboardValue("PlayMode", playMode);
}
protected override void OnStart()
{
_steps = ESteps.Update;
_machine.Run<FsmInitializePackage>();
}
protected override void OnUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.Update)
{
_machine.Update();
}
}
protected override void OnAbort()
{
}
public void SetFinish()
{
_steps = ESteps.Done;
_eventGroup.RemoveAllListener();
Status = EOperationStatus.Succeed;
Debug.Log($"Package {_packageName} patch done !");
}
/// <summary>
/// 接收事件
/// </summary>
private void OnHandleEventMessage(IEventMessage message)
{
if (message is UserEventDefine.UserTryInitialize)
{
_machine.ChangeState<FsmInitializePackage>();
}
else if (message is UserEventDefine.UserBeginDownloadWebFiles)
{
_machine.ChangeState<FsmDownloadPackageFiles>();
}
else if (message is UserEventDefine.UserTryRequestPackageVersion)
{
_machine.ChangeState<FsmRequestPackageVersion>();
}
else if (message is UserEventDefine.UserTryUpdatePackageManifest)
{
_machine.ChangeState<FsmUpdatePackageManifest>();
}
else if (message is UserEventDefine.UserTryDownloadWebFiles)
{
_machine.ChangeState<FsmCreateDownloader>();
}
else
{
throw new System.NotImplementedException($"{message.GetType()}");
}
}
}
FsmInitializePackage(有限状态机(FSM)的流程控制架构)
这段代码是 热更新/资源管理系统中的一个状态节点(State Node),它实现了 IStateNode 接口,属于一个 基于有限状态机(FSM)的流程控制架构。其核心作用是:
该状态节点负责:
- 从状态机的“黑板”(Blackboard)中读取:
- 资源包名称(
PackageName)- 当前运行模式(
PlayMode:编辑器模拟、离线、联机、WebGL 等)- 使用 YooAsset(一个 Unity 资源管理框架)创建并初始化对应的资源包(
Package)。- 根据不同平台和运行模式,配置不同的文件系统参数(如本地内置资源、缓存、远程服务器等)。
- 异步执行初始化操作(
InitializeAsync)。- 初始化成功后,切换状态机到下一个状态:
FsmRequestPackageVersion(请求资源包版本)。- 若失败,则发送失败事件(
InitializeFailed),通常用于弹出错误提示。
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UniFramework.Machine;
using YooAsset;
internal class FsmInitializePackage : IStateNode
{
private StateMachine _machine;
void IStateNode.OnCreate(StateMachine machine)
{
_machine = machine;
}
void IStateNode.OnEnter()
{
PatchEventDefine.PatchStepsChange.SendEventMessage("初始化资源包!");
GameManager.Instance.StartCoroutine(InitPackage());
}
void IStateNode.OnUpdate()
{
}
void IStateNode.OnExit()
{
}
private IEnumerator InitPackage()
{
var playMode = (EPlayMode)_machine.GetBlackboardValue("PlayMode");
var packageName = (string)_machine.GetBlackboardValue("PackageName");
// 创建资源包裹类
var package = YooAssets.TryGetPackage(packageName);
if (package == null)
package = YooAssets.CreatePackage(packageName);
// 编辑器下的模拟模式
InitializationOperation initializationOperation = null;
if (playMode == EPlayMode.EditorSimulateMode)
{
var buildResult = EditorSimulateModeHelper.SimulateBuild(packageName);
var packageRoot = buildResult.PackageRootDirectory;
var createParameters = new EditorSimulateModeParameters();
createParameters.EditorFileSystemParameters = FileSystemParameters.CreateDefaultEditorFileSystemParameters(packageRoot);
initializationOperation = package.InitializeAsync(createParameters);
}
// 单机运行模式
if (playMode == EPlayMode.OfflinePlayMode)
{
var createParameters = new OfflinePlayModeParameters();
createParameters.BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
initializationOperation = package.InitializeAsync(createParameters);
}
// 联机运行模式
if (playMode == EPlayMode.HostPlayMode)
{
string defaultHostServer = GetHostServerURL();
string fallbackHostServer = GetHostServerURL();
IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
var createParameters = new HostPlayModeParameters();
createParameters.BuildinFileSystemParameters = FileSystemParameters.CreateDefaultBuildinFileSystemParameters();
createParameters.CacheFileSystemParameters = FileSystemParameters.CreateDefaultCacheFileSystemParameters(remoteServices);
initializationOperation = package.InitializeAsync(createParameters);
}
// WebGL运行模式
if (playMode == EPlayMode.WebPlayMode)
{
#if UNITY_WEBGL && WEIXINMINIGAME && !UNITY_EDITOR
var createParameters = new WebPlayModeParameters();
string defaultHostServer = GetHostServerURL();
string fallbackHostServer = GetHostServerURL();
string packageRoot = $"{WeChatWASM.WX.env.USER_DATA_PATH}/__GAME_FILE_CACHE"; //注意:如果有子目录,请修改此处!
IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
createParameters.WebServerFileSystemParameters = WechatFileSystemCreater.CreateFileSystemParameters(packageRoot, remoteServices);
initializationOperation = package.InitializeAsync(createParameters);
#else
var createParameters = new WebPlayModeParameters();
createParameters.WebServerFileSystemParameters = FileSystemParameters.CreateDefaultWebServerFileSystemParameters();
initializationOperation = package.InitializeAsync(createParameters);
#endif
}
yield return initializationOperation;
// 如果初始化失败弹出提示界面
if (initializationOperation.Status != EOperationStatus.Succeed)
{
Debug.LogWarning($"{initializationOperation.Error}");
PatchEventDefine.InitializeFailed.SendEventMessage();
}
else
{
_machine.ChangeState<FsmRequestPackageVersion>();
}
}
/// <summary>
/// 获取资源服务器地址
/// </summary>
private string GetHostServerURL()
{
//string hostServerIP = "http://10.0.2.2"; //安卓模拟器地址
string hostServerIP = "http://127.0.0.1";
string appVersion = "v1.0";
#if UNITY_EDITOR
if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.Android)
return $"{hostServerIP}/CDN/Android/{appVersion}";
else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.iOS)
return $"{hostServerIP}/CDN/IPhone/{appVersion}";
else if (UnityEditor.EditorUserBuildSettings.activeBuildTarget == UnityEditor.BuildTarget.WebGL)
return $"{hostServerIP}/CDN/WebGL/{appVersion}";
else
return $"{hostServerIP}/CDN/PC/{appVersion}";
#else
if (Application.platform == RuntimePlatform.Android)
return $"{hostServerIP}/CDN/Android/{appVersion}";
else if (Application.platform == RuntimePlatform.IPhonePlayer)
return $"{hostServerIP}/CDN/IPhone/{appVersion}";
else if (Application.platform == RuntimePlatform.WebGLPlayer)
return $"{hostServerIP}/CDN/WebGL/{appVersion}";
else
return $"{hostServerIP}/CDN/PC/{appVersion}";
#endif
}
/// <summary>
/// 远端资源地址查询服务类
/// </summary>
private class RemoteServices : IRemoteServices
{
private readonly string _defaultHostServer;
private readonly string _fallbackHostServer;
public RemoteServices(string defaultHostServer, string fallbackHostServer)
{
_defaultHostServer = defaultHostServer;
_fallbackHostServer = fallbackHostServer;
}
string IRemoteServices.GetRemoteMainURL(string fileName)
{
return $"{_defaultHostServer}/{fileName}";
}
string IRemoteServices.GetRemoteFallbackURL(string fileName)
{
return $"{_fallbackHostServer}/{fileName}";
}
}
}
流程机制详解
1. 状态机集成(FSM Integration)
- 实现了
IStateNode接口(来自UniFramework.Machine),这是 UniFramework 提供的状态机节点标准。 - 在
OnEnter()中启动协程InitPackage(),这是状态进入时的主逻辑。 - 成功后调用
_machine.ChangeState<FsmRequestPackageVersion>(),实现状态转移,这是 FSM 的典型行为。
这里详细说明下IStateNode:
public interface IStateNode
{
void OnCreate(StateMachine machine); // 状态被创建时调用(绑定状态机)
void OnEnter(); // 进入该状态时调用(一次)
void OnUpdate(); // 每帧调用(可选,用于持续逻辑)
void OnExit(); // 离开该状态时调用(一次)
}
| 方法 | 调用时机 | 典型用途 | 特性 |
|---|---|---|---|
OnCreate |
状态首次被 StateMachine.AddNode<T>() 注册后创建实例时 |
缓存 StateMachine 引用、初始化内部变量 |
只调用一次,用于“注入上下文” |
OnEnter |
状态机切换到该状态时 | 启动逻辑(如协程、加载、发送事件) | 每次进入都调用,是状态的“入口” |
OnUpdate |
每帧(或每 tick)状态机更新时 | 持续检测条件、处理输入、计时等 | 可选实现,若无持续逻辑可留空 |
OnExit |
状态机即将切换到其他状态前 | 清理资源、取消协程、保存状态 | 每次离开都调用,是状态的“出口” |
在你的 FsmInitializePackage 中:
OnCreate:保存_machine引用OnEnter:启动InitPackage()协程(核心逻辑)OnUpdate:留空(因为初始化是异步一次性操作)OnExit:留空(无需要清理的内容)
2. 协程 InitPackage():按模式初始化
1:获取上下文参数
var playMode = (EPlayMode)_machine.GetBlackboardValue("PlayMode");
var packageName = (string)_machine.GetBlackboardValue("PackageName");
- 从状态机的“黑板(Blackboard)”中读取启动时传入的配置
步骤 2:获取或创建 Package
var package = YooAssets.TryGetPackage(packageName);
if (package == null)
package = YooAssets.CreatePackage(packageName);
- 确保目标资源包存在
步骤 3:根据 playMode 分支处理
| 模式 | 行为 | 是否联网 | 用途 |
|---|---|---|---|
EditorSimulateMode |
调用 EditorSimulateModeHelper.SimulateBuild(),从本地模拟构建目录加载 |
❌ 否 | 编辑器调试,模拟热更新流程 |
OfflinePlayMode |
使用内置资源(StreamingAssets) |
❌ 否 | 完全离线单机游戏 |
HostPlayMode |
从远程服务器(CDN)下载资源,使用缓存系统 | ✅ 是 | 标准在线热更新 |
WebPlayMode |
针对 WebGL(或微信小游戏)的特殊文件系统 | ✅ 是(WebGL) / 特殊存储(小游戏) | Web 或小程序平台 |
补充:联机模式(HostPlayMode)详解
string defaultHostServer = GetHostServerURL();
IRemoteServices remoteServices = new RemoteServices(defaultHostServer, fallbackHostServer);
var createParameters = new HostPlayModeParameters();
createParameters.BuildinFileSystemParameters = ...; // 内置资源(如初始资源)
createParameters.CacheFileSystemParameters = ...; // 远程资源缓存到本地
initializationOperation = package.InitializeAsync(createParameters);
BuildinFileSystem:读取StreamingAssets中的初始资源(随包发布)CacheFileSystem:将从服务器下载的更新资源缓存到Application.persistentDataPathIRemoteServices:提供资源文件的 URL 拼接逻辑(主地址 + 备用地址)
🔥 这是 远程热更新的核心:初始化阶段就建立了“本地缓存 + 远程下载”的混合文件系统。
3. GetHostServerURL():动态构建 CDN 地址
string hostServerIP = "http://127.0.0.1"; // ← 本地测试服务器
string appVersion = "v1.0";
return $"{hostServerIP}/CDN/Android/{appVersion}";
- 根据 构建平台(Android/iOS/WebGL/PC) 返回对应 CDN 路径
- 当前配置为
127.0.0.1,说明是 本地测试(需运行本地 HTTP 服务器) - 正式上线时应改为真实 CDN 地址,如
https://cdn.yourgame.com
⚠️ 注意:这段代码硬编码了 IP 和版本号,实际项目中通常从配置文件或远程配置接口获取。
4. 初始化结果处理
yield return initializationOperation;
if (initializationOperation.Status != EOperationStatus.Succeed)
{
PatchEventDefine.InitializeFailed.SendEventMessage();
}
else
{
_machine.ChangeState<FsmRequestPackageVersion>(); // 进入下一步
}
- 等待异步初始化完成
- 成功则进入 请求远程版本号 阶段(热更新流程继续)
- 失败则通知 UI(用户可能看到“初始化失败,请检查网络”)
RemoteServices
RemoteServices类的具体作用:1. 存储两个服务器地址
private readonly string _defaultHostServer; // 主 CDN 地址 private readonly string _fallbackHostServer; // 备用 CDN 地址2. 实现两个关键方法
方法 作用 GetRemoteMainURL(fileName)返回主服务器上的资源完整 URL GetRemoteFallbackURL(fileName)返回备用服务器上的资源完整 URL
// 内部实现:真正负责拼接远程地址的服务类
private class RemoteServices : IRemoteServices
{
// 默认主站地址(CDN 主节点)
private readonly string _defaultHostServer;
// 备用站地址(CDN 备节点或灾备域名)
private readonly string _fallbackHostServer;
/// <summary>
/// 构造远程服务实例
/// </summary>
/// <param name="defaultHostServer">主站基准地址,例如 "https://cdn.main.example.com"</param>
/// <param name="fallbackHostServer">备用站基准地址,例如 "https://cdn.backup.example.com"</param>
public RemoteServices(string defaultHostServer, string fallbackHostServer)
{
_defaultHostServer = defaultHostServer;
_fallbackHostServer = fallbackHostServer;
}
// 显式接口实现:返回主站完整文件链接
string IRemoteServices.GetRemoteMainURL(string fileName)
{
return $"{_defaultHostServer}/{fileName}";
}
// 显式接口实现:返回备用站完整文件链接
string IRemoteServices.GetRemoteFallbackURL(string fileName)
{
return $"{_fallbackHostServer}/{fileName}";
}
}
IRemoteServices接口:
namespace YooAsset
{
public interface IRemoteServices
{
/// <summary>
/// 获取主资源站的资源地址
/// </summary>
/// <param name="fileName">请求的文件名称</param>
string GetRemoteMainURL(string fileName);
/// <summary>
/// 获取备用资源站的资源地址
/// </summary>
/// <param name="fileName">请求的文件名称</param>
string GetRemoteFallbackURL(string fileName);
}
}
补充说明:补丁更新操作中
状态机和有限状态机
StateMachine是一个自定义类,用于管理一组状态(节点)。- 每个状态(如
FsmInitializePackage)是一个具体的类,代表 FSM 中的一个状态。
2. 你代码中的 _machine 是什么
_machine = new StateMachine(this);
_machine.AddNode<FsmInitializePackage>();
// ... 添加多个状态节点
从命名和用法来看:
StateMachine是一个自定义类,用于管理一组状态(节点)。- 每个状态(如
FsmInitializePackage)是一个具体的类,代表 FSM 中的一个状态。 - 状态之间通过某种机制(可能在每个状态内部调用
_machine.TransitionTo<NextState>())进行转移。
并且从代码结构上看:
判断依据:
| 特征 | 是否满足 | 说明 |
|---|---|---|
| 有限个状态 | ✅ | 只添加了 8 个明确的状态类 |
| 明确的状态转移 | ✅(隐含) | 虽然没看到转移代码,但每个状态类内部通常会决定下一步跳转 |
| 同一时间只处在一个状态 | ✅(通常如此) | 标准 FSM 行为 |
| 状态行为封装 | ✅ | 每个 FsmXXX 类封装了该状态的逻辑 |
因此,它的结构就是 FSM,只是用类和泛型做了封装,使其更模块化、可维护。
更多推荐




所有评论(0)