一、序列化器
除了事件以及回调以外还有序列化器。所有类型的序列化器都是继承了泛型抽象类GameFrameworkSerializer
public abstract class GameFrameworkSerializer<T>
{
private readonly Dictionary<byte, SerializeCallback> m_SerializeCallbacks;
private readonly Dictionary<byte, DeserializeCallback> m_DeserializeCallbacks;
private readonly Dictionary<byte, TryGetValueCallback> m_TryGetValueCallbacks;
private byte m_LatestSerializeCallbackVersion;
/// <summary>
/// 初始化游戏框架序列化器基类的新实例。
/// </summary>
public GameFrameworkSerializer()
{
m_SerializeCallbacks = new Dictionary<byte, SerializeCallback>();
m_DeserializeCallbacks = new Dictionary<byte, DeserializeCallback>();
m_TryGetValueCallbacks = new Dictionary<byte, TryGetValueCallback>();
m_LatestSerializeCallbackVersion = 0;
}
/// <summary>
/// 序列化回调函数。
/// </summary>
/// <param name="stream">目标流。</param>
/// <param name="data">要序列化的数据。</param>
/// <returns>是否序列化数据成功。</returns>
public delegate bool SerializeCallback(Stream stream, T data);
/// <summary>
/// 反序列化回调函数。
/// </summary>
/// <param name="stream">指定流。</param>
/// <returns>反序列化的数据。</returns>
public delegate T DeserializeCallback(Stream stream);
/// <summary>
/// 尝试从指定流获取指定键的值回调函数。
/// </summary>
/// <param name="stream">指定流。</param>
/// <param name="key">指定键。</param>
/// <param name="value">指定键的值。</param>
/// <returns>是否从指定流获取指定键的值成功。</returns>
public delegate bool TryGetValueCallback(Stream stream, string key, out object value);
/// <summary>
/// 注册序列化回调函数。
/// </summary>
/// <param name="version">序列化回调函数的版本。</param>
/// <param name="callback">序列化回调函数。</param>
public void RegisterSerializeCallback(byte version, SerializeCallback callback)
{
if (callback == null)
{
throw new GameFrameworkException("Serialize callback is invalid.");
}
m_SerializeCallbacks[version] = callback;
if (version > m_LatestSerializeCallbackVersion)
{
m_LatestSerializeCallbackVersion = version;
}
}
/// <summary>
/// 注册反序列化回调函数。
/// </summary>
/// <param name="version">反序列化回调函数的版本。</param>
/// <param name="callback">反序列化回调函数。</param>
public void RegisterDeserializeCallback(byte version, DeserializeCallback callback)
{
if (callback == null)
{
throw new GameFrameworkException("Deserialize callback is invalid.");
}
m_DeserializeCallbacks[version] = callback;
}
/// <summary>
/// 注册尝试从指定流获取指定键的值回调函数。
/// </summary>
/// <param name="version">尝试从指定流获取指定键的值回调函数的版本。</param>
/// <param name="callback">尝试从指定流获取指定键的值回调函数。</param>
public void RegisterTryGetValueCallback(byte version, TryGetValueCallback callback)
{
if (callback == null)
{
throw new GameFrameworkException("Try get value callback is invalid.");
}
m_TryGetValueCallbacks[version] = callback;
}
/// <summary>
/// 序列化数据到目标流中。
/// </summary>
/// <param name="stream">目标流。</param>
/// <param name="data">要序列化的数据。</param>
/// <returns>是否序列化数据成功。</returns>
public bool Serialize(Stream stream, T data)
{
if (m_SerializeCallbacks.Count <= 0)
{
throw new GameFrameworkException("No serialize callback registered.");
}
return Serialize(stream, data, m_LatestSerializeCallbackVersion);
}
/// <summary>
/// 序列化数据到目标流中。
/// </summary>
/// <param name="stream">目标流。</param>
/// <param name="data">要序列化的数据。</param>
/// <param name="version">序列化回调函数的版本。</param>
/// <returns>是否序列化数据成功。</returns>
public bool Serialize(Stream stream, T data, byte version)
{
byte[] header = GetHeader();
stream.WriteByte(header[0]);
stream.WriteByte(header[1]);
stream.WriteByte(header[2]);
stream.WriteByte(version);
SerializeCallback callback = null;
if (!m_SerializeCallbacks.TryGetValue(version, out callback))
{
throw new GameFrameworkException(Utility.Text.Format("Serialize callback '{0}' is not exist.", version.ToString()));
}
return callback(stream, data);
}
/// <summary>
/// 从指定流反序列化数据。
/// </summary>
/// <param name="stream">指定流。</param>
/// <returns>反序列化的数据。</returns>
public T Deserialize(Stream stream)
{
byte[] header = GetHeader();
byte header0 = (byte)stream.ReadByte();
byte header1 = (byte)stream.ReadByte();
byte header2 = (byte)stream.ReadByte();
if (header0 != header[0] || header1 != header[1] || header2 != header[2])
{
throw new GameFrameworkException(Utility.Text.Format("Header is invalid, need '{0}{1}{2}', current '{3}{4}{5}'.",
((char)header[0]).ToString(), ((char)header[1]).ToString(), ((char)header[2]).ToString(),
((char)header0).ToString(), ((char)header1).ToString(), ((char)header2).ToString()));
}
byte version = (byte)stream.ReadByte();
DeserializeCallback callback = null;
if (!m_DeserializeCallbacks.TryGetValue(version, out callback))
{
throw new GameFrameworkException(Utility.Text.Format("Deserialize callback '{0}' is not exist.", version.ToString()));
}
return callback(stream);
}
/// <summary>
/// 尝试从指定流获取指定键的值。
/// </summary>
/// <param name="stream">指定流。</param>
/// <param name="key">指定键。</param>
/// <param name="value">指定键的值。</param>
/// <returns>是否从指定流获取指定键的值成功。</returns>
public bool TryGetValue(Stream stream, string key, out object value)
{
value = null;
byte[] header = GetHeader();
byte header0 = (byte)stream.ReadByte();
byte header1 = (byte)stream.ReadByte();
byte header2 = (byte)stream.ReadByte();
if (header0 != header[0] || header1 != header[1] || header2 != header[2])
{
return false;
}
byte version = (byte)stream.ReadByte();
TryGetValueCallback callback = null;
if (!m_TryGetValueCallbacks.TryGetValue(version, out callback))
{
return false;
}
return callback(stream, key, out value);
}
/// <summary>
/// 获取数据头标识。
/// </summary>
/// <returns>数据头标识。</returns>
protected abstract byte[] GetHeader();
}
这个类结构上比较简单就不说了,需要注意的一点是框架里不同版本具有不同的序列化方法,而版本是通过byte字段标志的。
二、资源
先是大概理解了下虚拟文件系统(结果还是没有完全搞懂),还有事件和序列化辅助工具,终于要回到原来的方向上了。展开Resource文件夹,映入眼帘的是一大推脚本,看起来挺吓人的,但是稍微看下文件名就会发现大多数都是回调。整理一下以后发现资源这块内容还是比较多的。从命名上看,ResourceManager主要分为三部分:资源检查器(ResourceManager.ReourceChecker)、资源加载器(ResourceManager.ReourceLoader)和资源更新器(ResourceManager.ReourceUpdater)。资源也被分成了三类:本地版本资源、单机模式版本资源和可更新模式版本资源。
先看下不同版本的资源有什么区别。
/// <summary>
/// 本地版本资源列表。
/// </summary>
[StructLayout(LayoutKind.Auto)]
public partial struct LocalVersionList
{
private static readonly Resource[] EmptyResourceArray = new Resource[] { };
private static readonly FileSystem[] EmptyFileSystemArray = new FileSystem[] { };
private readonly bool m_IsValid;
private readonly Resource[] m_Resources;
private readonly FileSystem[] m_FileSystems;
/// <summary>
/// 初始化本地版本资源列表的新实例。
/// </summary>
/// <param name="resources">包含的资源集合。</param>
/// <param name="fileSystems">包含的文件系统集合。</param>
public LocalVersionList(Resource[] resources, FileSystem[] fileSystems)
{
m_IsValid = true;
m_Resources = resources ?? EmptyResourceArray;
m_FileSystems = fileSystems ?? EmptyFileSystemArray;
}
/// <summary>
/// 获取本地版本资源列表是否有效。
/// </summary>
public bool IsValid
{
get
{
return m_IsValid;
}
}
/// <summary>
/// 获取包含的资源集合。
/// </summary>
/// <returns>包含的资源集合。</returns>
public Resource[] GetResources()
{
return m_Resources;
}
/// <summary>
/// 获取包含的文件系统集合。
/// </summary>
/// <returns>包含的文件系统集合。</returns>
public FileSystem[] GetFileSystems()
{
return m_FileSystems;
}
}
这个结构体里面存的是本地版本的资源和文件系统。注意,这里的资源和文件系统指的并不是之前介绍的那个资源和文件系统,而是这个结构体里定义的两个子结构体。
public partial struct LocalVersionList
{
/// <summary>
/// 文件系统。
/// </summary>
[StructLayout(LayoutKind.Auto)]
public struct FileSystem
{
private static readonly int[] EmptyIntArray = new int[] { };
private readonly string m_Name;
private readonly int[] m_ResourceIndexes;
/// <summary>
/// 初始化文件系统的新实例。
/// </summary>
/// <param name="name">文件系统名称。</param>
/// <param name="resourceIndexes">文件系统包含的资源索引集合。</param>
public FileSystem(string name, int[] resourceIndexes)
{
if (name == null)
{
throw new GameFrameworkException("Name is invalid.");
}
m_Name = name;
m_ResourceIndexes = resourceIndexes ?? EmptyIntArray;
}
/// <summary>
/// 获取文件系统名称。
/// </summary>
public string Name
{
get
{
return m_Name;
}
}
/// <summary>
/// 获取文件系统包含的资源索引集合。
/// </summary>
/// <returns>文件系统包含的资源索引集合。</returns>
public int[] GetResourceIndexes()
{
return m_ResourceIndexes;
}
}
}
public partial struct LocalVersionList
{
/// <summary>
/// 资源。
/// </summary>
[StructLayout(LayoutKind.Auto)]
public struct Resource
{
private readonly string m_Name;
private readonly string m_Variant;
private readonly string m_Extension;
private readonly byte m_LoadType;
private readonly int m_Length;
private readonly int m_HashCode;
/// <summary>
/// 初始化资源的新实例。
/// </summary>
/// <param name="name">资源名称。</param>
/// <param name="variant">资源变体名称。</param>
/// <param name="extension">资源扩展名称。</param>
/// <param name="loadType">资源加载方式。</param>
/// <param name="length">资源长度。</param>
/// <param name="hashCode">资源哈希值。</param>
public Resource(string name, string variant, string extension, byte loadType, int length, int hashCode)
{
if (string.IsNullOrEmpty(name))
{
throw new GameFrameworkException("Name is invalid.");
}
m_Name = name;
m_Variant = variant;
m_Extension = extension;
m_LoadType = loadType;
m_Length = length;
m_HashCode = hashCode;
}
/// <summary>
/// 获取资源名称。
/// </summary>
public string Name
{
get { return m_Name; }
}
/// <summary>
/// 获取资源变体名称。
/// </summary>
public string Variant
{
get { return m_Variant; }
}
/// <summary>
/// 获取资源扩展名称。
/// </summary>
public string Extension
{
get { return m_Extension; }
}
/// <summary>
/// 获取资源加载方式。
/// </summary>
public byte LoadType
{
get { return m_LoadType; }
}
/// <summary>
/// 获取资源长度。
/// </summary>
public int Length
{
get { return m_Length; }
}
/// <summary>
/// 获取资源哈希值。
/// </summary>
public int HashCode
{
get { return m_HashCode; }
}
}
}
同理,另外两个版本的存储结构也是这样的,不过是添加了ResourceGroup之类的辅助结构,就不细看了。接下来去ResourceManager里看看。先看一下ResourceManager.cs这个脚本(这个脚本太长了就不放上来了),看一下它定义的属性及字段。
//配置文件相关的常量
private const string RemoteVersionListFileName = "GameFrameworkVersion.dat";
private const string LocalVersionListFileName = "GameFrameworkList.dat";
private const string DefaultExtension = "dat";
private const string BackupExtension = "bak";
private const int FileSystemMaxFileCount = 1024 * 16;
private const int FileSystemMaxBlockCount = 1024 * 256;
//数据存储结构
private Dictionary<string, AssetInfo> m_AssetInfos;
private Dictionary<ResourceName, ResourceInfo> m_ResourceInfos;
private SortedDictionary<ResourceName, ReadWriteResourceInfo> m_ReadWriteResourceInfos;
private readonly Dictionary<string, IFileSystem> m_ReadOnlyFileSystems;
private readonly Dictionary<string, IFileSystem> m_ReadWriteFileSystems;
private readonly Dictionary<string, ResourceGroup> m_ResourceGroups;
//不同版本资源列表的序列化器
private PackageVersionListSerializer m_PackageVersionListSerializer;
private UpdatableVersionListSerializer m_UpdatableVersionListSerializer;
private ReadOnlyVersionListSerializer m_ReadOnlyVersionListSerializer;
private ReadWriteVersionListSerializer m_ReadWriteVersionListSerializer;
private ResourcePackVersionListSerializer m_ResourcePackVersionListSerializer;
//需要用到的Manager
private IFileSystemManager m_FileSystemManager;
private ResourceIniter m_ResourceIniter;
private VersionListProcessor m_VersionListProcessor;
private ResourceChecker m_ResourceChecker;
private ResourceUpdater m_ResourceUpdater;
private ResourceLoader m_ResourceLoader;
private IResourceHelper m_ResourceHelper;
//资源路径、变体以及版本号
private string m_ReadOnlyPath;
private string m_ReadWritePath;
private ResourceMode m_ResourceMode;
private bool m_RefuseSetCurrentVariant;
private string m_CurrentVariant;
private string m_UpdatePrefixUri;
private string m_ApplicableGameVersion;
private int m_InternalResourceVersion;
//加载资源相关的回调
private MemoryStream m_DecompressCachedStream;
private DecryptResourceCallback m_DecryptResourceCallback;
private InitResourcesCompleteCallback m_InitResourcesCompleteCallback;
private UpdateVersionListCallbacks m_UpdateVersionListCallbacks;
private CheckResourcesCompleteCallback m_CheckResourcesCompleteCallback;
private ApplyResourcesCompleteCallback m_ApplyResourcesCompleteCallback;
private UpdateResourcesCompleteCallback m_UpdateResourcesCompleteCallback;
private EventHandler<ResourceApplySuccessEventArgs> m_ResourceApplySuccessEventHandler;
private EventHandler<ResourceApplyFailureEventArgs> m_ResourceApplyFailureEventHandler;
private EventHandler<ResourceUpdateStartEventArgs> m_ResourceUpdateStartEventHandler;
private EventHandler<ResourceUpdateChangedEventArgs> m_ResourceUpdateChangedEventHandler;
private EventHandler<ResourceUpdateSuccessEventArgs> m_ResourceUpdateSuccessEventHandler;
private EventHandler<ResourceUpdateFailureEventArgs> m_ResourceUpdateFailureEventHandler;
通过这些变量,大概能猜到这个脚本里会包含哪些内容:按逻辑顺序会先通过解析配置文件来检查资源,更新资源、然后加载资源以及一些辅助方法。当然这些方法都是通过调用Manager实现的。
关于变体,这里有个解释,说得挺浅显易懂的。
检查器会读取资源列表,并把检查信息以CheckInfo的形式保存起来。
private sealed partial class CheckInfo
{
private readonly ResourceName m_ResourceName;
private CheckStatus m_Status;
private bool m_NeedRemove;
private bool m_NeedMoveToDisk;
private bool m_NeedMoveToFileSystem;
private RemoteVersionInfo m_VersionInfo;
private LocalVersionInfo m_ReadOnlyInfo;
private LocalVersionInfo m_ReadWriteInfo;
private string m_CachedFileSystemName;
/// <summary>
/// 初始化资源检查信息的新实例。
/// </summary>
/// <param name="resourceName">资源名称。</param>
public CheckInfo(ResourceName resourceName)
{
m_ResourceName = resourceName;
m_Status = CheckStatus.Unknown;
m_NeedRemove = false;
m_NeedMoveToDisk = false;
m_NeedMoveToFileSystem = false;
m_VersionInfo = default(RemoteVersionInfo);
m_ReadOnlyInfo = default(LocalVersionInfo);
m_ReadWriteInfo = default(LocalVersionInfo);
m_CachedFileSystemName = null;
}
/// <summary>
/// 获取资源名称。
/// </summary>
public ResourceName ResourceName
{
get
{
return m_ResourceName;
}
}
/// <summary>
/// 获取资源检查状态。
/// </summary>
public CheckStatus Status
{
get
{
return m_Status;
}
}
/// <summary>
/// 获取是否需要移除读写区的资源。
/// </summary>
public bool NeedRemove
{
get
{
return m_NeedRemove;
}
}
/// <summary>
/// 获取是否需要将读写区的资源移动到磁盘。
/// </summary>
public bool NeedMoveToDisk
{
get
{
return m_NeedMoveToDisk;
}
}
/// <summary>
/// 获取是否需要将读写区的资源移动到文件系统。
/// </summary>
public bool NeedMoveToFileSystem
{
get
{
return m_NeedMoveToFileSystem;
}
}
/// <summary>
/// 获取资源所在的文件系统名称。
/// </summary>
public string FileSystemName
{
get
{
return m_VersionInfo.FileSystemName;
}
}
/// <summary>
/// 获取资源是否使用文件系统。
/// </summary>
public bool ReadWriteUseFileSystem
{
get
{
return m_ReadWriteInfo.UseFileSystem;
}
}
/// <summary>
/// 获取读写资源所在的文件系统名称。
/// </summary>
public string ReadWriteFileSystemName
{
get
{
return m_ReadWriteInfo.FileSystemName;
}
}
/// <summary>
/// 获取资源加载方式。
/// </summary>
public LoadType LoadType
{
get
{
return m_VersionInfo.LoadType;
}
}
/// <summary>
/// 获取资源大小。
/// </summary>
public int Length
{
get
{
return m_VersionInfo.Length;
}
}
/// <summary>
/// 获取资源哈希值。
/// </summary>
public int HashCode
{
get
{
return m_VersionInfo.HashCode;
}
}
/// <summary>
/// 获取压缩后大小。
/// </summary>
public int ZipLength
{
get
{
return m_VersionInfo.ZipLength;
}
}
/// <summary>
/// 获取压缩后哈希值。
/// </summary>
public int ZipHashCode
{
get
{
return m_VersionInfo.ZipHashCode;
}
}
/// <summary>
/// 临时缓存资源所在的文件系统名称。
/// </summary>
/// <param name="fileSystemName">资源所在的文件系统名称。</param>
public void SetCachedFileSystemName(string fileSystemName)
{
m_CachedFileSystemName = fileSystemName;
}
/// <summary>
/// 设置资源在版本中的信息。
/// </summary>
/// <param name="loadType">资源加载方式。</param>
/// <param name="length">资源大小。</param>
/// <param name="hashCode">资源哈希值。</param>
/// <param name="zipLength">压缩后大小。</param>
/// <param name="zipHashCode">压缩后哈希值。</param>
public void SetVersionInfo(LoadType loadType, int length, int hashCode, int zipLength, int zipHashCode)
{
if (m_VersionInfo.Exist)
{
throw new GameFrameworkException(Utility.Text.Format("You must set version info of '{0}' only once.", m_ResourceName.FullName));
}
m_VersionInfo = new RemoteVersionInfo(m_CachedFileSystemName, loadType, length, hashCode, zipLength, zipHashCode);
m_CachedFileSystemName = null;
}
/// <summary>
/// 设置资源在只读区中的信息。
/// </summary>
/// <param name="loadType">资源加载方式。</param>
/// <param name="length">资源大小。</param>
/// <param name="hashCode">资源哈希值。</param>
public void SetReadOnlyInfo(LoadType loadType, int length, int hashCode)
{
if (m_ReadOnlyInfo.Exist)
{
throw new GameFrameworkException(Utility.Text.Format("You must set readonly info of '{0}' only once.", m_ResourceName.FullName));
}
m_ReadOnlyInfo = new LocalVersionInfo(m_CachedFileSystemName, loadType, length, hashCode);
m_CachedFileSystemName = null;
}
/// <summary>
/// 设置资源在读写区中的信息。
/// </summary>
/// <param name="loadType">资源加载方式。</param>
/// <param name="length">资源大小。</param>
/// <param name="hashCode">资源哈希值。</param>
public void SetReadWriteInfo(LoadType loadType, int length, int hashCode)
{
if (m_ReadWriteInfo.Exist)
{
throw new GameFrameworkException(Utility.Text.Format("You must set read-write info of '{0}' only once.", m_ResourceName.FullName));
}
m_ReadWriteInfo = new LocalVersionInfo(m_CachedFileSystemName, loadType, length, hashCode);
m_CachedFileSystemName = null;
}
/// <summary>
/// 刷新资源信息状态。
/// </summary>
/// <param name="currentVariant">当前变体。</param>
/// <param name="ignoreOtherVariant">是否忽略处理其它变体的资源,若不忽略则移除。</param>
public void RefreshStatus(string currentVariant, bool ignoreOtherVariant)
{
if (!m_VersionInfo.Exist)
{
m_Status = CheckStatus.Disuse;
m_NeedRemove = m_ReadWriteInfo.Exist;
return;
}
if (m_ResourceName.Variant == null || m_ResourceName.Variant == currentVariant)
{
if (m_ReadOnlyInfo.Exist && m_ReadOnlyInfo.FileSystemName == m_VersionInfo.FileSystemName && m_ReadOnlyInfo.LoadType == m_VersionInfo.LoadType && m_ReadOnlyInfo.Length == m_VersionInfo.Length && m_ReadOnlyInfo.HashCode == m_VersionInfo.HashCode)
{
m_Status = CheckStatus.StorageInReadOnly;
m_NeedRemove = m_ReadWriteInfo.Exist;
}
else if (m_ReadWriteInfo.Exist && m_ReadWriteInfo.LoadType == m_VersionInfo.LoadType && m_ReadWriteInfo.Length == m_VersionInfo.Length && m_ReadWriteInfo.HashCode == m_VersionInfo.HashCode)
{
bool differentFileSystem = m_ReadWriteInfo.FileSystemName != m_VersionInfo.FileSystemName;
m_Status = CheckStatus.StorageInReadWrite;
m_NeedMoveToDisk = m_ReadWriteInfo.UseFileSystem && differentFileSystem;
m_NeedMoveToFileSystem = m_VersionInfo.UseFileSystem && differentFileSystem;
}
else
{
m_Status = CheckStatus.Update;
m_NeedRemove = m_ReadWriteInfo.Exist;
}
}
else
{
m_Status = CheckStatus.Unavailable;
m_NeedRemove = !ignoreOtherVariant && m_ReadWriteInfo.Exist;
}
}
}
在资源更新器中,定义了UpdateInfo和ApplyInfo两个结构体来记录资源信息,然后会在update方法中对保存的数据进行轮询,如果有要处理的数据就进行下载/应用操作。至于加载器就会再复杂一些,不仅包含新定义的数据结构来存储信息,而且加载是通过代理调用Task进行加载的。
这里要说明一下,这三部分各自对应一个流程,比如说检查器就对应ProcedureCheckResources流程,更新器对应ProcedureUpdateResources流程,而加载器对应的是ProcedurePreload流程。等到执行到相应的流程的时候,流程的OnEnter和Update方法会调用这些
三、辅助器接口
GameFramework框架中提供资源辅助器和加载资源代理辅助器的接口,这两个接口在框架内是没有实现的,应该是开放出来让开发者根据实际情况扩展的。接口写的很简洁,IResourceHelper里只有三个方法,不是很明白为什么会有个卸载场景的方法。
/// <summary>
/// 资源辅助器接口。
/// </summary>
public interface IResourceHelper
{
/// <summary>
/// 直接从指定文件路径加载数据流。
/// </summary>
/// <param name="fileUri">文件路径。</param>
/// <param name="loadBytesCallbacks">加载数据流回调函数集。</param>
/// <param name="userData">用户自定义数据。</param>
void LoadBytes(string fileUri, LoadBytesCallbacks loadBytesCallbacks, object userData);
/// <summary>
/// 卸载场景。
/// </summary>
/// <param name="sceneAssetName">场景资源名称。</param>
/// <param name="unloadSceneCallbacks">卸载场景回调函数集。</param>
/// <param name="userData">用户自定义数据。</param>
void UnloadScene(string sceneAssetName, UnloadSceneCallbacks unloadSceneCallbacks, object userData);
/// <summary>
/// 释放资源。
/// </summary>
/// <param name="objectToRelease">要释放的资源。</param>
void Release(object objectToRelease);
}
ILoadResourceAgentHelper里从声明的事件就能看出加载资源的过程:通知更新->读取文件->读取二进制流->二进制流转换->加载完成。方法也基本上和这些步骤对应。
public interface ILoadResourceAgentHelper
{
/// <summary>
/// 加载资源代理辅助器异步加载资源更新事件。
/// </summary>
event EventHandler<LoadResourceAgentHelperUpdateEventArgs> LoadResourceAgentHelperUpdate;
/// <summary>
/// 加载资源代理辅助器异步读取资源文件完成事件。
/// </summary>
event EventHandler<LoadResourceAgentHelperReadFileCompleteEventArgs> LoadResourceAgentHelperReadFileComplete;
/// <summary>
/// 加载资源代理辅助器异步读取资源二进制流完成事件。
/// </summary>
event EventHandler<LoadResourceAgentHelperReadBytesCompleteEventArgs> LoadResourceAgentHelperReadBytesComplete;
/// <summary>
/// 加载资源代理辅助器异步将资源二进制流转换为加载对象完成事件。
/// </summary>
event EventHandler<LoadResourceAgentHelperParseBytesCompleteEventArgs>
LoadResourceAgentHelperParseBytesComplete;
/// <summary>
/// 加载资源代理辅助器异步加载资源完成事件。
/// </summary>
event EventHandler<LoadResourceAgentHelperLoadCompleteEventArgs> LoadResourceAgentHelperLoadComplete;
/// <summary>
/// 加载资源代理辅助器错误事件。
/// </summary>
event EventHandler<LoadResourceAgentHelperErrorEventArgs> LoadResourceAgentHelperError;
/// <summary>
/// 通过加载资源代理辅助器开始异步读取资源文件。
/// </summary>
/// <param name="fullPath">要加载资源的完整路径名。</param>
void ReadFile(string fullPath);
/// <summary>
/// 通过加载资源代理辅助器开始异步读取资源文件。
/// </summary>
/// <param name="fileSystem">要加载资源的文件系统。</param>
/// <param name="name">要加载资源的名称。</param>
void ReadFile(IFileSystem fileSystem, string name);
/// <summary>
/// 通过加载资源代理辅助器开始异步读取资源二进制流。
/// </summary>
/// <param name="fullPath">要加载资源的完整路径名。</param>
void ReadBytes(string fullPath);
/// <summary>
/// 通过加载资源代理辅助器开始异步读取资源二进制流。
/// </summary>
/// <param name="fileSystem">要加载资源的文件系统。</param>
/// <param name="name">要加载资源的名称。</param>
void ReadBytes(IFileSystem fileSystem, string name);
/// <summary>
/// 通过加载资源代理辅助器开始异步将资源二进制流转换为加载对象。
/// </summary>
/// <param name="bytes">要加载资源的二进制流。</param>
void ParseBytes(byte[] bytes);
/// <summary>
/// 通过加载资源代理辅助器开始异步加载资源。
/// </summary>
/// <param name="resource">资源。</param>
/// <param name="assetName">要加载的资源名称。</param>
/// <param name="assetType">要加载资源的类型。</param>
/// <param name="isScene">要加载的资源是否是场景。</param>
void LoadAsset(object resource, string assetName, Type assetType, bool isScene);
/// <summary>
/// 重置加载资源代理辅助器。
/// </summary>
void Reset();
}
至于这两个接口的实现可以在UnityGameFramework中找到。在实现ILoadResourceAgentHelper的DefaultLoadResourceAgentHelper这个类里见到了把event 作为属性,赋值时不像普通属性那样使用get、set构造器而是add和remove。实现的方法,不管是读取文件还是加载ab包(从方法中看到ab包被区分为场景资源与非场景资源)都是返回了一个异步请求。而读取二进制流比较特殊,未使用流加载的返回的是UnityWebRequest(这个类是能加载本地资源的),使用文件系统流加载的直接使用了加载完成事件。然后在update里会统一轮询各种请求并进行处理,其处理请求的方式还是使用各种事件。
对于EventHandler不是很熟悉,所以这里事件的用法在这里看的有些懵。Eventhandler本质上是个委托,(EventHandler的)原型是下面这样的
/// <summary>表示当事件提供数据时将处理该事件的方法。</summary>
/// <param name="sender">事件源。</param>
/// <param name="e">包含事件数据的对象。</param>
/// <typeparam name="TEventArgs">事件生成的事件数据的类型</typeparam>
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
这两个接口命名挺像的,而且看方法好像也差不多,都是加载资源的。通过反向查找发现,资源辅助器是在检查器中被调用,用来加载存储版本资源列表的文件,而资源代理辅助器则是在更新器中被调用用来更新资源的。
资源这块的内容感觉还是在细节上还是一头雾水,看到的大多是加载相关的,很少见到卸载相关的内容;而且配置文件那块的内容也还没看,所以不太清楚资源是怎么存放、映射的,所以不同版本资源列表相关的代码,看的也不是很懂;还有打包相关的内容貌似是写在编辑器模块里,所以也还没看。