NVL 容器
NvlContainer 等 NVL 模式组件及 useNvl 系列状态钩子
NvlContainer 组件渲染 NVL(小说模式)的对话框叠加层,以可滚动列表形式展示多条对话。可直接使用 DefaultNvlContainer,或组合 NvlContainer、Nametag、Texts 构建自定义组件
示例
- 导入组件
import { useEffect } from "react";
import { DefaultNvlContainer, useGame } from "narraleaf-react";- 使用默认组件(或通过
game.configure自定义)
function App() {
const game = useGame();
useEffect(() => {
game.configure({
nvlDialog: DefaultNvlContainer,
});
}, []);
return /* ... */
}- 自定义组件
import {
NvlContainer,
Nametag,
Texts,
useGame,
type INvlContainerProps,
} from "narraleaf-react";
function CustomNvlContainer({ dialogs = [] }: INvlContainerProps) {
return (
<NvlContainer className="bg-black/80 text-white p-16">
{dialogs.map((d) => (
<div key={d.entry.id} className="space-y-2">
{d.entry.character && <Nametag entry={d.entry} />}
<Texts
entry={d.entry}
gameState={d.gameState}
words={d.words}
useTypeEffect={d.useTypeEffect}
isActive={d.isActive}
/>
</div>
))}
</NvlContainer>
);
}
function App() {
const game = useGame();
useEffect(() => {
game.configure({ nvlDialog: CustomNvlContainer });
}, []);
return /* ... */
}组件
NvlContainer
NVL 模式的外层容器,处理可见性、过渡动画和宽高比缩放
children?: React.ReactNode- 子元素(对话列表)className?: string- 类名style?: React.CSSProperties- 样式
DefaultNvlContainer
默认 slot 组件,接收播放器传入的 dialogs 和 renderDialogItem,用 Nametag 和 Texts 渲染每条对话
dialogs?: NvlDialogProxy[]- 对话条目列表renderDialogItem?: NvlDialogItemRenderer- 可选,仅需定制每条对话布局时使用
NvlDialogList
NvlDialogList 负责渲染条目本身。DefaultNvlContainer 用的就是它;基于 NvlContainer 自建的容器需要它(或自己的等价物)来画出列表。每个条目都被包进对话上下文,因此在其中渲染的 Nametag 与 Texts 读的是该条目,而不是 ADV 那一行
children?: React.ReactNode- 自定义的条目元素。它会按条目逐个克隆,并注入entry与indexrenderDialogItem?: NvlDialogItemRenderer- 优先级高于children。接收{ entry, index, isActive, nametag, texts }className?: string/style?: React.CSSProperties- 应用在列表容器上
children 与 renderDialogItem 都不传时,每个条目由 DefaultNvlDialogItem 绘制
DefaultNvlDialogItem
默认的条目样式:说话人名字加一个冒号,后面跟着这一行台词。可以在自定义列表里当兜底用,也可以当作参考实现
entry: NvlDialogEntry- 要绘制的条目index: number- 它在列表中的序号texts?: React.ReactNode- 替换渲染出来的正文,名牌不受影响className?: string/style?: React.CSSProperties
属性
INvlContainerProps
传入 NVL slot 组件的属性:
dialogs?: NvlDialogProxy[]- 包含已求值文本和状态的对话条目数组renderDialogItem?: NvlDialogItemRenderer- 可选,接收{ entry, index, isActive, nametag, texts }
NvlDialogItemRenderProps
传入 renderDialogItem 的属性:
entry: NvlDialogEntry- 对话条目index: number- 列表索引isActive: boolean- 是否为当前活动对话nametag: React.ReactNode- 预渲染的角色名texts: React.ReactNode- 预渲染的文本内容
使用 renderDialogItem 定制布局
只需调整每条对话的展示(如加边框、淡化非活动项)时,可包装 DefaultNvlContainer 并传入 renderDialogItem:
function CustomNvlWithRenderer({ dialogs }: INvlContainerProps) {
return (
<DefaultNvlContainer
dialogs={dialogs}
renderDialogItem={({ entry, index, isActive, nametag, texts }) => (
<div className={isActive ? "opacity-100" : "opacity-60"}>
{nametag}
<div className="border-l-4 border-blue-500 pl-2">{texts}</div>
</div>
)}
/>
);
}
// 注册:game.configure({ nvlDialog: CustomNvlWithRenderer });Hooks
有四个钩子用于读取 NVL 状态。它们都是不带参数的上下文读取器,返回的就是播放器挂载的那个 NvlProvider 所持有的值
该 provider 包住的是舞台、对话与 NVL 叠层——因此自定义 NVL 容器、自定义对话框,以及舞台渲染的其他任何东西都可以调用它们。由页面路由渲染的页面位于该 provider 之外,拿到的是惰性的默认值:未激活、不可见、没有条目
import { useIsNvlMode, useNvlDialogs } from "narraleaf-react";
function LineCount() {
const inNvl = useIsNvlMode();
const dialogs = useNvlDialogs();
if (!inNvl) return null;
return <span>{dialogs.length} 行</span>;
}useNvl
返回整个 NVL 上下文值
state: NvlState- 完整记录:active、visible、sessionId、dialogs、options、activeDialogId、phase("idle" | "typing" | "awaitAdvance")、pendingAdvance、isTypingdialogs: NvlDialogEntry[]- 当前列表中的条目isActive: boolean- 游戏是否处于 NVL 模式isVisible: boolean- NVL 层是否显示在屏幕上。会话在该层被隐藏期间仍然是激活的,所以这两件事问的不是同一个问题transitionOptions: Partial<TransformDefinitions.CommonTransformProps> | null- 当前生效的显示或隐藏过渡的 transform 选项,没有则为null
useNvlDialogs
即 useNvl().dialogs——当前列表中的条目
useIsNvlMode
即 useNvl().isActive——游戏是否处于 NVL 模式
useIsNvlVisible
即 useNvl().isVisible——NVL 层是否显示在屏幕上