对于大部分商业游戏而言,一些需要热更新的资源往往是通过远端,传输到客户端。再加工使用的,这些资源有静态的,比如tilemap画的背景板,地面,云朵等等;有动态的,比如玩家的人物、技能特效、各种动画,这些可能是需要实时发生变更的,比如升级了你的技能就变化了。这些数据的种类、内容量以及类型大有不同。
一般的流程是,市场部根据调研整理用户需求,策划人员根据市场部的需求做出设计图纸,同时向美工和程序提需求,美术人员通过设计图,制作自己的原画集。程序员人员根据设计图和美术人员做出的资源,完成整个项目。
那么,美术需要在什么时间节点交付给程序哪些美术资源呢,怎么完成资源映射呢?
使用配置表。
配置表一般是可以通过office软件的表格模式编写的,简单易维护的excel文件。
多表之间通过一张总控表关联。读取方式一般从总控表通过唯一主键关联到分表ID,如果有嵌套,重复上述过程即可。
图 1-1 设计图中的配置表
图 1-2 配置表的一张总表
图 1-3 配置表的一张子表
比如设计图划红线部分的意思是通过总控表的推荐书籍规则,找到20001到20005的五个ID为primary key和在百科场景的推荐关卡图中拿到卡片的AB包路径,就完成了资源的定位。
比如,下面在做排位系统时,
拿到的配置表是这样的
所以本地数据结构需要定义这样的存储字段。
namespace GameData.Game
{
[Serializable]
public class AnswerQuestionsData : GameBaseData<AnswerQuestionsSubData>
{
}
[Serializable]
public class AnswerQuestionsSubData : GameSubBaseData
{
// 竞赛id 竞赛阶段名称 竞赛阶段类型 竞赛阶段勋章icon 勋章阶级icon 晋级需要星数 胜利升星数量 失败扣星数量 挑战题库 挑战题库权重 晋级题库 晋级题库权重 机器人库
// competitionId competitionName competitionType competitionIcon1 competitionIcon2 competitionStart victoryAdd failReduce competitionQuestion1 competitionWeights1 competitionQuestion2 competitionWeights2 robot
// int32 string int32 string string int32 int32 int32 []int32 []int32 []int32 []int32 []int32
// cs cs cs cs cs cs cs cs cs cs cs cs cs
public override int DataId => competitionId;
public int competitionId; // 竞赛id
public string competitionName; // 竞赛阶段名称
public int competitionType; // 竞赛阶段类型
public string competitionIcon1; // 竞赛阶段勋章icon
public string competitionIcon2; // 勋章阶级icon
public int competitionStart; // 晋级需要星数
public int victoryAdd; // 胜利升星数量
public int failReduce; // 失败扣星数量
public int[] competitionQuestion1; // 挑战题库
public int[] competitionWeights1; // 挑战题库权重
public int[] competitionQuestion2; // 晋级题库
public int[] competitionWeights2; // 晋级题库权重
public int[] robot; // 机器人库
}
public class AnswerQuestions : ConfigBase
{
public override DataType GetDataType()
{
return DataType.AnswerQuestions;
}
protected override UniTask<GameBaseData> UnserializeData()
{
return Unserialize<AnswerQuestionsData, AnswerQuestionsSubData>(nameof(AnswerQuestions));
}
}
}
在完成序列化和反序列化后(*本文只谈数据的读取,传输部分见本专栏的其他文章)。我们就可以直观地对AnswerQuestionsSubData类,根据需求封装相应的功能,为我们的GameLogic使用。
比如说,这里案子有一个需求,
需要做一个晋级赛答题的判定(参考王者荣耀排位系统,满星后开启晋级赛,当且仅当第一次参加晋级赛需要进行晋级答题)
1.本地读取 AnswerQuestionsSubData数据内容,封装功能。
_userBase封装了角色相应字段和方法的基类
public class UserBase{
private UserBaseCache _userBaseCache;
private AnswerQuestionsData _answerQuestionsData;
//默认开始段位
private const int DefaultRankSegment = 60001;
public UserBase()
{
_userBaseCache = DataManager.Instance.GetGameDataByType(DataType.UserBaseCache) as UserBaseCache;
_answerQuestionsData = DataManager.Instance.GetGameDataByType(DataType.AnswerQuestions) as AnswerQuestionsData;
}
//这里用到的方法
//获取排位分段
public int GetRankSegment()
{
return _userBaseCache.RankSegment == 0 ? DefaultRankSegment : _userBaseCache.RankSegment;
}
//获取MaxRankSegment
public int GetMaxRankSegment()
{
return _userBaseCache.RankSegment == 0 ? DefaultRankSegment : _userBaseCache.RankSegment;;
}
//获取RankStar
public int GetRankStar()
{
return _userBaseCache.RankStar;
}
//其他方法
}
2.编写相关逻辑IsPromotionMatch()
DataManager是数据管理的单例,GetGameDataByType(T) as T初始化了根据T的初始化了数据,并强制返回T类型的实例化对象
public class RankTypeSettings{
private UserBase _userBase { get; set; }
private AnswerQuestionsData _answerQuestionsData { get; set; }
public RankTypeSettings()
{
_userBase = UserDataSystem.Instance.UserBase;
_answerQuestionsData = DataManager.Instance.GetGameDataByType(DataType.AnswerQuestions) as AnswerQuestionsData;
}
/// <summary>
/// 是否开启晋级赛
/// </summary>
/// <param name="level"></param>
/// <param name="currentType"></param>
/// <returns></returns>
public bool IsPromotionMatch()
{
AnswerQuestionsSubData[] dataList = _answerQuestionsData.data;
int startID = dataList[0].competitionId - 1;
int currentID = _userBase.GetRankSegment() - startID;
int currentLevel = _userBase.GetRankStar();
int competitionTypeMax = _answerQuestionsData.data.FirstOrDefault(data => data.competitionId == _userBase.GetMaxRankSegment())!.competitionType;
int competitionTypeCurrent = _answerQuestionsData.data.FirstOrDefault(data => data.competitionId == _userBase.GetRankSegment())!.competitionType;
//第一次到达目标段位并且目标段位是晋级段位可参加晋级答题
return (dataList[currentID].competitionStart == currentLevel) && (competitionTypeMax <= competitionTypeCurrent);
}
}