读取策划配置表
一般策划都是通过EXCEL来配置,如下图所示:
Unity读取EXCEL
游戏中并不是直接读取Excel
配置,实际项目中一般都是将Excel
转为xml
、json
、lua
或自定义的文本格式的配置,这样不会让Excel打包时占据额外空间。
对于 Json
格式,在 Excel
中能够安装额外的插件,直接导出为 Json
格式,当然也可以用 Python
批量化处理,这里不赘述
读取json
我们将json文件都放入Resource文件夹中。但一般不推荐按这样做,因为Resource文件夹只读,而且打包时会全部进行压缩后打包。建议将资源打成AssetBundle
放在StreamingAssets
目录或服务器中,方便热更资源
string txt = Resources.Load<TextAsset>("task_cfg").text;
引入LitJson
能帮助我们更好的配置 地址:https://hub.fastgit.org/LitJSON/litjson
使用时引入命名空间: using LitJson;
任务配置读取
LitJson
提供了一个JsonMapper.ToObject<T>(jsonString)
方法,可以直接将json
字符串转为类对象,前提是类的字段名要与json
的字段相同
/// <summary>
/// 任务配置结构
/// </summary>
public class TaskCfgItem
{
public int task_chain_id;
public int task_sub_id;
public string icon;
public string desc;
public string task_target;
public int target_amount;
public string award;
public string open_chain;
}
public class TaskCfg
{
// 任务配置,(链id : 子任务id : TaskCfgItem)
private Dictionary<int, Dictionary<int, TaskCfgItem>> m_cfg;
// 单例模式
private static TaskCfg s_instance;
public static TaskCfg instance
{
get
{
if (s_instance == null)
s_instance = new TaskCfg();
return s_instance;
}
}
// 读取配置
public void LoadCfg()
{
m_cfg = new Dictionary<int, Dictionary<int, TaskCfgItem>>();
var txt = Resources.Load<TextAsset>("task_cfg").text;
// 直接转换为类对象
var jd = JsonMapper.ToObject<JsonData>(txt);
// 根据转换之后的类对象的个数循环
for (int i = 0, cnt = jd.Count; i < cnt; ++i)
{
// 读取每个类对象,将其视作JsonData格式
var itemJd = jd[i] as JsonData;
// 转换为任务配置对象
TaskCfgItem cfgItem = JsonMapper.ToObject<TaskCfgItem>(itemJd.ToJson());
// 任务配置中字典的相应操作
if (!m_cfg.ContainsKey(cfgItem.task_chain_id))
{
m_cfg[cfgItem.task_chain_id] = new Dictionary<int, TaskCfgItem>();
}
m_cfg[cfgItem.task_chain_id].Add(cfgItem.task_sub_id, cfgItem);
}
}
// 获取任务配置
public TaskCfgItem GetCfgItem(int chainId, int taskSubId)
{
if (m_cfg.ContainsKey(chainId) && m_cfg[chainId].ContainsKey(taskSubId))
return m_cfg[chainId][taskSubId];
return null;
}
}
任务数据的更新
目前只考虑客户端进行任务数据的管理,与服务端进行交互不在本篇的介绍范围内
public class TaskDataItem
{
// 链id
public int task_chain_id;
// 任务子id
public int task_sub_id;
// 进度
public int progress;
// 奖励是否被领取了,0:未被领取,1:已被领取
public short award_is_get;
}
打算采用的是PlayerPrefs
进行持久化储存,虽说这会污染注册表,当时我们将所有任务的数据编写在一个string中,这样产生的影响就会少很多
public class TaskData
{
public TaskData()
{
m_taskDatas = new List<TaskDataItem>();
}
public List<TaskDataItem> taskDatas
{
get { return m_taskDatas; }
}
// 定义存储数据的容器
private List<TaskDataItem> m_taskDatas;
// 从数据库读取任务数据
public void GetTaskDataFromDB(Action cb)
{
// 正规是与服务端通信,从数据库中读取,这里纯客户端进行模拟,直接使用PlayerPrefs从客户端本地读取
var jsonStr = PlayerPrefs.GetString("TASK_DATA");
var taskList = JsonMapper.ToObject< List<TaskDataItem> >(jsonStr);
for (int i = 0, cnt = taskList.Count; i < cnt; ++i)
{
AddOrUpdateData(taskList[i]);
}
cb();
}
// 获取某个任务数据
public TaskDataItem GetData(int chainId, int subId)
{
for (int i = 0, cnt = m_taskDatas.Count; i < cnt; ++i)
{
var item = m_taskDatas[i];
if (chainId == item.task_chain_id && subId == item.task_sub_id)
return item;
}
return null;
}
// 添加或更新任务
public void AddOrUpdateData(TaskDataItem itemData)
{
bool isUpdate = false;
for (int i = 0, cnt = m_taskDatas.Count; i < cnt; ++i)
{
var item = m_taskDatas[i];
if (itemData.task_chain_id == item.task_chain_id && itemData.task_sub_id == item.task_sub_id)
{
// 更新数据
m_taskDatas[i] = itemData;
isUpdate = true;
break;
}
}
if(!isUpdate)
m_taskDatas.Add(itemData);
// 排序,确保主线在最前面
m_taskDatas.Sort((a, b) =>
{
return a.task_chain_id.CompareTo(b.task_chain_id);
});
SaveDataToDB();
}
}
任务逻辑
使用Action cb
回调是为了模拟实际场景中从服务端数据库读取数据(异步)的过程, 仅在客户端交互的情况可以直接忽略
public class TaskLogic
{
public TaskLogic()
{
m_taskData = new TaskData();
}
// 先把TaskData作为成员变量,并提供一个数据属性taskDatas,方便访问
private TaskData m_taskData;
public List<TaskDataItem> taskDatas
{
get { return m_taskData.taskDatas; }
}
// 获取任务数据
public void GetTaskData(Action cb)
{
m_taskData.GetTaskDataFromDB(cb);
}
// 更新任务进度
// 使用Action<int, bool>回调是为了模拟实际场景中与服务端通信(异步)
// 处理结果会有个返回码ErrorCode(回调函数第一个参数),客户端需判断ErrorCode的值来进行处理,一般约定ErrorCode为0表示成功
// 回调函数第二个参数是是否任务进度已达成,如果任务达成,客户端需要显示领奖按钮
public void AddProgress(int chainId, int subId, int deltaProgress, Action<int, bool> cb)
{
var data = m_taskData.GetData(chainId, subId);
if (null != data)
{
data.progress += deltaProgress;
m_taskData.AddOrUpdateData(data);
var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
if (cfg != null)
cb(0, data.progress >= cfg.target_amount);
else
cb(-1, false);
}
else
{
cb(-1, false);
}
}
// 领取任务奖励,解锁下一系列任务
// cb用于与服务端交互,解决错误产生原因
public void GetAward(int chainId, int subId, Action<int, string> cb)
{
var data = m_taskData.GetData(chainId, subId);
if (data == null)
{
cb(-1, "{}");
return;
}
if (data.award_is_get == 0)
{
data.award_is_get = 1;
m_taskData.AddOrUpdateData(data);
GoNext(chainId, subId);
var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
cb(0, cfg.award);
}
else
{
cb(-2, "{}");
}
}
// 开启下一任务
private void GoNext(int chainId, int subId)
{
var data = m_taskData.GetData(chainId, subId);
var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
var nextCfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id + 1);
if (data.award_is_get == 1)
{
// 移除掉已领奖的任务
m_taskData.RemoveData(chainId, subId);
// 开启下一个任务
if (nextCfg != null)
{
TaskDataItem dataItem = new TaskDataItem();
dataItem.task_chain_id = nextCfg.task_chain_id;
dataItem.task_sub_id = nextCfg.task_sub_id;
dataItem.progress = 0;
dataItem.award_is_get = 0;
m_taskData.AddOrUpdateData(dataItem);
}
// 开启支线任务
if (!string.IsNullOrEmpty(cfg.open_chain))
{
// 开启新分支
var chains = cfg.open_chain.Split(',');
for (int i = 0, len = chains.Length; i < len; ++i)
{
var task = chains[i].Split('|');
// 更新子任务状态
TaskDataItem subChainDataItem = new TaskDataItem();
subChainDataItem.task_chain_id = int.Parse(task[0]);
subChainDataItem.task_sub_id = int.Parse(task[1]);
subChainDataItem.progress = 0;
subChainDataItem.award_is_get = 0;
m_taskData.AddOrUpdateData(subChainDataItem);
}
}
}
}
}