? ? ? ? 本人能力有限,一切實現僅供參考,如有不足還請斧正
????????起因是我看到學校社團內有人做了對話系統的分享,我想了想之前沒寫過這種東西,而Fungus插件教程太老了,NodeCanvas插件學習成本又比較高,我就干脆尋找資料 加上自己迭代一下,花了一天時間完成了這個對話系統
目錄
1.介紹
2.核心腳本
對話管理器
對話事件
對話配置腳本
對話節點腳本?
3.使用指北
路徑配置
關于特性
關于接口?
關于UI?
其余內容請自行查看源碼
? ? ?Github:??Haki-sheep/Haki-sheep-UnityTools at DialogTools-dev1.0?
? ? ? ? 演示視頻:
Unity一個簡易可拓展的對話系統
1.介紹
? ? ? ? ?這個對話系統并不是可視化編輯節點(像是NodeCanvas插件那種),但也支持一鍵將Excel表轉為So文件,通過配表的方式輕量化這一過程
? ? ? ? 首先,算上DEMO一共632行,去掉以后可能?不到四百行 所以十分輕巧
? ? ? ? 但是由于代碼量擺在那,所以目前本對話系統只支持小玩具, 今后我說不定會將其拓展為課編輯節點的系統,當然,目前我個人使用起來還是比較方便的,畢竟是自己編寫的系統
? ? ? ? 其次 Base只涉及到了Odin插件EPPLUS 以及一個單例基類 無需其他支持
2.核心腳本
對話管理器
? ? ? ? 對話流程如下:
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.UI;public class DialogManager : SingltonMono<DialogManager>
{#region 基礎配置//配置相關private DialogConfig curDialogConfig;private int nodeIndex;public bool nodeNotOver => nodeIndex < curDialogConfig.nodeList.Count - 1;//角色相關private string characterName;private Sprite characterAvatar;//外部 可做替換public Player player;public DialogMainUI dialogMainUI;public SelectUI selectUI;#endregion#region 對話流程 /// <summary>/// 開始對話/// </summary>/// <param name="dialogConfig">想要對話角色的配置</param>/// <param name="nodeIndex">從第幾個節點開始對話</param>public void StartDialog(DialogConfig dialogConfig, int nodeIndex = 0){if (curDialogConfig == dialogConfig) return;//不要重復對話curDialogConfig = dialogConfig;this.nodeIndex = nodeIndex;characterName = curDialogConfig.characterName;characterAvatar = curDialogConfig.characterAvatar;StartCoroutine(PlayNode(curDialogConfig.nodeList[nodeIndex]));}public void CeckCharacterInfo(DialogNode node, Image ui_characterAvator, Text ui_characterName){//角色的信息if (node.player){ui_characterName.text = player.name;ui_characterAvator.sprite = player.Avator;}else{ui_characterName.text = characterName;ui_characterAvator.sprite = characterAvatar;}}private IEnumerator PlayNode(DialogNode node){dialogMainUI.Show();CeckCharacterInfo(node, dialogMainUI.ui_characterAvator, dialogMainUI.ui_characterName);//開始事件OnEvent(node.onStartEventList);yield return OnBlockEvent(node.onStartEventList);//打字機yield return Typing(node.content, dialogMainUI.ui_contentText);//等待交互while (!Input.GetMouseButtonDown(0)) { yield return null; }//結束事件OnEvent(node.onEndEventList);yield return OnBlockEvent(node.onEndEventList);if (nodeNotOver){nodeIndex++;StartCoroutine(PlayNode(curDialogConfig.nodeList[nodeIndex]));}else{CloseDialog();}}private void OnEvent(List<IDialogEvent> dialogEvents){foreach (IDialogEvent sEvent in dialogEvents){sEvent.Execute();}}private IEnumerator OnBlockEvent(List<IDialogEvent> dialogEvents){foreach (IDialogEvent sBEvnt in dialogEvents){IEnumerator enumerator = sBEvnt.ExecuteBlock();if (enumerator == null) continue;yield return enumerator;}}public void CloseDialog(){StopAllCoroutines();curDialogConfig = null;nodeIndex = 0;dialogMainUI.Hide();}#endregion#region 打字機相關 public float delayBetweenContent = 0.1f;private Dictionary<string, string> keywordDic = new Dictionary<string, string>();public void SetKeyword(string key, string value){keywordDic[key] = value;}public void RemoveKeyword(string key){keywordDic.Remove(key);}private IEnumerator Typing(string content, Text ui_contentText){StringBuilder builder = new StringBuilder();foreach (var item in keywordDic){content = content.Replace(item.Key, item.Value);}foreach (var s in content){builder.Append(s);ui_contentText.text = builder.ToString();yield return new WaitForSeconds(delayBetweenContent);}}#endregion#region 資源管理public T GetDialogConfig<T>(string path) where T : ScriptableObject, new(){return Resources.Load<T>(path);}#endregion}
對話事件
這里我就展示其中一種事件,檢查某一樣子東西玩家是否已經應有 從而跳過對話
using System.Collections;
[DialogEvent("CheckKeyWordEvent")]
public class CheckKeyWordEvent : IDialogEvent
{public void ConverString(string excelString){}public void Execute(){//檢查是否有選中物品 TODO:條件可以替換if (Player.Instance.selectItem != null){DialogManager.Instance.CloseDialog();}}public IEnumerator ExecuteBlock(){return null;}
}
對話配置腳本
using Sirenix.OdinInspector;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(menuName = "Tools", fileName = "創建新角色")]
public class DialogConfig : SerializedScriptableObject
{//角色名稱public string characterName;//角色頭像public Sprite characterAvatar;//顯示索引,開啟翻頁[ListDrawerSettings(ShowIndexLabels = true, ShowPaging = true)]public List<DialogNode> nodeList = new List<DialogNode>();
}
對話節點腳本?
using System.Collections.Generic;
/// <summary>
/// 對話節點配置
/// </summary>
public class DialogNode
{//是否是玩家public bool player;//說的內容public string content;//對話事件public List<IDialogEvent> onStartEventList = new List<IDialogEvent>();public List<IDialogEvent> onEndEventList = new List<IDialogEvent>();}
3.使用指北
路徑配置
????????這個文件填寫你的Excel表和so文件想在的位置
????????但是我推薦將so文件放在Res下面 方便管理器讀取
? ? ? ? ?如果有報錯就把你的DialogImprotSetting的路徑放在這里面
關于特性
? ? ? ? 這個特性內填寫你的事件名稱即可 可以不和腳本一樣 只需要和Excel表之中一樣便可以讀取
關于接口?
? ? ? ? 事件需要繼承這個接口
阻塞執行?里面直接return nul即可 因為外部會判斷
當然你直接yield rerun null也可以,但是會造成延遲一幀后才執行其他語句
? ? ? ? UI接口的話可以選擇性繼承,因為里面也沒什么方法,可以自己寫
關于UI?
? ? ? ? 在DialogManager里有兩個UI的對象,其實所有在外部這個注釋下的字段都可以自行做替換
? ? 只要讓Manager得到了你UI身上下面這些信息即可(方式自行選擇比如事件中心或者訂閱回調的方式)
? ? ? ? 剩下的UI樣式之類的自行配置即可