改善原因
有時候會希望某一句話結束或對話結束時,可以觸發某個行為或某件事,像是移動鏡頭到某個物件上幾秒等等的,讓對話的過程可以更豐富。
實作
增加要觸發的事件
Dialogue
而要實現這個功能,需要引入UnityEngine.Events的名稱空間(Namespace),如此才能使用其中的UnityEvent類別。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
[System.Serializable]
public class Dialogue
{
public string dialogueName;
public bool isCompelete = false;
public List<Condition> taskConditions;
[Space(15)]
public List<Sentence> sentences;
public UnityEvent finishEvent;
/// <summary>
/// 啟動對話結束時的事件
/// </summary>
public void InvokeFinishEvent()
{
finishEvent.Invoke();
}
}
當我們宣告了型別為UnityEvent的公開變數,我們可以在Inspector視窗上設定他要執行哪個物件的哪個元件的哪個方法。
FinishEvent的左側,其右邊有圓形icon的那一欄,是設定要是哪個物件
FinishEvent的右側,其右邊有三角形icon的那一欄,是設定要用物件的哪個元件的哪個方法。
如果要執行的方法有參數
如果方法有一個參數,底下還會顯示一欄,讓你輸入要傳入方法的值。
然而,若是想要執行有更多參數的方法,需要自己創建覆寫UnityEvent的新類別,可以參考這一篇,最多可以有四個參數。
Unity – Scripting API: UnityEvent<T0,T1,T2,T3> (unity3d.com)
改善玩家體驗
DialoguesManager
希望在每個對話結束後,都能在出現可對話的提示訊息,因為角色還在可進行對話的範圍內,如果要玩家走出去再回來才會顯示,覺得體驗上不是那麼好。
宣告型別為UnityEvent的變數,並在每一個對話結束後觸發事件。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class DialoguesManager : MonoBehaviour
{
private Dialogue dialogue;
public Dialogue Dialogue
{
get
{
return dialogue;
}
}
private bool isDialogueCompelete = true;
public bool IsDialogueCompelete
{
get
{
return isDialogueCompelete;
}
}
private RoleObj speakingRoleObjs;
private RoleObj[] roleObj;
private Dictionary<Role, RoleObj> dic_Role_and_DialogueObj = new Dictionary<Role, RoleObj>();
private void GetAllRoleObj()
{
roleObj = FindObjectsOfType<RoleObj>();
}
private void Set_Role_and_DialogueObj()
{
for (int i = 0; i < roleObj.Length; i++)
{
dic_Role_and_DialogueObj.Add(roleObj[i].role, roleObj[i]);
}
}
private RoleObj GetRoleObjs(Role role)
{
if (dic_Role_and_DialogueObj.ContainsKey(role))
{
return dic_Role_and_DialogueObj[role];
}
return null;
}
public List<AreaDialogues> areaDialogues;
public PlayerProgress playerProgress;
public UnityEvent finishEventAfterAnyDialogue;//宣告所有對話結束後要發生的事鍵的變數
private AreaDialogues GetAreaDialogues()
{
for (int i = 0; i < areaDialogues.Count; i++)
{
if (areaDialogues[i].areaNum == AreaNumKeeper.currentNum)
{
return areaDialogues[i];
}
}
return null;
}
public void GetDialogue()
{
if (isDialogueCompelete)
{
AreaDialogues areaDialogues = GetAreaDialogues();
if (areaDialogues != null)
{
dialogue = areaDialogues.GetDialogue(playerProgress);
}
}
}
public void KeepingTheDialogue(int dialogeIndex)
{
AreaDialogues areaDialogues = GetAreaDialogues();
areaDialogues.dialogues[dialogeIndex].isCompelete = false;
}
private Vector3 dialogueRolesCenterPosition;
public Vector3 DialogueRolesCenterPosition
{
get
{
return dialogueRolesCenterPosition;
}
}
private void CalculateCenterPosition()
{
dialogueRolesCenterPosition = Vector3.zero;
for (int l = 0; l < roleObj.Length; l++)
{
dialogueRolesCenterPosition += roleObj[l].transform.position;
}
dialogueRolesCenterPosition = dialogueRolesCenterPosition / roleObj.Length;
}
private void ScaleDownAllBubble()
{
for (int l = 0; l < roleObj.Length; l++)
{
roleObj[l].BubbleScaleDown();
}
}
private IEnumerator DialogueTextWriter(Dialogue dialogue)
{
ScaleDownAllBubble();
CalculateCenterPosition();
bool isFinishTextSentence = false;
int sentenceCounter = 0;
while (sentenceCounter < dialogue.sentences.Count)
{
speakingRoleObjs = GetRoleObjs(dialogue.sentences[sentenceCounter].speaker);
if (isFinishTextSentence == false)
{
string sentenceContent = dialogue.sentences[sentenceCounter].text;
speakingRoleObjs.BubbleScaleUp("<color=#00000000>" + sentenceContent + "</color>");
yield return new WaitForSeconds(0.2f);
float time = 0, duration = 0.04f;
for (int j = 0; j < dialogue.sentences[sentenceCounter].text.Length; j++)
{
string txt = sentenceContent.Substring(0, j + 1) + "<color=#00000000>" + sentenceContent.Substring(j + 1) + "</color>";
speakingRoleObjs.SetText(txt);
while (time < duration)
{
if (Input.GetKeyDown(KeyCode.Space))
{
duration = 0.02f;
}
time += Time.deltaTime;
yield return new WaitForSeconds(Time.deltaTime);
}
time = 0;
}
isFinishTextSentence = true;
}
else
{
if (Input.GetKeyDown(KeyCode.Space))
{
if (sentenceCounter < dialogue.sentences.Count - 1)
{
sentenceCounter++;
isFinishTextSentence = false;
speakingRoleObjs.BubbleScaleDown();
yield return new WaitForSeconds(0.2f);
}
else if (sentenceCounter >= dialogue.sentences.Count - 1)
{
sentenceCounter++;
speakingRoleObjs.BubbleScaleDown();
yield return new WaitForSeconds(0.2f);
speakingRoleObjs = null;
isDialogueCompelete = true;
dialogue.isCompelete = true;
dialogue.InvokeFinishEvent();//啟動特定對話結束時的事件
finishEventAfterAnyDialogue.Invoke();//啟動所有對話結束時的事件
}
}
}
yield return null;
}
}
public void StartDialogue()
{
if (dialogue != null)
{
isDialogueCompelete = false;
StartCoroutine(DialogueTextWriter(dialogue));
}
}
private void Start()
{
GetAllRoleObj();
Set_Role_and_DialogueObj();
}
private void OnValidate()
{
UpdateEditorUI();
}
public void UpdateEditorUI()
{
for (int i = 0; i < areaDialogues.Count; i++)
{
areaDialogues[i].Update();
for (int j = 0; j < areaDialogues[i].dialogues.Count; j++)
{
for (int k = 0; k < areaDialogues[i].dialogues[j].taskConditions.Count; k++)
{
if (playerProgress != null)
{
areaDialogues[i].dialogues[j].taskConditions[k].Update(playerProgress.SearchTaskName(areaDialogues[i].dialogues[j].taskConditions[k].taskIndex));
}
}
for (int l = 0; l < areaDialogues[i].dialogues[j].sentences.Count; l++)
{
areaDialogues[i].dialogues[j].sentences[l].Update();
}
}
}
}
}
成果如下圖所示
目前是持續顯示提示訊息,如果玩家按了對話建以後,沒有反應的話,玩家就知道沒有對話了。
還有另一種方式是,根據DialoguesManager那裡判斷還有沒有可以進行的對話,有的話才顯示提示訊息,沒有的話則收起提示訊息。
系列文章
《Carto》實作對話系統01-前言
《Carto》實作對話系統02-建立遊戲所需物件
《Carto》實作對話系統03-建構程式
《Carto》實作對話系統04-把腳本加到對應的物件上
《Carto》實作對話系統05-當TextMeshPro沒有辦法顯示中文
《Carto》實作對話系統06-改善對話系統的操作介面
《Carto》實作對話系統08-當條件滿足時,不用按按鍵,可以直接觸發的對話