NarraLeaf

自定义 Menu(游戏内选项)

用 GameMenu 与 Item 替换游戏内选项菜单,并用 Item 的 bindKey 绑定快捷键

作用

游戏内选项(Menu)用于在故事中展示选择分支,玩家点击或按快捷键选择后执行对应动作。通过 game.configuremenu 配置项,可替换默认的 Menu 组件,实现自定义的选项样式和布局

使用 GameMenu 作为选项容器,Item 渲染单个选项。Item.bindKey 用于绑定快捷键

1. 创建自定义 Menu 组件

自定义菜单组件接收 items(选项索引数组),使用 GameMenu 作为容器,Item 渲染每个选项。Item 会自动从上下文中获取对应选项的文本和点击逻辑

import { GameMenu, Item } from "narraleaf-react";

function CustomMenu({ items }: { items: number[] }) {
  return (
    <GameMenu
      className="absolute flex flex-col items-center justify-center min-w-full w-full h-full bg-black/50"
      // GameMenu:容器,负责宽高比缩放
    >
      {items.map((index) => (
        <Item
          key={index}
          className="bg-white/90 px-6 py-3 rounded-lg mt-3 w-64 text-center hover:bg-white transition-colors"
          defaultColor="black"
          // Item:渲染一个选项,索引来自 items 数组
        />
      ))}
    </GameMenu>
  );
}

2. 为选项绑定快捷键

Item 支持 bindKey 属性,按下对应按键时可直接选中该选项。键值参考 Key_Values

function CustomMenu({ items }: { items: number[] }) {
  return (
    <GameMenu className="absolute flex flex-col items-center justify-center min-w-full w-full h-full">
      {items.map((index) => (
        <Item
          key={index}
          className="bg-white p-2 mt-2 w-1/2"
          defaultColor="black"
          bindKey={String(index + 1)}
          // bindKey:"1"、"2"、"3" 等——按下对应按键即可选中该项
        />
      ))}
    </GameMenu>
  );
}

3. 只让选项区域接收点击

外层使用 pointer-events-none,避免全屏容器阻挡点击;内层使用 pointer-events-auto,只让选项区域可交互:

import { GameMenu, Item } from "narraleaf-react";

function CustomMenu({ items }: { items: number[] }) {
  return (
    <GameMenu
      className="absolute inset-0 flex items-center justify-center w-full h-full pointer-events-none"
      // pointer-events-none:菜单区域外的点击不被阻挡
    >
      <div className="relative w-full max-w-5xl pointer-events-auto px-4 md:px-8">
        {/* pointer-events-auto:只有菜单区域可交互 */}
        <div className="px-10 py-8 space-y-4">
          {items.map((index) => (
            <Item
              key={index}
              className="block md:w-3/4 lg:w-2/3 mx-auto text-center py-3 px-6
                hover:bg-white/10 transition-all duration-300 transform hover:-translate-y-1
                hover:[filter:drop-shadow(8px_8px_0_rgba(0,0,0,0.8))] disabled:opacity-50 disabled:cursor-not-allowed"
              defaultColor="white"
              fontSize={18}
              fontWeight={700}
              style={{
                backgroundColor: "rgba(64,168,197,0.9)",
                clipPath: "polygon(0 0,100% 0,97% 100%,3% 100%)",  // 平行四边形
                filter: "drop-shadow(4px 4px 0 rgba(0,0,0,0.6))",
              }}
            />
          ))}
        </div>
      </div>
    </GameMenu>
  );
}

菜单选项文字由自定义菜单组件控制。可以向 Item 传入 defaultColorfontSizefontFamilyfontWeightfontWeightBold,也可以让文字从 CSS 继承

4. 横向布局示例

function HorizontalMenu({ items }: { items: number[] }) {
  return (
    <GameMenu className="absolute flex flex-row items-center justify-center gap-4 bottom-20 left-0 right-0">
      {items.map((index) => (
        <Item
          key={index}
          className="bg-amber-500/80 px-4 py-2 rounded hover:bg-amber-400"
          defaultColor="black"
        />
      ))}
    </GameMenu>
  );
}

5. 在脚本中定义选项

选项在 Menu 元素中通过 choose 定义,支持 hideIfdisableIf 等条件控制:

// 在剧情脚本中:
Menu.prompt("你要怎么做?")
  .choose("往左走", [character.say("我往左走了")])
  .choose("往右走", [character.say("我往右走了")])
  .choose("原地不动", [character.say("我选择等待")])
    .hideIf(persis.isTrue("alreadyMoved"));  // 条件满足时隐藏

6. 注册到游戏

import { Game, GameProviders, Player } from "narraleaf-react";

const game = new Game({ menu: CustomMenu });

function App() {
  return (
    <GameProviders game={game}>
      <Player story={story} onReady={({ liveGame }) => liveGame.newGame()} />
    </GameProviders>
  );
}

参考

本页目录