-
Notifications
You must be signed in to change notification settings - Fork 0
/
ConversationSystem.cs
314 lines (272 loc) · 9.97 KB
/
ConversationSystem.cs
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class ConversationSystem : MonoBehaviour {
[Header("References")]
public Text title;
public Text content;
public Transform options;
public List<Image> sprites;
[Header("Custom Properties")]
public string main_character_name;
public bool typewriter_effect;
//initialization setup
private const string conversation_files_folder = "Conversations";
private const string conversation_sprites_folder = "ConversationSprites";
//references
public static ConversationSystem instance = null;
//events
public delegate void key_reached_delegate (string key);
public static event key_reached_delegate key_reached;
public delegate void variable_key_delegate (out string key);
public static event variable_key_delegate variable_key;
//collections
private static Dictionary<string, string> conversation_collection = new Dictionary<string, string> (); //string as key : conversation content
private static Dictionary<string, Sprite> sprite_collection = new Dictionary<string, Sprite> (); //character sprites to accompany conversation
private List<string> current_coversation_breakdown = new List<string> (); //content and options
private List<string> current_main_text_breakdown = new List<string> (); //content, broken down into segments, each including a title and a text
private Dictionary<Text, string> option_textfield_collection = new Dictionary<Text, string> (); //textfield as key : string used as key in conversation_collection
//status
public static bool active = false;
private static bool initialized = false;
private bool isTyping = false;
private string text_to_type = "";
void Awake () {
if (instance == null) {
instance = this;
} else if (instance != this) {
Destroy (gameObject);
}
}
void OnDestroy () {
if (instance == this) {
instance = null;
}
}
public static void Initialize() {
if (initialized) {
return;
}
TextAsset[] conversation_files = Resources.LoadAll (conversation_files_folder, typeof(TextAsset)).Cast<TextAsset>().ToArray();
foreach (TextAsset conversation_file in conversation_files) {
//split the text file by new lines, empty lines will be removed
string[] conversations = conversation_file.text.Split (new[] { "\r\n", "\r", "\n" }, System.StringSplitOptions.RemoveEmptyEntries);
//every conversation is divided into key (not shown in game) and body (shown in game), they are then added to the conversation collection
foreach (string conversation in conversations) {
if (conversation [0] == '#') {
continue;
}
string[] conversation_components = conversation.Split ('|');
string key = conversation_components [0].Trim ();
string body = conversation_components [1].Trim ();
conversation_collection.Add (key, body);
}
}
Sprite[] conversation_sprites = Resources.LoadAll (conversation_sprites_folder, typeof(Sprite)).Cast<Sprite> ().ToArray ();
foreach (Sprite conversation_sprite in conversation_sprites) {
sprite_collection.Add (conversation_sprite.name, conversation_sprite);
}
initialized = true;
}
void Start () {
//establish references with buttons on screen
Text[] option_texts = options.GetComponentsInChildren<Text> ();
foreach (Text t in option_texts) {
option_textfield_collection.Add (t, null);
}
}
public static void EnterConversation (string key) {
instance._EnterConversation (key);
}
public void _EnterConversation (string key) {
active = true;
GoToKey (key);
//custom animation
GetComponent<Animator> ().Play ("In");
}
public static void ExitConversation () {
instance._ExitConversation ();
}
public void _ExitConversation () {
active = false;
options.gameObject.SetActive (false);
//custom animation
GetComponent<Animator> ().Play ("Out");
}
//go to the next series of conversation after a text object (an option) is selected
public void GoToKey (Text target) {
GoToKey (option_textfield_collection [target]);
}
//go to a series of conversation using a string as a key
public void GoToKey (string key) {
if (key_reached != null) {
key_reached (key);
}
if (key == "") {
ExitConversation ();
return;
}
//experimental
if (key [0] == '#') {
if (variable_key != null) {
variable_key (out key);
}
}
//first, hide all previously shown options and bring up the content, clear any existing title
content.gameObject.SetActive (true);
options.gameObject.SetActive (false);
title.text = "";
//find the directory in the dictionary
string body = conversation_collection [key];
current_coversation_breakdown = body.Split ('(').ToList();
//break the main text down and display the first part, the subsequent parts will be triggered on mouse-click
string main_text = current_coversation_breakdown [0].Trim ();
current_main_text_breakdown = main_text.Split ('`').ToList();
DisplayConversation ();
}
public void DisplayConversation () {
if (isTyping) {
StopAllCoroutines ();
content.text = text_to_type;
isTyping = false;
return;
}
if (current_main_text_breakdown.Count == 0) {
DisplayOptions ();
return;
}
string text_to_display = current_main_text_breakdown.First().Trim();
current_main_text_breakdown.RemoveAt (0);
if (text_to_display.Contains ('[')) {
string[] text_and_sprites = text_to_display.Split ('[');
text_to_display = text_and_sprites [0].Trim ();
string sprite_names = text_and_sprites [1].Trim ();
sprite_names = sprite_names.TrimEnd (']');
DisplaySprites (sprite_names);
}
if (text_to_display.Contains ('\\')) {
string[] title_and_text = text_to_display.Split ('\\');
title.text = title_and_text [0].Trim ();
text_to_display = title_and_text [1].Trim ();
}
if (typewriter_effect) {
content.text = "";
text_to_type = text_to_display;
StartCoroutine (AppendText (text_to_type));
return;
}
content.text = text_to_display;
}
public void DisplaySprites (string sprite_names) {
string[] names = sprite_names.Split (',');
for (int i = 0; i < Mathf.Min (names.Length, sprites.Count); i++) {
string name = names [i].Trim ();
Sprite s = null;
if (sprite_collection.ContainsKey (name)) {
s = sprite_collection [name];
}
StartCoroutine (ChangeSprite (s, sprites[i]));
}
}
public void DisplayOptions () {
//check if there are options
if (current_coversation_breakdown.Count <= 1) {
ExitConversation ();
return;
}
//check if there's only 1 option
if (current_coversation_breakdown.Count == 2) {
string[] options = current_coversation_breakdown [1].Split (')');
if (options[1].Trim() == "") {
GoToKey (options[0]);
return;
}
}
//display the options, hide the content area and the title
content.gameObject.SetActive (false);
options.gameObject.SetActive (true);
title.text = main_character_name;
Text[] option_textfields = option_textfield_collection.Keys.ToArray();
for (int i = 1; i < current_coversation_breakdown.Count; i++) {
string[] option_components = current_coversation_breakdown [i].Split (')');
//store the key
string key = option_components [0].Trim();
option_textfield_collection[option_textfields[i - 1]] = key;
//display the option
option_textfields[i - 1].text = option_components [1].Trim ();
}
//hide options that aren't used
for (int i = 0; i < option_textfields.Length; i++) {
if (i > current_coversation_breakdown.Count - 2) {
option_textfields [i].transform.parent.gameObject.SetActive (false);
} else {
option_textfields [i].transform.parent.gameObject.SetActive (true);
}
}
}
private IEnumerator ChangeSprite (Sprite s, Image img) {
RectTransform rect = img.GetComponent <RectTransform> ();
float direction = rect.anchoredPosition.x / Mathf.Abs (rect.anchoredPosition.x);
float width = img.GetComponentInParent<CanvasScaler> ().referenceResolution.x;
float transition_time = 45f;
if (s == img.sprite || (s == null && img.sprite == null)) { //nothing happens
yield break;
} else if (s != null && img.sprite == null) { //enter character
img.sprite = s;
rect.anchoredPosition += direction * new Vector2 (width, 0f);
for (float i = 0; i <= transition_time; i++) {
rect.anchoredPosition -= new Vector2 (width * direction / transition_time, 0f);
img.color = new Color (1f, 1f, 1f, i / transition_time);
yield return null;
}
} else if (s == null && img.sprite != null) { //exit character
for (float i = 0; i <= transition_time; i++) {
rect.anchoredPosition += new Vector2 (width * direction / transition_time, 0f);
img.color = new Color (1f, 1f, 1f, (transition_time - i) / transition_time);
yield return null;
}
rect.anchoredPosition -= direction * new Vector2 (width, 0f);
img.sprite = null;
} else if (s.name.Split ('_')[0] != img.sprite.name.Split ('_')[0]) { //switch character
for (float i = 0; i <= transition_time; i++) {
rect.anchoredPosition += new Vector2 (width * direction / transition_time, 0f);
img.color = new Color (1f, 1f, 1f, (transition_time - i) / transition_time);
yield return null;
}
img.sprite = s;
for (float i = 0; i <= transition_time; i++) {
rect.anchoredPosition -= new Vector2 (width * direction / transition_time, 0f);
img.color = new Color (1f, 1f, 1f, i / transition_time);
yield return null;
}
} else { //same character different expression
img.sprite = s;
yield break;
}
}
private IEnumerator AppendText (string text) {
int i = 0;
isTyping = true;
while (i < text.Length) {
if (text.Substring (i, 1) == "<") { //resolving display issues with unity rich text
int j = 1;
int k = 1;
while (j > 0 || text.Substring (i + k - 1, 1) != ">") {
if (text.Substring (i + k, 1) == "/") {
j--;
}
k++;
}
content.text = content.text + text.Substring (i, k);
i += k;
goto done;
}
content.text = content.text + text.Substring (i, 1); //normal appending with no rich text
i++;
done: yield return null;
}
isTyping = false;
}
}