轻量、模块化、可扩展的 Unity 游戏开发基础框架,基于 MVC 架构,内置常用游戏开发工具模块。
- 命名空间(核心):
FFramework.Core - 命名空间(工具):
FFramework.Utility - 命名空间(编辑器):
FFramework.Editor - !!!需要FMOD和Animancer支持
using FFramework.Core;
public class GameModel : ArchitectureModel
{
public BindableProperty<int> Coin = new(0);
public BindableProperty<int> Level = new(1);
public void AddCoin(int amount) => Coin.Value += amount;
}using FFramework.Core;
using UnityEngine;
public class GameViewController : ArchitectureViewController
{
private GameModel model;
protected override void OnInitialize()
{
model = GetModel<GameModel>();
model.Coin.Register(OnCoinChanged, gameObject);
model.Level.Register(OnLevelChanged, gameObject);
}
private void OnCoinChanged(int coin) => Debug.Log($"Coin: {coin}");
private void OnLevelChanged(int level) => Debug.Log($"Level: {level}");
}using FFramework.Core;
using UnityEngine;
public class GameStartup : MonoBehaviour
{
public GameObject viewPrefab;
void Start()
{
var arch = Architecture.Instance;
arch.RegisterModel<GameModel>();
arch.RegisterViewController<GameViewController>(viewPrefab);
arch.GetModel<GameModel>().AddCoin(10);
}
}架构管理器 Architecture 继承自 SingletonMono<Architecture>(DefaultExecutionOrder(-100)),负责 Model / ViewController / Singleton 的注册、解析与生命周期管理。
- 使用
Architecture.Instance获取单例 - 通过
[Inject]属性标记依赖字段/属性,架构自动解析注入 - 依赖注入由
InjectHelper静态工具类实现,通过Resolver委托完成类型解析 - 统一卸载顺序:Model → ViewController → Singleton(基础设施最后销毁)
// 注册与获取
Architecture.Instance.RegisterModel<MyModel>();
Architecture.Instance.GetModel<MyModel>();
Architecture.Instance.RegisterViewController<MyViewController>(gameObject);
Architecture.Instance.GetViewController<MyViewController>();
// 批量保存 / 加载所有 Model 数据
Architecture.Instance.SaveAllData("slot_save1");
Architecture.Instance.LoadAllData("slot_save1");
// 完全卸载(场景切换时)
Architecture.Instance.UnloadAll();ArchitectureModel 是数据层基类,继承自 IModel,通过 [Inject] 自动注入 EventSystem,可发送和监听事件。Dispose() 时自动注销所有通过 RegisterEvent 注册的事件。
public class PlayerModel : ArchitectureModel
{
public BindableProperty<int> Hp = new(100);
public BindableProperty<int> MaxHp = new(100);
public void TakeDamage(int dmg)
{
Hp.Value = Mathf.Max(0, Hp.Value - dmg);
SendEvent("OnPlayerDamaged", dmg);
}
}ArchitectureViewController 是视图层基类(MonoBehaviour),通过 [Inject] 自动注入 EventSystem。Awake 时自动注册到 Architecture 并执行依赖注入。
AutoRegister属性:子类可重写返回false来禁用自动注册Initialize()/Dispose()幂等设计,防止重复调用OnDestroy()时自动注销事件并取消注册
public class HUDViewController : ArchitectureViewController
{
protected override void OnInitialize()
{
var model = GetModel<PlayerModel>();
model.Hp.Register(OnHpChanged, gameObject);
RegisterEvent("OnPlayerDamaged", OnDamaged);
}
private void OnHpChanged(int hp) { /* 更新血条 */ }
private void OnDamaged() { /* 播放受伤特效 */ }
}使用 [Inject] 标记字段或属性,Architecture 在注册对象时自动解析并注入依赖(Model / Singleton / EventSystem / Architecture 等)。注入时机在 Initialize() 调用之前。
public class BattleModel : ArchitectureModel
{
[Inject] private EventSystem eventSystem;
protected override void OnInitialize()
{
// eventSystem 已被自动注入,可直接使用
}
}支持的解析顺序:已注册的 Model → 自动注册未注册的 IModel → 已注册的 Singleton → 自动注册非 Mono 的 ISingleton → EventSystem / Architecture 等内置单例。
BindableProperty<T> 包装值类型,值变化时自动触发回调。线程安全(双锁策略),防止死锁。
// 基础用法
public BindableProperty<string> PlayerName = new("Default");
// 注册监听(推荐:绑定 GameObject,对象销毁时自动注销)
PlayerName.Register(OnNameChanged, gameObject);
// 手动注册 / 注销
var token = PlayerName.Register(OnNameChanged);
token.UnRegister();
// 注册时立即回调当前值
PlayerName.Register(OnNameChanged, gameObject, callImmediately: true);
// 触发回调
PlayerName.Value = "NewName"; // 值变化时自动调用 OnNameChanged特性:
- 线程安全(双锁:valueLock + eventLock,值变更回调移出锁外执行)
- 注册时可选立即回调当前值
- 自动随 GameObject 销毁注销(通过
BindablePropertyAutoUnregister组件) - 支持
IUnRegister接口统一注销
EventSystem 继承自 SingletonMono<EventSystem>,提供全局事件通信,支持 string 和 struct 两种键类型。
// 实例 API
EventSystem.Instance.Register("EVENT_NAME", OnEvent);
EventSystem.Instance.Trigger("EVENT_NAME");
EventSystem.Instance.Trigger("EVENT_NAME", 42); // 带参数
// 静态 API(更便捷)
EventSystem.S_Register("OnGameStart", OnGameStart);
EventSystem.S_Trigger("OnGameStart");
EventSystem.S_Unregister("OnGameStart", OnGameStart);
// 支持 struct 键
EventSystem.S_Register<MyEventType>(MyEventType.PlayerDie, OnPlayerDie);
EventSystem.S_Trigger(MyEventType.PlayerDie);
// 自动注销(随 GameObject 销毁)
this.RegisterEvent(_eventSystem, "OnLevelUp", OnLevelUp, gameObject);新增特性:
- 触发历史记录:Editor 模式下自动记录事件触发位置、调用堆栈、参数信息,方便调试
- 惰性清理:触发时自动清理已销毁对象的监听器
UnregisterTarget(object):按目标对象批量注销所有事件
提供两种单例实现,统一实现 ISingleton 接口:
| 类型 | 基类 | 说明 |
|---|---|---|
| Mono 单例 | SingletonMono<T> |
MonoBehaviour 单例,自动创建 GameObject,DontDestroyOnLoad |
| 非 Mono 单例 | 实现 ISingleton + Architecture.RegisterInstance<T>() |
纯 C# 单例,由 Architecture 管理生命周期 |
所有单例采用懒加载模式,首次访问 Instance 时自行初始化,不再需要 Priority 排序。
// Mono 单例(自动创建 GameObject,DontDestroyOnLoad)
public class AudioManager : SingletonMono<AudioManager>
{
protected override void InitializeSingleton() { /* 初始化 */ }
public void Play(string name) { }
}
AudioManager.Instance.Play("bgm");
// 非 Mono 单例(通过 Architecture 注册)
public class ConfigManager : ISingleton
{
public void OnSingletonInit() { /* 初始化 */ }
public void OnSingletonDispose() { /* 清理 */ }
}
Architecture.Instance.RegisterInstance<ConfigManager>();SingletonMono<T> 会在实例创建后自动注册到 Architecture 进行依赖注入和初始化。
ArchitectureDataPersistence 支持三种模式的成员发现,序列化为 JSON(Newtonsoft)。使用 ConcurrentDictionary 缓存反射结果,避免重复扫描。
| 模式 | 识别方式 | 适用范围 |
|---|---|---|
| ① 自动检测 | BindableProperty<T> 类型自动识别 |
所有 public 字段 / 属性 |
| ② 显式标记 | [SaveData] 属性标记 |
任意可访问性的字段 / 属性(最高优先级) |
| ③ Unity 序列化 | [SerializeField] 自动识别 |
非 UnityEngine.Object 引用类型的字段 |
三种模式混合扫描时自动去重(按成员名),同一成员仅保存一次。
public class PlayerModel : ArchitectureModel
{
// BindableProperty 自动识别(模式①)
public BindableProperty<int> Hp = new(100);
// [SaveData] 显式标记(模式②)
[SaveData] public string PlayerName;
[SaveData] public List<string> Inventory;
[SaveData] public Vector3 SpawnPosition;
[SaveData] private float hiddenValue;
// [SerializeField] 自动识别(模式③)
[SerializeField] private int level;
[SerializeField] private SerializableClass cfg;
// [SerializeField] private GameObject go; // ✗ Unity 对象自动跳过
}
// 单个 Model 保存 / 加载
var model = Architecture.Instance.GetModel<PlayerModel>();
model.SaveData("slot1");
model.LoadData("slot1");
// 批量保存 / 加载所有 Model
Architecture.Instance.SaveAllData("slot1");
Architecture.Instance.LoadAllData("slot1");将业务操作封装为独立的命令类,实现操作与执行的分离。支持对象池复用(高频场景)。
// 1. 定义命令
public class AddCoinCommand : AbstractCommand
{
private readonly int amount;
public AddCoinCommand(int amount) => this.amount = amount;
protected override void OnExecute()
{
var model = GetModel<GameResourceModel>();
model.Coin.Value += amount;
SendEvent("CoinChanged", model.Coin.Value);
}
}
// 2. 发送命令(任何 ICommandSender:ViewController / Model / 另一个 Command)
this.SendCommand(new AddCoinCommand(100));
// 3. 带返回值
int damage = this.SendCommand(new CalculateDamageCommand(50, 1.5f));
// 4. 对象池复用(高频场景)
this.SendCommand<MoveCommand>(cmd => cmd.SetDirection(Vector3.forward));
// 5. 无需手动 new,自动创建实例
this.SendCommand<LogCommand>();- 接口:
ICommand、ICommand<TResult>、ICommandSender - 基类:
AbstractCommand/AbstractCommand<TResult>(提供GetModel<T>()/SendEvent()方法) - 对象池:
CommandPool<T> - 扩展方法:
CommandExtensions
CustomScriptableObject 是 ScriptableObject 的基类,内置 Editor 按钮,可在 Inspector 中一键保存资源。
public class GameConfig : CustomScriptableObject
{
public int maxLevel;
public float playerSpeed;
}
// Inspector 中显示 [保存资源] 按钮,点击即可保存ObjectPool 支持通过 Resources 路径或 Prefab 获取 / 回收对象,提供链式扩展方法。每个池独立管理,支持容量限制、惰性清理。
// 从 Resources 路径获取 / 回收
GameObject bullet = ObjectPool.Instance.GetResourcesObjectFromPool("Bullets/Bullet");
ObjectPool.Instance.ReturnObjectToPool(bullet);
// 从 Prefab 获取(链式调用)
GameObject go = myPrefab.GetPoolObject()
.SetPosition(new Vector3(1, 0, 0))
.SetParent(parentTransform);
// 延迟回收
go.ReturnPool(2.0f); // 2 秒后自动回收
// 预热(预创建对象)
"Bullets/Bullet".PrewarmPool(10);
// 实现 IPoolObject 接口自动回调
public class MyBullet : MonoBehaviour, IPoolObject
{
public void OnBeforeGetFromPool() { /* 显示、重置状态 */ }
public void OnAfterReturnToPool() { /* 隐藏、清理 */ }
}详细文档见
ObjectPool/ObjectPoolDoc.md。
FSMStateMachine 泛型状态机,自动缓存状态实例,支持生命周期回调。状态基类 FSMStateBase 实现 IFSMState 接口,支持 OnUpdate、OnFixedUpdate、OnLateUpdate 多种更新模式。
// 定义状态(继承 FSMStateBase)
public class PatrolState : FSMStateBase
{
public override void OnEnter(FSMStateMachine machine) { /* 进入巡逻 */ }
public override void OnUpdate(FSMStateMachine machine) { /* 巡逻逻辑 */ }
public override void OnExit(FSMStateMachine machine) { /* 退出巡逻 */ }
}
public class ChaseState : FSMStateBase
{
public override void OnEnter(FSMStateMachine machine) { /* 进入追击 */ }
public override void OnUpdate(FSMStateMachine machine) { /* 追击逻辑 */ }
public override void OnExit(FSMStateMachine machine) { }
}
// 使用
var fsm = new FSMStateMachine(enemy);
fsm.SetDefault<PatrolState>();
fsm.ChangeState<ChaseState>();
// 获取持有者(强类型)
fsm.GetOwner<Enemy>();
// 查询当前状态
if (fsm.IsCurrentState<PatrolState>()) { /* ... */ }
var state = fsm.GetCurrentState<ChaseState>();
// 每帧驱动
void Update() => fsm.Update();
void FixedUpdate() => fsm.FixedUpdate();
void LateUpdate() => fsm.LateUpdate();详细文档见
FSM/README.md。
ResLoad 封装 Resources 加载,支持缓存、同步 / 异步、类型安全。
// 同步加载
GameObject prefab = ResLoad.Instance.LoadRes<GameObject>("Prefabs/Enemy");
// 异步加载
ResLoad.Instance.LoadResAsync<GameObject>("Prefabs/UI/Panel", (result) =>
{
Instantiate(result);
});
// 从完整路径加载(Editor 用 AssetDatabase,Runtime 自动提取 Resources 路径)
Sprite sprite = ResLoad.Instance.LoadAssetFromPath<Sprite>("Assets/Art/icon.png");
// 批量异步加载
ResLoad.Instance.LoadMultipleResAsync(
new[] { "Prefabs/A", "Prefabs/B", "Prefabs/C" },
(results) => { /* 全部加载完成 */ },
(progress) => Debug.Log($"进度: {progress:P}")
);详细文档见
ResLoad/ResLoadDoc.md。
SceneLoad 提供场景加载的同步 / 异步 / 预加载模式。
// 异步加载
SceneLoad.Instance.LoadSceneAsync("Battle", LoadSceneMode.Single,
progress: (p) => UpdateLoadingBar(p),
complete: () => Debug.Log("加载完成")
);
// 预加载(允许 SceneActivation=false 时,加载资源后等待手动激活)
AsyncOperation op = SceneLoad.Instance.PreloadScene("Battle");
// ... 加载进度到达 0.9 时 ...
SceneLoad.Instance.ActivatePreloadedScene(op);
// 卸载场景
SceneLoad.Instance.UnloadScene("UIScene");详细文档见
SceneLoad/SceneLoadDoc.md。
PlayAnima 基于 Animancer 的动画播放,支持动画事件。
public class PlayerAnima : MonoBehaviour
{
private PlayAnima _anima;
void Awake() => _anima = GetComponent<PlayAnima>();
void Attack()
{
_anima.PlayAnimaClip(new AnimaArgs
{
clip = attackClip,
transitionTime = 0.1f
});
// 带事件(在 0.5 归一化时间触发伤害判定)
_anima.PlayAnimaWithEvent(new AnimaArgs { clip = attackClip },
0.5f, () => DealDamage());
}
}详细文档见
Anima/AnimaDoc.md。
ShakeBase / SmoothShake 基于 PerlinNoise 的平滑震动,支持位置和旋转。
var shake = GetComponent<SmoothShake>();
// 快速震动
shake.StartQuickShake(positionIntensity: 0.5f, rotationIntensity: 5f, duration: 0.3f);
// 使用 ShakePreset(ScriptableObject)
shake.PlayShake(myShakePreset);
// 自定义参数
shake.PlayShake(new Vector3(0.2f, 0.2f, 0), new Vector3(0, 0, 3f), 0.5f);详细文档见
Shake/ShakeDoc.md。
UISystem 是 UI 系统核心管理器(继承 SingletonMono<UISystem>),负责面板创建、缓存、层级管理和生命周期调度。
特性:
- 六层 UI 层级:Background / PostProcessing / Content / Popup / Guide / Debug
- 面板缓存:自动缓存已创建的面板,支持复用
- 面板栈管理:按打开顺序管理面板,支持
CurrentPanel查询 - 自动锁定/解锁:同层级面板打开时自动锁定前一个
- 可替换资源加载器:通过
IUIResLoader接口支持自定义加载策略
// 打开面板(从 Resources 加载)
UISystem.Instance.OpenPanel<MainMenuPanel>(UILayer.ContentLayer);
// 打开面板(从 Prefab)
UISystem.Instance.OpenPanel<MainMenuPanel>(mainMenuPrefab, UILayer.ContentLayer);
// 关闭面板
UISystem.Instance.CloseCurrentPanel();
UISystem.Instance.ClosePanel<MainMenuPanel>();
// 查询面板
UIPanel panel = UISystem.Instance.GetPanel<MainMenuPanel>();
bool isTop = UISystem.Instance.IsCurrentPanel<MainMenuPanel>();
// 清理
UISystem.Instance.ClearAllPanels(); // 清理所有
UISystem.Instance.ClearPanelsInLayer(UILayer.PopupLayer); // 清理指定层级
// 自定义资源加载器
UISystem.Instance.SetUIResLoader(new MyCustomLoader());| 层级 | 说明 |
|---|---|
BackgroundLayer |
背景层 - 静态背景 |
PostProcessingLayer |
后期处理层 - UI 特效 |
ContentLayer |
内容层 - 主要 UI 功能 |
PopupLayer |
弹窗层 - 消息弹窗 |
GuideLayer |
引导层 - 引导操作 |
DebugLayer |
调试层 - 调试 UI |
详细文档见
UISystem/UISystemDoc.md。
UIPanel 继承自 ArchitectureViewController,提供面板的显示 / 隐藏 / 锁定和 CanvasGroup 管理。面板销毁时自动清理所有事件绑定。
public class MainMenuPanel : UIPanel
{
protected override void OnInitialize()
{
// 通过 UIEventExtensions 绑定子控件
BindButton("StartBtn", () => SceneLoad.Instance.LoadSceneAsync("Game"));
BindToggle("SoundToggle", (on) => AudioManager.Instance.SetSound(on));
}
protected override void OnShow() { /* 面板显示时 */ }
protected override void OnHide() { /* 面板隐藏时 */ }
}
// 使用
mainMenuPanel.Show();
mainMenuPanel.Hide();
mainMenuPanel.OnLock(); // 锁定交互
mainMenuPanel.OnUnLock(); // 解锁交互
// 面板属性
panel.SetAlpha(0.5f);
panel.SetInteractable(false);
panel.SetBlocksRaycasts(true);UIPanel 生命周期:Awake → OnEnable / OnInitialize → Start → OnShow / OnHide → OnDestroy / OnPanelDestroy
UIEventExtensions 提供按钮、开关、滑动条等组件的便捷绑定,自动随面板销毁清理。支持子物体递归查找和路径查找。
// 绑定按钮
panel.BindButton("AttackBtn", OnAttack);
panel.BindButton("SkillBtn", OnUseSkill);
// 绑定开关
panel.BindToggle("SoundToggle", (on) => AudioListener.volume = on ? 1 : 0);
// 绑定滑动条
panel.BindSlider("VolumeSlider", (v) => AudioListener.volume = v);
// 绑定输入框
panel.BindTMPInputField("NameInput", (text) => playerName = text);
// 批量绑定
panel.BindButtons(new Dictionary<string, Action>
{
{ "Btn1", () => Debug.Log("1") },
{ "Btn2", () => Debug.Log("2") },
});
// 直接组件绑定
Button attackBtn = panel.GetButton("AttackBtn");
attackBtn.BindClick(OnAttack, panel);
// 组件获取(支持路径 "Panel/Sub/Button")
panel.GetComponent<Button>("StartBtn");
panel.GetButton("StartBtn");
panel.GetImage("Avatar");
// 属性设置(链式)
panel.SetButtonInteractable("StartBtn", false);
panel.SetTMPText("LevelText", "Level 5");
panel.SetImageSprite("Avatar", mySprite);
// 主动清理所有已绑定事件
panel.UnbindAllEvents();LocalizationManager 提供多语言文本和字体切换方案,使用 CSV 文件管理语言数据,支持运行时按组加载 / 卸载。
// 切换语言
LocalizationManager.Instance.SetLanguage("English");
LocalizationManager.Instance.SetLanguage("ChineseSimplified");
// 获取翻译文本
string text = LocalizationManager.Instance.GetText("ui_start_game");
string levelText = LocalizationManager.Instance.GetText("ui_level_display", 5);
// CSV 分组管理(按需加载 / 卸载)
LocalizationManager.Instance.LoadCsvGroup("story_act1");
LocalizationManager.Instance.UnloadGroup("story_act1");
// 监听语言 / 数据变更
LocalizationManager.Instance.OnLanguageChanged += (lang) => { };
LocalizationManager.Instance.OnDataChanged += (groupId, type) => { };| 组件 | 说明 |
|---|---|
LocalizationManager |
核心管理器(单例,CSV 解析,语言切换) |
LocalizationComponent |
UI 组件(可拖拽,配置 Key 后自动监听语言变化刷新文本和字体) |
LocalizationConfig |
ScriptableObject 配置资产,定义 CSV 分组和字体映射 |
LocalizationData |
运行时数据模型(O(1) Key 查询,分组管理) |
| 工具 | 入口 | 说明 |
|---|---|---|
| 创建文件夹工具 | FFramework / 创建文件夹工具 |
快速创建项目文件夹结构,支持预设模板 |
| 资源压缩工具 | FFramework / 资源压缩工具 |
图片 / 音频 / 模型资源压缩,支持拖拽批量处理 |
| 命令控制台 | Tools / 命令控制台(或 LeftCtrl+Tab) |
运行时嵌入 Game 视图的命令行调试工具(仅 Play 模式) |
| 打开数据持久化文件夹 | FFramework / 打开数据持久化文件夹 |
一键打开 Application.persistentDataPath/SaveData,查看 Model 的 JSON 存档文件 |
| 组件 | 说明 |
|---|---|
| UIPanel Inspector | 自动附加到 UIPanel Inspector,列出所有绑定事件的 UI 组件 |
| LocalizationManager Editor | 提供语言切换、状态信息、调试面板 |
| LocalizationComponent Editor | 提供 Key 选择、预览等功能 |
| 特性 | 说明 |
|---|---|
[Button] |
Inspector 中显示可点击按钮,调用指定方法 |
[ShowOnly] |
Inspector 中只读显示字段值 |
[TextLabel] |
为字段添加自定义描述文本 |
[ConsoleCommand] |
标记命令类 / 方法,注册到命令控制台 |
Q: ViewController 必须绑定 GameObject 吗?
A: 是的。RegisterViewController<T>(GameObject) 会在 GameObject 上添加组件并初始化。也可以通过将脚本挂载到 GameObject 上,Awake 时会自动注册。
Q: 如何跨场景共享 Model?
A: 将 Architecture 所在对象设为 DontDestroyOnLoad,或在新场景重新注册并迁移数据。
Q: BindableProperty 性能如何?
A: 仅在值真正变化时触发回调(内部有相等性判断),适用于 UI 更新等场景。线程安全设计,值变更回调在锁外执行防止死锁。
Q: 事件名如何管理?
A: 建议集中定义在一个静态类中,避免魔法字符串。
Q: 如何做数据持久化?
A: 三种选择:
- Model 自带
SaveData()/LoadData()方法,自动序列化 Model 中所有BindableProperty<T>、[SaveData]和[SerializeField]标记的成员 - 使用
SaveDataAttribute精确控制需要持久化的非 BindableProperty 成员
Q: UI 面板如何管理?
A: 使用 UISystem 统一管理面板的创建、缓存、层级和生命周期。UIPanel 继承自 ArchitectureViewController,自动获得依赖注入和事件系统支持。
Q: 如何自定义 UI 资源加载方式?
A: 实现 IUIResLoader 接口,通过 UISystem.Instance.SetUIResLoader(loader) 注入自定义加载器。