steamsheep-ts-game-engine
v3.2.0
Published
通用游戏引擎框架 - 基于 TypeScript 和 Zustand 构建的文字冒险/RPG 游戏引擎
Maintainers
Readme
SteamSheep TypeScript Game Engine - 完整 API 参考
一个功能强大、类型安全的文字冒险/RPG 游戏引擎框架。
📚 目录
🚀 快速开始
npm install steamsheep-ts-game-engineimport { createGameEngineStore } from 'steamsheep-ts-game-engine';
// 定义游戏类型
type Stats = 'hp' | 'mp' | 'gold';
type Items = 'sword' | 'potion';
type Flags = 'quest_done';
// 创建 Store
const useGameStore = createGameEngineStore(initialState, 'my-game');📋 核心 API 列表
1. 类型定义 (core/types.ts)
核心接口
GameState<S, I, F, X>- 游戏状态ActionDef<S, I, F, X>- 动作定义EffectDef<S, I, F, X>- 效果定义RequirementDef<S, I, F, X>- 需求定义LocationDef<S, I, F, X>- 地点定义RequirementCheckResult- 需求检查结果FlagsBatchOperation<F>- 批量标记操作GameStoreActions<S, I, F, X>- Store 操作方法SnapshotInfo<S, I, F, X>- 快照信息
2. Store API (state/store.ts)
状态操作
updateStat(stat, delta)- 更新单个属性setStats(stats)- 批量更新属性setExtra(updater)- 更新扩展数据addItem(item)- 添加物品removeItem(item)- 移除物品setFlag(flag, value)- 设置标记
系统操作
addLog(text, type?)- 添加日志(持久化)showToast(text, type?)- 显示飘字(瞬时)showModal(text, type?)- 显示弹窗(瞬时)advanceTime(amount?)- 推进时间teleport(locationId)- 传送reset()- 重置游戏
快照操作
saveSnapshot(description?)- 保存匿名快照saveNamedSnapshot(name, description?)- 保存命名快照undo()- 撤销到上一个状态restoreSnapshot(name)- 恢复到命名快照deleteSnapshot(name)- 删除命名快照listSnapshots()- 列出所有快照hasSnapshot(name)- 检查快照是否存在
3. 效果系统 (EffectDef)
静态效果
statsChange- 数值属性变更(支持函数)itemsAdd- 添加物品itemsRemove- 移除物品flagsSet- 设置标记(支持函数)flagsBatch- 批量标记操作(支持函数)teleport- 传送timeAdvance- 时间推进(支持函数)onTimeAdvance- 时间推进钩子triggerEvent- 触发自定义事件
动态效果
conditionalEffects(state)- 条件性效果custom(draft, state)- 修改 extra 数据customFull(draft, originalState)- 修改完整状态
后置钩子
afterEffects(state, originalState, actions)- 后置效果钩子
4. 需求系统 (RequirementDef)
stats- 数值属性需求hasItems- 必须拥有的物品noItems- 不能拥有的物品hasFlags- 必须为 true 的标记noFlags- 必须为 false 的标记custom(state)- 自定义需求(支持返回失败原因)
5. 查询系统 (systems/query.ts)
QuerySystem.checkRequirements(state, reqs)- 检查需求(boolean)QuerySystem.checkRequirementsWithReason(state, reqs)- 检查需求(带原因)QuerySystem.canAfford(state, costs)- 检查成本
6. 流程系统 (systems/flow.ts)
FlowSystem.executeAction(store, action)- 执行动作
7. 事件系统 (systems/events.ts)
gameEvents.on(event, handler)- 监听事件gameEvents.emit(event, data)- 发射事件gameEvents.off(event, handler)- 取消监听
预定义事件
EngineEvents.STAT_CHANGE- 属性变化EngineEvents.ITEM_ADD- 物品添加EngineEvents.ITEM_REMOVE- 物品移除EngineEvents.FLAG_CHANGE- 标记变化EngineEvents.ACTION_EXECUTED- 动作执行EngineEvents.NOTIFICATION- 通知EngineEvents.CUSTOM- 自定义事件
8. 历史管理 (state/history.ts)
history.push(state, description?)- 保存匿名快照history.pushNamed(state, name, description?)- 保存命名快照history.pop()- 撤销history.restoreNamed(name)- 恢复命名快照history.deleteNamed(name)- 删除命名快照history.hasNamed(name)- 检查快照是否存在history.listSnapshots()- 列出所有快照history.listNamedSnapshots()- 列出命名快照history.peek()- 查看最近快照history.clear(includeNamed?)- 清空快照history.size- 匿名快照数量history.namedSize- 命名快照数量history.totalSize- 总快照数量
📖 详细用法
1. 动态 Flag 支持
effects: {
// 动态生成 flag 名称
flagsSet: (state) => {
const day = state.world.day;
return {
[`event_day_${day}`]: true,
[`gazed_stars_day_${day}`]: true
};
}
}2. 完整状态访问 (customFull)
effects: {
customFull: (draft, original) => {
// 可以修改所有状态
draft.stats.sanity -= 10;
draft.flags.some_flag = true;
draft.inventory.push('new_item');
draft.extra.customData = 'value';
}
}3. 条件性效果
effects: {
conditionalEffects: (state) => {
if (state.flags.some_condition) {
return {
statsChange: { sanity: -10 },
flagsSet: { flag_a: true }
};
} else {
return {
statsChange: { sanity: -5 },
flagsSet: { flag_b: true }
};
}
}
}4. 职责分离 (afterEffects + resultText 双参数)
{
effects: {
statsChange: { sanity: -10 }
},
// 副作用在这里执行
afterEffects: (state, original, actions) => {
if (state.stats.sanity < 20) {
actions.setFlag('going_mad', true);
actions.showToast('理智危险!', 'warn');
}
},
// 文本生成 - 可以比较前后状态
resultText: (state, original) => {
const sanityLoss = original.stats.sanity - state.stats.sanity;
return sanityLoss > 15
? `你失去了 ${sanityLoss} 点理智,感到崩溃...`
: '仪式完成了。';
}
}5. 动态 StatsChange
effects: {
statsChange: (state) => {
const maxAP = state.stats.max_action_point || 4;
return {
action_point: maxAP - state.stats.action_point
};
}
}6. 批量 Flag 操作
effects: {
// 清除所有 daily_ 开头的 flags
flagsBatch: {
clear: /^daily_/,
set: { new_day: true }
}
}
// 或使用函数
effects: {
flagsBatch: (state) => ({
clear: Object.keys(state.flags).filter(k => k.startsWith('daily_')),
set: { new_day: true }
})
}7. 时间推进副作用
effects: {
timeAdvance: 1,
onTimeAdvance: (currentDay, previousDay, state, actions) => {
// 恢复每日资源
const maxAP = state.stats.max_action_point || 4;
actions.setStats({
action_point: maxAP
});
// 清除每日标记
Object.keys(state.flags)
.filter(f => f.startsWith('daily_'))
.forEach(f => actions.setFlag(f as F, false));
}
}8. 灵活的 Requirements
requirements: {
custom: (state) => {
if (!state.inventory.includes('key')) {
return {
passed: false,
reason: "需要钥匙才能打开"
};
}
if (state.stats.sanity < 20) {
return {
passed: false,
reason: "理智太低,无法集中精神"
};
}
return { passed: true };
}
}9. 效果执行顺序
effects: {
// 【阶段 1:静态效果】按以下顺序执行
statsChange: { sanity: -10 }, // 1
itemsAdd: ['item'], // 2
itemsRemove: ['old_item'], // 3
flagsSet: { flag: true }, // 4
flagsBatch: { clear: /^temp_/ }, // 5
teleport: 'new_location', // 6
timeAdvance: 1, // 7
onTimeAdvance: (day) => {}, // 7.1
triggerEvent: 'custom_event', // 8
// 【阶段 2:条件效果】
conditionalEffects: (state) => { // 9
// 基于阶段1后的状态
},
// 【阶段 3:自定义效果】
custom: (draft, state) => {}, // 10
customFull: (draft, original) => {}, // 11
}
// 【阶段 4:后置钩子】
afterEffects: (state, original, actions) => {}, // 12
// 【阶段 5:结果文本】
resultText: (state) => {} // 1310. 命名快照系统
// 保存命名快照
store.saveNamedSnapshot('before_boss_fight', '挑战Boss前');
// 恢复到指定快照
if (store.restoreSnapshot('before_boss_fight')) {
console.log('已恢复到Boss战前');
}
// 查看所有快照
const snapshots = store.listSnapshots();
snapshots.forEach(snap => {
console.log(`${snap.name || '匿名'}: ${snap.description}`);
console.log(`时间: ${new Date(snap.timestamp).toLocaleString()}`);
});
// 检查快照是否存在
if (store.hasSnapshot('before_boss_fight')) {
// 可以恢复
}
// 删除快照
store.deleteSnapshot('before_boss_fight');11. ResultText 比较前后状态
// 简化前:需要通过 afterEffects + extra 传递信息
{
effects: {
conditionalEffects: () => {
if (Math.random() < 0.15) {
return { statsChange: { gnosis: 1 } };
}
return null;
}
},
afterEffects: (state, original, actions) => {
const gainedGnosis = state.stats.gnosis > original.stats.gnosis;
actions.setExtra({ lastMeditateGainedGnosis: gainedGnosis });
},
resultText: (state) => {
return state.extra.lastMeditateGainedGnosis
? "获得了灵知!"
: "平静地冥想。";
}
}
// 简化后:直接比较前后状态
{
effects: {
conditionalEffects: () => {
if (Math.random() < 0.15) {
return { statsChange: { gnosis: 1 } };
}
return null;
}
},
resultText: (state, original) => {
const gainedGnosis = state.stats.gnosis > original.stats.gnosis;
return gainedGnosis
? "获得了灵知!"
: "平静地冥想。";
}
}
// 显示具体变化量
resultText: (state, original) => {
const hpChange = state.stats.hp - original.stats.hp;
const goldChange = state.stats.gold - original.stats.gold;
const parts = [];
if (hpChange < 0) parts.push(`失去 ${-hpChange} HP`);
if (goldChange > 0) parts.push(`获得 ${goldChange} 金币`);
return parts.join(',') || '什么都没发生。';
}🎮 完整示例
// 克苏鲁风格的仪式动作
const performRitualAction: ActionDef<Stats, Items, Flags, Extra> = {
id: 'perform_ritual',
label: '进行禁忌仪式',
costs: {
action_point: 2,
sanity: 5
},
requirements: {
hasItems: ['ancient_book'],
stats: {
knowledge: { min: 10 },
sanity: { min: 20 }
},
custom: (state) => {
const time = state.world.time;
if (time < 18 && time > 6) {
return {
passed: false,
reason: '禁忌仪式只能在夜晚进行'
};
}
return { passed: true };
}
},
effects: {
// 动态属性变更
statsChange: (state) => ({
knowledge: state.world.time === 0 ? 5 : 3,
sanity: state.world.time === 0 ? -15 : -10
}),
// 动态标记
flagsSet: (state) => ({
[`ritual_day_${state.world.day}`]: true,
'has_performed_ritual': true
}),
// 条件效果
conditionalEffects: (state) => {
if (state.stats.sanity < 30) {
return {
statsChange: { sanity: -5 },
flagsSet: { 'going_mad': true },
itemsAdd: ['cursed_artifact']
};
}
return null;
},
// 修改扩展数据
custom: (draft) => {
draft.lastActionTime = Date.now();
draft.reputation += 5;
},
// 完整状态修改
customFull: (draft, original) => {
if (original.inventory.includes('protective_charm')) {
const sanityLoss = original.stats.sanity - draft.stats.sanity;
draft.stats.sanity = original.stats.sanity - Math.floor(sanityLoss * 0.5);
draft.inventory = draft.inventory.filter(i => i !== 'protective_charm');
draft.flags.charm_used = true;
}
}
},
afterEffects: (state, original, actions) => {
if (state.stats.knowledge >= 100 && !state.flags.master_scholar) {
actions.setFlag('master_scholar', true);
actions.showModal('成就解锁:博学大师!', 'success');
}
if (state.flags.midnight_ritual) {
actions.saveNamedSnapshot('after_midnight_ritual', '午夜仪式后');
}
},
resultText: (state) => {
const parts = [];
if (state.flags.midnight_ritual) {
parts.push('午夜时分,仪式达到了高潮');
}
if (state.stats.sanity < 20) {
parts.push('你的理智岌岌可危');
}
if (state.flags.charm_used) {
parts.push('护符保护了你,但它已经碎裂了');
}
return parts.join(',') + '。';
}
};📝 总结
本引擎提供了以下增强功能:
- ✅ 动态 Flag 支持 - flagsSet 支持函数形式
- ✅ 完整状态访问 - customFull 可修改所有状态
- ✅ 条件性效果 - conditionalEffects 根据状态决定效果
- ✅ 职责分离 - afterEffects 和 resultText 分离副作用和文本
- ✅ 动态 StatsChange - statsChange 支持函数形式
- ✅ 批量 Flag 操作 - flagsBatch 支持正则、前缀、列表
- ✅ 时间推进副作用 - onTimeAdvance 自动处理每日重置
- ✅ 灵活的 Requirements - custom 可返回失败原因
- ✅ 明确的执行顺序 - 5 个阶段,清晰的文档说明
- ✅ 命名快照系统 - 支持保存和恢复命名快照
- ✅ ResultText 双参数 - 可以比较前后状态,无需 extra 传递
所有功能都是类型安全的,并且完全向后兼容!
⚠️ 重要:通知系统使用
如果你使用 showModal() 或 showToast() 但没有看到显示,请确保在你的应用中渲染了 OverlaySystem 组件:
import { OverlaySystem } from 'steamsheep-ts-game-engine';
function App() {
return (
<>
<YourGameUI />
{/* ⚠️ 必须添加这个组件 */}
<OverlaySystem />
</>
);
}详细说明请查看:通知系统使用指南
📚 更多文档
📄 许可证
MIT
