Skip to Content
Welcome to Vyno! 🍷
文档快速开始

快速上手

本文将介绍Vyno中一些基础的使用方法,以便快速熟悉Vyno的开发风格。

例如:现在我们要开发一个简单的视觉小说游戏,这个游戏涵盖:游戏旁白、角色、对话、场景、分支、外部输入、组件等概念。

1. 游戏流程

  1. 游戏开始,游戏旁白:欢迎来到Vyno小游戏
  2. 接下来你会连续回答三组题目
  3. 但是你只有两点生命值 !!!
  4. 每答对一组题目,得10分
  5. 每答错一组题目,扣1点生命值
  6. 如果你答错了3组题目,则游戏失败
  7. 如果你答对了3组题目,则游戏成功
  8. 游戏成功后,游戏旁白:恭喜你过关
  9. 游戏失败后,游戏旁白:很遗憾,你失败了
  10. 游戏失败后,游戏旁白:是否再来一次

2. 代码实现

import { c } from "@core/figure/speak"; import type { PixiProperty } from "@core/figure/types"; import { createGameState, createImageFigure, Game, useGameStore } from "@core/index"; import { createScene } from "@core/scene"; import InputDialog from "@plugins/default/components/input-dialog"; const state = createGameState("life", { life: 2, score: 0, }); const right_position: PixiProperty = { x: 592.36, y: 145.13, scaleX: 0.59, scaleY: 0.59, rotation: 0, alpha: 0, anchorX: 0, anchorY: 0, }; const judegment = createImageFigure("judegment", { path: "/figures/EVE/Eve_Neutral.png", name: "考官", avatar: "/figures/EVE/Eve_Neutral.png", }); export const entry = createScene("entry", async () => { // Game 上的操作默认串行 // 角色链上的操作则默认并行 Game.parallel(() => // 创建并行语句块,内部操作并行执行 Game.audio.bgm("/audio/bgm/happy.mp3", { volume: 0.5, fadeIn: 1 }) .background.img("/backgrounds/bg-classroom.png") ).narrate("欢迎来到Vyno小游戏") // 游戏旁白 .narrate`接下来你会连续回答三组题目` .narrate`但是你只有${c("两点生命值", "red")} !!!`; // 使用富文本 judegment.show(right_position); // 以指定pixi属性加载角色 Game.wait(0); judegment.fadeIn({ duration: 0.5 }); // 使用角色淡入动画 const Panel = () => { // 创建一个面板组件 const store = useGameStore(state); return ( <div className="absolute top-4 left-1/2 -translate-x-1/2"> <div className="text-black bg-white p-4 rounded-2xl text-2xl"> <p>你还有${store.life}点生命值</p> <p>你得了${store.score}分</p> </div> </div> ); }; Game.mount("panel", <Panel />).parallel(); // 挂载面板组件到游戏 let userInput = ""; for (let i = 0; i < 3; i++) { judegment.switch("/figures/EVE/Eve_Neutral.png"); Game.parallel(); // 从外部系统获取随机数 const num1 = await Game.use(() => Math.floor(Math.random() * 10)); const num2 = await Game.use(() => Math.floor(Math.random() * 10)); const result = num1 + num2; Game.narrate(`${num1} + ${num2} = ?`).parallel(); // 从用户组件获取输入 userInput = await Game.use<string>("target", <InputDialog title="请输入你的答案" type="text" />); if (userInput === result.toString()) { judegment.switch("/figures/EVE/Eve_Smile.png"); Game.wait(0); judegment.bounce().speak`回答正确`; Game.parallel(); // 响应式更新分数 await state.set((prev) => ({ ...prev, score: prev.score + 10, })); } else { judegment.switch("/figures/EVE/Eve_Angry.png"); Game.wait(0); judegment.shake().speak`回答错误!${{ text: `正确答案是${result}`, style: { color: "red", fontWeight: "bold", textDecorationLine: "underline", }, }}`; await state.set((prev) => ({ ...prev, life: prev.life - 1, })); const life = await state.get(); if (life.life <= 0) { return failure; } } } return successs; }); const successs = createScene("successs", async () => { judegment.switch("/figures/EVE/Eve_Laugh.png").speak("恭喜你过关"); const score = await state.get(); Game.narrate(`你总共得了${score.score}分`); }); const failure = createScene("failure", async () => { judegment.switch("/figures/EVE/Eve_Cry.png").speak("很遗憾,你失败了"); const score = await state.get(); Game.narrate(`你总共得了${score.score}分`); // 使用选择框组件 const again = await Game.use<string>( "target", <InputDialog title="是否再来一次" type="select-single" options={[ { label: "是", value: "yes" }, { label: "否", value: "no" }, ]} /> ); if (again === "yes") { await state.set((prev) => ({ ...prev, life: 2, score: 0, })); judegment.hide(); // 隐藏角色 Game.narrate``; // 关闭对话框 return entry; } });

3.主要内容解析

状态管理

const state = createGameState("life", { life: 2, score: 0, });

使用 createGameState 创建游戏状态,状态会自动持久化,存档时保存,加载时恢复。

角色创建

const judegment = createImageFigure("judegment", { path: "/figures/EVE/Eve_Neutral.png", name: "考官", avatar: "/figures/EVE/Eve_Neutral.png", });

使用 createImageFigure 创建图片角色,指定图片路径、名称和头像。

场景定义

export const entry = createScene("entry", async () => { // 场景内容 return nextScene; // 返回下一个场景 });

使用 createScene 创建场景,通过返回 Scene 实例实现场景跳转。

执行顺序

链式调用

Game 上的操作支持链式调用,链上操作默认串行执行:

Game.narrate("第一句") .narrate("第二句") .narrate("第三句");

语句块并行

使用 Game.parallel(() => { ... }) 包裹一组操作,块内操作并行执行:

Game.parallel(() => Game.audio.bgm("/audio/bgm/happy.mp3", { volume: 0.5, fadeIn: 1 }) .background.img("/backgrounds/bg.webp") // 与 BGM 同时加载 );

链上标记并行

使用 .parallel() 标记当前操作与下一个操作并行:

// mount 与旁白操作并行 Game.mount("panel", <Panel />).parallel().narrate(`问题`)

等待执行

Game.wait(0); // 等待0秒后自动执行下一个操作

外部输入

// 获取随机数 const num1 = await Game.use(() => Math.floor(Math.random() * 10)); // 获取用户输入 userInput = await Game.use<string>("target", <InputDialog title="请输入你的答案" type="text" />);

使用 Game.use() 获取外部输入,输入会被自动缓存用于存档恢复。

React 组件集成

const ScorePanel = () => { const store = useGameStore(state); return <div>分数: {store.score}</div>; }; Game.mount("panel", <ScorePanel />);

使用 useGameStore Hook 订阅状态,使用 Game.mount() 挂载组件。

状态操作

获取状态

const current = await state.get(); console.log(current.score);

使用 get() 获取当前状态值(需要 await)。

更新状态

await state.set((prev) => ({ ...prev, score: prev.score + 10, }));

使用 set() 响应式更新状态(需要 await),组件会自动重新渲染。

场景跳转

const life = await state.get(); if (life.life <= 0) { return failure; // 跳转到失败场景 } return successs; // 跳转到成功场景

通过返回 Scene 实例实现条件跳转。

Last updated on