feat(godot): 新增Godot核心系统架构与资源管理模块

- 添加抽象架构类AbstractArchitecture,提供模型、系统、工具注册框架
- 实现资源目录系统AbstractAssetCatalogSystem,支持场景和资源的注册与查询
- 创建资源工厂系统AbstractResourceFactorySystem,统一管理资源创建与预加载逻辑
- 定义资源标识符结构体SceneId和ResourceId,用于类型安全的资源引用
- 建立资源加载系统ResourceLoadSystem,提供资源加载、缓存和实例化功能
- 设计工厂注册表Registry,支持资源工厂的注册、解析和批量预加载
- 实现系统间依赖注入机制,确保各组件协同工作
- 添加完整的接口定义IAssetCatalogSystem、IResourceFactorySystem和IResourceLoadSystem
This commit is contained in:
GwWuYou 2025-12-16 21:38:30 +08:00
parent 7e93d7d089
commit e2036975ee
9 changed files with 666 additions and 0 deletions

View File

@ -0,0 +1,35 @@
using GFramework.Core.architecture;
namespace GFramework.Core.Godot.architecture;
/// <summary>
/// 抽象架构类,为特定类型的架构提供基础实现框架
/// </summary>
/// <typeparam name="T">架构的具体类型必须继承自Architecture且能被实例化</typeparam>
public abstract class AbstractArchitecture<T> : Architecture<T> where T : Architecture<T>, new()
{
/// <summary>
/// 初始化架构,按顺序注册模型、系统和工具
/// </summary>
protected override void Init()
{
RegisterModels();
RegisterSystems();
RegisterUtilities();
}
/// <summary>
/// 注册工具抽象方法,由子类实现具体的工具注册逻辑
/// </summary>
protected abstract void RegisterUtilities();
/// <summary>
/// 注册系统抽象方法,由子类实现具体系统注册逻辑
/// </summary>
protected abstract void RegisterSystems();
/// <summary>
/// 注册模型抽象方法,由子类实现具体模型注册逻辑
/// </summary>
protected abstract void RegisterModels();
}

View File

@ -0,0 +1,89 @@
using GFramework.Core.system;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源目录系统抽象基类,用于管理和注册游戏中的场景和资源
/// </summary>
public abstract class AbstractAssetCatalogSystem : AbstractSystem, IAssetCatalogSystem
{
private readonly Dictionary<string, AssetCatalog.SceneId> _scenes = new();
private readonly Dictionary<string, AssetCatalog.ResourceId> _resources = new();
/// <summary>
/// 系统初始化时调用,用于注册所有资产
/// </summary>
protected override void OnInit()
{
RegisterAssets();
}
/// <summary>
/// 抽象方法,由子类实现具体的资产注册逻辑
/// </summary>
protected abstract void RegisterAssets();
#region Register or Module 使
/// <summary>
/// 注册场景资源
/// </summary>
/// <param name="key">场景的唯一标识键</param>
/// <param name="path">场景资源的路径</param>
/// <exception cref="InvalidOperationException">当场景键已存在时抛出异常</exception>
public void RegisterScene(string key, string path)
{
if (_scenes.ContainsKey(key))
throw new InvalidOperationException($"Scene key duplicated: {key}");
_scenes[key] = new AssetCatalog.SceneId(path);
}
/// <summary>
/// 注册普通资源
/// </summary>
/// <param name="key">资源的唯一标识键</param>
/// <param name="path">资源的路径</param>
/// <exception cref="InvalidOperationException">当资源键已存在时抛出异常</exception>
public void RegisterResource(string key, string path)
{
if (_resources.ContainsKey(key))
throw new InvalidOperationException($"Resource key duplicated: {key}");
_resources[key] = new AssetCatalog.ResourceId(path);
}
#endregion
#region Query
/// <summary>
/// 根据键获取场景ID
/// </summary>
/// <param name="key">场景的唯一标识键</param>
/// <returns>对应的场景ID</returns>
public AssetCatalog.SceneId GetScene(string key) => _scenes[key];
/// <summary>
/// 根据键获取资源ID
/// </summary>
/// <param name="key">资源的唯一标识键</param>
/// <returns>对应的资源ID</returns>
public AssetCatalog.ResourceId GetResource(string key) => _resources[key];
/// <summary>
/// 检查是否存在指定键的场景
/// </summary>
/// <param name="key">场景的唯一标识键</param>
/// <returns>如果存在返回true否则返回false</returns>
public bool HasScene(string key) => _scenes.ContainsKey(key);
/// <summary>
/// 检查是否存在指定键的资源
/// </summary>
/// <param name="key">资源的唯一标识键</param>
/// <returns>如果存在返回true否则返回false</returns>
public bool HasResource(string key) => _resources.ContainsKey(key);
#endregion
}

View File

@ -0,0 +1,90 @@
using GFramework.Core.extensions;
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源工厂系统抽象基类,用于统一管理各类资源的创建与预加载逻辑。
/// 提供注册场景和资源的方法,并通过依赖的资源加载系统和资产目录系统完成实际资源的获取与构造。
/// </summary>
public abstract class AbstractResourceFactorySystem : AbstractSystem, IResourceFactorySystem
{
private ResourceFactory.Registry? _registry;
private IResourceLoadSystem? _resourceLoadSystem;
private IAssetCatalogSystem? _assetCatalogSystem;
/// <summary>
/// 系统初始化方法,在系统启动时执行一次。
/// 初始化资源注册表,并获取依赖的资源加载系统和资产目录系统。
/// 最后执行所有已注册资源的预加载操作。
/// </summary>
protected override void OnInit()
{
_registry = new ResourceFactory.Registry();
_resourceLoadSystem = this.GetSystem<IResourceLoadSystem>();
_assetCatalogSystem = this.GetSystem<IAssetCatalogSystem>();
RegisterResources();
// 执行预加载
_registry.PreloadAll();
}
/// <summary>
/// 注册系统所需的各种资源类型。由子类实现具体注册逻辑。
/// </summary>
protected abstract void RegisterResources();
/// <summary>
/// 获取指定类型的资源工厂函数。
/// </summary>
/// <typeparam name="T">要获取工厂的资源类型</typeparam>
/// <returns>返回创建指定类型资源的工厂函数</returns>
public Func<T> Get<T>() => _registry!.Resolve<T>();
#region Register Helpers
/// <summary>
/// 注册场景资源工厂。
/// 根据场景键名获取场景ID并将场景加载工厂注册到注册表中。
/// </summary>
/// <typeparam name="T">场景节点类型必须继承自Node</typeparam>
/// <param name="sceneKey">场景在资产目录中的键名</param>
/// <param name="preload">是否需要预加载该场景资源</param>
private void RegisterScene<T>(
string sceneKey,
bool preload = false)
where T : Node
{
var id = _assetCatalogSystem!.GetScene(sceneKey);
_registry!.Register(
_resourceLoadSystem!.GetOrRegisterSceneFactory<T>(id),
preload
);
}
/// <summary>
/// 注册普通资源工厂。
/// 根据资源键名获取资源ID并将资源加载工厂注册到注册表中。
/// </summary>
/// <typeparam name="T">资源类型必须继承自Resource</typeparam>
/// <param name="resourceKey">资源在资产目录中的键名</param>
/// <param name="duplicate">是否需要复制资源实例</param>
/// <param name="preload">是否需要预加载该资源</param>
private void RegisterResource<T>(
string resourceKey,
bool duplicate = false,
bool preload = false)
where T : Resource
{
var id = _assetCatalogSystem!.GetResource(resourceKey);
_registry!.Register(
_resourceLoadSystem!.GetOrRegisterResourceFactory<T>(id, duplicate),
preload
);
}
#endregion
}

View File

@ -0,0 +1,20 @@

namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源目录类,用于定义和管理游戏中的场景和资源标识符
/// </summary>
public static class AssetCatalog
{
/// <summary>
/// 场景标识符结构体,用于唯一标识一个场景资源
/// </summary>
/// <param name="Path">场景资源的路径</param>
public readonly record struct SceneId(string Path);
/// <summary>
/// 资源标识符结构体,用于唯一标识一个游戏资源
/// </summary>
/// <param name="Path">游戏资源的路径</param>
public readonly record struct ResourceId(string Path);
}

View File

@ -0,0 +1,37 @@
using GFramework.Core.system;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源目录系统接口,用于管理场景和资源的获取与查询
/// </summary>
public interface IAssetCatalogSystem : ISystem
{
/// <summary>
/// 根据键名获取场景标识符
/// </summary>
/// <param name="key">场景的唯一键名</param>
/// <returns>返回对应的场景ID</returns>
AssetCatalog.SceneId GetScene(string key);
/// <summary>
/// 根据键名获取资源标识符
/// </summary>
/// <param name="key">资源的唯一键名</param>
/// <returns>返回对应的资源ID</returns>
AssetCatalog.ResourceId GetResource(string key);
/// <summary>
/// 检查是否存在指定键名的场景
/// </summary>
/// <param name="key">要检查的场景键名</param>
/// <returns>如果存在返回true否则返回false</returns>
bool HasScene(string key);
/// <summary>
/// 检查是否存在指定键名的资源
/// </summary>
/// <param name="key">要检查的资源键名</param>
/// <returns>如果存在返回true否则返回false</returns>
bool HasResource(string key);
}

View File

@ -0,0 +1,16 @@
using GFramework.Core.system;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源工厂系统接口,用于获取指定类型的资源创建函数
/// </summary>
public interface IResourceFactorySystem : ISystem
{
/// <summary>
/// 获取指定类型T的资源创建函数
/// </summary>
/// <typeparam name="T">要获取创建函数的资源类型</typeparam>
/// <returns>返回一个创建T类型实例的函数委托</returns>
Func<T> Get<T>();
}

View File

@ -0,0 +1,68 @@
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源加载系统接口,提供资源和场景的加载、实例化、预加载等功能
/// </summary>
public interface IResourceLoadSystem : ISystem
{
/// <summary>
/// 加载指定路径的资源
/// </summary>
/// <typeparam name="T">资源类型必须继承自Resource</typeparam>
/// <param name="path">资源路径</param>
/// <returns>加载的资源实例</returns>
public T? LoadResource<T>(string path) where T : Resource;
/// <summary>
/// 获取场景加载器,用于延迟加载场景
/// </summary>
/// <param name="path">场景路径</param>
/// <returns>场景的延迟加载包装器</returns>
public Lazy<PackedScene> GetSceneLoader(string path);
/// <summary>
/// 创建指定路径场景的实例
/// </summary>
/// <typeparam name="T">节点类型必须继承自Node</typeparam>
/// <param name="path">场景路径</param>
/// <returns>场景实例化的节点对象</returns>
public T? CreateInstance<T>(string path) where T : Node;
/// <summary>
/// 获取或注册场景工厂函数
/// </summary>
/// <typeparam name="T">节点类型必须继承自Node</typeparam>
/// <param name="id">场景资源标识符</param>
/// <returns>创建场景实例的工厂函数</returns>
public Func<T> GetOrRegisterSceneFactory<T>(AssetCatalog.SceneId id) where T : Node;
/// <summary>
/// 获取或注册资源工厂函数
/// </summary>
/// <typeparam name="T">资源类型必须继承自Node</typeparam>
/// <param name="id">资源标识符</param>
/// <param name="duplicate">是否创建副本默认为false</param>
/// <returns>创建资源实例的工厂函数</returns>
public Func<T> GetOrRegisterResourceFactory<T>(AssetCatalog.ResourceId id, bool duplicate = false)
where T : Resource;
/// <summary>
/// 预加载指定路径的多个资源
/// </summary>
/// <param name="paths">需要预加载的资源路径集合</param>
public void Preload(IEnumerable<string> paths);
/// <summary>
/// 卸载指定路径的资源
/// </summary>
/// <param name="path">需要卸载的资源路径</param>
public void Unload(string path);
/// <summary>
/// 清除所有已加载的资源
/// </summary>
public void ClearAll();
}

View File

@ -0,0 +1,102 @@
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源工厂类,用于注册和解析各种资源的创建工厂
/// </summary>
public static class ResourceFactory
{
/// <summary>
/// 可预加载条目接口,定义了是否需要预加载以及执行工厂的方法
/// </summary>
private interface IPreloadableEntry
{
/// <summary>
/// 获取一个值,表示该资源是否需要预加载
/// </summary>
bool Preload { get; }
/// <summary>
/// 执行与该条目关联的工厂方法
/// </summary>
void ExecuteFactory();
}
/// <summary>
/// 表示一个具体的资源工厂条目,实现 IPreloadableEntry 接口
/// </summary>
/// <typeparam name="T">资源类型</typeparam>
private sealed class Entry<T>(Func<T> factory, bool preload) : IPreloadableEntry
{
/// <summary>
/// 获取用于创建资源的工厂函数
/// </summary>
public Func<T> Factory { get; } = factory;
/// <summary>
/// 获取一个值,表示该资源是否需要预加载
/// </summary>
public bool Preload { get; } = preload;
/// <summary>
/// 执行工厂函数以创建资源实例
/// </summary>
public void ExecuteFactory() => Factory();
}
/// <summary>
/// 工厂注册表,管理所有已注册的资源工厂
/// </summary>
internal sealed class Registry
{
/// <summary>
/// 存储所有已注册的工厂函数,键为资源类型,值为对应的工厂条目对象
/// </summary>
private readonly Dictionary<Type, object> _factories = new();
/// <summary>
/// 注册指定类型的资源工厂
/// </summary>
/// <typeparam name="T">要注册的资源类型</typeparam>
/// <param name="factory">创建该类型资源的工厂函数</param>
/// <param name="preload">是否需要预加载该资源默认为false</param>
public void Register<T>(Func<T> factory, bool preload = false)
{
_factories[typeof(T)] = new Entry<T>(factory, preload);
}
/// <summary>
/// 解析并获取指定类型的工厂函数
/// </summary>
/// <typeparam name="T">要获取工厂函数的资源类型</typeparam>
/// <returns>指定类型的工厂函数</returns>
/// <exception cref="InvalidOperationException">当指定类型的工厂未注册时抛出异常</exception>
public Func<T> Resolve<T>()
{
// 尝试从字典中查找对应类型的工厂条目
if (_factories.TryGetValue(typeof(T), out var obj)
&& obj is Entry<T> entry)
return entry.Factory;
// 若未找到则抛出异常
throw new InvalidOperationException($"Factory not registered: {typeof(T).Name}");
}
/// <summary>
/// 预加载所有标记为需要预加载的资源
/// </summary>
public void PreloadAll()
{
// 遍历所有已注册的工厂
foreach (var entry in _factories.Values)
{
// 检查当前条目是否支持预加载且被标记为需预加载
if (entry is IPreloadableEntry preloadable && preloadable.Preload)
{
// 执行其工厂方法进行预加载
preloadable.ExecuteFactory();
}
}
}
}
}

View File

@ -0,0 +1,209 @@
using GFramework.Core.system;
using Godot;
namespace GFramework.Core.Godot.system;
/// <summary>
/// 资源加载系统用于统一管理和缓存Godot资源如场景、纹理等的加载与实例化。
/// 提供基础加载、场景实例化、资源工厂注册以及缓存管理功能。
/// </summary>
public class ResourceLoadSystem : AbstractSystem, IResourceLoadSystem
{
/// <summary>
/// 已加载的资源缓存字典键为资源路径值为已加载的Resource对象。
/// </summary>
private readonly Dictionary<string, Resource> _loadedResources = new();
/// <summary>
/// 场景懒加载器缓存键为场景路径值为延迟加载的PackedScene对象。
/// </summary>
private readonly Dictionary<string, Lazy<PackedScene>> _sceneLoaders = new();
/// <summary>
/// 场景实例化工厂委托缓存键为场景路径值为创建该场景实例的Func委托。
/// </summary>
private readonly Dictionary<string, Delegate> _sceneFactories = new();
/// <summary>
/// 资源获取/复制工厂委托缓存键为资源路径值为获取或复制资源的Func委托。
/// </summary>
private readonly Dictionary<string, Delegate> _resourceFactories = new();
/// <summary>
/// 初始化方法,在系统初始化时打印日志信息。
/// </summary>
protected override void OnInit()
{
}
#region
/// <summary>
/// 加载指定类型的资源并进行缓存。如果资源已经加载过则直接从缓存中返回。
/// </summary>
/// <typeparam name="T">要加载的资源类型必须继承自Resource。</typeparam>
/// <param name="path">资源在项目中的相对路径。</param>
/// <returns>成功加载的资源对象若路径无效或加载失败则返回null。</returns>
public T? LoadResource<T>(string path) where T : Resource
{
if (string.IsNullOrEmpty(path))
return null;
if (_loadedResources.TryGetValue(path, out var cached))
return cached as T;
var res = GD.Load<T>(path);
if (res == null)
{
GD.PrintErr($"[ResourceLoadSystem] Load failed: {path}");
return null;
}
_loadedResources[path] = res;
return res;
}
/// <summary>
/// 获取一个场景的懒加载器用于按需加载PackedScene资源。
/// 若对应路径尚未注册加载器则会自动创建一个新的Lazy实例。
/// </summary>
/// <param name="path">场景文件的相对路径。</param>
/// <returns>表示该场景懒加载逻辑的Lazy&lt;PackedScene&gt;对象。</returns>
public Lazy<PackedScene> GetSceneLoader(string path)
{
if (_sceneLoaders.TryGetValue(path, out var loader))
return loader;
loader = new Lazy<PackedScene>(() =>
{
var scene = LoadResource<PackedScene>(path);
return scene ?? throw new InvalidOperationException($"Failed to load scene: {path}");
});
_sceneLoaders[path] = loader;
return loader;
}
#endregion
#region
/// <summary>
/// 根据给定路径加载场景,并创建其节点实例。
/// </summary>
/// <typeparam name="T">期望返回的节点类型必须是Node的子类。</typeparam>
/// <param name="path">场景文件的相对路径。</param>
/// <returns>新创建的场景根节点实例如果加载失败则返回null。</returns>
public T? CreateInstance<T>(string path) where T : Node
{
var scene = GetSceneLoader(path).Value;
return scene.Instantiate<T>();
}
/// <summary>
/// 注册或获取一个用于创建特定场景实例的工厂函数。
/// 如果已存在相同路径的工厂函数,则尝试转换后复用。
/// </summary>
/// <typeparam name="T">目标场景根节点的类型。</typeparam>
/// <param name="id">场景文件的id。</param>
/// <returns>用于创建该场景实例的Func委托。</returns>
/// <exception cref="InvalidCastException">当已有工厂不是Func&lt;T&gt;类型时抛出。</exception>
/// <exception cref="InvalidOperationException">当无法加载场景或实例化失败时抛出。</exception>
public Func<T> GetOrRegisterSceneFactory<T>(AssetCatalog.SceneId id) where T : Node
{
var path = id.Path;
if (_sceneFactories.TryGetValue(path, out var d))
return d as Func<T> ??
throw new InvalidCastException($"Factory for path '{path}' is not of type Func<{typeof(T)}>");
var factory = () =>
{
var scene = GetSceneLoader(path).Value
?? throw new InvalidOperationException($"Scene not loaded: {path}");
return scene.Instantiate<T>()
?? throw new InvalidOperationException($"Instantiate failed: {path}");
};
_sceneFactories[path] = factory;
return factory;
}
#endregion
#region
/// <summary>
/// 注册或获取一个用于加载或复制资源的工厂函数。
/// 可选择是否每次调用都返回副本Duplicate适用于需要独立状态的资源。
/// </summary>
/// <typeparam name="T">资源的具体类型。</typeparam>
/// <param name="id">资源文件的id。</param>
/// <param name="duplicate">是否每次都返回资源的一个副本默认为false。</param>
/// <returns>用于加载或复制资源的Func委托。</returns>
/// <exception cref="InvalidCastException">当已有工厂不是Func&lt;T&gt;类型时抛出。</exception>
/// <exception cref="InvalidOperationException">当资源加载失败时抛出。</exception>
public Func<T> GetOrRegisterResourceFactory<T>(AssetCatalog.ResourceId id, bool duplicate = false)
where T : Resource
{
var path = id.Path;
if (_resourceFactories.TryGetValue(path, out var d))
return d as Func<T> ??
throw new InvalidCastException($"Factory for path '{path}' is not of type Func<{typeof(T)}>");
var factory = () =>
{
var res = LoadResource<T>(path)
?? throw new InvalidOperationException($"Load failed: {path}");
if (!duplicate) return res;
return res.Duplicate() as T ?? res;
};
_resourceFactories[path] = factory;
return factory;
}
#endregion
#region
/// <summary>
/// 预加载一组资源和场景到内存中以提升后续访问速度。
/// </summary>
/// <param name="paths">待预加载的资源路径集合。</param>
public void Preload(IEnumerable<string> paths)
{
foreach (var path in paths)
{
GetSceneLoader(path);
LoadResource<Resource>(path);
}
}
/// <summary>
/// 清除指定路径的所有相关缓存数据,包括资源、场景加载器及各类工厂。
/// </summary>
/// <param name="path">要卸载的资源路径。</param>
public void Unload(string path)
{
_loadedResources.Remove(path);
_sceneLoaders.Remove(path);
_sceneFactories.Remove(path);
_resourceFactories.Remove(path);
}
/// <summary>
/// 清空所有当前系统的资源缓存、加载器和工厂列表。
/// </summary>
public void ClearAll()
{
_loadedResources.Clear();
_sceneLoaders.Clear();
_sceneFactories.Clear();
_resourceFactories.Clear();
}
#endregion
}