OpenXR開發(fā)實戰(zhàn)項目之VR接入ChatGPT:實現(xiàn)游戲AI對話系統(tǒng)

一百新、框架視圖

二仿贬、關(guān)鍵代碼

ChatGptManager

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
/// <summary>
/// ChatGpt的管理器。
/// </summary>
public class ChatGptManager : MonoBehaviour
{
    //構(gòu)造方法私有化系枪,防止外部new對象曙砂。
    private ChatGptManager() { }

    //提供一個屬性給外部訪問,這個屬性就相當于是單例對象狐蜕。
    private static ChatGptManager instance;
    public static ChatGptManager Instance
    {
        get
        {
            if (instance==null)
            {
                instance = FindObjectOfType<ChatGptManager>();
                if (instance == null)
                {
                    GameObject go = new GameObject("ChatGptManager");//創(chuàng)建游戲?qū)ο?                    instance=go.AddComponent<ChatGptManager>();//掛載腳本到游戲?qū)ο笊砩?                    DontDestroyOnLoad(go);
                }
            }
            return instance;
        }
    }

    //要調(diào)用ChatGpt的API的網(wǎng)址宠纯。
    string chatGptUrl = "https://api.openai.com/v1/chat/completions";

    //使用的ChatGPT的模型
    string chatGptModel = "gpt-3.5-turbo";

    //使用的ChatGPT的API Key
    string chatGptApiKey = "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";

    //AI人設(shè)的提示詞
    public string aiRolePrompt = "和我是青梅竹馬的女孩子";

    //與ChatGPT的聊天記錄。
    public List<PostDataBody> chatRecords = new List<PostDataBody>();


    void Awake()
    {
        //給AI設(shè)定的人設(shè)层释。
        chatRecords.Add(new PostDataBody("system", aiRolePrompt));
    }

    /// <summary>
    /// 異步向ChatGPT發(fā)送消息(不連續(xù)對話)
    /// </summary>
    /// <param name="message">詢問ChatGPT的內(nèi)容</param>
    /// <param name="callback">回調(diào)</param>
    /// <param name="aiRole">ChatGPT要扮演的角色</param>
    public void ChatDiscontinuously(string message,UnityAction<string> callback,string aiRole="")
    {
        //構(gòu)造要發(fā)送的數(shù)據(jù)婆瓜。
        PostData postData = new PostData
        {
            //使用的ChatGPT的模型
            model = chatGptModel,

            //要發(fā)送的消息
            messages = new List<PostDataBody>()
            {
                new PostDataBody("system",aiRole),
                new PostDataBody("user",message)
            }
        };

        //異步向ChatGPT發(fā)送數(shù)據(jù)。
        SendPostData(postData, callback);
    }

    /// <summary>
    /// 異步向ChatGPT發(fā)送消息(連續(xù)對話)
    /// </summary>
    /// <param name="message">詢問ChatGPT的內(nèi)容</param>
    /// <param name="callback">回調(diào)</param>
    public void ChatContinuously(string message,UnityAction<string> callback)
    {
        //緩存聊天記錄
        chatRecords.Add(new PostDataBody("user",message));

        //構(gòu)造要發(fā)送的數(shù)據(jù)贡羔。
        PostData postData = new PostData
        {
            //使用的ChatGPT的模型
            model = chatGptModel,

            //要發(fā)送的消息
            messages = chatRecords
        };

        //異步向ChatGPT發(fā)送數(shù)據(jù)廉白。
        SendPostData(postData, callback);
    }

    /// <summary>
    /// 清空ChatGPT的聊天記錄,并重新設(shè)置連續(xù)對話時乖寒,AI的人設(shè)猴蹂。
    /// </summary>
    /// <param name="aiRolePrompt">AI的人設(shè)。我們可以用一段話來描述這個人設(shè)楣嘁。</param>
    public void ClearChatRecordsAndSetAiRole(string aiRolePrompt="")
    {
        //清空聊天記錄磅轻。
        chatRecords.Clear();

        //給AI設(shè)定人設(shè)。
        chatRecords.Add(new PostDataBody("system", aiRolePrompt));
    }

    public void SendPostData(PostData postData, UnityAction<string> callback)
    {
        StartCoroutine(SendPostDataCoroutine(postData,callback));
    }
    IEnumerator SendPostDataCoroutine(PostData postData, UnityAction<string> callback)
    {
        //創(chuàng)建一個UnityWebRequest類的對象用于發(fā)送網(wǎng)絡(luò)請求逐虚。POST表示向服務(wù)器發(fā)送數(shù)據(jù)聋溜。using關(guān)鍵字用于在執(zhí)行完這段語句之后釋放這個UnityWebRequest類的對象。
        using (UnityWebRequest request = new UnityWebRequest(chatGptUrl, "POST"))
        {
            //把傳輸?shù)南⒌膶ο筠D(zhuǎn)換為JSON格式的字符串叭爱。
            string jsonString = JsonUtility.ToJson(postData);

            //把JSON格式的字符串轉(zhuǎn)換為字節(jié)數(shù)組撮躁,以便進行網(wǎng)絡(luò)傳輸。
            byte[] data = System.Text.Encoding.UTF8.GetBytes(jsonString);

            //設(shè)置要上傳到遠程服務(wù)器的主體數(shù)據(jù)买雾。
            request.uploadHandler = (UploadHandler)new UploadHandlerRaw(data);

            //設(shè)置從遠程服務(wù)器接收到的主體數(shù)據(jù)把曼。
            request.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer();

            //設(shè)置HTTP網(wǎng)絡(luò)請求的標頭杨帽。表示這個網(wǎng)絡(luò)請求的正文采用JSON格式進行編碼。
            request.SetRequestHeader("Content-Type", "application/json");

            //設(shè)置HTTP網(wǎng)絡(luò)請求的標頭祝迂。這里的寫法是按照OpenAI官方要求來寫的睦尽。
            request.SetRequestHeader("Authorization", string.Format("Bearer {0}", chatGptApiKey));

            //等待ChatGPT回復(fù)。
            yield return request.SendWebRequest();

            //回復(fù)碼是200表示成功型雳,404表示未找到当凡,500表示服務(wù)器內(nèi)部錯誤。
            if (request.responseCode == 200)
            {
                //獲取ChatGPT回復(fù)的字符串纠俭,此時它是一個JSON格式的字符串沿量。
                string respondedString = request.downloadHandler.text;

                //將ChatGPT回復(fù)的JSON格式的字符串轉(zhuǎn)換為指定的類的對象。
                RespondedData respondedMessages = JsonUtility.FromJson<RespondedData>(respondedString);

                //如果ChatGPT有回復(fù)我們冤荆,則我們就挑第0條消息來顯示朴则。
                if (respondedMessages != null && respondedMessages.choices.Count > 0)
                {
                    //獲取第0條消息的字符串。
                    string respondedMessage = respondedMessages.choices[0].message.content;

                    //執(zhí)行回調(diào)钓简。
                    callback?.Invoke(respondedMessage);
                }
            }
        }
    }













    //發(fā)送給ChatGPT的數(shù)據(jù)
    [Serializable]
    public class PostData
    {
        //使用哪一個ChatGPT的模型
        public string model;

        //發(fā)送給ChatGPT的消息乌妒。
        //如果發(fā)送的列表含有多條消息,則ChatGPT會根據(jù)上下文來回復(fù)外邓。
        public List<PostDataBody> messages;
    }

    [Serializable]
    public class PostDataBody
    {
        //說話的角色
        public string role;

        //說話的內(nèi)容
        public string content;

        //構(gòu)造方法
        public PostDataBody() { }
        public PostDataBody(string role, string content)
        {
            this.role = role;
            this.content = content;
        }
    }

    //ChatGPT回復(fù)我們的數(shù)據(jù)
    [Serializable]
    public class RespondedData
    {
        public string id;
        public string created;
        public string model;
        public List<RespondedChoice> choices;
    }
    [Serializable]
    public class RespondedChoice
    {
        public RespondedDataBody message;
        public string finish_reason;
        public int index;
    }
    [Serializable]
    public class RespondedDataBody
    {
        public string role;
        public string content;
    }





}

ChatPanel

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.UI;
/// <summary>
/// 對話面板
/// </summary>
public class ChatPanel : MonoBehaviour
{
    //說話者的名字
    Text speakerName;
    //對話內(nèi)容
    Text content;
    //對話框
    InputField inputField;
    //發(fā)送按鈕
    Button sendButton;
    //關(guān)閉按鈕
    Button closeButton;

    //逐字顯示的協(xié)程
    Coroutine verbatimCoroutine;

    //逐字顯示時撤蚊,每兩個字之間的間隔時間。
    public float verbatimIntervalTime=0.1f;

    //角色的名字
    public string characterName="拉媞琺";

    int thinkingAnimationIndex = 0;

    void Awake()
    {
        //獲取引用
        speakerName = transform.Find("BG/SpeakerName").GetComponent<Text>();
        content = transform.Find("BG/Content").GetComponent<Text>();
        inputField = transform.Find("BG/InputField").GetComponent<InputField>();
        sendButton = transform.Find("BG/SendButton").GetComponent<Button>();
        closeButton = transform.Find("BG/CloseButton").GetComponent<Button>();

        //添加按鈕事件
        sendButton.onClick.AddListener(Send);
        closeButton.onClick.AddListener(Close);
    }

    //關(guān)閉對話面板
    void Close()
    {
        gameObject.SetActive(false);

        FirstPersonController firstPersonController = FindObjectOfType<FirstPersonController>();
        if (firstPersonController!=null)
        {
            firstPersonController.enabled = true;
        }

        Cursor.lockState = CursorLockMode.Locked;

        FindObjectOfType<CheckTalkTip>().isTalking = false;
    }

    void Send()
    {
        //清空上一次的對話內(nèi)容损话。
        ShowDialogue("");

        //取消發(fā)送按鈕的互動
        sendButton.interactable = false;

        //每隔0.5秒顯示一次“對方正在思考中”侦啸。
        InvokeRepeating("Thinking", 0.5f, 0.5f);

        //與ChatGPT交互
        ChatGptManager.Instance.ChatContinuously(inputField.text, (content) => {

            //思考結(jié)束,取消“對方正在思考中”的顯示丧枪。
            CancelInvoke();
            thinkingAnimationIndex = 0;

            ShowDialogue(characterName, content);
            sendButton.interactable = true;
        });

        //清空輸入框
        inputField.text = "";
    }
    
    void Thinking()
    {
        if (thinkingAnimationIndex==0)
        {
            content.text = "對方正在思考中.";
            thinkingAnimationIndex += 1;
        }else if (thinkingAnimationIndex == 1)
        {
            content.text = "對方正在思考中..";
            thinkingAnimationIndex += 1;
        }
        else
        {
            content.text = "對方正在思考中...";
            thinkingAnimationIndex = 0;
        }
    }







    /// <summary>
    /// 顯示對話
    /// </summary>
    /// <param name="speakerName">說話者的名字</param>
    /// <param name="content">對話內(nèi)容</param>
    public void ShowDialogue(string speakerName,string content,bool isVerbatim=true)
    {
        this.speakerName.text = speakerName;

        if (!isVerbatim)
        {
            this.content.text = content;
        }
        else
        {
            //清空上一次的對話內(nèi)容
            this.content.text = "";

            //關(guān)閉上一次的協(xié)程
            if (verbatimCoroutine!=null)
                StopCoroutine(verbatimCoroutine);

            //開啟逐字顯示的協(xié)程
            verbatimCoroutine=StartCoroutine(VerbatimCoroutine(content));
        }
    }


    /// <summary>
    /// 顯示對話(旁白)
    /// </summary>
    /// <param name="content">對話內(nèi)容</param>
    public void ShowDialogue(string content, bool isVerbatim = true)
    {
        ShowDialogue("", content, isVerbatim);
    }

    //逐字顯示對話內(nèi)容
    IEnumerator VerbatimCoroutine(string content)
    {
        //暫時等待1幀光涂,用于跳過外部,把協(xié)程記錄起來拧烦。
        yield return null;

        //記錄當前顯示到哪一個字
        int letter = 0;

        //開始逐字顯示
        while (letter<content.Length)
        {
            //讀到一個小于號忘闻,就判斷它是不是富文本的標簽。
            if (content[letter]=='<')
            {
                //截取第一個<號及其后面的內(nèi)容作為子字符串
                string remainingString = content.Substring(letter);

                //獲取子字符串中開始標簽的長度屎篱。
                int startTagLength= remainingString.IndexOf('>') + 1;

                if (startTagLength!=0)
                {
                    //截取<號和>號及其之間的內(nèi)容服赎,用于判斷是不是開始標簽。
                    string startTag=remainingString.Substring(0, startTagLength);

                    if (startTag == "<b>")
                    {
                        //結(jié)束標簽交播。
                        string endTag = "</b>";

                        //獲取結(jié)束標簽的<號的索引
                        int endTagIndex = remainingString.Substring(startTagLength).IndexOf(endTag);

                        if (endTagIndex != -1)
                        {
                            //截取第一個>號及其之后的字符串
                            string tempString = remainingString.Substring(startTagLength);

                            //真正的字符串的內(nèi)容
                            string stringContent = tempString.Substring(0, endTagIndex);

                            //顯示文本
                            this.content.text += $"<b>{stringContent}</b>";
                            letter += startTagLength + stringContent.Length + endTag.Length;
                            yield return new WaitForSeconds(verbatimIntervalTime);
                            continue;
                        }
                    }
                    else if (startTag == "<i>")
                    {
                        //結(jié)束標簽重虑。
                        string endTag = "</i>";

                        //獲取結(jié)束標簽的<號的索引
                        int endTagIndex = remainingString.Substring(startTagLength).IndexOf(endTag);

                        if (endTagIndex != -1)
                        {
                            //截取第一個>號及其之后的字符串
                            string tempString = remainingString.Substring(startTagLength);

                            //真正的字符串的內(nèi)容
                            string stringContent = tempString.Substring(0, endTagIndex);

                            //顯示文本
                            this.content.text += $"<i>{stringContent}</i>";
                            letter += startTagLength + stringContent.Length + endTag.Length;
                            yield return new WaitForSeconds(verbatimIntervalTime);
                            continue;
                        }
                    }else if (startTag.StartsWith("<size")&&startTag.EndsWith(">") )
                    {
                        //結(jié)束標簽。
                        string endTag = "</size>";

                        //截取=號后面的值(不包括=號秦士,也不包括后面的>號)
                        string value = startTag.Substring(startTag.IndexOf('=') + 1).TrimEnd('>');

                        //開始標簽之后的字符串
                        string tempString = remainingString.Substring(startTagLength);

                        //結(jié)束標簽的<號的索引
                        int endTagIndex=tempString.IndexOf(endTag);

                        if (endTagIndex!=-1)
                        {
                            //獲取標簽包裹的文本內(nèi)容
                            string stringContent = tempString.Substring(0, endTagIndex);

                            //顯示文本
                            this.content.text += $"<size={value}>{stringContent}</size>";
                            letter += startTagLength + stringContent.Length + endTag.Length;
                            yield return new WaitForSeconds(verbatimIntervalTime);
                            continue;
                        }
                    }
                    else if (startTag.StartsWith("<color") && startTag.EndsWith(">"))
                    {
                        //結(jié)束標簽缺厉。
                        string endTag = "</color>";

                        //截取=號后面的值(不包括=號,也不包括后面的>號)
                        string value = startTag.Substring(startTag.IndexOf('=') + 1).TrimEnd('>');

                        //開始標簽之后的字符串
                        string tempString = remainingString.Substring(startTagLength);

                        //結(jié)束標簽的<號的索引
                        int endTagIndex = tempString.IndexOf(endTag);

                        if (endTagIndex != -1)
                        {
                            //獲取標簽包裹的文本內(nèi)容
                            string stringContent = tempString.Substring(0, endTagIndex);

                            //顯示文本
                            this.content.text += $"<color={value}>{stringContent}</color>";
                            letter += startTagLength + stringContent.Length + endTag.Length;
                            yield return new WaitForSeconds(verbatimIntervalTime);
                            continue;
                        }
                    }
                    else
                    {
                        this.content.text += content[letter];
                        letter++;
                        yield return new WaitForSeconds(verbatimIntervalTime);
                        continue;
                    }
                }
            }

            this.content.text += content[letter];
            letter++;
            yield return new WaitForSeconds(verbatimIntervalTime);
        }



    }

}

CheckTalkTip

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 檢測對話的提示
/// </summary>
public class CheckTalkTip : MonoBehaviour
{

    public GameObject talkTipPanel;

    public GameObject chatPanel;

    public KeyCode chatPanelKeyCode = KeyCode.F;

    public float rayCastDistance = 5f;

    RaycastHit hitInfo;

    Vector3 origin;

    public bool isTalking = false;

    void Update()
    {
        //如果已經(jīng)進入了對話的狀態(tài),則直接返回提针。
        if (isTalking) return;

        origin.Set(Screen.width / 2, Screen.height / 2, 0);

        if (Physics.Raycast(Camera.main.ScreenPointToRay(origin),out hitInfo, rayCastDistance, 1<<6))
        {
            talkTipPanel.SetActive(true);

            if (Input.GetKeyDown(chatPanelKeyCode))
            {
                //進入了對話的狀態(tài)
                isTalking = true;

                //讓NPC瞬間朝向我們玩家
                hitInfo.transform.LookAt(transform);
                hitInfo.transform.eulerAngles = new Vector3(0, hitInfo.transform.eulerAngles.y, hitInfo.transform.eulerAngles.z);

                //禁用交談的提示的面板
                talkTipPanel.SetActive(false);

                //顯示對話面板
                chatPanel.SetActive(true);

                //把玩家的移動給禁止掉
                FirstPersonController firstPersonController = FindObjectOfType<FirstPersonController>();
                if (firstPersonController != null)
                {
                    firstPersonController.enabled = false;
                }

                //解放鼠標的光標
                Cursor.lockState = CursorLockMode.None;

                //顯示NPC說得話
                chatPanel.GetComponent<ChatPanel>().ShowDialogue("拉媞琺","主人命爬,你有什么吩咐嗎?都可以向我提問哦~");

            }
        }
        else
        {
            talkTipPanel.SetActive(false);
        }
    }

}

DialoguePanel

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.UI;
/// <summary>
/// 對話面板
/// </summary>
public class DialoguePanel : MonoBehaviour
{
    //說話者的名字
    Text speakerName;
    //對話內(nèi)容
    Text content;
    //對話框
    InputField inputField;
    //發(fā)送按鈕
    Button sendButton;

    //逐字顯示的協(xié)程
    Coroutine verbatimCoroutine;

    //逐字顯示時辐脖,每兩個字之間的間隔時間饲宛。
    public float verbatimIntervalTime=0.1f;

    void Awake()
    {
        //獲取引用
        speakerName = transform.Find("BG/SpeakerName").GetComponent<Text>();
        content = transform.Find("BG/Content").GetComponent<Text>();
        inputField = transform.Find("BG/InputField").GetComponent<InputField>();
        sendButton = transform.Find("BG/SendButton").GetComponent<Button>();

        //添加按鈕事件
        sendButton.onClick.AddListener(Send);
    }

    void Send()
    {
        //清空上一次的對話內(nèi)容。
        ShowDialogue("");

        //取消發(fā)送按鈕的互動
        sendButton.interactable = false;

        //與ChatGPT交互
        ChatGptManager.Instance.ChatContinuously(inputField.text, (content) => {
            ShowDialogue("<color=green>小青</color>", content);
            sendButton.interactable = true;
        });

        //清空輸入框
        inputField.text = "";
    }
    
    /// <summary>
    /// 顯示對話
    /// </summary>
    /// <param name="speakerName">說話者的名字</param>
    /// <param name="content">對話內(nèi)容</param>
    public void ShowDialogue(string speakerName,string content,bool isVerbatim=true)
    {
        this.speakerName.text = speakerName;

        if (!isVerbatim)
        {
            this.content.text = content;
        }
        else
        {
            //清空上一次的對話內(nèi)容
            this.content.text = "";

            //關(guān)閉上一次的協(xié)程
            if (verbatimCoroutine!=null)
                StopCoroutine(verbatimCoroutine);

            //開啟逐字顯示的協(xié)程
            verbatimCoroutine=StartCoroutine(VerbatimCoroutine(content));
        }
    }


    /// <summary>
    /// 顯示對話(旁白)
    /// </summary>
    /// <param name="content">對話內(nèi)容</param>
    public void ShowDialogue(string content, bool isVerbatim = true)
    {
        ShowDialogue("", content, isVerbatim);
    }

    //逐字顯示對話內(nèi)容
    IEnumerator VerbatimCoroutine(string content)
    {
        //暫時等待1幀嗜价,用于跳過外部艇抠,把協(xié)程記錄起來。
        yield return null;

        //記錄當前顯示到哪一個字
        int letter = 0;

        //開始逐字顯示
        while (letter<content.Length)
        {
            //讀到一個小于號久锥,就判斷它是不是富文本的標簽家淤。
            if (content[letter]=='<')
            {
                //截取第一個<號及其后面的內(nèi)容作為子字符串
                string remainingString = content.Substring(letter);

                //獲取子字符串中開始標簽的長度。
                int startTagLength= remainingString.IndexOf('>') + 1;

                if (startTagLength!=0)
                {
                    //截取<號和>號及其之間的內(nèi)容瑟由,用于判斷是不是開始標簽絮重。
                    string startTag=remainingString.Substring(0, startTagLength);

                    if (startTag == "<b>")
                    {
                        //結(jié)束標簽。
                        string endTag = "</b>";

                        //獲取結(jié)束標簽的<號的索引
                        int endTagIndex = remainingString.Substring(startTagLength).IndexOf(endTag);

                        if (endTagIndex != -1)
                        {
                            //截取第一個>號及其之后的字符串
                            string tempString = remainingString.Substring(startTagLength);

                            //真正的字符串的內(nèi)容
                            string stringContent = tempString.Substring(0, endTagIndex);

                            //顯示文本
                            this.content.text += $"<b>{stringContent}</b>";
                            letter += startTagLength + stringContent.Length + endTag.Length;
                            yield return new WaitForSeconds(verbatimIntervalTime);
                            continue;
                        }
                    }
                    else if (startTag == "<i>")
                    {
                        //結(jié)束標簽歹苦。
                        string endTag = "</i>";

                        //獲取結(jié)束標簽的<號的索引
                        int endTagIndex = remainingString.Substring(startTagLength).IndexOf(endTag);

                        if (endTagIndex != -1)
                        {
                            //截取第一個>號及其之后的字符串
                            string tempString = remainingString.Substring(startTagLength);

                            //真正的字符串的內(nèi)容
                            string stringContent = tempString.Substring(0, endTagIndex);

                            //顯示文本
                            this.content.text += $"<i>{stringContent}</i>";
                            letter += startTagLength + stringContent.Length + endTag.Length;
                            yield return new WaitForSeconds(verbatimIntervalTime);
                            continue;
                        }
                    }else if (startTag.StartsWith("<size")&&startTag.EndsWith(">") )
                    {
                        //結(jié)束標簽青伤。
                        string endTag = "</size>";

                        //截取=號后面的值(不包括=號,也不包括后面的>號)
                        string value = startTag.Substring(startTag.IndexOf('=') + 1).TrimEnd('>');

                        //開始標簽之后的字符串
                        string tempString = remainingString.Substring(startTagLength);

                        //結(jié)束標簽的<號的索引
                        int endTagIndex=tempString.IndexOf(endTag);

                        if (endTagIndex!=-1)
                        {
                            //獲取標簽包裹的文本內(nèi)容
                            string stringContent = tempString.Substring(0, endTagIndex);

                            //顯示文本
                            this.content.text += $"<size={value}>{stringContent}</size>";
                            letter += startTagLength + stringContent.Length + endTag.Length;
                            yield return new WaitForSeconds(verbatimIntervalTime);
                            continue;
                        }
                    }
                    else if (startTag.StartsWith("<color") && startTag.EndsWith(">"))
                    {
                        //結(jié)束標簽殴瘦。
                        string endTag = "</color>";

                        //截取=號后面的值(不包括=號潮模,也不包括后面的>號)
                        string value = startTag.Substring(startTag.IndexOf('=') + 1).TrimEnd('>');

                        //開始標簽之后的字符串
                        string tempString = remainingString.Substring(startTagLength);

                        //結(jié)束標簽的<號的索引
                        int endTagIndex = tempString.IndexOf(endTag);

                        if (endTagIndex != -1)
                        {
                            //獲取標簽包裹的文本內(nèi)容
                            string stringContent = tempString.Substring(0, endTagIndex);

                            //顯示文本
                            this.content.text += $"<color={value}>{stringContent}</color>";
                            letter += startTagLength + stringContent.Length + endTag.Length;
                            yield return new WaitForSeconds(verbatimIntervalTime);
                            continue;
                        }
                    }
                    else
                    {
                        this.content.text += content[letter];
                        letter++;
                        yield return new WaitForSeconds(verbatimIntervalTime);
                        continue;
                    }
                }
            }

            this.content.text += content[letter];
            letter++;
            yield return new WaitForSeconds(verbatimIntervalTime);
        }



    }

}

三、效果展示

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末痴施,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子究流,更是在濱河造成了極大的恐慌辣吃,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芬探,死亡現(xiàn)場離奇詭異神得,居然都是意外死亡,警方通過查閱死者的電腦和手機偷仿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門哩簿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酝静,你說我怎么就攤上這事节榜。” “怎么了别智?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵宗苍,是天一觀的道長。 經(jīng)常有香客問我,道長讳窟,這世上最難降的妖魔是什么让歼? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮丽啡,結(jié)果婚禮上谋右,老公的妹妹穿的比我還像新娘。我一直安慰自己补箍,他們只是感情好改执,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著馏予,像睡著了一般天梧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上霞丧,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天呢岗,我揣著相機與錄音,去河邊找鬼蛹尝。 笑死后豫,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的突那。 我是一名探鬼主播梁棠,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼趁舀!你這毒婦竟也來了妻柒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤猫缭,失蹤者是張志新(化名)和其女友劉穎葱弟,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猜丹,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡芝加,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了射窒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藏杖。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖脉顿,靈堂內(nèi)的尸體忽然破棺而出蝌麸,到底是詐尸還是另有隱情,我是刑警寧澤弊予,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布祥楣,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏误褪。R本人自食惡果不足惜责鳍,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望兽间。 院中可真熱鬧历葛,春花似錦、人聲如沸嘀略。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帜羊。三九已至咒程,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間讼育,已是汗流浹背帐姻。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奶段,地道東北人饥瓷。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像痹籍,于是被迫代替她去往敵國和親呢铆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容