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_EDITORvar 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);
#elsevar 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_EDITORif (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}";elsereturn $"{hostServerIP}/CDN/PC/{appVersion}";
#elseif (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}";elsereturn $"{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.persistentDataPath
IRemoteServices
:提供资源文件的 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,只是用类和泛型做了封装,使其更模块化、可维护。