Skip to content

GameSystemUI 对话控件 ​

GameSystemUI.ConversationSystemUI 提供官方对话 UI 实现,主要用于接入 对话系统 的 IConversationUI,也可以单独使用 BubbleUI 做单位头顶气泡提示。

它是对话系统的客户端表现层,不负责创建对话数据、推进剧情状态或执行任务奖励。剧情流程、选项跳转、条件和奖励仍应放在 GameUI.ConversationSystem / GameDataConversation* 或业务逻辑里。

常用命名空间:

csharp
#if CLIENT
using GameCore.BaseType;
using GameCore.EntitySystem;
using GameSystemUI.ConversationSystemUI.Advanced;
using GameSystemUI.ConversationSystemUI.Data;
using GameUI.ConversationSystem;
using GameUI.ConversationSystem.Data;
using GameUI.ConversationSystem.Data.Enum;
using GameUI.Struct;
#endif

推荐公开入口 ​

控件作用推荐用法
ConversationPanel默认对话面板,实现 IConversationUI注册到 ConversationUI,作为对话系统默认 UI
BubbleUI跟随单位显示的气泡 UI用于 NPC 闲聊、战斗短提示、环境反馈
ConversationPanelBase对话 UI 抽象基类需要完全自定义对话 UI 时继承
TypewriterEffectBehavior给 Label 添加打字机效果只在自定义控件中直接使用

ConversationChoiceItem 通常作为选择项模板的内部控件使用,普通项目不需要直接 new。选择项外观优先通过 GameDataControlConversationChoiceItem 模板改。

注册默认对话面板 ​

如果你使用框架对话系统,最常见的接法是在客户端创建一个 ConversationPanel,添加到 UI 根节点,然后注册为当前 IConversationUI:

csharp
#if CLIENT
private static ConversationPanel? conversationPanel;

private static void CreateConversationUI()
{
    conversationPanel = new ConversationPanel
    {
        EnableTypewriter = true,
        TypewriterSpeed = 0.5f,
        BubbleDirection = BubbleDirection.Right,
        BackgroundVisible = false,
    };
    conversationPanel.AddToRoot();

    ConversationUI.Register(conversationPanel);
}

private static void CleanupConversationUI()
{
    ConversationUI.Unregister();

    conversationPanel?.RemoveFromParent();
    conversationPanel = null;
}
#endif

注册后,通过 GameDataConversation.StartConversation(...) 启动的对话会调用 ConversationUI.Current,再由 ConversationPanel 显示台词、选项和气泡。

csharp
// 客户端示意:通常由交互、任务或触发器逻辑启动。
CmdResult result = await conversationData.StartConversation();

显示模式 ​

ConversationPanel 支持两种显示模式,模式来自 ConversationLineInfo.DisplayMode 或对应的对话数据。

模式效果适合场景
ConversationDisplayMode.UI在屏幕对话框中显示角色名、立绘、文本和选项剧情、任务、关键选择
ConversationDisplayMode.OnUnit在角色绑定单位附近显示 BubbleUINPC 闲聊、战斗提示、环境氛围

OnUnit 模式要求对话角色能解析到有效的 InGameUnit。如果没有有效单位,默认面板会退回到 UI 对话框模式。

台词完成规则 ​

ConversationPanelBase 已处理 AllowSkip、WaitForConfirmation 和 Duration 的组合:

配置行为
AllowSkip = true玩家可立即点击完成,跳过按钮可见
AllowSkip = false, WaitForConfirmation = false, Duration = 3s3 秒后自动完成
AllowSkip = false, WaitForConfirmation = true, Duration = 3s3 秒后才允许点击确认
AllowSkip = false, WaitForConfirmation = true, Duration = nullUI 会一直等待外部逻辑完成,不适合普通台词

打字机效果播放中点击会先补全文本;文本补全后,只有 CanFinish = true 才会结束当前台词。

单独使用 BubbleUI ​

如果你只是想在单位头顶显示一句短提示,可以不启动完整对话系统,直接创建 BubbleUI:

csharp
#if CLIENT
private static BubbleUI? npcBubble;

private static void ShowNpcHint(Unit npcUnit)
{
    npcBubble ??= new BubbleUI
    {
        Direction = BubbleDirection.Up,
        EnableTypewriter = true,
        TypewriterSpeed = 0.8f,
    };

    npcBubble.BindUnit = npcUnit;
    npcBubble.ShowText("前面有危险。");
    npcBubble.Show();
}

private static void HideNpcHint()
{
    npcBubble?.Hide();
}
#endif

BubbleUI 会每帧把绑定单位的绑点位置转换到 UI 坐标,并带有 1 像素级别的防抖动。绑定单位失效或转换失败时,它会停止思考并隐藏。

BubbleUI 布局模板 ​

BubbleUI 的不同方向由 GameDataControlBubbleUI.DirectionLayouts 配置。默认数据已经提供 Right、Left、Up、Down 四组布局;项目需要换图、改偏移或换绑点时,新建模板即可。

csharp
using GameData;
using GameSystemUI.ConversationSystemUI.Data;
using GameUI.Control.Data;
using GameUI.Struct;

public static class MyConversationControls
{
    public static readonly GameLink<GameDataControl, GameDataControlBubbleUI> Bubble =
        new("MyBubbleUI"u8);

    public static readonly GameLink<GameDataBubbleUILayout, GameDataBubbleUILayout> BubbleUp =
        new("MyBubbleUIUp"u8);
}

private static void OnGameDataInitialization()
{
    _ = new GameDataBubbleUILayout(MyConversationControls.BubbleUp)
    {
        BubbleImage = new Image("image/conversation/bubble_up.png"),
        UIOffset = new UIPosition(0, -20),
        OffsetZ = -50,
        CharsPerLine = 16,
        TextMargin = new Thickness(24, 18, 24, 28),
        SlicedEdges = new Thickness(24, 18, 24, 28),
        SocketName = "socket_blood_bar",
    };

    _ = new GameDataControlBubbleUI(MyConversationControls.Bubble)
    {
        DefaultDirection = BubbleDirection.Up,
        DirectionLayouts = new()
        {
            [BubbleDirection.Up] = MyConversationControls.BubbleUp,
        },
        FontSize = 30,
    };
}

#if CLIENT
var bubble = new BubbleUI(MyConversationControls.Bubble)
{
    BindUnit = npcUnit,
};
bubble.ShowText("自定义气泡。");
bubble.Show();
#endif

图片路径遵循 GameUI 资源规则。项目图片通常放在 ui/image/...,代码里从 image/... 开始写;内置资源使用 @gameui/...。详见 项目资源路径指南。

自定义对话 UI ​

默认面板不满足需求时,继承 ConversationPanelBase。基类已经封装异步等待、取消、计时和 CanFinish 状态,你只需要实现具体显示和在玩家确认时调用受保护方法。

csharp
#if CLIENT
public sealed class MyConversationPanel : ConversationPanelBase
{
    public override void ShowLine(ConversationLineInfo lineInfo, GameDataConversationLine? lineData)
    {
        // 显示角色名、立绘和文本。
    }

    public override void ShowChoices(
        List<ConversationChoiceInfo> choices,
        ConversationChoicePromptInfo? promptInfo,
        GameDataConversationChoiceGroup? choiceGroupData)
    {
        // 显示选项,并在点击某项时调用 CompleteChoiceSelection(index)。
    }

    public override void HideLine() { }
    public override void HideChoices() { }
    public override void Clear() { }
    public override void Hide() => Visible = false;

    protected override void OnCanFinishChanged(bool canFinish)
    {
        // 更新“可继续”提示。
    }
}
#endif

不要在 UI 层直接修改对话状态或发放奖励。IConversationUI 的职责是显示和返回玩家选择,业务状态应由对话节点、任务系统或服务端逻辑处理。

清理 ​

模式切换、退出关卡或重新创建 HUD 时,应注销当前对话 UI 并移除控件:

csharp
#if CLIENT
private static void CleanupConversationUI()
{
    ConversationUI.Unregister();

    conversationPanel?.RemoveFromParent();
    conversationPanel = null;

    npcBubble?.Destroy();
    npcBubble = null;
}
#endif

如果使用自定义 ConversationPanelBase,注意在 DisposeManaged() 中取消事件订阅、停止计时器或打字机效果,避免旧面板继续响应输入。

常见问题 ​

现象常见原因处理
启动对话但没有 UI没有 ConversationUI.Register(...),或面板没有加入 UI 根节点客户端初始化时创建 ConversationPanel 并注册
OnUnit 没有显示气泡角色没有绑定有效 InGameUnit,或单位不在屏幕内检查对话角色数据和单位生命周期
台词一直不能点掉AllowSkip = false 且 WaitForConfirmation = true,但没有 Duration普通台词给出 Duration,或改为允许跳过
选择项显示但点击无效选择项 IsEnabled = false用禁用状态表达条件未满足;可用时再设为 true
气泡位置不合适绑点、UIOffset 或 OffsetZ 不符合模型用 GameDataBubbleUILayout.SocketName、UIOffset、OffsetZ 调整