最近在一家上市公司的游戏部实习。组里用的T-Engine,此后准备开一个专栏记录一下,自己从框架的角度,看待游戏项目中(自己涉及到的模块)部分的编写。
大概会从具体例子from top to bottom的流程浅浅地聊一下框架。
为什么选择from top to bottom?
我思考了好几个书写的流程,因为商业框架的继承关系非常复杂,大量的交叉引用,如果直接从底层开始讲,那牵扯的内容估计够大几十页,那显然是不合理的,也不符合用户观感。
出于保护,我把项目中的美术资源全部打码了。
数据部分
看前须知:
Unity基础篇:Serializable总结与深入研究
彻底搞明白Unity-async-task特性
从顶层应用【探索卡片的付费弹窗】浅谈数据
ExploreWidgetUI预制件
它的核心是一个Scroll View,支持横向滑动,涉及一些热更的数据。
namespace GameLogic.UI
{
[Window(UILayer.UI)]
public class ExploreWidgetUI : UIWidget
{
#region 脚本工具生成的代码
private GameObject m_goCloseBtn;
private GameObject m_goContent;
public override void ScriptGenerator()
{
m_goCloseBtn = FindChild("m_goCloseBtn").gameObject;
m_goContent = FindChild("Panel/ScrollView/ViewPort/m_goContent").gameObject;
}
#endregion
public override void OnCreate()
{
base.OnCreate();
m_goCloseBtn.GetComponent<Button>().onClick.AddListener(() =>
{
//关闭弹窗
DestroyUIWidget(this);
});
//动态加载资源卡片
LoadImageContentExploreLevel();
}
#region 事件
/// <summary>
/// 动态加载探索卡片
/// </summary>
private void LoadImageContentExploreLevel()
{
List<BookGallerySystem.RecommendBookData> recommendPictureLists = BookGallerySystem.Instance.LoadRecommendPicture();
foreach (BookGallerySystem.RecommendBookData item in recommendPictureLists)
{
//实例化对象
Item_RecommendPicture item_RecommendPicture = CreateWidgetByPath<Item_RecommendPicture>
(m_goContent.transform, "Item_RecommendPicture");
//更新
item_RecommendPicture.Update(item);
}
}
#endregion
}
}
ExploreWidgetUI 是一个Widget组件,定位于一种小窗口组件。继承UIWidget比较合适。
下面是探索卡片RecommendBookData的获取
#region Explore
/// <summary>
/// 推荐书籍数据
/// </summary>
public class RecommendBookData
{
public string PictureName;
public bool IsLocked;
}
/// <summary>
/// 返回配置表中需更改字段
/// </summary>
/// <returns></returns>
public List<RecommendBookData> LoadRecommendPicture()
{
var recommendBookList = new List<RecommendBookData>();
foreach (var bookId in ControlData.data[0].recommendedBooks)
{
var bookData = FindBookData(bookId);
if (bookData == null) continue;
recommendBookList.Add(new RecommendBookData
{
PictureName = bookData.recommendPicture,
IsLocked = bookData.unlockBookType == 1 // 1: locked, 0: unlocked
});
}
return recommendBookList;
}
#endregion
第19行遍历的ControlData.data[0]是配置表中每个卡片的结构,比如我里需要加载卡片的img,需要根据唯一主键ID 访问对应的recommendPicture,拿到sprite2D and UI 图片然后替换原位置image对象的Sourge Image即可
这些美术资源是存放在服务器里的,通过AB加载到本地,与策划提供的配置表一一绑定,
本地定义了存储的数据结构
namespace GameData.Game
{
[Serializable]
public class ControlData : GameBaseData<ControlSubData>
{
}
[Serializable]
public class ControlSubData: GameSubBaseData
{
// 总控id 控制角色移动速度 推荐书籍规则
// id characterSpeed recommendedBooks
// int32 int32 []int32
public override int DataId => id;
public int id; //总控id
public int characterSpeed; //控制角色移动速度
public int[] recommendedBooks; //推荐书籍规则
public int[] mainBooks; //主界面书籍
public float[] coefficientTime; //系数时间
public float[] coefficientScore; //系数分数
}
public class Control : ConfigBase
{
public override DataType GetDataType()
{
return DataType.Control;
}
protected override UniTask<GameBaseData> UnserializeData()
{
return Unserialize<ControlData, ControlSubData>(nameof(Control));
}
}
}
怎样处理接受到的服务器数据呢?
反序列化。因为接收到的服务器中的二进制流。
上述方法中的UnserializeData方法中的返回值是一个GameBaseData(来源于框架定义的基类)泛型的UniTask.
下面给出Unserialize<,>()的方法
protected static async UniTask<GameBaseData> Unserialize<TData, TSubData>(string typeName) where TData : GameBaseData<TSubData>, new()
where TSubData : GameSubBaseData
{
var jsonData = AssetsLoadManager.LoadAssets<TextAsset>(char.ToLower(typeName[0]) + typeName[1..]);
return new TData
{
data = JSONSerializer.Unserialize<TSubData[]>(jsonData.text)
};
}
这里是异步的形式,以纯文本的形式AB加载了TextAsset类型的数据,拿到了json串
然后解析json,反序列化封装成对象,构建上面用到的ControlData类,这是总控表,在顶层需要用到的时候,实例化类,使用就可以了。
下面是json序列化和反序列化的方法
namespace PandaScript.Helpers
{
public class JSONSerializer
{
public static string Serialize<T>(T data) where T : class
{
string serializedData = null;
try
{
serializedData = JsonConvert.SerializeObject(data, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
MissingMemberHandling = MissingMemberHandling.Ignore
});
}
catch (Exception e)
{
Debug.LogError(string.Format("An error happened while serializing the object : {0}\n{1}", e.Message,
e.StackTrace));
}
return serializedData;
}
public static T Unserialize<T>(string serializedData) where T : class
{
var unserializedData = default(T);
try
{
unserializedData = JsonConvert.DeserializeObject<T>(serializedData, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
MissingMemberHandling = MissingMemberHandling.Ignore
});
}
catch (Exception e)
{
Debug.LogError(string.Format("An error happened while serializing the object : {0}\n{1}", e.Message,
e.StackTrace));
}
return unserializedData;
}
}
}
JsonConvert.DeserializeObject()方法对T类型的序列化数据解析,返回T类型的非序列化数据。
这样,我们等于是通过读取服务器中的Byte流,反序列化还原成了类似下面(策划提供)的json文件,再与本地的数据结构关联,最终就到了我需要用到ControlData.data列表,拿到了相应美术资源的地址。
策划人员只需要通过.excel表格提出资源的配置,关联程序部的工作。详见另一篇从配置表角度浅谈游戏公司的资源管理