【Unity】創建對話系統

對話系統 其實就是由一連串的字串組成的

只是要怎麼讀取那些要自己慢慢寫

最基礎的對話系統就是兩三個string然後用一個label去讀取 ,像這樣

每次點擊都會讀取一個string 到達到最大長度-1為止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;
using System.Collections;

public class Base_Dialog : MonoBehaviour {

public string[] All_text;

public UILabel Visible_label;
int index = 0;

void Update () {

if (All_text != null)
{
if (Input.GetMouseButtonDown(0)&&index<All_text.Length-1)
{
index++;
}
Visible_label.text = All_text[index];
}
}

}

而接下來就是延伸這個基礎的對話系統了

我想要的功能有

1.按鍵跳下一格

2.顯示對話人物

3.不必選擇Yes or No

4.遇到npc 按下某一個按鍵可開始對話

做完看似如下

對話系統

一、保存對話資料

一般很多都是載入xml或txt資料來讀取對話,但我比較喜歡這種

遊戲蠻牛 RPG對話系統

其實也可以直接看上下就做出一個比較好的對話系統,但我看不懂IEnumerator那個使用方式…

如果有人願意解釋一下當然是最好的啦XD 但身邊沒什麼程式人… QQ

所以我使用了他的保存對話資料的方式 , 大概看一看就懂了

他使用了兩層

ConversationData - SpeakerData 然後就開始填對話者、講的話

我的話是填了三層

Dialog_Npc_Data - Dialog_Chapter - Dialog_eachPart_sentence

我的想法是,每個NPC都有一個對話資料(Dialog_Npc_Data),而NPC你很常看到他會有講話的分支,就像故事的章節一樣(Dialog_Chapter),而每個章節保存了很多詳細的對話內容(Dialog_eachPart_sentence)

像是 小紅帽故事書(對話資料),分三個章節(Dialog_Chapter),第一個章節小紅帽出去玩,然後

奶奶: 走阿 永遠不要回來了

小紅帽 : 是的船長

像這樣,每個章節內都保存了哪個人物講什麼

因此 Dialog_eachPart_sentence 這個Class內容就是

Dialog_eachPart_sentence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class Dialog_eachPart_sentence
{
public UISprite spaker;
public List<string> sentence;
public AnchorPosition pos ;

}

public enum AnchorPosition
{
Left,
Right
}

speakr就是誰講的,sentence就是這個人講了幾句話,pos是定義這個人的頭像位置在哪

Dialog_Chapter

是章節,定義了有幾個”人”去講話

1
2
3
4
5
6
7
8
9
10
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class Dialog_Chapter {

public List<Dialog_eachPart_sentence> eachpart_sentence;

}

最後是對話資料,定義了有幾個章節、這個對話資料的ID,indexpart可以不用理,當初是想用它來定義當前講到第幾章節

1
2
3
4
5
6
7
8
9
10
11
12
13
using UnityEngine;
using System.Collections;
using System.Collections.Generic;


[System.Serializable]
public class Dialog_Npc_Data : ScriptableObject
{
public int ID;
public int IndexPart = 0;
public List<Dialog_Chapter> eachChapter;

}

看完遊戲蠻牛那個教學後,改一改 創建一個對話資料,對話資料的內容就會像這樣

PS. 省略了創建對話資料那邊的過程,蠻牛上有

img

接下來就是創建一個對話系統來使用這些對話資料了

part 1 宣告一個字典保存所有對話資料,以後用Key來尋找這個資料、一個List來把對話資料載入

1
2
public Dictionary<int, Dialog_Npc_Data> System_npc = new Dictionary<int, Dialog_Npc_Data>();
public List<Dialog_Npc_Data> all_Dialog;

在Awake方法中把資料加入字典裡

1
2
3
4
5
6
7
8
void Awake()
{
foreach (Dialog_Npc_Data dnd in all_Dialog)
{
if (!System_npc.ContainsKey(dnd.ID))
System_npc.Add(dnd.ID, dnd);
}
}

part 2 宣告一些需要用到的資料

Npc_ID是NPC的ID,index_Chapter是哪個章節,isDialog是判斷有沒有在對話

1
2
3
4
5
6
7
8
9
10
public bool isDialog = false;
int Npc_ID;

int index_Chapter=0;

//這兩個是一直變動的,一個是當前讀取句子,一個是讀取哪個speaker
int index_sentence=0; //最下層的句子
int index_Chapter_sentence=0;
// 設定一個當前的資料選項
Dialog_Npc_Data current_Npc_Data;

接下來定義兩個函數,一個是外部調用這個對話系統時傳入值的函數,一個是初始化的函數

傳入值有兩個,一個是要讀取的NPC的ID,也就是哪個對話資料,一個是Chapter,要讀取的章節

想像一下,當你接觸到某個npc範圍的時候,按下空白建,然後開始對話,然後就把某個npc的id還有定義好的章節傳入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void SetDialogInfo(int Npc,int Chapter)
{
Npc_ID = Npc;
index_Chapter = Chapter;
current_Npc_Data = System_npc[Npc];
Initialize();
isDialog = true;
}

public void Initialize()
{
index_sentence = 0;
index_Chapter_sentence = 0;
}

當isDialog = true的時候 就開始對話了,開始對話每當按下滑鼠左鍵,最底層的index_sentence就開始跑,這跟最上面那個基礎對話機的原理一樣

其中有呼叫一個函數StartDialog(),這個函數用來顯示、讀取對話的內容

1
2
3
4
5
6
7
8
void Update()
{
if (isDialog)
{
StartDialog();
if (Input.GetMouseButtonDown(0)) index_sentence++;
}
}

接下來再宣告一些會用到的東西

1
2
3
4
5
6
7
8
9
10
11
12
13
//part4
public UILabel Visible_Message;
public UISprite Visible_Spaker;
public string current_content;

//part5
public Transform Left;
public Transform right;
//
/*
宣告一個Label用來顯示
一個Sprite用來當面板
*/

Visible_Message用來在螢幕上顯示 對話的內容

Visible_Spaker是對話的人

current_content是用來回傳的

Left、right是用來定義這個對話人物的位置在哪,傳統都是看到人物一個在左邊,一個在右邊

然後就是實現上面的StartDialog方法了

1
2
3
4
5
6
7
8
9
10
11
//傳入哪一段之後,就開始把這一段給看完
void StartDialog()
{
Visible_Spaker.spriteName = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence[index_Chapter_sentence].spaker.spriteName;
AnchorPosition ap = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence[index_Chapter_sentence].pos;
if (ap == AnchorPosition.Right) Visible_Spaker.transform.position = right.position;
if (ap == AnchorPosition.Left) Visible_Spaker.transform.position = Left.position;
Visible_Message.text = ChapterSentence();


}

第一行是每個npc資料裡面都會有個speaker,,把顯示的UISprite換成當前npc的章節裡面的speaker的名子(好拗口

第2.3.4行是定義了位置訊息

第5行是 UIlabel顯示的內容,透過ChapterSentence()方法得到

ChapterSentence()方法內容是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
string ChapterSentence()
{
int Max_sentence = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence[index_Chapter_sentence].sentence.Count;
int Max_Chapter_sentence = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence.Count;

if (index_sentence < Max_sentence)
{

current_content = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence[index_Chapter_sentence].sentence[index_sentence];

}
else
{
index_sentence = 0;
index_Chapter_sentence++;
}

if (index_Chapter_sentence >= Max_Chapter_sentence)
{

isDialog = false;

}


return current_content;
}

跟基礎對話系統差不多,獲得兩個最大可讀取的內容長度然後更新這兩個來讀取整個章節

應該都不算太難,看看應該就懂了

難的還是遊戲蠻牛那個方法來讀取,感覺好多了

完整版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class System_Dialog : MonoBehaviour {

/*
對話狀態
*/
//part 1 宣告一個字典保存所有對話資料,以後用Key來尋找這個資料、一個List來把對話資料載入

public Dictionary<int, Dialog_Npc_Data> System_npc = new Dictionary<int, Dialog_Npc_Data>();
public List<Dialog_Npc_Data> all_Dialog;
//part4
public UILabel Visible_Message;
public UISprite Visible_Spaker;
public string current_content;

//part5
public Transform Left;
public Transform right;
//
/*
宣告一個Label用來顯示
一個Sprite用來當面板
一個開關表示狀態
*/
public bool isDialog = false;

//兩個方法,一個用來在外部調用時可以傳入要用到的資料..等
//一個用來初始化整個腳本
//傳入的參數有 : 調用哪個對話資料(id)、章節(eachPart)
//章節由於每次結束遊戲都要保存


int Npc_ID;

int index_Chapter=0;

//這兩個是一直變動的
int index_sentence=0; //最下層的句子
int index_Chapter_sentence=0;
// 設定一個當前的資料選項
Dialog_Npc_Data current_Npc_Data;

public void SetDialogInfo(int Npc,int Chapter)
{
Npc_ID = Npc;
index_Chapter = Chapter;
current_Npc_Data = System_npc[Npc];

Initialize();
isDialog = true;


}

public void Initialize()
{
index_sentence = 0;
index_Chapter_sentence = 0;
}

//part 1 加入資料
void Awake()
{
foreach (Dialog_Npc_Data dnd in all_Dialog)
{
if (!System_npc.ContainsKey(dnd.ID))
System_npc.Add(dnd.ID, dnd);
}
}

void Update()
{
if (isDialog)
{
StartDialog();
if (Input.GetMouseButtonDown(0)) index_sentence++;
}
}
//傳入哪一段之後,就開始把這一段給看完
void StartDialog()
{

Visible_Spaker.spriteName = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence[index_Chapter_sentence].spaker.spriteName;
AnchorPosition ap = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence[index_Chapter_sentence].pos;
if (ap == AnchorPosition.Right) Visible_Spaker.transform.position = right.position;
if (ap == AnchorPosition.Left) Visible_Spaker.transform.position = Left.position;

Visible_Message.text = ChapterSentence();


}

string ChapterSentence()
{
int Max_sentence = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence[index_Chapter_sentence].sentence.Count;
int Max_Chapter_sentence = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence.Count;

if (index_sentence < Max_sentence)
{

current_content = System_npc[Npc_ID].eachChapter[index_Chapter].eachpart_sentence[index_Chapter_sentence].sentence[index_sentence];

}
else
{
index_sentence = 0;
index_Chapter_sentence++;
}

if (index_Chapter_sentence >= Max_Chapter_sentence)
{

isDialog = false;

}


return current_content;
}


}

然後使用另外一個class來調用,我是放在npc上面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using UnityEngine;
using System.Collections;

public class TestSystem : MonoBehaviour {

NPCspeakerData npc;
public UIPanel panels;
public int nUM = 0;
public int CurrentChapther = 0;

public bool isCanTalk = false;
// Use this for initialization
void Start () {
npc = gameObject.GetComponent<NPCspeakerData>();
panels.enabled = false;
isCanTalk = false;
}

// Update is called once per frame
void Update () {
//npc.Npc_ID = 1;

if (Input.GetMouseButtonDown(1))
{
panels.enabled = true;
npc.Npc_ID = nUM;
npc.currentNPC.IndexPart = CurrentChapther;
}
if (!npc.IsConversation)
panels.enabled = false;
else panels.enabled = true;

}


}