快速上手
本文将介绍Vyno中一些基础的使用方法,以便快速熟悉Vyno的开发风格。
例如:现在我们要开发一个简单的视觉小说游戏,这个游戏涵盖:游戏旁白、角色、对话、场景、分支、外部输入、组件等概念。
1. 游戏流程
- 游戏开始,游戏旁白:欢迎来到Vyno小游戏
- 接下来你会连续回答三组题目
- 但是你只有两点生命值 !!!
- 每答对一组题目,得10分
- 每答错一组题目,扣1点生命值
- 如果你答错了3组题目,则游戏失败
- 如果你答对了3组题目,则游戏成功
- 游戏成功后,游戏旁白:恭喜你过关
- 游戏失败后,游戏旁白:很遗憾,你失败了
- 游戏失败后,游戏旁白:是否再来一次
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