Unity之ConversaDialogueSystem 对话插件使用

news2024/10/7 16:23:04

一、插件下载或购买

官方购买地址:Conversa Dialogue System | 可视化脚本 | Unity Asset Store

百度网盘地址:提取码:syq1

此插件没有官方案例,插件作者也明确回复说后期不会出教程,所以此教程根据插件demo案例进行学习总结,所以有问题欢迎大家留言指正。

二、Demo介绍

导入插件后 可以在Conversa—>Demo文件下找到官方的实例场景。运行可以看到效果,我们接下来就根据官方demo教程来逐步分解学习

三、Lineardialogue节点(线性人物对话)

学习使用Linear dialogue节点和其Actor、Message子节点完成一次简单的对话。

需要实现的效果如下: 

 1.新建Canversation资源文件

双击打开可以看到内部有一个名为Start的bookmark标签节点,这个节点代表一个功能模块的入口,通此节点进行一段话的开始。

2.创建LinearDialogue节点

LinearDialogue 线性对话节点是用来按照顺序执行对话的。

LinearDialogue 线性对话节点内部只能创建简单的Actor和Message。交叉使用可以完成人物和对话的结合

3.创建人物属性Actor资源文件

我们使用Actor profile节点,需要创建并配置人物资源。这里我们创建一个player(玩家)和Merchant(商人)两个人物资源。

 player玩家

Merchant商人

 4.制作线性人物对话

5.制作UI界面

 制作一个播放对话的按钮和展示人物对话的界面

按钮: 

人物对话的界面

6.脚本控制对话

ConversationControllerTest.cs 

using Conversa.Runtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using Conversa.Runtime.Events;
using Conversa.Runtime.Interfaces;
using Conversa.Demo.Scripts;

//对话控制器
public class ConversationControllerTest : MonoBehaviour
{
    ConversationRunner runner; //控制谈话执行

    [SerializeField] Conversation conversation;//对话类
    [SerializeField] UIControllerTest uiController; //ui控制器

    [Header("按钮")]
    [SerializeField]  Button restartConversationButton;  //重启对话按钮



    void Start()
    {
        runner = new ConversationRunner(conversation);//实例化
        runner.OnConversationEvent.AddListener(HandleConversationEvent); //所有谈话事件监听

        //添加按钮监听
        restartConversationButton.onClick.AddListener(HandleRestartConversation);
    }

    void Update()
    {
        
    }

    #region 处理会话事件
    private void HandleConversationEvent(IConversationEvent e)
    {
        Debug.Log(e.ToString());
        switch (e)
        {
            case MessageEvent messageEvent:
                break;
            case ChoiceEvent choiceEvent:
                break;
            case ActorMessageEvent actorMessageEvent:
                //Debug.Log("actorMessage节点调用");
                //使用LinearDialogue节点将Actor和Message结合所以是actorMessageEvent事件
                HandleActorMessageEvent(actorMessageEvent); 

                break;
            case ActorChoiceEvent actorChoiceEvent:
                break;
            case UserEvent userEvent:
                break;
            case EndEvent _:
                HandleEnd();
                break;
        }
    }

    #endregion

    #region 事件触发函数

    //Actor Message触发
    private void HandleActorMessageEvent(ActorMessageEvent evt)
    {
        //Debug.Log("3.触发Actor Message节点事件");

        var actorDisplayName = evt.Actor == null ? "" : evt.Actor.DisplayName;//获取人物名称

        //判断evt.Actor是否是带头像的Actor
        if (evt.Actor is AvatarActor avatarActor)
            uiController.ShowMessage(actorDisplayName, evt.Message, avatarActor.Avatar, evt.Advance); //执行有头像的方法
        else
            uiController.ShowMessage(actorDisplayName, evt.Message, null, evt.Advance);//执行没有头像的方法
    }

    //HandleEnd触发
    private void HandleEnd()
    {
        Debug.Log("没有事件触发了,隐藏ui");
        uiController.Hide();
    }
    #endregion


    #region 按钮监听
    private void HandleRestartConversation()
    {
        Debug.Log("开始谈话");
        runner.Begin();//执行start节点
    }
  
    #endregion

}

UIControllerTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System;

//UI控制器
public class UIControllerTest : MonoBehaviour
{
    [Header("信息面板")]
    [SerializeField] private GameObject messageWindow; //信息ui窗口
    [SerializeField] private Image avatarImage;//人物头像
    [SerializeField] private Text actorNameText;//人物名称
    [SerializeField] private Text messageText; //文字信息
    [SerializeField] private Button nextMessageButton;//下一条信息按钮


    #region 显示信息面板
    /// <summary>
    /// 显示信息面板
    /// </summary>
    /// <param name="actor">人物名称</param>
    /// <param name="message">要说的话</param>
    /// <param name="avatar">人物头像</param>
    /// <param name="onContinue">下一步要执行的行为</param>
    public void ShowMessage(string actor, string message, Sprite avatar, Action onContinue)
    {
        //显示信息面板
        messageWindow.SetActive(true);

        //更新头像 人物名称和要说的话
        UpdateImage(avatar);
        actorNameText.text = actor;
        messageText.text = message;

        //下一步按钮监听
        nextMessageButton.enabled = true;
        nextMessageButton.onClick.RemoveAllListeners();
        nextMessageButton.onClick.AddListener(() => onContinue());
    }

    //更新人物头像图片
    private void UpdateImage(Sprite sprite)
    {
        avatarImage.enabled = sprite != null;//如果为空不执行
        avatarImage.sprite = sprite;//更换图片
    }
    #endregion

    public void Hide()
    {
        messageWindow.SetActive(false);
    }
}

 将以上脚本拖入ConversationControllerTest并进行如下设置

四、 Choice选择节点

我们上一节进入商店之后就要出现多种选择项,根据不同选择项我们可以进行不同的对话和行为的操作。使用Choice节点实现选择项。

不过这里我们先根据选项创建出对应的按钮,至于按钮点击后的操作行为后面在详细解说

实现效果:

1.添加并设置Choice选择节点

2.设置choice选择UI面板

 选项面板中的按钮是根据choice节点的选项自动生成的,这里我们直接使用Conversa—>Prefabs文件下的Choice option button预设体即可

3.脚本修改

添加choiceEvent需要触发的事件函数

ConversationControllerTest.cs 

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System;
using Conversa.Runtime.Events;

//UI控制器
public class UIControllerTest : MonoBehaviour
{
    [Header("信息面板")]
    [SerializeField] private GameObject messageWindow; //信息ui窗口
    [SerializeField] private Image avatarImage;//人物头像
    [SerializeField] private Text actorNameText;//人物名称
    [SerializeField] private Text messageText; //文字信息
    [SerializeField] private Button nextMessageButton;//下一条信息按钮

    [Header("选择面板")]
    [SerializeField] private GameObject choiceWindow; //对话选择ui
    [SerializeField] private GameObject choiceOptionButtonPrefab;//选择单选按钮预设体



    #region 显示信息面板
    /// <summary>
    /// 显示信息面板
    /// </summary>
    /// <param name="actor">人物名称</param>
    /// <param name="message">要说的话</param>
    /// <param name="avatar">人物头像</param>
    /// <param name="onContinue">下一步要执行的行为</param>
    public void ShowMessage(string actor, string message, Sprite avatar, Action onContinue)
    {
        //显示信息面板
        choiceWindow.SetActive(false);
        messageWindow.SetActive(true);

        //更新头像 人物名称和要说的话
        UpdateImage(avatar);
        actorNameText.text = actor;
        messageText.text = message;

        //下一步按钮监听
        nextMessageButton.enabled = true;
        nextMessageButton.onClick.RemoveAllListeners();
        nextMessageButton.onClick.AddListener(() => onContinue());
    }

    //更新人物头像图片
    private void UpdateImage(Sprite sprite)
    {
        avatarImage.enabled = sprite != null;//如果为空不执行
        avatarImage.sprite = sprite;//更换图片
    }
    #endregion

    #region 显示选择面板
    /// <summary>
    /// 显示选择面板
    /// </summary>
    /// <param name="actor">人物名称</param>
    /// <param name="message">要说的话</param>
    /// <param name="avatar">人物头像</param>
    /// <param name="options">选项</param>
    public void ShowChoice(string actor, string message, Sprite avatar, List<Option> options)
    {
        //显示选择面板
        messageWindow.SetActive(true);
        choiceWindow.SetActive(true);

        //更新头像 人物名称和要说的话
        UpdateImage(avatar);
        actorNameText.text = actor;
        messageText.text = message;
        nextMessageButton.enabled = false; //下一步按钮禁用

        //清除选择面板的所有子物体
        foreach (Transform child in choiceWindow.transform)
            Destroy(child.gameObject);

        //设置列表的每一个选项
        options.ForEach(option =>
        {
            //创建选项物体 设置信息和点击事件
            var instance = Instantiate(choiceOptionButtonPrefab, Vector3.zero, Quaternion.identity);
            instance.transform.SetParent(choiceWindow.transform);
            instance.GetComponentInChildren<Text>().text = option.Message;
            instance.GetComponent<Button>().onClick.AddListener(() => option.Advance());
        });
    }
    #endregion

    public void Hide()
    {
        messageWindow.SetActive(false);
        choiceWindow.SetActive(false);
    }
}

 UIControllerTest.cs

using Conversa.Runtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using Conversa.Runtime.Events;
using Conversa.Runtime.Interfaces;
using Conversa.Demo.Scripts;

//对话控制器
public class ConversationControllerTest : MonoBehaviour
{
    ConversationRunner runner; //控制谈话执行

    [SerializeField] Conversation conversation;//对话类
    [SerializeField] UIControllerTest uiController; //ui控制器

    [Header("按钮")]
    [SerializeField]  Button restartConversationButton;  //重启对话按钮



    void Start()
    {
        runner = new ConversationRunner(conversation);//实例化
        runner.OnConversationEvent.AddListener(HandleConversationEvent); //所有谈话事件监听

        //添加按钮监听
        restartConversationButton.onClick.AddListener(HandleRestartConversation);
    }

    void Update()
    {
        
    }

    #region 处理会话事件
    private void HandleConversationEvent(IConversationEvent e)
    {
        Debug.Log(e.ToString());
        switch (e)
        {
            case MessageEvent messageEvent:
                break;
            case ChoiceEvent choiceEvent:
                break;
            case ActorMessageEvent actorMessageEvent:
                //使用LinearDialogue节点将Actor和Message结合所以是actorMessageEvent事件
                HandleActorMessageEvent(actorMessageEvent); 

                break;
            case ActorChoiceEvent actorChoiceEvent:
                HandleActorChoiceEvent(actorChoiceEvent);

                break;
            case UserEvent userEvent:
                break;
            case EndEvent _:
                HandleEnd();
                break;
        }
    }

    #endregion

    #region 事件触发函数

    //Actor Message触发
    private void HandleActorMessageEvent(ActorMessageEvent evt)
    {
        //Debug.Log("3.触发Actor Message节点事件");

        var actorDisplayName = evt.Actor == null ? "" : evt.Actor.DisplayName;//获取人物名称

        //判断evt.Actor是否是带头像的Actor
        if (evt.Actor is AvatarActor avatarActor)
            uiController.ShowMessage(actorDisplayName, evt.Message, avatarActor.Avatar, evt.Advance); //执行有头像的方法
        else
            uiController.ShowMessage(actorDisplayName, evt.Message, null, evt.Advance);//执行没有头像的方法
    }

    //Actor Choice事件
    private void HandleActorChoiceEvent(ActorChoiceEvent evt)
    {

        //Debug.Log("4.触发Actor Choice事件");

        var actorDisplayName = evt.Actor == null ? "" : evt.Actor.DisplayName;
        if (evt.Actor is AvatarActor avatarActor)
            uiController.ShowChoice(actorDisplayName, evt.Message, avatarActor.Avatar, evt.Options);
        else
            uiController.ShowChoice(actorDisplayName, evt.Message, null, evt.Options);
    }

    //HandleEnd触发
    private void HandleEnd()
    {
        Debug.Log("没有事件触发了,隐藏ui");
        uiController.Hide();
    }
    #endregion


    #region 按钮监听
    private void HandleRestartConversation()
    {
        Debug.Log("开始谈话");
        runner.Begin();//执行start节点
    }
  
    #endregion

}

脚本属性进行如下设置 

五、“检查钱包”对话模块

购买食物对话模块会牵扯到其他模块,所以这里先从“检查钱包”模块制作。所有的对话模块开始和跳跃都需要用到bookmark标签节点。

例如:我们上方选择节点有四个选项,我们单独制作这四个选项的功能就需要使用bookmark标签节点进行两者的连接。

检查钱包对话模块效果:

1.创建名为“检查钱包”的bookmark标签节点。

2.创建float属性用于记录钱包中钱的数量

3.使用Parse分析节点将文本语言和“当前金币”整合

message节点无法将要写的文本语言和创建的属性节点进行结合,所以需要使用Parse分析节点进行组合。

我们最后输入“我有${0}”其中{}是占位符,0代表使用Parse分析节点的第0个参数(当前金币)

4.使用Advancedmessage高级信息节点整合人物属性和Parse分析节点输出的文本信息

上一步我们把要说的对话整合了,如果使用message你会发现此节点没有办法接收整合信息。所以这里我们采用Advancedmessage高级信息节点。

5.使用跳转节点跳转到“选择”标签节点

对话模块结束后重新跳转到choice选择节点,在第一步我们知道了bookmark标签节点的作用。这是我们使用此节点完成对话模块的跳转

 先给创建“选择”标签节点

节点跳转

这样就可以完成一个闭环,  “检查钱包”模块结束后跳转到“选择标签”,而“选择标签”指向“Choice”选择节点。

6.最后给 “检查钱包”模块打个组

将“检查钱包”模块中的所有节点进行组合并注释,这样方便移动操作和理解此模块的作用。

但是使用组需要注意一些问题,可以查看注意选项进行详细了解。

六、“购买食物”对话模块

“购买食物”对话模块较为复杂,分为购买成功购买失败两个功能分支。

1.创建属性“食物价格”并使用CompareNumber进行数值对比

 CompareNumber“比较节点”的GreatOrEqual代表A>=B时输出true。然后使用Brabch分支将两个进行分割

2. 使用Brabch分支节点创建True逻辑对话

True代表食物购买成功需要进行如下几步操作:

  • 神秘商人进行购买成功的对话提示
  • 计算“当前金币”减去“食物价格”后所生的金币数量。
  • 将计算值赋值给“当前金币”属性。
  • 创建名为“购买了食物”的bool属性并修改为true,用于判断防止下次重复购买。
  • 创建名为“更新所有金币价格”的Event属性,用于通知脚本修改场景中UI文字价格
  • 最后跳转到“Start”标签节点

①使用Message节点设置购买成功的对话提示

 ②使用Subtract节点计算购买后的金币价格

③使用Setproperty修改“当前金币”属性

④ 创建名为“购买了食物”的bool属性并修改为true

购买成功后使用一个bool值属性存储,用于判断防止下次重复购买。

⑤创建名为“更新所有金币价格”的Event属性 

后面场景中会添加食物价格、我的金币和钱包金币三个uiText,通过调用此事件来动态修改场景中的uiText值

⑥最后跳转到“Start”标签节点 

七、False分支的 “购买失败”对话模块

  • 添加“购买失败”bookMark节点
  • 计算“食物价格”减去“当前金币”的差价
  • 使用Parse节点将文本语言和差价整合
  • 使用AdvancedMessage节点将Parse节点语言与Actor整合
  • 最后跳转到“选择”标签

1. 购买失败分支图

2.“购买食物”添加跳转“购买失败”和“购买食物”标签

八、“搜索口袋”对话模块

九、 设置choice选择节点所有选项的跳转标签

十、最终UI界面布局

更新保存点:用于保存当前对话执行的位置

从保存点加载:从上一次保存的对话位置处开始执行

十一、最终脚本

ConversationControllerTest.cs

using Conversa.Runtime;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using Conversa.Runtime.Events;
using Conversa.Runtime.Interfaces;
using Conversa.Demo.Scripts;

//对话控制器
public class ConversationControllerTest : MonoBehaviour
{
    ConversationRunner runner; //控制谈话执行
    private string savepointGuid = string.Empty; //保存点指南

    [SerializeField] Conversation conversation;//对话类
    [SerializeField] UIControllerTest uiController; //ui控制器

    [Header("按钮")]
    [SerializeField]  Button restartConversationButton;  //重启对话按钮
    [SerializeField] private Button updateSavepointButton;      //更新保存点按钮
    [SerializeField] private Button loadSavepointButton;        //加载保存点按钮

    [Header("食物、当前金币、钱包")]
    [SerializeField] float foodMoney = 100;
    [SerializeField] float currentMoney = 20;
    [SerializeField] float walletMoney = 40;

    [Header("食物、当前金币、钱包文本")]
    [SerializeField] Text foodMoneyTxt;
    [SerializeField] Text currentMoneyTxt;
    [SerializeField] Text walletMoneyTxt;



    void Start()
    {
        runner = new ConversationRunner(conversation);//实例化
        runner.OnConversationEvent.AddListener(HandleConversationEvent); //所有谈话事件监听

        //添加按钮监听
        restartConversationButton.onClick.AddListener(HandleRestartConversation);
        updateSavepointButton.onClick.AddListener(HandleUpdateSavepoint);
        loadSavepointButton.onClick.AddListener(HandleLoadSavepoint);
        updateSavepointButton.interactable = false;//更新保存点按钮默认不执行

        SetUIMoneyNumber();
    }

    void Update()
    {
        
    }

    #region 处理会话事件
    private void HandleConversationEvent(IConversationEvent e)
    {
        Debug.Log(e.ToString());
        switch (e)
        {
            case MessageEvent messageEvent:
                HandleMessage(messageEvent);

                break;
            case ChoiceEvent choiceEvent:
                HandleChoice(choiceEvent);

                break;
            case ActorMessageEvent actorMessageEvent:
                //使用LinearDialogue节点将Actor和Message结合所以是actorMessageEvent事件
                HandleActorMessageEvent(actorMessageEvent); 

                break;
            case ActorChoiceEvent actorChoiceEvent:
                HandleActorChoiceEvent(actorChoiceEvent);

                break;
            case UserEvent userEvent:
                HandleUserEvent(userEvent);

                break;
            case EndEvent _:
                HandleEnd();
                break;
        }
    }

    #endregion

    #region 事件触发函数

    //Message事件
    //private void HandleMessage(MessageEvent e) => uiController.ShowMessage(e.Actor, e.Message, null, () => e.Advance());
    private void HandleMessage(MessageEvent e)
    {
        //Debug.Log("1.触发Message节点事件");
        uiController.ShowMessage(e.Actor, e.Message, null, () => e.Advance());
    }

    //Choice事件
    //private void HandleChoice(ChoiceEvent e) => uiController.ShowChoice(e.Actor, e.Message, null, e.Options);
    private void HandleChoice(ChoiceEvent e)
    {
        //Debug.Log("2.触发Choice节点事件");
        uiController.ShowChoice(e.Actor, e.Message, null, e.Options);
    }

    //Actor Message触发
    private void HandleActorMessageEvent(ActorMessageEvent evt)
    {
        //Debug.Log("3.触发Actor Message节点事件");

        var actorDisplayName = evt.Actor == null ? "" : evt.Actor.DisplayName;//获取人物名称

        //判断evt.Actor是否是带头像的Actor
        if (evt.Actor is AvatarActor avatarActor)
            uiController.ShowMessage(actorDisplayName, evt.Message, avatarActor.Avatar, evt.Advance); //执行有头像的方法
        else
            uiController.ShowMessage(actorDisplayName, evt.Message, null, evt.Advance);//执行没有头像的方法
    }

    //Actor Choice事件
    private void HandleActorChoiceEvent(ActorChoiceEvent evt)
    {

        //Debug.Log("4.触发Actor Choice事件");

        var actorDisplayName = evt.Actor == null ? "" : evt.Actor.DisplayName;
        if (evt.Actor is AvatarActor avatarActor)
            uiController.ShowChoice(actorDisplayName, evt.Message, avatarActor.Avatar, evt.Options);
        else
            uiController.ShowChoice(actorDisplayName, evt.Message, null, evt.Options);
    }

    //User Event触发
    private void HandleUserEvent(UserEvent userEvent)
    {
        if (userEvent.Name == "更新所有金币价格")
        {
            Debug.Log("更新所有金币价格");
            SetUIMoneyNumber();
        }

    }

    //HandleEnd触发
    private void HandleEnd()
    {
        Debug.Log("没有事件触发了,隐藏ui");
        uiController.Hide();
        updateSavepointButton.interactable = false;

    }
    #endregion


    #region 按钮监听
    private void HandleRestartConversation()
    {
        Debug.Log("开始谈话");
        runner.Begin();//执行start节点
        updateSavepointButton.interactable = true;

    }

    private void HandleUpdateSavepoint()
    {
        //保存当前节点名称。
        savepointGuid = runner.CurrentNodeGuid;
    }

    private void HandleLoadSavepoint()
    {
        //设置从指定节点开始执行
        runner.BeginByGuid(savepointGuid);
    }

    #endregion


    #region 修改ui界面金币数量
    public void SetUIMoneyNumber()
    {
        //修改指定属性的方法
        //runner.SetProperty("食物价格", foodMoney);
        //runner.SetProperty("当前金币", currentMoney);
        //runner.SetProperty("钱包金币", walletMoney);

        //设置uiText
        foodMoneyTxt.text = runner.GetProperty<float>("食物价格").ToString();
        currentMoneyTxt.text = runner.GetProperty<float>("当前金币").ToString();
        walletMoneyTxt.text = runner.GetProperty<float>("钱包金币").ToString();

        Debug.Log($"食物价格:{runner.GetProperty<float>("食物价格")},当前金币:{runner.GetProperty<float>("当前金币")},钱包金币:{runner.GetProperty<float>("钱包金币")}");
    }
    #endregion

}

 UIControllerTest.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine;
using System;
using Conversa.Runtime.Events;

//UI控制器
public class UIControllerTest : MonoBehaviour
{
    [Header("信息面板")]
    [SerializeField] private GameObject messageWindow; //信息ui窗口
    [SerializeField] private Image avatarImage;//人物头像
    [SerializeField] private Text actorNameText;//人物名称
    [SerializeField] private Text messageText; //文字信息
    [SerializeField] private Button nextMessageButton;//下一条信息按钮

    [Header("选择面板")]
    [SerializeField] private GameObject choiceWindow; //对话选择ui
    [SerializeField] private GameObject choiceOptionButtonPrefab;//选择单选按钮预设体



    #region 显示信息面板
    /// <summary>
    /// 显示信息面板
    /// </summary>
    /// <param name="actor">人物名称</param>
    /// <param name="message">要说的话</param>
    /// <param name="avatar">人物头像</param>
    /// <param name="onContinue">下一步要执行的行为</param>
    public void ShowMessage(string actor, string message, Sprite avatar, Action onContinue)
    {
        //显示信息面板
        choiceWindow.SetActive(false);
        messageWindow.SetActive(true);

        //更新头像 人物名称和要说的话
        UpdateImage(avatar);
        actorNameText.text = actor;
        messageText.text = message;

        //下一步按钮监听
        nextMessageButton.enabled = true;
        nextMessageButton.onClick.RemoveAllListeners();
        nextMessageButton.onClick.AddListener(() => onContinue());
    }

    //更新人物头像图片
    private void UpdateImage(Sprite sprite)
    {
        avatarImage.enabled = sprite != null;//如果为空不执行
        avatarImage.sprite = sprite;//更换图片
    }
    #endregion

    #region 显示选择面板
    /// <summary>
    /// 显示选择面板
    /// </summary>
    /// <param name="actor">人物名称</param>
    /// <param name="message">要说的话</param>
    /// <param name="avatar">人物头像</param>
    /// <param name="options">选项</param>
    public void ShowChoice(string actor, string message, Sprite avatar, List<Option> options)
    {
        //显示选择面板
        messageWindow.SetActive(true);
        choiceWindow.SetActive(true);

        //更新头像 人物名称和要说的话
        UpdateImage(avatar);
        actorNameText.text = actor;
        messageText.text = message;
        nextMessageButton.enabled = false; //下一步按钮禁用

        //清除选择面板的所有子物体
        foreach (Transform child in choiceWindow.transform)
            Destroy(child.gameObject);

        //设置列表的每一个选项
        options.ForEach(option =>
        {
            //创建选项物体 设置信息和点击事件
            var instance = Instantiate(choiceOptionButtonPrefab, Vector3.zero, Quaternion.identity);
            instance.transform.SetParent(choiceWindow.transform);
            instance.GetComponentInChildren<Text>().text = option.Message;
            instance.GetComponent<Button>().onClick.AddListener(() => option.Advance());
        });
    }
    #endregion

    public void Hide()
    {
        messageWindow.SetActive(false);
        choiceWindow.SetActive(false);
    }
}

十四、 注意事项

1.修改节点后必须点击Save按钮,快捷键Ctrl+S无用

无保存标识。

2.节点无法进行复制,只能创建

目前版本我们设置好节点模块后,想要复制一份新的进行更改是不允许的,只能一个个重新创建连接。如果复制Conversation数据资源文件的话是可行的。

复制资源Conversation数据资源文件

3.GroupNodes无法删除,并且节点也无法分离

GroupNodes将多个节点包围成组合后,就无法解除组合。并且组合后的节点也无法单独脱离出来。(至少目前没有找到解除的方式,可能作者还没加)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1479512.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT C++实践|超详细数据库的连接和增删改查操作|附源码

0&#xff1a;前言 &#x1faa7; 什么情况需要数据库? 1 大规模的数据需要处理&#xff08;比如上千上万的数据量&#xff09;2 需要把数据信息存储起来&#xff0c;无论是本地还是服务上&#xff0c;而不是断电后数据信息就消失了。 如果不是上面的原因化&#xff0c;一般…

计算机网络-典型网络组网架构

前面基本网络知识已经能够满足中小企业的需要了&#xff0c;今天来看下一些基本网络组网架构。 首先网络是分层架构&#xff0c;从接入层到汇聚层再到核心层&#xff0c;然后接入运营商出口。内部包括有线网络、无线网络&#xff0c;出口一般可以使用路由器或者防火墙进行安全防…

人才测评工具 找准需求才能测出效果

怎么挖掘人才&#xff1f;怎么利用人才&#xff1f; 让每个员工发挥出最大的优势&#xff1f; 让每个员工奋斗在最适合的岗位&#xff1f; 每个HRer都考虑过&#xff0c;每个HR也都有自己独特的见解。 1、找准测评目的 我们都希望测评是全面的&#xff0c;360度的。要是仔…

有多少成年人学英语,全靠八卦和腹肌?

昨个儿&#xff0c;“知识大航海”群里针对老龄化问题讨论的非常激烈。 我挺喜欢这种氛围的&#xff0c;只要大家不将争论上升到地域歧视人身攻击就可以。 很多事情没有绝对的对与错&#xff0c;经过计论&#xff0c;能够开悟心智&#xff0c;这未尝不是一件好事呀。 最后&…

“平民化”非结构数据处理

在全球信息产业高速发展的背景下&#xff0c;IDC预测&#xff0c;2018 到 2025 年之间&#xff0c;全球产生的数据量将会从 33 ZB 增长到 175 ZB&#xff0c; 复合增长率27%&#xff0c;其中超过 80%的数据都会是处理难度较大的非结构化数据&#xff0c;如文档、文本、图形、图…

32-树-在每个树行中找最大值

这是树的第32篇算法&#xff0c;力扣链接。 给定一棵二叉树的根节点 root &#xff0c;请找出该二叉树中每一层的最大值。 示例1&#xff1a; 输入: root [1,3,2,5,3,null,9] 输出: [1,3,9] 层级遍历似乎天生适合解这道题&#xff1a; func largestValues(root *TreeNode) []…

实例:NX二次开发抽取平面以及标准柱面中心线

一、概述 最近体验许多外挂&#xff0c;包括胡波外挂、星空外挂及模圣等都有抽取面的中心线&#xff0c;由于刚刚学习&#xff0c;我尝试看看能不能做出来&#xff0c;本博客代码没有封装函数&#xff0c;代码有待改进&#xff0c;但基本可以实现相应的功能。 二、案例实现的功…

浅谈MySQL的B树索引与索引优化

MySQL的MyISAM、InnoDB引擎默认均使用B树索引&#xff08;查询时都显示为“BTREE”&#xff09;&#xff0c;本文讨论两个问题&#xff1a; 为什么MySQL等主流数据库选择B树的索引结构&#xff1f;如何基于索引结构&#xff0c;理解常见的MySQL索引优化思路&#xff1f; 为什…

火锅底料加工厂污废水如何处理达标排放

火锅底料加工厂作为食品加工行业的一员&#xff0c;其生产过程中不可避免地会产生大量的污废水。为了保护环境和维护公共健康&#xff0c;火锅底料加工厂应当采取措施对污废水进行处理&#xff0c;使其达到国家相关排放标准。那么&#xff0c;火锅底料加工厂污废水该如何处理才…

ffmpeg单张图片生成固定时长的视频

ffmpeg -r 25 -f image2 -loop 1 -i fps_1.jpg -vcodec libx264 -pix_fmt yuv420p -s 1080*1920 -r 25 -t 30 -y fps.mp4这个命令将 fps_1.jpg 图片转换为一个 30 秒长的视频&#xff0c;分辨率为 1920x1080&#xff0c;帧率为 25 帧/秒&#xff0c;并使用 libx264 编码器进行压…

数据中心GPU集群高性能组网技术分析

数据中心GPU集群组网技术是指将多个GPU设备连接在一起&#xff0c;形成一个高性能计算的集群系统。通过集群组网技术&#xff0c;可以实现多个GPU设备之间的协同计算&#xff0c;提供更大规模的计算能力&#xff0c;适用于需要大规模并行计算的应用场景。 常用的组网技术&…

HTML5+CSS3小实例:环绕小球弹性loading动画

实例:环绕小球弹性loading动画 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge&quo…

C# WPF编程-创建项目

1.创建新项目 选择“WPF应用程序”》“下一步” 2. 设置项目 设置项目名称&#xff0c;保存位置等参数>下一步 3.选择框架 4.项目创建成功 5.运行项目

Failed to build tree: parent link [base_link] of joint [lidar_joint] not found

参考&#xff1a; Failed to build tree: parent link [base_link] of joint 在古月居gazebo 的基础教程里&#xff0c;运行古月居的mbot的launch文件报错&#xff0c;小机器人不出现。 主要原因是提供的xacro文件的宏定义没有放在xacro的命名空间。 解决&#xff1a; 将<mb…

网络编程第二天

1.基于TCP的通信(面向连接的通信) 服务器代码实现&#xff1a; #include <myhead.h> #define IP "192.168.126.91" #define PORT 9999 int main(int argc, const char *argv[]) {//1、创建套接字int sfd-1;if((sfdsocket(AF_INET,SOCK_STREAM,0))-1){perror(…

ROS 2基础概念#2:节点(Node)| ROS 2学习笔记

ROS 2节点简介 节点是执行计算的进程。节点组合在一起形成一个图&#xff08;graph&#xff09;&#xff0c;并使用主题&#xff08;topic&#xff09;、服务&#xff08;service&#xff09;和参数服务器&#xff08;paramter server&#xff09;相互通信。这些节点旨在以细粒…

Ps:路径面板

Ps菜单&#xff1a;窗口/路径 Window/Paths “路径”面板 Paths Panel提供了一系列功能&#xff0c;使用户能够创建、编辑、保存和利用路径。 ◆ ◆ ◆ 路径分类 在“路径”面板上的路径可分为五大类。 常规路径 Saved Path 也称“已保存的路径”&#xff0c;指的是已经存储在…

【三维重建】【SLAM】SplaTAM:基于3D高斯的密集RGB-D SLAM

题目&#xff1a;SplaTAM: Splat, Track & Map 3D Gaussians for Dense RGB-D SLAM 地址&#xff1a;spla-tam.github.io 机构&#xff1a;CMU&#xff08;卡内基梅隆大学&#xff09;、MIT&#xff08;美国麻省理工&#xff09; 总结&#xff1a;SplaTAM&#xff0c;一个新…

MyBatis 学习(三)之 MyBatis 全局配置文件

目录 1 MyBatis 全局配置文件 2 properties 元素 3 setting 设置 4 typeAlianses 别名处理器 5 typeHandler 类型处理器 6 objectFacotry 对象工厂&#xff08;了解&#xff09; 7 plugins 插件&#xff08;了解&#xff09; 8 environments 运行环境 9 databaseIdPro…

如何对酒店开展科学的定岗定编——以酒店健身房、娱乐房为例

近年来&#xff0c;随着旅游行业的快速发展&#xff0c;也带动了酒店业的兴盛。酒店的经营效益不仅受益于旅游业&#xff0c;同时也受制于旅游行业。由于旅游业存在明显的季节性差异&#xff0c;旅游旺季客流量多、淡季客流量少&#xff0c;造成人员忙闲不均的问题。酒店行业也…