主頁 >  其他 > 【游戲開發實戰】Unity從零做一個任務系統,人生如夢,畢業大學生走上人生巔峰(含原始碼工程 | 鏈式任務 | 主線支線)

【游戲開發實戰】Unity從零做一個任務系統,人生如夢,畢業大學生走上人生巔峰(含原始碼工程 | 鏈式任務 | 主線支線)

2021-07-27 09:00:48 其他

本文最終效果如下:
請添加圖片描述
請添加圖片描述
請添加圖片描述
工程思維導圖:
在這里插入圖片描述

工程原始碼見文章末尾,

文章目錄

      • 一、前言
      • 二、什么是任務系統
      • 三、需求檔案
        • 1、故事背景
        • 2、任務鏈設計
        • 3、任務規則
        • 4、界面樣式設計
      • 四、從哪里開始著手
      • 五、任務配置表
        • 1、定義表頭欄位
        • 2、配置表格資料
        • 3、轉表工具:Excel轉Json
      • 六、讀取配置表
        • 1、資源加載:Resources.Load
        • 2、C#的json庫:LitJson
        • 3、任務配置配置讀取:TaskCfg.cs腳本
          • 3.1、創建TaskCfg.cs腳本
          • 3.2、定義任務配置結構:TaskCfgItem
          • 3.3、定義存盤配置的容器
          • 3.4、讀取配置:LoadCfg
          • 3.5、索引任務配置項:GetCfgItem
          • 3.6、使用單例模式
          • 3.7、思維導圖
          • 3.8、TaskCfg.cs完整代碼
      • 七、任務資料增刪改查:TaskData.cs腳本
        • 1、創建TaskData.cs腳本
        • 2、定義任務資料:TaskDataItem
        • 3、本地資料讀寫:PlayerPrefs
        • 4、定義存盤資料的容器
        • 5、從本地讀取任務資料
        • 6、寫任務資料到本地
        • 7、查詢指定任務的資料
        • 8、任務資料增加或更新
        • 9、任務資料洗掉
        • 10、思維導圖
        • 11、TaskData.cs完整代碼
      • 八、任務邏輯:TaskLogic.cs
        • 1、創建TaskLogics腳本
        • 2、成員:TaskData
        • 3、獲取任務資料
        • 4、更新任務進度
        • 5、領取任務獎勵
        • 6、一鍵領取任務的獎勵
        • 7、開啟下一個任務(含支線)
        • 8、思維導圖
        • 9、TaskLogic.cs完整代碼
      • 九、UI界面制作
        • 1、UI資源獲取
        • 2、場景界面制作:Main場景,人生如夢
        • 3、制作串列界面預設:TaskPanel.prefab
        • 4、制作提示框界面預設:TipsPanel.prefab
        • 5、制作獎勵界面預設:AwardPanel.prefab
      • 十、撰寫界面代碼
        • 1、入口腳本:Main.cs
        • 2、任務串列界面
          • 2.1、回圈復用串列
          • 2.2、串列項腳本:TaskItemUI.cs
          • 2.3、串列界面腳本:TaskPanel.cs
        • 3、提示框界面:TipsPanel.cs
        • 4、獎勵界面:AwardPanel.cs
        • 5、精靈資源管理器
      • 十一、運行測驗
      • 十二、工程原始碼
      • 十三、完畢

一、前言

嗨,大家好,我是新發,
事情是這樣的,有小朋友微信問我如何做任務系統,作為一個熱心的技術博主,我都是能幫就幫,今天,我就來做一個任務系統吧,
在這里插入圖片描述

二、什么是任務系統

任務系統就是一個有明確目標性的系統,通過設定任務來引導玩家進行游戲,讓玩家更快的融入游戲中,
可以說任務系統幾乎是游戲必備的模塊,我們隨便找個游戲都可以看到任務系統,
在這里插入圖片描述
根據這位小朋友的需求,是要做 主線任務/支線任務 的系統,
簡單的說,就是有一條 主線任務鏈,在完成主線任務鏈上的某個節點時,開啟下一個任務,并可以開啟一潭訓多條 支線任務鏈,主線任務和多條支線任務并行,畫個圖,方便大家理解:
在這里插入圖片描述

三、需求檔案

由于只有我一個人,沒有策劃,那我就先充當策劃,給自己寫個需求檔案吧~

1、故事背景

主人公林新發剛剛大學畢業,開始面臨一個人生難題:如何走上人生巔峰!
現在我們為林新發設計一套任務,幫助他走上人生巔峰吧~

2、任務鏈設計

下面,就是走上人生巔峰的任務鏈啦~
請添加圖片描述

3、任務規則

主線任務必須按順序完成;
主線任務與支線任務可以并行;
支線任務并不影響主線任務;
每完成一個任務都可以得到相應的獎勵;
任務界面只顯示當前要執行或已完成但還未領取獎勵的任務;
任務界面中要顯示每個任務當前的進度;
每個任務有個前往按鈕,點擊前往按鈕觸發任務執行或跳轉到相應的界面;
每個任務有對應的圖示,可配置;
界面底部有一鍵領獎按鈕,點擊一鍵領獎領取所有可以領獎的任務獎勵,

4、界面樣式設計

使用 原型圖設計 軟體制作界面樣式,如下:
在這里插入圖片描述

四、從哪里開始著手

對于萌新來說,拿到需求時可能不知道從哪里開始做,是先寫代碼還是先做界面?代碼又是從哪里開始寫?

我總結了一個客戶端開發流程,大家可以按這個流程執行,
在這里插入圖片描述

五、任務配置表

1、定義表頭欄位

根據需求,我們先定義表頭欄位,
在這里插入圖片描述

欄位解釋:

欄位資料型別說明
task_chain_idint鏈id,每個任務都有它對應的鏈id,同一條鏈上的任務的鏈id相同
task_sub_idint任務id,它是鏈上的任務id,不同鏈的任務id可以重復,從1開始往下自增
iconstring任務圖示
descstring任務描述,這個會顯示到界面中
task_targetstring任務目標,定義一個字串來表示任務的目標類別,比如加班5次加班10次的任務目標是一樣的,只是數量不同,同理,寫博客5篇寫博客100篇的任務目標也是一樣的
target_amountint目標數量,比如加班5次的目標數量就是5,寫博客100篇的目標數量就是100
awardstring獎勵,json格式,例:{"gold":1000},表示獎勵1000金幣
open_chainstring要打開的支線任務,格式:鏈id|任務id,開啟多個鏈以英文逗號隔開,例:2|1,4|1表示打開 鏈2的子任務1和打開鏈4的子任務1

2、配置表格資料

根據我們上面設計的任務鏈,在配置表中配置任務資料,入下:

注:黃色的是主線任務,每條支線任務我都單獨標了顏色方便閱讀,

在這里插入圖片描述

表格保存為鏈式任務.xlsx,如下
在這里插入圖片描述

3、轉表工具:Excel轉Json

Excel表格是方便策劃進行配置數值,游戲中并不是直接讀取Excel配置,實際專案中一般都是將Excel轉為xmljsonlua或自定義的文本格式的配置,
我這里就以ExcelJson為例,處理Excel我推薦大家使用python來寫工具,我之前寫過一篇文章:《教你使用python讀寫Excel表格(增刪改查操作),使用openpyxl庫》,里面我詳細介紹了使用pythonopenpyxl庫來讀寫Excel,建議大家先認真看一下這篇文章,
這里我就直接把最終我寫好的python代碼貼出來,代碼也很簡單,這里不贅述了~

import openpyxl
import json

# excel表格轉json檔案
def excel_to_json(excel_file, json_f_name):
    jd = []
    heads = []
    book = openpyxl.load_workbook(excel_file)
    sheet = book[u'Sheet1']
    
    max_row = sheet.max_row
    max_column = sheet.max_column
    # 決議表頭
    for column in range(max_column):
        heads.append(sheet.cell(1, column + 1).value)
    # 遍歷每一行
    for row in range(max_row):
        if row < 2:
        	# 前兩行跳過
            continue
        one_line = {}
        # 遍歷一行中的每一個單元格
        for column in range(max_column): 
            k = heads[column]
            v = sheet.cell(row + 1, column + 1).value
            one_line[k] = v
        jd.append(one_line)
    book.close()
    # 將json保存為檔案
    save_json_file(jd, json_f_name)

# 將json保存為檔案
def save_json_file(jd, json_f_name):
    f = open(json_f_name, 'w', encoding='utf-8')
    txt = json.dumps(jd, indent=2, ensure_ascii=False)
    f.write(txt)
    f.close()

if '__main__' == __name__:
     excel_to_json(u'鏈式任務.xlsx', 'task_cfg.bytes')

上面的python代碼保存為excel_to_json.py,如下
在這里插入圖片描述
excel_to_json.py放在上面的鏈式任務.xlsx檔案的同級目錄中,執行excel_to_json.py,生成task_cfg.bytes
在這里插入圖片描述
使用文本編輯器打開task_cfg.bytes,看下生成效果,如下,格式正確:
在這里插入圖片描述

六、讀取配置表

上面配置表做好了,接下來就可以開始動手Unity部分了,
Unity中如何讀取配置表呢?其實配置表也是一種資源,關于資源讀取我之前寫過相關文章:
《Unity游戲開發——新發教你做游戲(三):3種資源加載方式》
在這里插入圖片描述

這里我就簡單處理,通過Resources.Load來讀取檔案,

1、資源加載:Resources.Load

先新建一個Resources檔案夾,
在這里插入圖片描述
task_cfg.bytes放在Resources目錄中,
在這里插入圖片描述
這樣我們就可以直接使用Resources.Load來讀取task_cfg.bytes檔案了,如下:

string txt = Resources.Load<TextAsset>("task_cfg").text;

2、C#的json庫:LitJson

因為我們使用的是json格式的文本,要決議它我們需要使用json庫,這里我推薦使用LitJson,可以在GitHub中找到LitJson的開源專案,
地址:https://hub.fastgit.org/LitJSON/litjson
在這里插入圖片描述
我們下載下來后,把src目錄中的LitJson檔案夾整個拷貝到我們Unity工程中,如下:
在這里插入圖片描述
這樣我們就可以在C#中使用LitJson了,
使用時引入命名空間:

using LitJson;

3、任務配置配置讀取:TaskCfg.cs腳本

3.1、創建TaskCfg.cs腳本

現在我們開始寫C#代碼,養成好習慣,先建好Scripts目錄,我們的資料代碼、邏輯代碼和界面代碼要分開,所以建立DataLogicView三個子目錄,
在這里插入圖片描述
Data目錄中新建一個TaskCfg.cs腳本,
在這里插入圖片描述

3.2、定義任務配置結構:TaskCfgItem

LitJson提供了一個JsonMapper.ToObject<T>(jsonString)方法,可以直接將json字串轉為類物件,前提是類的欄位名要與json的欄位相同,所以我們先定義一個與json欄位名相同的類TaskCfgItem,如下:

// TaskCfg.cs

/// <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;
}
3.3、定義存盤配置的容器

為了方便在記憶體中索引配置表,我們使用字典來存盤,定義一個用來存放配置資料的字典:

// TaskCfg.cs

// 任務配置,(鏈id : 子任務id : TaskCfgItem)
private Dictionary<int, Dictionary<int, TaskCfgItem>> m_cfg;
3.4、讀取配置:LoadCfg

我們封裝一個LoadCfg方法來讀取配置,如下:

// TaskCfg.cs

/// <summary>
/// 讀取配置
/// </summary>
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)
    {
        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);
    }

}
3.5、索引任務配置項:GetCfgItem

為了索引任務配置項,我們再封裝一個GetCfgItem方法,

// TaskCfg.cs

/// <summary>
/// 獲取配置項
/// </summary>
/// <param name="chainId">鏈id</param>
/// <param name="taskSubId">任務子id</param>
/// <returns></returns>
public TaskCfgItem GetCfgItem(int chainId, int taskSubId)
{
    if (m_cfg.ContainsKey(chainId) && m_cfg[chainId].ContainsKey(taskSubId))
        return m_cfg[chainId][taskSubId];
    return null;
}
3.6、使用單例模式

我們希望TaskCfg全域只有一個物件,我們使用單例模式,

// TaskCfg.cs

// 單例模式
private static TaskCfg s_instance;
public static TaskCfg instance
{
    get
    {
        if (null == s_instance)
            s_instance = new TaskCfg();
        return s_instance;
    }
}

這樣我們就可以通過TaskCfg.instance來呼叫它的public方法了,如下

// 呼叫讀取配置的方法
TaskCfg.instance.LoadCfg();
3.7、思維導圖

畫個圖,
在這里插入圖片描述

3.8、TaskCfg.cs完整代碼

最終,TaskCfg.cs完整代碼如下:

/// <summary>
/// 任務配置讀取與查詢
/// 作者:林新發,博客:https://blog.csdn.net/linxinfa
/// </summary>

using System.Collections.Generic;
using UnityEngine;
using LitJson;

public class TaskCfg
{
    /// <summary>
    /// 讀取配置
    /// </summary>
    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)
        {
            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);
        }
    }

    /// <summary>
    /// 獲取配置項
    /// </summary>
    /// <param name="chainId">鏈id</param>
    /// <param name="taskSubId">任務子id</param>
    /// <returns></returns>
    public TaskCfgItem GetCfgItem(int chainId, int taskSubId)
    {
        if (m_cfg.ContainsKey(chainId) && m_cfg[chainId].ContainsKey(taskSubId))
            return m_cfg[chainId][taskSubId];
        return null;
    }

    // 任務配置,(鏈id : 子任務id : TaskCfgItem)
    private Dictionary<int, Dictionary<int, TaskCfgItem>> m_cfg;

    private static TaskCfg s_instance;
    public static TaskCfg instance
    {
        get
        {
            if (null == s_instance)
                s_instance = new TaskCfg();
            return s_instance;
        }
    }
}

/// <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;
}

七、任務資料增刪改查:TaskData.cs腳本

1、創建TaskData.cs腳本

嚴格來說,我們需要在服務端存盤任務資料、更新任務進度等,這里我就只是在客戶端進行模擬,不做服務端了,
Scripts / Data目錄中新建一個TaskData.cs腳本,來實作任務資料增刪改查的功能,
在這里插入圖片描述

2、定義任務資料:TaskDataItem

我們要讀寫任務資料,需要先定義任務資料結構TaskDataItem

// TaskData.cs

/// <summary>
/// 任務資料
/// </summary>
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;
}

3、本地資料讀寫:PlayerPrefs

Unity提供了一個PlayerPrefs類給我們,可以很方便進行本地持久化資料讀寫,
在這里插入圖片描述
讀:

string defaultJson = "[{'task_chain_id':1,'task_sub_id':1,'progress':0,'award_is_get':0}]";
string jsonStr = PlayerPrefs.GetString("TASK_DATA", defaultJson);

寫:

string jsonStr = "[{'task_chain_id':1,'task_sub_id':1,'progress':0,'award_is_get':0}]";
PlayerPrefs.SetString("TASK_DATA", jsonStr);

清空:

PlayerPrefs.DeleteKey("TASK_DATA");

4、定義存盤資料的容器

定義一個容器用于記憶體中存盤資料,

private List<TaskDataItem> m_taskDatas;

5、從本地讀取任務資料

使用PlayerPrefs.GetString介面從本地讀取資料,使用Action cb回呼是為了模擬實際場景中從服務端資料庫讀取資料(異步)的程序,

/// <summary>
/// 從資料庫讀取任務資料
/// </summary>
/// <param name="cb"></param>
public void GetTaskDataFromDB(Action cb)
{
    // 正規是與服務端通信,從資料庫中讀取,這里純客戶端進行模擬,直接使用PlayerPrefs從客戶端本地讀取
    var jsonStr = PlayerPrefs.GetString("TASK_DATA", "[{'task_chain_id':1,'task_sub_id':1,'progress':0,'award_is_get':0}]");
    var taskList = JsonMapper.ToObject<List<TaskDataItem>>(jsonStr);
    for (int i = 0, cnt = taskList.Count; i < cnt; ++i)
    {
        AddOrUpdateData(taskList[i]);
    }
    cb();
}

6、寫任務資料到本地

使用PlayerPrefs.SetString介面寫資料到本地,

/// <summary>
/// 寫資料到資料庫
/// </summary>
private void SaveDataToDB()
{
    var jsonStr = JsonMapper.ToJson(m_taskDatas);
    PlayerPrefs.SetString("TASK_DATA", jsonStr);
}

7、查詢指定任務的資料

/// <summary>
/// 獲取某個任務資料
/// </summary>
/// <param name="chainId">鏈id</param>
/// <param name="subId">任務子id</param>
/// <returns></returns>
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;
}

8、任務資料增加或更新

新增任務時,需要對串列進行重新排序,確保主線任務(即task_chain_id1)的任務排在最前面,

/// <summary>
/// 添加或更新任務
/// </summary>
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();
}

9、任務資料洗掉

/// <summary>
/// 移除任務資料
/// </summary>
/// <param name="chainId">鏈id</param>
/// <param name="subId">任務子id</param>
public void RemoveData(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)
        {
            m_taskDatas.Remove(item);
            SaveDataToDB();
            return;
        }
    }
}

10、思維導圖

按照慣例,畫個圖:
在這里插入圖片描述

11、TaskData.cs完整代碼

最終TaskData.cs完整代碼如下:

/// <summary>
/// 任務資料增刪改查
/// 作者:林新發,博客:https://blog.csdn.net/linxinfa
/// </summary>

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LitJson;
using System;

public class TaskData
{
    public TaskData()
    {
        m_taskDatas = new List<TaskDataItem>();
    }

    /// <summary>
    /// 從資料庫讀取任務資料
    /// </summary>
    /// <param name="cb"></param>
    public void GetTaskDataFromDB(Action cb)
    {
        // 正規是與服務端通信,從資料庫中讀取,這里純客戶端進行模擬,直接使用PlayerPrefs從客戶端本地讀取
        var jsonStr = PlayerPrefs.GetString("TASK_DATA", "[{'task_chain_id':1,'task_sub_id':1,'progress':0,'award_is_get':0}]");
        var taskList = JsonMapper.ToObject<List<TaskDataItem>>(jsonStr);
        for (int i = 0, cnt = taskList.Count; i < cnt; ++i)
        {
            AddOrUpdateData(taskList[i]);
        }
        cb();
    }

    /// <summary>
    /// 添加或更新任務
    /// </summary>
    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();
    }

    /// <summary>
    /// 獲取某個任務資料
    /// </summary>
    /// <param name="chainId">鏈id</param>
    /// <param name="subId">任務子id</param>
    /// <returns></returns>
    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;
    }

    /// <summary>
    /// 移除任務資料
    /// </summary>
    /// <param name="chainId">鏈id</param>
    /// <param name="subId">任務子id</param>
    public void RemoveData(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)
            {
                m_taskDatas.Remove(item);
                SaveDataToDB();
                return;
            }
        }
    }

    /// <summary>
    /// 寫資料到資料庫
    /// </summary>
    private void SaveDataToDB()
    {
        var jsonStr = JsonMapper.ToJson(m_taskDatas);
        PlayerPrefs.SetString("TASK_DATA", jsonStr);
    }

    public void ResetData(Action cb)
    {
        PlayerPrefs.DeleteKey("TASK_DATA");
        m_taskDatas.Clear();
        GetTaskDataFromDB(cb);
    }

    public List<TaskDataItem> taskDatas
    {
        get { return m_taskDatas; }
    }

    // 任務資料
    private List<TaskDataItem> m_taskDatas;
}

/// <summary>
/// 任務資料
/// </summary>
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;
}

八、任務邏輯:TaskLogic.cs

1、創建TaskLogics腳本

Scripts / Logic目錄中創建TaskLogic.cs腳本,
在這里插入圖片描述
任務的邏輯其實就是進度更新、任務完成后領獎、開啟下一個任務、開啟分支任務等,我們挨個來實作,

2、成員:TaskData

先把TaskData作為成員變數,并提供一個資料屬性taskDatas,方便訪問,

private TaskData m_taskData;

public List<TaskDataItem> taskDatas
{
    get { return m_taskData.taskDatas; }
}

public TaskLogic()
{
    m_taskData = new TaskData();
}

3、獲取任務資料

/// <summary>
/// 獲取任務資料
/// </summary>
/// <param name="cb">回呼</param>
public void GetTaskData(Action cb)
{
    m_taskData.GetTaskDataFromDB(cb);
}

4、更新任務進度

使用Action<int, bool>回呼是為了模擬實際場景中與服務端通信(異步),處理結果會有個回傳碼ErrorCode(回呼函式第一個引數),客戶端需判斷ErrorCode的值來進行處理,一般約定ErrorCode0表示成功,回呼函式第二個引數是是否任務進度已達成,如果任務達成,客戶端需要顯示領獎按鈕,

/// <summary>
/// 更新任務進度
/// </summary>
/// <param name="chainId">鏈id</param>
/// <param name="subId">任務子id</param>
/// <param name="deltaProgress">進度增加量</param>
/// <param name="cb">回呼</param>
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 (null != cfg)
            cb(0, data.progress >= cfg.target_amount);
        else
            cb(-1, false);
    }
    else
    {
        cb(-1, false);
    }
}

5、領取任務獎勵

是否領獎的狀態欄位為award_is_get,為0表示未領獎,為1表示已領過獎,
領完獎的任務從串列中移除,并開啟下一個任務(如果配置了開啟支線任務,則還需要配套開啟對應的支線任務),

/// <summary>
/// 領取任務獎勵
/// </summary>
/// <param name="chainId">鏈id</param>
/// <param name="subId">任務子id</param>
/// <param name="cb">回呼</param>
public void GetAward(int chainId, int subId, Action<int, string> cb)
{
    var data = m_taskData.GetData(chainId, subId);
    if (null == data)
    {
        cb(-1, "{}");
        return;
    }
    if (0 == data.award_is_get)
    {
        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, "{}");
    }
}

6、一鍵領取任務的獎勵

遍歷所有達成進度且未領獎的任務,支線領獎,并開開啟每個領完獎的任務的下一個任務(如果配置了開啟支線任務,則還需要配套開啟對應的支線任務),

/// <summary>
/// 一鍵領取所有任務的獎勵
/// </summary>
/// <param name="cb"></param>
public void OneKeyGetAward(Action<int, string> cb)
{
    int totalGold = 0;
    var tmpTaskDatas = new List<TaskDataItem>(m_taskData.taskDatas);

    for (int i = 0, cnt = tmpTaskDatas.Count; i < cnt; ++i)
    {
        var oneTask = tmpTaskDatas[i];
        var cfg = TaskCfg.instance.GetCfgItem(oneTask.task_chain_id, oneTask.task_sub_id);
        if (oneTask.progress >= cfg.target_amount && 0 == oneTask.award_is_get)
        {
            oneTask.award_is_get = 1;
            m_taskData.AddOrUpdateData(oneTask);
            var awardJd = JsonMapper.ToObject(cfg.award);
            totalGold += int.Parse(awardJd["gold"].ToString());
            GoNext(oneTask.task_chain_id, oneTask.task_sub_id);
        }
    }
    if (totalGold > 0)
    {
        JsonData totalAward = new JsonData();
        totalAward["gold"] = totalGold;
        cb(0, JsonMapper.ToJson(totalAward));
    }
    else
    {
        cb(-1, null);
    }
}

7、開啟下一個任務(含支線)

約定任務id遞增,開啟下一個任務就是查找id+1的任務并開啟,
支線任務的開啟是open_chain欄位,格式鏈id|任務子id,多個支線以,號隔開,例:3|1,5|1表示開啟鏈3的子任務1鏈5的子任務1

/// <summary>
/// 開啟下一個任務(含支線)
/// </summary>
/// <param name="chainId">鏈id</param>
/// <param name="subId">任務子id</param>
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 (1 == data.award_is_get)
    {
        // 移除掉已領獎的任務
        m_taskData.RemoveData(chainId, subId);

        // 開啟下一個任務
        if (null != nextCfg)
        {
            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);
            }
        }
    }
}

8、思維導圖

畫一下圖,
在這里插入圖片描述

9、TaskLogic.cs完整代碼

最終TaskLogic.cs完整代碼如下

/// <summary>
/// 任務邏輯
/// 作者:林新發,博客:https://blog.csdn.net/linxinfa
/// </summary>

using System.Collections.Generic;
using UnityEngine;
using LitJson;
using System;

public class TaskLogic
{
    public TaskLogic()
    {
        m_taskData = new TaskData();
    }

    /// <summary>
    /// 獲取任務資料
    /// </summary>
    /// <param name="cb">回呼</param>
    public void GetTaskData(Action cb)
    {
        m_taskData.GetTaskDataFromDB(cb);
    }

    /// <summary>
    /// 更新任務進度
    /// </summary>
    /// <param name="chainId">鏈id</param>
    /// <param name="subId">任務子id</param>
    /// <param name="deltaProgress">進度增加量</param>
    /// <param name="cb">回呼</param>
    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 (null != cfg)
                cb(0, data.progress >= cfg.target_amount);
            else
                cb(-1, false);
        }
        else
        {
            cb(-1, false);
        }
    }

    /// <summary>
    /// 一鍵領取所有任務的獎勵
    /// </summary>
    /// <param name="cb"></param>
    public void OneKeyGetAward(Action<int, string> cb)
    {
        int totalGold = 0;
        var tmpTaskDatas = new List<TaskDataItem>(m_taskData.taskDatas);

        for (int i = 0, cnt = tmpTaskDatas.Count; i < cnt; ++i)
        {
            var oneTask = tmpTaskDatas[i];
            var cfg = TaskCfg.instance.GetCfgItem(oneTask.task_chain_id, oneTask.task_sub_id);
            if (oneTask.progress >= cfg.target_amount && 0 == oneTask.award_is_get)
            {
                oneTask.award_is_get = 1;
                m_taskData.AddOrUpdateData(oneTask);
                var awardJd = JsonMapper.ToObject(cfg.award);
                totalGold += int.Parse(awardJd["gold"].ToString());
                GoNext(oneTask.task_chain_id, oneTask.task_sub_id);
            }
        }
        if (totalGold > 0)
        {
            JsonData totalAward = new JsonData();
            totalAward["gold"] = totalGold;
            cb(0, JsonMapper.ToJson(totalAward));
        }
        else
        {
            cb(-1, null);
        }
    }

    /// <summary>
    /// 領取任務獎勵
    /// </summary>
    /// <param name="chainId">鏈id</param>
    /// <param name="subId">任務子id</param>
    /// <param name="cb">回呼</param>
    public void GetAward(int chainId, int subId, Action<int, string> cb)
    {
        var data = m_taskData.GetData(chainId, subId);
        if (null == data)
        {
            cb(-1, "{}");
            return;
        }
        if (0 == data.award_is_get)
        {
            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, "{}");
        }
    }

    /// <summary>
    /// 觸發下一個任務,并開啟支線任務
    /// </summary>
    /// <param name="chainId">鏈id</param>
    /// <param name="subId">任務子id</param>
    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 (1 == data.award_is_get)
        {
            // 移除掉已領獎的任務
            m_taskData.RemoveData(chainId, subId);

            // 開啟下一個任務
            if (null != nextCfg)
            {
                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);
                }
            }
        }
    }

    public void ResetAll(Action cb)
    {
        m_taskData.ResetData(cb);
    }

    public List<TaskDataItem> taskDatas
    {
        get { return m_taskData.taskDatas; }
    }

    private TaskData m_taskData;
    private static TaskLogic s_instance;
    public static TaskLogic instance
    {
        get
        {
            if (null == s_instance)
                s_instance = new TaskLogic();
            return s_instance;
        }
    }
}

九、UI界面制作

1、UI資源獲取

簡單的UI資源我是在阿里巴巴矢量圖庫上找,地址:https://www.iconfont.cn/
比如搜索按鈕
在這里插入圖片描述
找一個形狀合適的,可以進行調色,我一般是調成白色,
在這里插入圖片描述

因為Unity中可以設定Color,這樣我們只需要一個白色按鈕就可以在Unity中創建不同顏色的按鈕了,
弄點基礎的美術資源,
在這里插入圖片描述
注:那個頭像是我自己用PhotoShop畫的哦,我之前用PhotoShop畫過一幅原創連環畫,如下:
在這里插入圖片描述
同時,我們還需要任務圖示,也找一些,
在這里插入圖片描述
注意所有要使用UGUI來展示資源都設定為Sprite (2D and UI)格式,
在這里插入圖片描述

注,關于資源的獲取,我之前寫過一篇文章:《Unity游戲開發——新發教你做游戲(二):60個Unity免費資源獲取網站》,感興趣的同學可以看下,

2、場景界面制作:Main場景,人生如夢

養成好習慣,不管你是單場景還是多場景,入口場景命名為Main
在場景中使用UGUI簡單做下入口界面:MainPanel
在這里插入圖片描述
這個任務系統的主題我定為:人生如夢,
在這里插入圖片描述

3、制作串列界面預設:TaskPanel.prefab

根據需求,我們的任務要以串列的顯示展示,使用UGUI制作任務串列界面,
在這里插入圖片描述
如下,
在這里插入圖片描述
界面保存為TaskPanel.prefab,放在Resources目錄中,
在這里插入圖片描述

4、制作提示框界面預設:TipsPanel.prefab

為了在客戶端模擬測驗,做一個提示框界面,
在這里插入圖片描述
如下:
在這里插入圖片描述
界面保存為TipsPanel.prefab,放在Resources目錄中,
在這里插入圖片描述
嘛,順手做個界面影片吧,

注:關于影片相關的教程,我之前寫過一些,感興趣的同學可以看下:
《Unity使用Animator控制影片播放,皮皮貓打字機游戲》
《Unity影片狀態機Animator使用》
《Unity影片使用混合樹BlendTree實作影片過渡控制》
《新發教你做游戲(七):Animator控制角色影片播放》

請添加圖片描述

5、制作獎勵界面預設:AwardPanel.prefab

領取任務獎勵要有個獎勵UI展示,做一個,
在這里插入圖片描述
界面保存為AwardPanel.prefab,放在Resources目錄中,
在這里插入圖片描述

也順手做個影片,
請添加圖片描述

十、撰寫界面代碼

界面預設制作好了,接下來就是寫界面互動的代碼了,

1、入口腳本:Main.cs

C/C++Main入口函式一樣,我們的游戲也需要有一個腳本作為入口腳本,
我們創建一個Main.cs腳本,掛到場景中的MainPanel節點上,
在這里插入圖片描述
Main.cs腳本代碼如下,主要是做一些全域變數、配置、資料等的初始化,然后顯示界面,不過我們任務界面代碼還沒寫,先留個TODO

using UnityEngine;

/// <summary>
/// 入口腳本
/// </summary>
public class Main : MonoBehaviour
{
    void Start()
    {
        GlobalObj.s_canvasTrans = GameObject.Find("Canvas").transform;
        // 加載任務配置
        TaskCfg.instance.LoadCfg();
        // 獲取任務資料
        TaskLogic.instance.GetTaskData(()=> 
        {
            // TODO: 顯示任務界面
            
        });
    }
}

public class GlobalObj
{
    public static Transform s_canvasTrans;
}

2、任務串列界面

2.1、回圈復用串列

任務界面以串列展示任務,我之前做過一個回圈復用串列的功能,可以參見我之前這篇文章:《Unity UGUI實作回圈復用串列,顯示巨量串列資訊,含Demo工程原始碼》
在這里插入圖片描述
我把之前寫的RecyclingList腳本拷貝過來,
RecyclingList腳本地址:https://codechina.csdn.net/linxinfa/UnityRecyclingListDemo/-/tree/master/Assets/Scripts/RecyclingList
在這里插入圖片描述
ScrollVIew掛上RecyclingListView腳本,腳本的ChildObj物件需要是RecyclingListViewItem型別的,我們下面會寫一個TaskItemUI繼承RecyclingListViewItem,這里ChildObj先留空,
在這里插入圖片描述

2.2、串列項腳本:TaskItemUI.cs

Scripts / View目錄中創建TaskItemUI.cs腳本,它要繼承RecyclingListViewItem

public class TaskItemUI : RecyclingListViewItem

定義一些UI物件,

// 描述
public Text desText;
// 進度
public Text progressText;
// 前往按鈕
public Button goAheadBtn;
// 領獎按鈕
public Button getAwardBtn;
// 進度條
public Slider progressSlider;
// 任務圖示
public Image icon;
// 任務型別標記,主線/支線 
public Image taskType;

TaskItemUI腳本掛到ChildItem節點上,并賦值各個UI物件,
在這里插入圖片描述
現在我們可以給RecyclingListView腳本賦值ChildObj物件了,
在這里插入圖片描述
TaskItemUI.cs腳本唯一要做的事情就是根據資料更新UI
在這里插入圖片描述

// TaskItemUI.cs

public Action updateListCb;

/// <summary>
/// 更新UI
/// </summary>
/// <param name="data"></param>
public void UpdateUI(TaskDataItem data)
{
    var cfg = TaskCfg.instance.GetCfgItem(data.task_chain_id, data.task_sub_id);
    if (null != cfg)
    {
        desText.text = cfg.desc;
        // TODO 設定圖示
        // icon.sprite 
        
        // TODO 設定主線/支線圖示
        // var taskTypeSpriteName = 1 == cfg.task_chain_id ? "zhu" : "zhi";
        // taskType.sprite
        
        progressText.text = data.progress + "/" + cfg.target_amount;
        progressSlider.value = (float)data.progress / cfg.target_amount;
        // 前往按鈕
        goAheadBtn.onClick.RemoveAllListeners();
        goAheadBtn.onClick.AddListener(() =>
        {
            // TODO 前往任務
        });

		// 領獎按鈕
        getAwardBtn.onClick.RemoveAllListeners();
        getAwardBtn.onClick.AddListener(() =>
        {
            TaskLogic.instance.GetAward(data.task_chain_id, data.task_sub_id, (errorCode, award) => 
            {
                if(0 == errorCode)
                {
                    // TODO 領獎界面
                    
                    updateListCb();
                }
            });
        });

        goAheadBtn.gameObject.SetActive(data.progress < cfg.target_amount);
        getAwardBtn.gameObject.SetActive(data.progress >= cfg.target_amount && 0 == data.award_is_get);
    }
}

上面代碼有幾個TODO
1 設定圖示我們等下寫個圖示資源管理器;
2 任務的前往邏輯,我們要彈出提示框;
3 領獎要顯示獎勵界面,
現在,我們繼續往下做,

2.3、串列界面腳本:TaskPanel.cs

Scripts / View目錄中創建TaskPanel.cs腳本,把它掛到TaskPanel界面的根節點上,
在這里插入圖片描述
最關鍵的,定義RecyclingListView成員物件,

// TaskPanel.cs

public RecyclingListView scrollList;

我們的串列更新就是監聽它的ItemCallback回呼的,

// TaskPanel.cs

// 串列item更新回呼
scrollList.ItemCallback = PopulateItem;

// ...

private void PopulateItem(RecyclingListViewItem item, int rowIndex)
{
	var child = item as TaskItemUI;
	// 重繪某個item
	child.UpdateUI(TaskLogic.instance.taskDatas[rowIndex]);
	child.updateListCb = () =>
    {
    	// 重繪整個串列
        RefreshAll();
    };
}

/// <summary>
/// 重繪整個串列
/// </summary>
private void RefreshAll()
{
    scrollList.RowCount = TaskLogic.instance.taskDatas.Count;
    scrollList.Refresh();
}

我們需要告訴RecyclingListView我們的串列的item的數量,方便它進行計算,

// TaskPanel.cs

// 設定資料,此時串列會執行更新
scrollList.RowCount = TaskLogic.instance.taskDatas.Count;

為了便于顯示TaskPanel界面,我們封裝一個staticShow方法,

// TaskPanel.cs

private static GameObject s_taskPanelPrefab;

// 顯示任務界面
public static void Show()
{
    if (null == s_taskPanelPrefab)
        s_taskPanelPrefab = Resources.Load<GameObject>("TaskPanel");
    var panelObj = Instantiate(s_taskPanelPrefab);
    panelObj.transform.SetParent(GlobalObj.s_canvasTrans, false);
}

這樣,我們就可以在Main.cs腳本中加上這個TaskPanel.Show()的呼叫了,

// Main.cs

void Start()
{
	// ...
	
    // 獲取任務資料
    TaskLogic.instance.GetTaskData(()=> 
    {
        // 顯示任務界面
        TaskPanel.Show();
    });
}

3、提示框界面:TipsPanel.cs

Scripts / View目錄中創建TipsPanel.cs腳本,先定義三個按鈕物件,

public Button closeBtn;
public Button addProgressBtn;
public Button onekeyBtn;

在這里插入圖片描述
TipsPanel預設跟節點掛上TipsPanel.cs腳本,賦值按鈕物件,
在這里插入圖片描述
分別寫三個按鈕的點擊邏輯,
關閉按鈕:

// TipsPanel.cs

// 關閉按鈕
closeBtn.onClick.AddListener(() =>
{
    Destroy(gameObject);
});

進度+1按鈕:

// TipsPanel.cs

private int m_taskChainId;
private int m_tasksubId;
private Action updateTaskDataCb;

// 進度+1
addProgressBtn.onClick.AddListener(() =>
{
    Destroy(gameObject);
    TaskLogic.instance.AddProgress(m_taskChainId, m_tasksubId, 1, (errorCode, finishTask) =>
    {
        updateTaskDataCb();
    });
});

一鍵完成按鈕:

// TipsPanel.cs

// 一鍵完成
onekeyBtn.onClick.AddListener(() =>
{
    Destroy(gameObject);
    var cfg = TaskCfg.instance.GetCfgItem(m_taskChainId, m_tasksubId);
    TaskLogic.instance.AddProgress(m_taskChainId, m_tasksubId, cfg.target_amount, (errorCode, finishTask) =>
    {
        updateTaskDataCb();
    });
});

同理,為了方便顯示,也封裝一個靜態的Show方法:

// TipsPanel.cs

private static GameObject s_tipsPanelPrefab;
// 顯示任務界面
public static void Show(int chainId, int subId, Action cb)
{
	if (null == s_tipsPanelPrefab)
	    s_tipsPanelPrefab = Resources.Load<GameObject>("TipsPanel");
	var panelObj = Instantiate(s_tipsPanelPrefab);
	panelObj.transform.SetParent(GlobalObj.s_canvasTrans, false);
	var panelBhv = panelObj.GetComponent<TipsPanel>();
	panelBhv.Init(chainId, subId, cb);
}

public void Init(int chainId, int subId, Action cb)
{
   	m_taskChainId = chainId;
   	m_tasksubId = subId;
   	updateTaskDataCb = cb;
}

TaskItemUI.cs腳本的前往按鈕補上TipsPanel.Show呼叫,

// TaskItemUI.cs

goAheadBtn.onClick.AddListener(() =>
{
   TipsPanel.Show(data.task_chain_id, data.task_sub_id, () =>
   {
       UpdateUI(data);
   });
});

在這里插入圖片描述

4、獎勵界面:AwardPanel.cs

Scripts / View目錄中創建AwardPanel.cs腳本,
定義UI物件,

public Text goldText;
public Button bgBtn;
private GameObject m_selfGo;

private void Awake()
{
    m_selfGo = gameObject;
}

AwardPanel.cs腳本掛到AwardPanel預設跟節點上,賦值UI物件,
在這里插入圖片描述
邏輯很簡單,顯示金幣獎勵,加個1.5秒自動銷毀,點擊空白處銷毀的邏輯,如下:

// AwardPanel.cs

public void Init(string award)
{
    var jd = JsonMapper.ToObject(award);
    goldText.text = jd["gold"].ToString();
    bgBtn.onClick.AddListener(() =>
    {
        SelfDestroy();
    });

    // 3秒后自動銷毀
    Invoke("SelfDestroy", 1.5f);
}

private void Awake()
{
    m_selfGo = gameObject;
}

private void SelfDestroy()
{
    if (null != m_selfGo)
    {
        Destroy(m_selfGo);
        m_selfGo = null;
    }
}

也封裝一個靜態的Show方法,

private static GameObject s_awardPanelPrefab;

/// <summary>
/// 顯示獎勵界面
/// </summary>
public static void Show(string award)
{
    if (null == s_awardPanelPrefab)
        s_awardPanelPrefab = Resources.Load<GameObject>("AwardPanel");
    var panelObj = Instantiate(s_awardPanelPrefab);
    panelObj.transform.SetParent(GlobalObj.s_canvasTrans, false);
    var panelBhv = panelObj.GetComponent<AwardPanel>();
    panelBhv.Init(award);
}

TaskItemUI.cs腳本的領獎按鈕補上AwardPanel.Show呼叫,

// TaskItemUI.cs

getAwardBtn.onClick.AddListener(() =>
{
    TaskLogic.instance.GetAward(data.task_chain_id, data.task_sub_id, (errorCode, award) => 
    {
        Debug.Log("errorCode: " + errorCode + ", award: " + award);
        if(0 == errorCode)
        {
            AwardPanel.Show(award);
            updateListCb();
        }
    });
});

在這里插入圖片描述

5、精靈資源管理器

我們需要根據任務配置來顯示任務的圖示,封裝一個精靈管理器,
在這里插入圖片描述

Scripts / View目錄中創建一個SpriteManager.cs腳本,
代碼如下:

// SpriteManager.cs

using System.Collections.Generic;
using UnityEngine;

public class SpriteManager
{
    /// <summary>
    /// 根據名字加載精靈資源
    /// </summary>
    public Sprite GetSprite(string name)
    {
        if (m_sprites.ContainsKey(name))
            return m_sprites[name];
        var sprite = Resources.Load<Sprite>("Sprites/" + name);
        m_sprites.Add(name, sprite);
        return sprite;
    }


    private Dictionary<string, Sprite> m_sprites = new Dictionary<string, Sprite>();
    private static SpriteManager s_instance;
    public static SpriteManager instance
    {
        get
        {
            if (null == s_instance)
                s_instance = new SpriteManager();
            return s_instance;
        }
    }
}

回到TaskItemUI.cs腳本中,補上精靈設定的呼叫:

// TaskItemUI.cs

public void UpdateUI(TaskDataItem data)
{
	// ...
	
	// 圖示
	icon.sprite = SpriteManager.instance.GetSprite(cfg.icon);
	// 主線/支線標記
	var taskTypeSpriteName = 1 == cfg.task_chain_id ? "zhu" : "zhi";
	taskType.sprite = SpriteManager.instance.GetSprite(taskTypeSpriteName);
	
}

這樣就可以根據配置顯示不同的圖示了,
在這里插入圖片描述

十一、運行測驗

代碼寫完了,一切就緒,運行Unity,測驗效果如下:
請添加圖片描述
請添加圖片描述
人生如夢,究竟是要選夢醒來還是繼續做夢呢?
請添加圖片描述

十二、工程原始碼

本文的工程我一上傳到CODE CHINA,感興趣的同學可以自行下載下來學習,
工程地址:https://codechina.csdn.net/linxinfa/UnityChainTaskDemo
注:我的Unity版本是Unity 2020.1.14f1c1 (64-bit)
在這里插入圖片描述

十三、完畢

好了,寫得有點多了,就寫到這里吧~
人生如夢,祝大家都能走上人生巔峰~
我是林新發:https://blog.csdn.net/linxinfa
原創不易,若轉載請注明出處,感謝大家~
喜歡我的可以點贊、關注、收藏,如果有什么技術上的疑問,歡迎留言或私信,拜拜~

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/290371.html

標籤:其他

上一篇:「 硬核分享」 ?? FPS類游戲CSGO透視外掛完整原始碼??「 復制即用」

下一篇:亂數的生成+猜數字游戲

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more