自定义 Dialog
概述
对话框(Dialog)用于渲染 Say 模式下的角色对话,包括角色名牌和文本内容。通过 game.configure 替换默认的 Dialog 组件,即可实现完全自定义的对话框样式。
本文档演示如何创建自定义对话框,并配合 Nametag 与 Texts 调整名牌和文本的展示。
1. 创建自定义 Dialog 组件
使用 Dialog、Nametag、Texts 三个子组件构建布局。Dialog 为容器,Nametag 渲染角色名,Texts 渲染对话文本。
import { Dialog, Nametag, Texts } from "narraleaf-react";
function GameDialog() {
return (
<Dialog
className="bg-black/70 rounded-lg px-6 py-4"
// Dialog: container for the dialog box, supports className/style
>
<Nametag className="text-lg font-bold text-amber-400" />
{/* Nametag: character name, color is defined in character config */}
<Texts className="text-white text-base leading-relaxed" />
{/* Texts: dialogue content, inherits from word/sentence config */}
</Dialog>
);
}2. 使用 useDialog 获取状态(可选)
若需在子组件中读取当前对话状态(如 done、text、isNarrator),可使用 useDialog Hook:
import { Dialog, Nametag, Texts, useDialog } from "narraleaf-react";
function GameDialog() {
const { done, text, isNarrator } = useDialog();
// done: whether typing is complete
// text: current displayed text
// isNarrator: true when speaker is null (narrator)
return (
<Dialog className="bg-black/70 rounded-lg px-6 py-4">
{!isNarrator && <Nametag className="text-lg font-bold" />}
{/* Hide nametag when narrator speaks */}
<Texts className="text-white" />
</Dialog>
);
}3. 在 App 中注册
通过 useGame 获取游戏实例,在 useEffect 中调用 game.configure 注册自定义组件:
import { useEffect } from "react";
import { useGame, Player } from "narraleaf-react";
import GameDialog from "./GameDialog";
function App() {
const game = useGame();
useEffect(() => {
game.configure({
dialog: GameDialog, // Replace default dialog with custom one
});
}, [game]);
return (
<Player>
{/* ... */}
</Player>
);
}4. 完整示例(参考 narraleaf-react-skeleton (opens in a new tab))
以下示例展示:使用 useDialog 的 done 控制打字完成指示器,isNarrator 控制名牌显隐,以及背景图样式:
import { Dialog, Nametag, Texts, useDialog } from "narraleaf-react";
import clsx from "clsx";
// Sub-component: show triangle/underline when typing is done
function SentenceContext() {
const { done } = useDialog();
return (
<>
<Texts className="text-[22px] max-w-max flex items-center" />
<div className="flex flex-col items-center">
<div className={clsx(
"w-0 h-0 border-l-[6px] border-l-transparent border-r-[6px] border-r-transparent border-t-[10px] border-t-white",
done ? "opacity-100" : "opacity-0" // Show indicator when done
)} />
<div className="w-[12px] h-[2px] bg-white mt-[2px]" />
</div>
</>
);
}
export function GameDialog() {
const { isNarrator } = useDialog();
return (
<Dialog
className="absolute bottom-4 left-1/2 -translate-x-1/2 p-12 px-16 w-[90%] h-[216px]"
style={{
backgroundImage: "url('/ui/game-dialog.png')",
backgroundSize: "contain",
backgroundPosition: "bottom",
backgroundRepeat: "no-repeat",
}}
>
<div className={clsx("absolute left-[30px] -top-[15px]", { "hidden": isNarrator })}>
<Nametag
className="px-4 py-2 min-w-[220px] min-h-[56px] flex items-center justify-center"
style={{
backgroundImage: "url('/ui/game-dialog-nametag.png')",
backgroundSize: "contain",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
}}
/>
</div>
<div className="flex items-center gap-[5px] h-full">
<SentenceContext />
</div>
</Dialog>
);
}注册时还可设置 defaultTextColor、defaultNametagColor 等默认颜色:
game.configure({
dialog: GameDialog,
defaultTextColor: "white",
defaultNametagColor: "#2987a1",
});5. 简单样式示例
function GameDialog() {
return (
<Dialog
style={{
backgroundColor: "rgba(0, 0, 0, 0.6)",
borderRadius: "12px",
padding: "24px",
border: "1px solid rgba(255, 255, 255, 0.2)",
}}
>
<Nametag style={{ marginBottom: "8px", paddingBottom: "4px", borderBottom: "2px solid rgba(255, 193, 7, 0.8)" }} />
<Texts />
</Dialog>
);
}