NarraLeaf

快捷菜单(Quick Menu)

整合回退、重做、历史、自动前进、存读档、设置与退出的快捷菜单面板

范围

快捷菜单是游戏中的一个上下文操作面板,允许玩家快速执行常用动作,例如回退与前进、历史、自动播放、保存 / 读取、设置以及退出

下面的组件包含回退与前进、历史、自动播放、保存、读取、设置和退出。路由名称和退出逻辑由应用决定

undo()redo() 在无处可去时返回 false,因此上面的按钮在时间线的任一端按下都是安全的。若想改为把它们置灰,请使用 canUndo() / canRedo()

1. 创建组件

import { useGame, usePreference, useRouter } from "narraleaf-react";
import { ArrowLeft, ArrowRight, History, Play, Save, FileText, Settings, Home } from "lucide-react";

export default function QuickMenu() {
  const game = useGame();
  const router = useRouter();
  const liveGame = game.getLiveGame();
  const [autoForward, setAutoForward] = usePreference("autoForward");

  // 基本操作 --------------------------------------------
  const undo = () => liveGame.undo();
  const redo = () => liveGame.redo();
  const toHistory = () => router.navigate("/history");
  const toggleAuto = () => setAutoForward(!autoForward);
  const save = () => router.navigate("/save");
  const load = () => router.navigate("/load");
  const openSettings = () => router.navigate("/settings");
  const exitGame = () => /* 你的退出逻辑(例如 useApp().exitGame()) */ undefined;

  // 菜单项 ----------------------------------------------
  const Item = ({ icon: Icon, label, onClick }: { icon: any; label: string; onClick: () => void }) => (
    <button onClick={onClick} className="flex items-center gap-1 px-2 py-1 rounded-full hover:bg-white/20">
      <Icon className="w-4 h-4" />
      <span className="text-xs">{label}</span>
    </button>
  );

  return (
    <div className="fixed bottom-5 left-0 right-0 flex justify-center pointer-events-none">
      <div className="flex gap-2 bg-black/40 rounded-full px-4 py-1 pointer-events-auto">
        <Item icon={ArrowLeft} label="上一步" onClick={undo} />
        <Item icon={ArrowRight} label="下一步" onClick={redo} />
        <Item icon={History} label="历史" onClick={toHistory} />
        <Item icon={Play} label={autoForward ? "自动:开" : "自动:关"} onClick={toggleAuto} />
        <Item icon={Save} label="保存" onClick={save} />
        <Item icon={FileText} label="读取" onClick={load} />
        <Item icon={Settings} label="设置" onClick={openSettings} />
        <Item icon={Home} label="主页" onClick={exitGame} />
      </div>
    </div>
  );
}

2. 在 LayoutRouter 中使用

QuickMenu 放在根布局的默认 Page(路径 /)中。Player 已经自动创建根布局

import { GameProviders, Page, Player } from "narraleaf-react";
import QuickMenu from "./QuickMenu";

function MyApp() {
  return (
    <GameProviders>
      <Player story={story} onReady={({ liveGame }) => liveGame.newGame()}>
        {/* QuickMenu 将在路由为 `/` 时显示,即默认页面 */}
        {/* 你也可以使用 `router.clear().navigate("/")` 在游戏中显示快捷菜单 */}
        {/* 这意味着关闭其他页面(设置、存档等)后快捷菜单会在舞台上显示 */}
        <Page name={null}>
          <QuickMenu />
        </Page>
      </Player>
    </GameProviders>
  );
}

3. 过渡动画

上面的组件是静态的。把顶级元素换成 Motion 的 motion.* 组件即可添加进入和退出动画。页面路由会让组件保持挂载,直到退出动画完成

import { motion } from "motion/react";

export default function QuickMenu() {
  // ...hooks & helpers...

  return (
    <motion.div
      /* initial ➜ 进入前 */
      initial={{ opacity: 0, scale: 0.8, y: 20 }}
      /* animate ➜ 进入后 */
      animate={{ opacity: 1, scale: 1, y: 0 }}
      /* exit ➜ 卸载前 */
      exit={{ opacity: 0, scale: 0.8, y: 20 }}
      /* 动画曲线 */
      transition={{ type: "spring", stiffness: 300, damping: 25, duration: 0.3 }}
      className="fixed bottom-5 left-0 right-0 flex justify-center pointer-events-none"
    >
      <div className="flex gap-2 bg-black/40 rounded-full px-4 py-1 pointer-events-auto">
        {/* 菜单项 */}
      </div>
    </motion.div>
  );
}

指南:

  • 根级 motion 元素 – 只需将最外层节点设置为 motion.*;内部内容可保持普通 JSX
  • 通过自定义 initial / animate / exit 实现淡入、滑动、缩放等效果
  • 如需更复杂的效果,可把每个按钮包裹在 motion.button 中或使用 Motion variants 进行分段动画
  • NarraLeaf 页面路由已经为 Page 内容管理 presence。只有在 Page 外自行条件渲染时,才需要额外使用 <AnimatePresence>

本页目录