mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(architecture): 添加环境配置支持并重构架构初始化
- 引入IEnvironment接口和EnvironmentBase抽象类实现环境管理 - 在Architecture类中添加environment参数和Environment属性 - 修改ArchitectureContext构造函数以接收环境对象 - 添加DefaultEnvironment默认环境实现类 - 更新ContextAwareExtensions扩展方法支持环境获取 - 移除NoopLogger和NoopLoggerFactory无操作日志类 - 重构架构初始化流程以包含环境初始化 - 添加GetEnvironment扩展方法用于获取环境对象
This commit is contained in:
parent
4adf3f11d4
commit
49ceb35156
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using GFramework.Core.Abstractions.command;
|
||||
using GFramework.Core.Abstractions.environment;
|
||||
using GFramework.Core.Abstractions.events;
|
||||
using GFramework.Core.Abstractions.model;
|
||||
using GFramework.Core.Abstractions.query;
|
||||
@ -83,4 +84,10 @@ public interface IArchitectureContext
|
||||
/// <typeparam name="TEvent">事件类型</typeparam>
|
||||
/// <param name="onEvent">要取消注册的事件回调方法</param>
|
||||
void UnRegisterEvent<TEvent>(Action<TEvent> onEvent);
|
||||
|
||||
/// <summary>
|
||||
/// 获取环境对象
|
||||
/// </summary>
|
||||
/// <returns>环境对象实例</returns>
|
||||
IEnvironment GetEnvironment();
|
||||
}
|
||||
50
GFramework.Core.Abstractions/environment/IEnvironment.cs
Normal file
50
GFramework.Core.Abstractions/environment/IEnvironment.cs
Normal file
@ -0,0 +1,50 @@
|
||||
namespace GFramework.Core.Abstractions.environment;
|
||||
|
||||
/// <summary>
|
||||
/// 定义环境接口,提供应用程序运行环境的相关信息
|
||||
/// </summary>
|
||||
public interface IEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取环境名称
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据键值获取指定类型的环境配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的值的类型,必须为引用类型</typeparam>
|
||||
/// <param name="key">用于查找配置值的键</param>
|
||||
/// <returns>与指定键关联的配置值,如果未找到则返回null</returns>
|
||||
T? Get<T>(string key) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取环境值(显式判断)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的值的类型,必须为引用类型</typeparam>
|
||||
/// <param name="key">用于查找配置值的键</param>
|
||||
/// <param name="value">输出参数,如果找到配置值则返回该值,否则返回默认值</param>
|
||||
/// <returns>如果找到指定键的配置值则返回true,否则返回false</returns>
|
||||
bool TryGet<T>(string key, out T value) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 获取必须存在的环境值(强依赖)
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的值的类型,必须为引用类型</typeparam>
|
||||
/// <param name="key">用于查找配置值的键</param>
|
||||
/// <returns>与指定键关联的配置值,如果未找到则抛出异常</returns>
|
||||
T GetRequired<T>(string key) where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// 注册键值对到环境值字典中
|
||||
/// </summary>
|
||||
/// <param name="key">要注册的键</param>
|
||||
/// <param name="value">要注册的值</param>
|
||||
void Register(string key, object value);
|
||||
|
||||
/// <summary>
|
||||
/// 初始化环境值字典
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using GFramework.Core.Abstractions.architecture;
|
||||
using GFramework.Core.Abstractions.command;
|
||||
using GFramework.Core.Abstractions.enums;
|
||||
using GFramework.Core.Abstractions.environment;
|
||||
using GFramework.Core.Abstractions.events;
|
||||
using GFramework.Core.Abstractions.ioc;
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
@ -8,6 +9,7 @@ using GFramework.Core.Abstractions.model;
|
||||
using GFramework.Core.Abstractions.query;
|
||||
using GFramework.Core.Abstractions.system;
|
||||
using GFramework.Core.Abstractions.utility;
|
||||
using GFramework.Core.environment;
|
||||
using GFramework.Core.events;
|
||||
using GFramework.Core.logging;
|
||||
|
||||
@ -20,19 +22,29 @@ namespace GFramework.Core.architecture;
|
||||
/// </summary>
|
||||
public abstract class Architecture(
|
||||
IArchitectureConfiguration? configuration = null,
|
||||
IEnvironment? environment = null,
|
||||
IArchitectureServices? services = null,
|
||||
IArchitectureContext? context = null
|
||||
)
|
||||
: IArchitecture
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取架构配置对象
|
||||
/// 获取架构配置对象
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// 返回一个IArchitectureConfiguration接口的实例,默认为DefaultArchitectureConfiguration类型
|
||||
/// 返回一个IArchitectureConfiguration接口的实例,默认为DefaultArchitectureConfiguration类型
|
||||
/// </value>
|
||||
private IArchitectureConfiguration Configuration { get; } = configuration ?? new ArchitectureConfiguration();
|
||||
|
||||
/// <summary>
|
||||
/// 获取环境配置对象
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// 返回一个IEnvironment接口的实例,默认为DefaultEnvironment类型
|
||||
/// </value>
|
||||
private IEnvironment Environment { get; } = environment ?? new DefaultEnvironment();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取架构服务对象
|
||||
/// </summary>
|
||||
@ -304,9 +316,9 @@ public abstract class Architecture(
|
||||
LoggerFactoryResolver.Provider = Configuration.LoggerProperties.LoggerFactoryProvider;
|
||||
// 创建日志记录器实例
|
||||
_logger = LoggerFactoryResolver.Provider.CreateLogger(GetType().Name);
|
||||
|
||||
Environment.Initialize();
|
||||
// 初始化架构上下文(如果尚未初始化)
|
||||
_context ??= new ArchitectureContext(Container, TypeEventSystem, CommandBus, QueryBus);
|
||||
_context ??= new ArchitectureContext(Container, TypeEventSystem, CommandBus, QueryBus, Environment);
|
||||
// 将当前架构类型与上下文绑定到游戏上下文
|
||||
GameContext.Bind(GetType(), _context);
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using GFramework.Core.Abstractions.architecture;
|
||||
using GFramework.Core.Abstractions.command;
|
||||
using GFramework.Core.Abstractions.environment;
|
||||
using GFramework.Core.Abstractions.events;
|
||||
using GFramework.Core.Abstractions.ioc;
|
||||
using GFramework.Core.Abstractions.model;
|
||||
@ -16,11 +17,14 @@ public class ArchitectureContext(
|
||||
IIocContainer container,
|
||||
ITypeEventSystem typeEventSystem,
|
||||
ICommandBus commandBus,
|
||||
IQueryBus queryBus)
|
||||
IQueryBus queryBus,
|
||||
IEnvironment environment)
|
||||
: IArchitectureContext
|
||||
{
|
||||
private readonly ICommandBus _commandBus = commandBus ?? throw new ArgumentNullException(nameof(commandBus));
|
||||
private readonly IIocContainer _container = container ?? throw new ArgumentNullException(nameof(container));
|
||||
|
||||
private readonly IEnvironment _environment = environment ?? throw new ArgumentNullException(nameof(environment));
|
||||
private readonly IQueryBus _queryBus = queryBus ?? throw new ArgumentNullException(nameof(queryBus));
|
||||
|
||||
private readonly ITypeEventSystem _typeEventSystem =
|
||||
@ -145,5 +149,14 @@ public class ArchitectureContext(
|
||||
_typeEventSystem.UnRegister(onEvent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前环境对象
|
||||
/// </summary>
|
||||
/// <returns>环境对象实例</returns>
|
||||
public IEnvironment GetEnvironment()
|
||||
{
|
||||
return _environment;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
19
GFramework.Core/environment/DefaultEnvironment.cs
Normal file
19
GFramework.Core/environment/DefaultEnvironment.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace GFramework.Core.environment;
|
||||
|
||||
/// <summary>
|
||||
/// 默认环境实现类,继承自EnvironmentBase
|
||||
/// </summary>
|
||||
public class DefaultEnvironment : EnvironmentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取环境名称
|
||||
/// </summary>
|
||||
public override string Name { get; } = "Default";
|
||||
|
||||
/// <summary>
|
||||
/// 初始化环境
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
}
|
||||
}
|
||||
83
GFramework.Core/environment/EnvironmentBase.cs
Normal file
83
GFramework.Core/environment/EnvironmentBase.cs
Normal file
@ -0,0 +1,83 @@
|
||||
using GFramework.Core.Abstractions.environment;
|
||||
using GFramework.Core.rule;
|
||||
|
||||
namespace GFramework.Core.environment;
|
||||
|
||||
/// <summary>
|
||||
/// 环境基础抽象类,实现了IEnvironment接口,提供环境值的存储和获取功能
|
||||
/// </summary>
|
||||
public abstract class EnvironmentBase : ContextAwareBase, IEnvironment
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储环境值的字典,键为字符串,值为对象类型
|
||||
/// </summary>
|
||||
protected readonly Dictionary<string, object> Values = new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取环境名称的抽象属性
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 根据键获取指定类型的值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的值的类型,必须为引用类型</typeparam>
|
||||
/// <param name="key">用于查找值的键</param>
|
||||
/// <returns>如果找到则返回对应类型的值,否则返回null</returns>
|
||||
public virtual T? Get<T>(string key) where T : class
|
||||
{
|
||||
return TryGet<T>(key, out var value) ? value : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试根据键获取指定类型的值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的值的类型,必须为引用类型</typeparam>
|
||||
/// <param name="key">用于查找值的键</param>
|
||||
/// <param name="value">输出参数,如果成功则包含找到的值,否则为null</param>
|
||||
/// <returns>如果找到指定键且类型匹配则返回true,否则返回false</returns>
|
||||
public virtual bool TryGet<T>(string key, out T value) where T : class
|
||||
{
|
||||
if (Values.TryGetValue(key, out var obj) && obj is T typed)
|
||||
{
|
||||
value = typed;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据键获取必需的指定类型值,如果找不到则抛出异常
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的值的类型,必须为引用类型</typeparam>
|
||||
/// <param name="key">用于查找值的键</param>
|
||||
/// <returns>找到的对应类型的值</returns>
|
||||
/// <exception cref="InvalidOperationException">当指定键的值不存在时抛出</exception>
|
||||
public virtual T GetRequired<T>(string key) where T : class
|
||||
{
|
||||
if (TryGet<T>(key, out var value))
|
||||
return value;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Environment [{Name}] missing required value: key='{key}', type='{typeof(T).Name}'");
|
||||
}
|
||||
|
||||
void IEnvironment.Register(string key, object value)
|
||||
{
|
||||
Register(key, value);
|
||||
}
|
||||
|
||||
public abstract void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 注册键值对到环境值字典中
|
||||
/// </summary>
|
||||
/// <param name="key">要注册的键</param>
|
||||
/// <param name="value">要注册的值</param>
|
||||
protected void Register(string key, object value)
|
||||
{
|
||||
Values[key] = value;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using GFramework.Core.Abstractions.command;
|
||||
using GFramework.Core.Abstractions.environment;
|
||||
using GFramework.Core.Abstractions.events;
|
||||
using GFramework.Core.Abstractions.model;
|
||||
using GFramework.Core.Abstractions.query;
|
||||
@ -22,7 +23,7 @@ public static class ContextAwareExtensions
|
||||
/// <exception cref="ArgumentNullException">当 contextAware 为 null 时抛出</exception>
|
||||
public static TSystem? GetSystem<TSystem>(this IContextAware contextAware) where TSystem : class, ISystem
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
var context = contextAware.GetContext();
|
||||
return context.GetSystem<TSystem>();
|
||||
}
|
||||
@ -36,7 +37,7 @@ public static class ContextAwareExtensions
|
||||
/// <exception cref="ArgumentNullException">当 contextAware 为 null 时抛出</exception>
|
||||
public static TModel? GetModel<TModel>(this IContextAware contextAware) where TModel : class, IModel
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
var context = contextAware.GetContext();
|
||||
return context.GetModel<TModel>();
|
||||
}
|
||||
@ -50,7 +51,7 @@ public static class ContextAwareExtensions
|
||||
/// <exception cref="ArgumentNullException">当 contextAware 为 null 时抛出</exception>
|
||||
public static TUtility? GetUtility<TUtility>(this IContextAware contextAware) where TUtility : class, IUtility
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
var context = contextAware.GetContext();
|
||||
return context.GetUtility<TUtility>();
|
||||
}
|
||||
@ -65,8 +66,8 @@ public static class ContextAwareExtensions
|
||||
/// <exception cref="ArgumentNullException">当 contextAware 或 query 为 null 时抛出</exception>
|
||||
public static TResult SendQuery<TResult>(this IContextAware contextAware, IQuery<TResult> query)
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
if (query == null) throw new ArgumentNullException(nameof(query));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
ArgumentNullException.ThrowIfNull(query);
|
||||
|
||||
var context = contextAware.GetContext();
|
||||
return context.SendQuery(query);
|
||||
@ -80,8 +81,8 @@ public static class ContextAwareExtensions
|
||||
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
|
||||
public static void SendCommand(this IContextAware contextAware, ICommand command)
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
if (command == null) throw new ArgumentNullException(nameof(command));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
var context = contextAware.GetContext();
|
||||
context.SendCommand(command);
|
||||
@ -97,8 +98,8 @@ public static class ContextAwareExtensions
|
||||
/// <exception cref="ArgumentNullException">当 contextAware 或 command 为 null 时抛出</exception>
|
||||
public static TResult SendCommand<TResult>(this IContextAware contextAware, ICommand<TResult> command)
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
if (command == null) throw new ArgumentNullException(nameof(command));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
var context = contextAware.GetContext();
|
||||
return context.SendCommand(command);
|
||||
@ -112,7 +113,7 @@ public static class ContextAwareExtensions
|
||||
/// <exception cref="ArgumentNullException">当 contextAware 为 null 时抛出</exception>
|
||||
public static void SendEvent<TEvent>(this IContextAware contextAware) where TEvent : new()
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
var context = contextAware.GetContext();
|
||||
context.SendEvent<TEvent>();
|
||||
}
|
||||
@ -126,8 +127,8 @@ public static class ContextAwareExtensions
|
||||
/// <exception cref="ArgumentNullException">当 contextAware 或 e 为 null 时抛出</exception>
|
||||
public static void SendEvent<TEvent>(this IContextAware contextAware, TEvent e) where TEvent : class
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
if (e == null) throw new ArgumentNullException(nameof(e));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
ArgumentNullException.ThrowIfNull(e);
|
||||
|
||||
var context = contextAware.GetContext();
|
||||
context.SendEvent(e);
|
||||
@ -142,8 +143,8 @@ public static class ContextAwareExtensions
|
||||
/// <returns>事件注销接口</returns>
|
||||
public static IUnRegister RegisterEvent<TEvent>(this IContextAware contextAware, Action<TEvent> handler)
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
if (handler == null) throw new ArgumentNullException(nameof(handler));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
ArgumentNullException.ThrowIfNull(handler);
|
||||
|
||||
var context = contextAware.GetContext();
|
||||
return context.RegisterEvent(handler);
|
||||
@ -157,10 +158,39 @@ public static class ContextAwareExtensions
|
||||
/// <param name="onEvent">之前绑定的事件处理器</param>
|
||||
public static void UnRegisterEvent<TEvent>(this IContextAware contextAware, Action<TEvent> onEvent)
|
||||
{
|
||||
if (contextAware == null) throw new ArgumentNullException(nameof(contextAware));
|
||||
if (onEvent == null) throw new ArgumentNullException(nameof(onEvent));
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
ArgumentNullException.ThrowIfNull(onEvent);
|
||||
|
||||
// 获取上下文对象并取消事件注册
|
||||
var context = contextAware.GetContext();
|
||||
context.UnRegisterEvent(onEvent);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定类型的环境对象
|
||||
/// </summary>
|
||||
/// <typeparam name="T">要获取的环境对象类型</typeparam>
|
||||
/// <param name="contextAware">上下文感知对象</param>
|
||||
/// <returns>指定类型的环境对象,如果无法转换则返回null</returns>
|
||||
public static T? GetEnvironment<T>(this IContextAware contextAware) where T : class
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
// 获取上下文对象并返回其环境
|
||||
var context = contextAware.GetContext();
|
||||
return context.GetEnvironment() as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取环境对象
|
||||
/// </summary>
|
||||
/// <param name="contextAware">上下文感知对象</param>
|
||||
/// <returns>环境对象</returns>
|
||||
public static IEnvironment GetEnvironment(this IContextAware contextAware)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(contextAware);
|
||||
// 获取上下文对象并返回其环境
|
||||
var context = contextAware.GetContext();
|
||||
return context.GetEnvironment();
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
|
||||
namespace GFramework.Core.logging;
|
||||
|
||||
/// <summary>
|
||||
/// 空操作日志记录器实现,不执行任何实际的日志记录操作
|
||||
/// </summary>
|
||||
/// <param name="name">日志记录器名称,默认为null</param>
|
||||
/// <param name="minLevel">最小日志级别,默认为Info</param>
|
||||
internal sealed class NoopLogger(
|
||||
string? name = null,
|
||||
LogLevel minLevel = LogLevel.Info) : AbstractLogger(name ?? RootLoggerName, minLevel)
|
||||
{
|
||||
/// <summary>
|
||||
/// 重写写入方法,空操作实现,不执行任何实际的日志记录操作
|
||||
/// </summary>
|
||||
/// <param name="level">日志级别</param>
|
||||
/// <param name="message">日志消息</param>
|
||||
/// <param name="exception">异常信息</param>
|
||||
protected override void Write(LogLevel level, string message, Exception? exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
|
||||
namespace GFramework.Core.logging;
|
||||
|
||||
/// <summary>
|
||||
/// 无操作日志工厂实现,用于提供空的日志记录功能
|
||||
/// </summary>
|
||||
public class NoopLoggerFactory : ILoggerFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取指定名称的无操作日志记录器
|
||||
/// </summary>
|
||||
/// <param name="name">日志记录器的名称</param>
|
||||
/// <param name="minLevel">日志记录器的最小日志级别</param>
|
||||
/// <returns>返回一个NoopLogger实例,该实例不执行任何实际的日志记录操作</returns>
|
||||
public ILogger GetLogger(string name, LogLevel minLevel = LogLevel.Info)
|
||||
{
|
||||
return new NoopLogger();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,8 @@
|
||||
using GFramework.Core.Abstractions.architecture;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using GFramework.Core.Abstractions.architecture;
|
||||
using GFramework.Core.Abstractions.environment;
|
||||
using GFramework.Core.architecture;
|
||||
using GFramework.Core.constants;
|
||||
using GFramework.Godot.extensions;
|
||||
@ -12,9 +16,10 @@ namespace GFramework.Godot.architecture;
|
||||
/// </summary>
|
||||
public abstract class AbstractArchitecture(
|
||||
IArchitectureConfiguration? configuration = null,
|
||||
IEnvironment? environment = null,
|
||||
IArchitectureServices? services = null,
|
||||
IArchitectureContext? context = null
|
||||
) : Architecture(configuration, services, context)
|
||||
) : Architecture(configuration, environment, services, context)
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储所有已安装的Godot架构扩展组件列表
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using Godot;
|
||||
using System;
|
||||
using Godot;
|
||||
|
||||
namespace GFramework.Godot.architecture;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user