这个Dialogue System对话系统是用来解决游戏中的复杂的对话逻辑、支持多语言、保存、可视化流程等功能。如果是一般的单线性故事是没必要用这个的,自己写写就好。
另外一提,这个系统能帮助不会代码的开发者也能做出多支线、复杂的故事流程(可视化、多种碰撞按键等集成功能),下面介绍流程我会以一个第一次接触这个插件的方式来深入(事实也差不多),也就是说会把我接手和切入一个新插件的思考流程放进去。
我自己的独立游戏准备使用该插件,并且已经入手,如果有相关问题直接留言很乐意互相探讨。
导入插件,发现包含三部分:
1、 Editor Default Resources文件夹包含editor 脚本使用的默认资源。
2、Gizmos文件夹存放用Gizmos.DrawIcon方法使用的贴图、图标资源。
然后会有个弹窗,按自己需求勾选即可,我这里选了TMP(TextMeshPro)
4、目录下面有文档,不过里面有些内容是老版本的应该是没来得及更新。文档包括:
Text_Table_Manual-文本表格手册
Save_System_Manual-保存系统手册
Input_Device_Manager_Manual-输入设备管理器手册
找到Demo场景进去点击Dialogue Manager物体上的同名脚本上的引导功能
就是选择自己的对话数据资源,Creat New按钮可以新建资源,也就是说manager可以是唯一的但是里面的内容、故事性可以预设多个资源,来回切换。(目前看来是这样的)
这里可以分配一个对话UI。您可以在Plugins/Pixel Crushers/Dialogue System/Prefabs/Standard Ul Prefabs找到预构建UI。您还可以指定一个UI场景对象。字面意思,就是指定一个UI预制体,咱们可以打开这个UI预制,把对话图标改了运行Demo,发现对话框全都变了,真是太神奇了。
如图创建文本表格
双击打开表格,有两个列表,语言列表和字段列表;字段列表的下拉菜单咱们填进去
顺便一提,这里的表格能被导出成CSV格式,给什么玩野都不懂的策划用,然后再导入进来;开个玩笑,其实是为了只会excel表格的策划方便多国语言管理用的。
然后我们把表格拖进去,可以在初始语言写好自己刚才设置的语言,游戏时候做切换用,然后我们发现对应的对话管理控制系统同样添加了文本表格,说明这个引导用起来挺方便的,很舒服。
点击本地化系统,发现场景的Dialogue Manager预制上面多了给管理脚本,具体设置清晰明了,什么字体、表格大家看着要求点或者拖就行了。Additonal这个是附加的意思,如果游戏有不同区域分支,感觉这个还是很有必要的。
最后一提,很多时候我们想要用代码获得这些东西怎么办呢,下图1为只使用文本表格的时候,输入字段标记即可获得对应文本。
图2为使用官方提供的Manager,初始语言填的什么就会显示文本表格里面对应标记的字段,如果想更改使用UILocalizationManager.instance.currentLanguage即可,使用该api会缓存下来,点击管理脚本的清空即可初始化语言到默认的,更多使用方法直接看这个本地化系统的源码即可,很简单。
`using PixelCrushers;
public TextTable textTable;
void Start()
{
string str = textTable.GetFieldTextForLanguage("问候语", "EN");
Debug.Log(str);
}
-------------------------------------------------------------------------------
void Start()
{
string str= UILocalizationManager.instance.GetLocalizedText("问候语");
Debug.Log(str);
}
NPC Subtitles Durign Line:NPC说话时候对话字幕显示,如果你的项目有唇同步就别点,这个我点了因为好像做不起这个,哈哈。
With Response Menu:勾选可在“播放器响应”菜单中显示最后一个NPC字幕。这有助于提醒玩家–他们的反应是什么。一般都勾上吧。
PC Subtitles:PC字幕,在PC说话时,勾选以显示PC字幕。如果您使用不同的菜单文本和对话文本,勾选。否则,你可以选择让它不勾选。
Chars/Second:每秒输出的字符量,和Dotween的文本输出动画差不多。这个旁白、游戏中加上我觉得挺好的,但最好是快速一点跟着语境变化或者说能点击直接跳到最后(参考下面的Continue Button)。
Min Seconds:字幕显示最短时间。
Continue Button:继续按钮,对应事件很多,这里咱们选择Always运行Demo发现多了个按钮一点就完成。其他的解释如下,其实试下就知道了。
Never Before Response Menu:“从未在响应之前”菜单:如果“玩家响应”菜单是下一个,则不显示。对于除“从未”之外的任何设置,您的Ul必须包含“继续”按钮。
Optional Before Response Menu: “响应前可选”菜单:如果“玩家响应”菜单是下一个,则显示但不需要单击。
Rich Text:富文本,需要UI支持,我选择勾上。
我的李姐是过场动画,在玩家和Npc对话的时候,通常会出现视角转换、移动等需求(配合唇语也算其内但是做那种工作的估计不会看这个攻略),大概意思就是利用序列的手段来实现摄像机的各种属性变换。
这里细节应该不少先暂存放着,以后开个新档。
有对应的文档,说明了输入系统相关的设置和新输入系统,就是unity新出的那个。‘
牛逼之处说明:自动化管理,自动检测鼠标还是手柄之类的,隐藏光标什么的各种配置还是很全的。支持新、旧输入系统,提供了很方便的写法:
bool InputDeviceManager.IsButtonDown(“button name”)
bool InputDeviceManager.IsButtonUp(“button name”)
bool InputDeviceManager.IsKeyDown(KeyCode)
float InputDeviceManager.GetAxis(“axis name”)
Vector3 InputDeviceManager.GetMousePosition()
具体API文档
如果使用的是新系统,需要定义USE_NEW_INPUT这个scripting symbol(在欢迎页面勾选新系统可以自动添加,界面打开方式Tools/Pixel Crushers/Dialogue System/Welcome Windows。)。下面是示例代码(该脚本注册各种动作,然后我们其他地方调用即可):
using UnityEngine;
using UnityEngine.InputSystem;
using PixelCrushers;
public class RegisterMyControls : MonoBehaviour
{
protected static bool isRegistered = false;
private bool didIRegister = false;
private Controls controls;
void Awake()
{
controls = new MyControls();
}
void OnEnable()
{
if (!isRegistered)
{
isRegistered = true;
didIRegister = true;
controls.Enable();
InputDeviceManager.RegisterInputAction("Back", controls.Gameplay.Back);
InputDeviceManager.RegisterInputAction("Interact", controls.Gameplay.Interact);
}
}
void OnDisable()
{
if (didIRegister)
{
isRegistered = false;
didIRegister = false;
controls.Disable();
InputDeviceManager.UnregisterInputAction("Back");
InputDeviceManager.UnregisterInputAction("Interact");
}
}
}
如果需要使用请查看文档,上面代码开始注册,结束注销,这里不多说了。
配置:(实际上不写代码也是可以的,引导界面包含下面两个设置模块)
Player Response Menu: 播放器响应菜单
Always Force Menu:始终聚焦菜单,勾选即强制响应菜单。如果未勾选,则当玩家只有一个有效响应时,Ul将自动选择该响应,而不显示响应菜单。简单意思是玩家和NPC对话了想离开就得选择一个选项。
Include Invalid Entries:包含无效条目(实体),如果勾选了,那么菜单中为假的条目就会包含期内,按钮能看见但是不能选。意思是游戏中某些对话是有要求的(如:力量达到20才能在对话中威胁别人——说的就是你V(20力量),类2077里面的对话不可选择项目)
Timer:响应时间。就是紧急选项,如果打勾选择10s,那么10s内未选择就会执行你想要的操作,下拉菜单默认为时间到了选择第一项。
Quick Time Event(QTE) Trigger Buttons:快速触发事件按钮(具体内容在预制体上的Display Settings下的Input Settings更改,高级点的可以加OverrideDisplaySettings脚本重写设置)
Cancel Line:取消线的按钮、名字等,意为中断对话。名字可以方便脚本调用。
Cancel Conversation:取消对话的按钮、名字等,意为取消对话。
Allow In Conversations:勾选以允许在会话中显示警报。如果取消标记,则在对话结束之前不会显示警报。
Monitor Alerts:监视警报器,不断监视Lua值。
Chars/Second:显示警报的时间,为0就默认使用字幕的那个属性。
Min Seconds:显示警报的最小时间,为0就默认字幕的最小时间。
砖家建议不用管默认就好,反正我的项目都不需要。
该系统提供数据保存和记录跨场景的变化。
保存系统详见第一部分的提到的文档,具体使用如下(如果不需要自定义各种操作,以下脚本会在运行时自动添加):
1、首先在场景的Dialogue Manager上添加一个Save System脚本。
2、添加Json Data Serializer,数据序列化脚本。
3、添加PlayerPrefs Saved Game Data Storer或者Disk Saved Game Data Storer;分别是两种储存器。
ok,举个栗子。咱们的游戏的设置菜单有两个按钮分别对应保存和加载游戏,那我们使用SaveSystem.SaveGameToSlot和SaveSystem.LoadGameFromSlot绑定两个按钮就行,如果有加载场景的需求还提供了SaveSystem.LoadScene方法或者添加SaveSystemMethods脚本到预制上。其中Demo场景里面搜索Demo Menu查看上面的脚本跳到继承的DemoMenu里面发现他也是这么写的,如下:
{
private void SaveGame()
{
var saveSystem = FindObjectOfType<SaveSystem>();
if (saveSystem != null)
{
SaveSystem.SaveToSlot(1);
}
else
{
string saveData = PersistentDataManager.GetSaveData();
PlayerPrefs.SetString("SavedGame", saveData);
Debug.Log("Save Game Data: " + saveData);
}
DialogueManager.ShowAlert("Game saved.");
}
private void LoadGame()
{
PersistentDataManager.LevelWillBeUnloaded();
var saveSystem = FindObjectOfType<SaveSystem>();
if (saveSystem != null)
{
if (SaveSystem.HasSavedGameInSlot(1))
{
SaveSystem.LoadFromSlot(1);
DialogueManager.ShowAlert("Game loaded.");
}
else
{
DialogueManager.ShowAlert("Save a game first.");
}
}
else
{
if (PlayerPrefs.HasKey("SavedGame"))
{
string saveData = PlayerPrefs.GetString("SavedGame");
Debug.Log("Load Game Data: " + saveData);
LevelManager levelManager = FindObjectOfType<LevelManager>();
if (levelManager != null)
{
levelManager.LoadGame(saveData);
}
else
{
PersistentDataManager.ApplySaveData(saveData);
DialogueManager.SendUpdateTracker();
}
DialogueManager.ShowAlert("Game loaded.");
}
else
{
DialogueManager.ShowAlert("Save a game first.");
}
}
}
private void ClearSavedGame()
{
var saveSystem = FindObjectOfType<SaveSystem>();
if (saveSystem != null)
{
if (SaveSystem.HasSavedGameInSlot(1))
{
SaveSystem.DeleteSavedGameInSlot(1);
}
}
else if (PlayerPrefs.HasKey("SavedGame"))
{
PlayerPrefs.DeleteKey("SavedGame");
Debug.Log("Cleared saved game data");
}
DialogueManager.ShowAlert("Saved Game Cleared");
}
}
总结就是会代码的自己调用文档的方法写就行,不然添加对应写好的脚本,除了上面提到的组件脚本其余如下:
好了,先了解到这,以后添加对应的专属模块示例。
之前设置的总览页面,包含对话信息数据、UI模板、展示字幕方式、响应菜单、响应时间、允许警报、保存系统。
Database:基本数据信息
Actors:演员,包含玩家自身
Quests:任务
Locations:位置,定位
Variables:变量
Conversations:对话
Templates:模板
基本上包含变量、人物、任务、对话文本和其他杂项设置,目录还是很清晰的,想研究可以随便修改Demo看看。
通过分析插件自带的引导系统,把整个插件包含的设置相关了解了一遍,其中各种系统还是很全面的如果自己开发需要不少时间。下一篇直接开干,自己从头到尾搞个Demo出来,交大火怎么整合到项目里面。总的来讲这个是把该插件的功能概念都列举出来,可能有些人觉得无用,但很多时候脑子里对插件有了概念李姐是很关键的,这样就不用死记硬背而是可以灵活的组合各种功能,游刃有余。
基本功能使用可以去b站、油管搜Dialogue System,是不使用代码的;下一篇实战篇,不过几天出来不保证,毕竟还有本职工作。