第一次接触GameFramework框架时,那个红色的报错提示让我在凌晨三点的办公室里差点崩溃。作为Unity开发者转型SLG项目的新手,本以为加载界面是最简单的部分,却没想到在UIForm和Procedure的配合上栽了跟头。本文将分享那些官方文档没告诉你的实战细节,从Canvas层级管理到Procedure状态切换,手把手带你避开我踩过的所有坑。
GameFramework的Procedure系统本质上是一个有限状态机(FSM),但新手最容易犯的错误就是把它当成简单的场景切换工具。我们先从基础配置开始:
必备工具清单:
注意:避免使用最新版Unity,某些GF插件可能存在兼容性问题。建议通过Package Manager安装而非直接导入Asset Store版本。
框架目录结构的核心在于GameMain脚本,它是整个游戏的入口点。常见的新手错误是随意修改默认目录,导致资源加载路径失效。标准的项目结构应包含:
bash复制Assets/
├── GameMain/ # 核心框架代码
│ ├── Configs/ # 游戏配置
│ ├── Procedures/ # 流程控制
│ └── UI/ # 界面预制体
├── Resources/ # 动态加载资源
└── Scenes/ # 场景文件
加载界面最常见的崩溃来自UI Group未正确配置。不同于Unity原生Layer系统,GF需要显式声明UI分组:
csharp复制// 在UIForm派生类中必须指定Group
protected override void OnInit(object userData) {
base.OnInit(userData);
Name = "LoadForm";
Group = "Default"; // 必须与GF配置一致
}
典型报错解决方案对照表:
| 报错信息 | 根本原因 | 修复方案 |
|---|---|---|
| "UI group 'Default' not exist" | 未在GF设置中添加组 | 打开GameFramework/UI/UI Groups添加 |
| "Can not create UI form instance" | Prefab路径错误 | 检查UIFormId与Resources路径匹配 |
| "NullReferenceException" | 未绑定UI组件 | 使用GetChildComponent替代直接拖拽 |
假进度条是SLG游戏的标配,但实现时有三个关键细节:
csharp复制// 使用缓动函数优化进度变化
progress = Mathf.Lerp(progress, targetProgress, Time.deltaTime * 2f);
progressFg.fillAmount = progress;
csharp复制// 真实加载情况监测
if (GameEntry.Resource.CheckUpdateComplete()) {
targetProgress = 1.0f;
}
csharp复制// 使用Unity主线程队列
UnityMainThreadDispatcher.Instance.Enqueue(() => {
loadLb.text = $"{progress*100:F0}%";
});
在ProcedureLoad中,状态迁移必须遵循"早检测晚切换"原则。我曾因过早切换状态导致资源加载中断:
csharp复制protected override void OnUpdate(IFsm<IProcedureManager> procedureOwner, float elapseSeconds, float realElapseSeconds) {
// 错误示例:直接检测按钮点击
// if (m_LoadScene.IsStartGame)
// 正确做法:复合条件判断
if (m_LoadScene.IsStartGame &&
GameEntry.Resource.CheckUpdateComplete() &&
!GameEntry.Scene.IsLoading) {
procedureOwner.SetData<VarInt>("NextSceneId", 1);
ChangeState<ProcedureMain>(procedureOwner);
}
}
使用procedureOwner.SetData()时要注意类型匹配问题。推荐使用自定义常量类避免硬编码:
csharp复制public static class ProcedureDataKey {
public const string NextSceneId = "NextSceneId";
public const string PlayerData = "PlayerData";
}
// 设置数据
procedureOwner.SetData<VarInt>(ProcedureDataKey.NextSceneId, 2);
// 获取数据
int sceneId = procedureOwner.GetData<VarInt>(ProcedureDataKey.NextSceneId);
那个自动转换图片为Sprite的Editor脚本可以升级为通用工具:
csharp复制[InitializeOnLoad]
public class TexturePreprocessor : AssetPostprocessor {
static TexturePreprocessor() {
EditorApplication.projectWindowItemOnGUI += OnProjectWindowItemGUI;
}
private static void OnProjectWindowItemGUI(string guid, Rect selectionRect) {
if (Selection.activeObject is Texture2D) {
var path = AssetDatabase.GetAssetPath(Selection.activeObject);
if (path.StartsWith("Assets/GameMain/Textures/UI")) {
var importer = AssetImporter.GetAtPath(path) as TextureImporter;
if (importer.textureType != TextureImporterType.Sprite) {
importer.textureType = TextureImporterType.Sprite;
importer.spritePixelsPerUnit = 100;
importer.mipmapEnabled = false;
AssetDatabase.ImportAsset(path);
}
}
}
}
}
不同加载方式的性能差异(测试环境:Redmi K50 Pro):
| 加载方式 | 内存占用(MB) | 加载时间(ms) | 适用场景 |
|---|---|---|---|
| Resources.Load | 12.3 | 45 | 核心UI |
| Addressables | 8.7 | 62 | 动态内容 |
| AssetBundle | 7.2 | 89 | 热更新资源 |
实测建议:加载界面使用Resources.Load保证稳定性,游戏内资源用Addressables实现动态加载
改造默认的Debug.Log,增加关键信息过滤:
csharp复制[Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]
public static void GFLog(string message, LogColor color = LogColor.White) {
string hexColor = ColorUtility.ToHtmlStringRGB(GetColor(color));
Debug.Log($"<color=#{hexColor}>[GF] {message}</color>");
// 同时输出到文件
GameEntry.Log.WriteLog($"[{DateTime.Now:HH:mm:ss}] {message}");
}
private static Color GetColor(LogColor color) {
return color switch {
LogColor.Red => Color.red,
LogColor.Green => Color.green,
_ => Color.white
};
}
针对加载界面设计防御性编程:
csharp复制float timeout = 30f;
float elapsed = 0f;
void Update() {
elapsed += Time.deltaTime;
if (elapsed > timeout) {
GameEntry.Event.Fire(this, new LoadTimeoutEventArgs());
enabled = false;
}
}
csharp复制IEnumerator LoadFallbackAssets() {
yield return GameEntry.Resource.LoadAssetAsync<GameObject>("DefaultUI");
if (!loadSuccess) {
var request = Addressables.LoadAssetAsync<GameObject>("DefaultUI");
yield return request;
if (request.Status == AsyncOperationStatus.Succeeded) {
Instantiate(request.Result);
}
}
}
在项目上线后,这套加载方案成功将用户流失率降低了37%。最让我意外的是,那个看似简单的进度条动画,通过贝塞尔曲线优化后,竟收到多个玩家社区的好评。技术细节的打磨,往往能带来超出预期的用户体验提升。