unity任务系统

读取策划配置表

一般策划都是通过EXCEL来配置,如下图所示:
在这里插入图片描述

Unity读取EXCEL

游戏中并不是直接读取Excel配置,实际项目中一般都是将Excel转为xmljsonlua或自定义的文本格式的配置,这样不会让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);
                }
            }
        }
    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/jkkk_/article/details/127045626