在众多RPG等游戏中,对话框系统显然属于不可或缺的一部分。玩家通过对话了解剧情以及接下来的主线。可以说,对话系统就是RPG游戏的灵魂(bushi,而这个文章介绍的对话系统可以在无需插件并且完成对话系统基本功能的同时完成以下三点内容:

1.剧情内容只用按照规律写在txt文件里

2.可以选择跳过,自动播放或者手动播放

3.文字会一个一个显示,而不是整句话蹦出来

撒,那我们开始把(

搭建对话框

​ 按照以往经验,对话框肯定要包括对话信息框,角色的头像和名字。然后我在此基础上还要实现自动播放,跳过,所以加了两个按钮。如果还要其它功能可以自己再加。。。我直接简单搭了一个:

对话框

其中对话框主体直接在canvas的最下面加了panel,角色名称,对话内容分开来各用一个text(因为到时候要随着角色的改变而改变),并且以panel为父物体。(这个没什么难度,直接简写了)

注意!:

无论要怎么改,对话框物体一定要设置锚点卡在屏幕的各个角落,否则会因为屏幕尺寸发生变化而偏移!

注意

锚点设置方法:点击红箭头指的按钮,按住alt键,选择要固定在屏幕的哪个地点。

构建基础功能

要实现基础功能,就要通过三个流程:

1.将txt文本文件导入列表

2.将列表内的台词依次播放,同时根据角色的转变而变换头像

3.判断结束对话并且把所有对话框组件setactive改为false

先是声明的各种物体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 [Header("UI组件")]
public Text textLabel; //对话框的内容text
public GameObject SkipButton; //跳过按钮
public GameObject AutoButton; //自动播放按钮
public GameObject Log; //对话框的panel
public Text AutoText; //自动播放按钮的“自动播放”文本的text(用于实现自动手动切换的功能)
public GameObject Warning; //提醒玩家按左键继续,可要可不要
[Header("文本文件")]
public TextAsset textfile; //剧情文本的txt文件
public int index; //进行到了第几句话,用于实现基础功能
public float textSpeed = 0.05f; //每个字多久才能出现

[Header("头像")]
public GameObject A; //角色的头像以及名字
public GameObject B; //角色的头像以及名字
//因为我直接把名字和头像绑定为父子物体了,所以只声明了一个GameObect

bool textFinished; //判断对话是否结束
float i=0f; //判断是否为自动还是手动播放

List<string> textList = new List<string>(); //将txt文件的对话内容导入列表内

接着进unity把每个组件都挂载一下(具体是啥我在注释都写了,所以就不贴图了)

将txt文本文件导入列表

在说明代码前先把txt的格式说明一下(为什么这样之后再说):

1
2
3
4
5
6
A
换一个角色说话之前要把角色的名字单独打一行
就像这样
B
这样就换成了我(
结束的时候不需要标记什么

先贴上代码:

1
2
3
4
5
6
7
8
9
10
11
 void GetTextFromFile(TextAsset file)
{
textList.Clear(); //如果不是第一次调用对话框,就需要把之前一次所有对话的列表给清空
index = 0; //并且把显示第几句话的index清零
var lineData = file.text.Split('\n'); //把文本文件以回车分割一句句台词和角色名称

foreach (var line in lineData)
{
textList.Add(line); //把一句句台词录入line列表里
}
}

总而言之就是:重置之前用过的台词和数据,处理文本文件,用回车键分割一句句台词,然后把台词录入列表以供对话系统使用。

注意:

在调用时要放在Awake函数里,因为Awake函数是一启用脚本就开始运行,而这个工序也要一经使用就开始运行。我的脚本调用是这样:

1
2
3
4
void Awake()
{
GetTextFromFile(textfile);
}

将列表内台词播放

注意:看懂这段代码需要学习unity协程函数的知识,如果之前没有听过建议百度学习一下或者等我的文章更新(算了我天天鸽还是别等了)

单个句子的播放

还是贴代码(这个代码实现的只是单个句子的播放,而不是整个对话):

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
IEnumerator SetTextUI() //协程函数的标准形式
{
textFinished = false; //正在播放,所以bool值为false
textLabel.text = ""; //可能上一个句子使用结束后还在text上,所以要清空
switch (textList[index]) //index是指播放到了第几句,所以这是来判断当前播放到的台词
//用来判断是谁说出来的(所以txt要按标准写嘛(叉腰))
{
case "A\r"://如果A要说话
{
A.SetActive(true); //头像名字显示出来
index++; //直接跳到下一句,这样就不会直接出现在对话框的对话里面
break;
}
//如果还要写别人的话,case信息和上面一样
case "旁白\r": //与上面同理
{
QingShan.SetActive(false);
index++;
break;
}
}
for (int i = 0; i < textList[index].Length; i++)
//按照一句话的一个字慢慢来,如果没有到一句台词最后一个字的话就一直向后
{
textLabel.text += textList[index][i]; //往之前已经打出的台词中加进新的文字
yield return new WaitForSeconds(textSpeed);//等待textSpeed后继续循环
}
textFinished = true; //如果跳出循环说明文字已经放完了,所以bool值是true
index++; //这句话已经放完了,index++跳进下一句话
}

其中yield return的意思就是,在这一帧return,并且在下一帧继续开始。如果看不懂的话还是建议百度一下unity协程函数。这样的话不用放进update函数里面也可以实现延时等效果。(暴论)

常见问答(bushi):

为什么判断是角色名字后不直接跳出函数,而是继续打出呢?

因为txt里面角色名字后面一定是角色说的话而不是另一个角色的名字,所以可以直接不需要判断而进入循环。同时在写txt剧情时也要注意:哪有一个角色还没讲话就出现另外一个角色的道理哼哼啊啊啊啊啊

为什么角色名字后面要加/r?

因为在windows的txt文本中,每次回车都会有\r的存在。如果要判断是不是角色名就一定要加上\r,除非你不是拿windows做游戏。


整个对话文本的播放

还是先贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
void Update(){
AutoText.text = "自动播放"; //切换自动手动播放时要用的
textSpeed = 0.05f; //每个字出来的间隔时0.05秒
if (Input.GetKeyDown(KeyCode.Mouse0) && index == textList.Count)
{
EndDialogue();//如果按下鼠标左键并且播放到了最后一句的话就启动结束对话的函数
}
if (Input.GetKeyDown(KeyCode.Mouse0) && textFinished)
{
StartCoroutine(SetTextUI());//如果按下鼠标左键的同时一句话已经放完了,就继续启动下一句的播放
}
}
常见问答(bushi):

为什么要设置一个textFinished的布尔值?

因为如果不设置的话前面一句还没有看完下一句的台词直接来了,这样会严重影响读者的阅读体验,所以我决定直接卡死不让玩家快进。


结束播放对话文本

依旧是先贴上代码:

1
2
3
4
5
6
7
8
9
10
public void EndDialogue()
{//把114514个对话框组件全部隐藏,代表对话已经结束了
gameObject.SetActive(false);
QingShan.SetActive(false);
Log.SetActive(false);
SkipButton.SetActive(false);
AutoButton.SetActive(false);
index = 0; //把目录调整成最开始,这样下一次开始就是从第一句播放
return;
}

这样一个完整的对话就结束了。

但是如何实现对话的自动手动播放,以及跳过播放呢?

完成增值功能:

实现跳过对话的功能

这活儿很简单,只要把脚本放进空物体,然后把按钮按下的脚本挂载进并选择EndDailogue函数里就可以了。

挂载

实现自动手动播放的功能

这个功能其实比较简陋,因为我目前还没有尝试在每句话结束后调节时间的功能,只是把每个字播放的顺序调慢而已。(请大神轻喷)

还是代码:

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
void Update()
{
if (i % 2 == 0f)//当i是偶数时,说明点了偶数按钮,就是手动播放,奇数同理
{
AutoText.text = "自动播放"; //把按钮文本改了,因为现在是手动播放
textSpeed = 0.05f;
if (Input.GetKeyDown(KeyCode.Mouse0) && index == textList.Count)
{
EndDialogue();
}
if (Input.GetKeyDown(KeyCode.Mouse0) && textFinished)
{
StartCoroutine(SetTextUI());
}
}
else
{
AutoText.text = "手动播放";
textSpeed = 0.15f; //把每个字出现的时长调整长了
if (index == textList.Count) //因为是自动播放,所以去掉了按鼠标左键的限制
{
EndDialogue();
}
if (textFinished) //这里也是
{
StartCoroutine(SetTextUI());
}
}
}
public void AutoPlay() //要把这个函数挂载进自动播放的按钮里
{
i = i + 1f;
}

说起来这个功能还是比较简陋,它的是否是自动手动的判定机制仅仅是判断点击按钮的次数是奇数还是偶数,按钮每次点击也只是计算点击次数的变量+1,但是它就是这么实现了?就很怪

再次提醒一下,AutoPlay函数要挂载进按钮里面,挂载方式和之前跳过对话的按钮一样,否则无法实现功能。


就这样差不多就完成了,虽然小编也很惊奇,但是对话框系统就是这样完成了。有疑惑的小伙伴欢迎在评论区留下点赞评论吧!我们下期再见!(你是营销号吗??)

友情链接:

大多数代码以及思想借鉴的视频:https://www.bilibili.com/video/BV1WJ411Y71J

(Michael真的很强,建议初学者都去康康他的视频嗷)