红楼梦·梦境漫游
AI文字MUD游戏

详细设计文档
技术栈: Next.js 16 + Tailwind CSS 4 + shadcn/ui + Zustand + Prisma/SQLite + z-ai-web-dev-sdk
总代码量: ~88K行 (TS/TSX 67K+ + CSS 21K+)
1 项目概述
1.1 项目背景
"红楼梦·梦境漫游"是一款基于大语言模型(LLM)驱动的《红楼梦》主题沉浸式文字冒险游戏(AI MUD)。玩家以贾府远房亲戚等等的身份进入大观园,与林黛玉、贾宝玉、薛宝钗等经典角色交互,体验红楼梦世界中的诗词、茶道、节庆、人情与故事。
游戏通过智谱AI大模型实时生成文学风格的叙事内容,结合红楼梦知识库(RAG)提升叙事的专业性和原汁原味,营造真实的沉浸体验。
1.2 核心特性
- 8位红楼梦经典角色,各具独特性格、语言风格、情感倾向和心情系统
- 10个大观园场景,每个场景含4个子场景,共计40个可探索地点
- 31个中国传统小游戏(投壶/围棋/古琴/花灯/华容道/七巧板等)
- 157+个成就,6大类别,4级稀有度
- LLM驱动叙事,支持对话/吹诗/灯谜/礼物/梦境等多种交互
- 7种天气粒子实时渲染 + 季节氛围 + 中国风音效
- 主线/支线任务系统、亲密度关系事件、角色日程、物品合成等深度玩法
1.3 技术栈总览
| 层次 | 技术选型 | 用途 |
|---|---|---|
| 前端框架 | Next.js 16 (App Router) + React 19 | 全栈框架,SR/SSR支持 |
| UI组件库 | shadcn/ui (30+ Radix UI组件) | 复杂交互组件 |
| 样式方案 | Tailwind CSS 4 + tailwindcss-animate | 响应式布局 + 动画 |
| 状态管理 | Zustand 5.0.6 | 全局游戏状态 |
| 数据库 | Prisma 6.11 + SQLite | 持久化存储 |
| AI SDK | z-ai-web-dev-sdk (智谱AI) | LLM文本生成 |
| 运行时 | Bun | 服务端运行 |
| 部署 | Caddy反向代理 (81->3000) | 生产环境部署 |
| 动画 | framer-motion + embla-carousel | 页面过渡和轮播 |
| 图表 | recharts | 数据可视化 |
| 拖拽 | @dnd-kit/core + @dnd-kit/sortable | 物品拖拽操作 |
2 系统架构设计
2.1 整体架构
项目采用前后端一体化架构,基于Next.js App Router实现。前端负责游戏界面渲染和交互,后端通过API Routes处理游戏逻辑和LLM调用,数据通过Prisma/SQLite持久化。
核心数据流向图
#mermaid-svg-Nx2W5HcOp0YUiOVL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Nx2W5HcOp0YUiOVL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Nx2W5HcOp0YUiOVL .error-icon{fill:#552222;}#mermaid-svg-Nx2W5HcOp0YUiOVL .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Nx2W5HcOp0YUiOVL .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Nx2W5HcOp0YUiOVL .marker.cross{stroke:#333333;}#mermaid-svg-Nx2W5HcOp0YUiOVL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Nx2W5HcOp0YUiOVL p{margin:0;}#mermaid-svg-Nx2W5HcOp0YUiOVL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Nx2W5HcOp0YUiOVL .cluster-label text{fill:#333;}#mermaid-svg-Nx2W5HcOp0YUiOVL .cluster-label span{color:#333;}#mermaid-svg-Nx2W5HcOp0YUiOVL .cluster-label span p{background-color:transparent;}#mermaid-svg-Nx2W5HcOp0YUiOVL .label text,#mermaid-svg-Nx2W5HcOp0YUiOVL span{fill:#333;color:#333;}#mermaid-svg-Nx2W5HcOp0YUiOVL .node rect,#mermaid-svg-Nx2W5HcOp0YUiOVL .node circle,#mermaid-svg-Nx2W5HcOp0YUiOVL .node ellipse,#mermaid-svg-Nx2W5HcOp0YUiOVL .node polygon,#mermaid-svg-Nx2W5HcOp0YUiOVL .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Nx2W5HcOp0YUiOVL .rough-node .label text,#mermaid-svg-Nx2W5HcOp0YUiOVL .node .label text,#mermaid-svg-Nx2W5HcOp0YUiOVL .image-shape .label,#mermaid-svg-Nx2W5HcOp0YUiOVL .icon-shape .label{text-anchor:middle;}#mermaid-svg-Nx2W5HcOp0YUiOVL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Nx2W5HcOp0YUiOVL .rough-node .label,#mermaid-svg-Nx2W5HcOp0YUiOVL .node .label,#mermaid-svg-Nx2W5HcOp0YUiOVL .image-shape .label,#mermaid-svg-Nx2W5HcOp0YUiOVL .icon-shape .label{text-align:center;}#mermaid-svg-Nx2W5HcOp0YUiOVL .node.clickable{cursor:pointer;}#mermaid-svg-Nx2W5HcOp0YUiOVL .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Nx2W5HcOp0YUiOVL .arrowheadPath{fill:#333333;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Nx2W5HcOp0YUiOVL .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Nx2W5HcOp0YUiOVL .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Nx2W5HcOp0YUiOVL .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Nx2W5HcOp0YUiOVL .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Nx2W5HcOp0YUiOVL .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Nx2W5HcOp0YUiOVL .cluster text{fill:#333;}#mermaid-svg-Nx2W5HcOp0YUiOVL .cluster span{color:#333;}#mermaid-svg-Nx2W5HcOp0YUiOVL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Nx2W5HcOp0YUiOVL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Nx2W5HcOp0YUiOVL rect.text{fill:none;stroke-width:0;}#mermaid-svg-Nx2W5HcOp0YUiOVL .icon-shape,#mermaid-svg-Nx2W5HcOp0YUiOVL .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Nx2W5HcOp0YUiOVL .icon-shape p,#mermaid-svg-Nx2W5HcOp0YUiOVL .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Nx2W5HcOp0YUiOVL .icon-shape .label rect,#mermaid-svg-Nx2W5HcOp0YUiOVL .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Nx2W5HcOp0YUiOVL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Nx2W5HcOp0YUiOVL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Nx2W5HcOp0YUiOVL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 外部服务
后端 API Routes
前端
玩家输入指令
前端解析命令类型
调用对应API
服务端构建LLM提示词
LLM生成叙事
服务端解析心情/亲密度变化
更新数据库 + 返回前端
Zustand更新状态 + 渲染UI
2.2 目录结构
| 目录/文件 | 职责说明 |
|---|---|
| src/app/ | Next.js App Router页面和布局 |
| src/app/api/ | 19个API路由端点 |
| src/components/game/ | 80+游戏组件(含31个小游戏) |
| src/components/ui/ | shadcn/ui基础组件库 |
| src/components/config/ | 6个配置中心组件 |
| src/hooks/ | 7个自定义React Hooks |
| src/lib/ | 24个核心业务逻辑模块 |
| src/app/styles/ | 9个CSS样式文件(~21K行) |
| prisma/ | 数据库模型定义和种子数据 |
| mini-services/ | 独立微服务(game-service) |
2.3 数据库设计
数据库采用SQLite,通过Prisma ORM管理,共定义11个模型:
| 模型名 | 用途 | 关键字段 |
|---|---|---|
| Character | 红楼梦角色 | name, personality(JSON), speechStyle, emotionalTendency, backgroundStories(JSON), taboos(JSON), avatarEmoji |
| Relationship | 角色间关系 | fromCharacterId, toCharacterId, type, affectionLevel(0-100) |
| GameSession | 游戏会话 | playerName, playerGender, currentLocation, currentPeriod, gamePhase, playerIdentity |
| Conversation | 对话记录 | sessionId, characterId, role, content, scene |
| CharacterState | 角色实时状态 | sessionId+characterId(联合唯一), currentMood, relationshipWithPlayer, isPresent |
| GameEvent | 游戏事件 | sessionId, eventType, title, content, choices(JSON) |
| GameConfig | 游戏配置 | name, config(JSON), isActive |
| Location | 大观园场景 | name, description, area, subLocations(JSON), characters(JSON), atmosphere |
| Inventory | 玩家物品 | sessionId+itemName(联合唯一), itemType, quantity |
| KnowledgeEntry | 知识库条目 | category, title, content, source, tags(JSON) |
| GameSave | 游戏存档 | saveName, playerName, sessionId, saveData(JSON) |
2.4 状态管理架构
项目采用双层状态管理策略:
2.4.1 Zustand全局状态 (game-store.ts)
- 单一数据源(Single Source of Truth),包含50+个action方法
- 管理内容:游戏会话、消息历史、角色状态、物品、任务、成就、天气、时辰、统计数据等
- 防抖持久化:1秒间隔写入localStorage,最多保留200条消息
2.4.2 角色记忆状态 (character-memory.ts)
- 独立Zustand store,管理玩家与各角色的互动记忆
- 记忆类型:conversation | gift | event | location_visit | examine | poetry | milestone
- 每角色最多50条记忆,独立localStorage持久化
2.4.3 内存存储
- 物品栏采用服务端内存Map存储(非DB,服务器重启丢失)
状态管理架构图
#mermaid-svg-zExGUywO5PFY1tUS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zExGUywO5PFY1tUS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zExGUywO5PFY1tUS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zExGUywO5PFY1tUS .error-icon{fill:#552222;}#mermaid-svg-zExGUywO5PFY1tUS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zExGUywO5PFY1tUS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zExGUywO5PFY1tUS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zExGUywO5PFY1tUS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zExGUywO5PFY1tUS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zExGUywO5PFY1tUS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zExGUywO5PFY1tUS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zExGUywO5PFY1tUS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zExGUywO5PFY1tUS .marker.cross{stroke:#333333;}#mermaid-svg-zExGUywO5PFY1tUS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zExGUywO5PFY1tUS p{margin:0;}#mermaid-svg-zExGUywO5PFY1tUS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zExGUywO5PFY1tUS .cluster-label text{fill:#333;}#mermaid-svg-zExGUywO5PFY1tUS .cluster-label span{color:#333;}#mermaid-svg-zExGUywO5PFY1tUS .cluster-label span p{background-color:transparent;}#mermaid-svg-zExGUywO5PFY1tUS .label text,#mermaid-svg-zExGUywO5PFY1tUS span{fill:#333;color:#333;}#mermaid-svg-zExGUywO5PFY1tUS .node rect,#mermaid-svg-zExGUywO5PFY1tUS .node circle,#mermaid-svg-zExGUywO5PFY1tUS .node ellipse,#mermaid-svg-zExGUywO5PFY1tUS .node polygon,#mermaid-svg-zExGUywO5PFY1tUS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zExGUywO5PFY1tUS .rough-node .label text,#mermaid-svg-zExGUywO5PFY1tUS .node .label text,#mermaid-svg-zExGUywO5PFY1tUS .image-shape .label,#mermaid-svg-zExGUywO5PFY1tUS .icon-shape .label{text-anchor:middle;}#mermaid-svg-zExGUywO5PFY1tUS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zExGUywO5PFY1tUS .rough-node .label,#mermaid-svg-zExGUywO5PFY1tUS .node .label,#mermaid-svg-zExGUywO5PFY1tUS .image-shape .label,#mermaid-svg-zExGUywO5PFY1tUS .icon-shape .label{text-align:center;}#mermaid-svg-zExGUywO5PFY1tUS .node.clickable{cursor:pointer;}#mermaid-svg-zExGUywO5PFY1tUS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zExGUywO5PFY1tUS .arrowheadPath{fill:#333333;}#mermaid-svg-zExGUywO5PFY1tUS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zExGUywO5PFY1tUS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zExGUywO5PFY1tUS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zExGUywO5PFY1tUS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zExGUywO5PFY1tUS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zExGUywO5PFY1tUS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zExGUywO5PFY1tUS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zExGUywO5PFY1tUS .cluster text{fill:#333;}#mermaid-svg-zExGUywO5PFY1tUS .cluster span{color:#333;}#mermaid-svg-zExGUywO5PFY1tUS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zExGUywO5PFY1tUS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zExGUywO5PFY1tUS rect.text{fill:none;stroke-width:0;}#mermaid-svg-zExGUywO5PFY1tUS .icon-shape,#mermaid-svg-zExGUywO5PFY1tUS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zExGUywO5PFY1tUS .icon-shape p,#mermaid-svg-zExGUywO5PFY1tUS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zExGUywO5PFY1tUS .icon-shape .label rect,#mermaid-svg-zExGUywO5PFY1tUS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zExGUywO5PFY1tUS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zExGUywO5PFY1tUS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zExGUywO5PFY1tUS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 服务端状态
客户端状态
防抖写入
独立写入
API调用
API调用
Zustand 全局状态
game-store.ts
Zustand 记忆状态
character-memory.ts
localStorage 持久化
SQLite Database
Prisma ORM
内存Map存储
物品栏
3 API接口设计
3.1 API路由总览
共计19个API端点,均基于Next.js API Routes实现:
| 路径 | 方法 | 功能说明 |
|---|---|---|
| /api | GET | 健康检查端点 |
| /api/config | GET/POST | 游戏配置读取/保存 |
| /api/characters | GET | 角色列表 |
| /api/locations | GET | 场景查询 |
| /api/game/start | POST | 创建新游戏会话 |
| /api/game/command | POST | 核心指令处理(LLM叙事) |
| /api/game/event | POST | 随机事件生成 |
| /api/game/move | POST | 地点移动 |
| /api/game/save | GET/POST/DELETE | 存档管理 |
| /api/game/load | POST | 读取存档 |
| /api/game/inventory | GET/POST | 物品栏管理 |
| /api/game/use-item | POST | 对角色使用物品 |
| /api/game/poetry | POST | 诗词创作 |
| /api/game/riddle | POST | 灯谜出题 |
| /api/game/riddle/answer | POST | 灯谜答题 |
| /api/game/locations | GET | 游戏内场景列表 |
| /api/game/schedule | POST | 时间推进与角色移动 |
| /api/game/session/id | GET | 获取会话详情 |
| /api/test-connection | GET | LLM连接测试 |
3.2 核心API详细设计
3.2.1 POST /api/game/command --- 核心指令处理
这是整个游戏的核心API,负责处理玩家的所有文字指令并返回LLM生成的叙事内容。
请求参数:
sessionId: 游戏会话IDcommand: 玩家输入的指令文本characterId: 目标角色ID(可选)providerConfig: LLM提供商配置weather: 天气描述useItemName: 使用的物品名memoriesText/allMemoriesText: 角色记忆文本
处理流程:
#mermaid-svg-LPnyDPOngsldK7Df{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LPnyDPOngsldK7Df .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LPnyDPOngsldK7Df .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LPnyDPOngsldK7Df .error-icon{fill:#552222;}#mermaid-svg-LPnyDPOngsldK7Df .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LPnyDPOngsldK7Df .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LPnyDPOngsldK7Df .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LPnyDPOngsldK7Df .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LPnyDPOngsldK7Df .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LPnyDPOngsldK7Df .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LPnyDPOngsldK7Df .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LPnyDPOngsldK7Df .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LPnyDPOngsldK7Df .marker.cross{stroke:#333333;}#mermaid-svg-LPnyDPOngsldK7Df svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LPnyDPOngsldK7Df p{margin:0;}#mermaid-svg-LPnyDPOngsldK7Df .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LPnyDPOngsldK7Df .cluster-label text{fill:#333;}#mermaid-svg-LPnyDPOngsldK7Df .cluster-label span{color:#333;}#mermaid-svg-LPnyDPOngsldK7Df .cluster-label span p{background-color:transparent;}#mermaid-svg-LPnyDPOngsldK7Df .label text,#mermaid-svg-LPnyDPOngsldK7Df span{fill:#333;color:#333;}#mermaid-svg-LPnyDPOngsldK7Df .node rect,#mermaid-svg-LPnyDPOngsldK7Df .node circle,#mermaid-svg-LPnyDPOngsldK7Df .node ellipse,#mermaid-svg-LPnyDPOngsldK7Df .node polygon,#mermaid-svg-LPnyDPOngsldK7Df .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LPnyDPOngsldK7Df .rough-node .label text,#mermaid-svg-LPnyDPOngsldK7Df .node .label text,#mermaid-svg-LPnyDPOngsldK7Df .image-shape .label,#mermaid-svg-LPnyDPOngsldK7Df .icon-shape .label{text-anchor:middle;}#mermaid-svg-LPnyDPOngsldK7Df .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LPnyDPOngsldK7Df .rough-node .label,#mermaid-svg-LPnyDPOngsldK7Df .node .label,#mermaid-svg-LPnyDPOngsldK7Df .image-shape .label,#mermaid-svg-LPnyDPOngsldK7Df .icon-shape .label{text-align:center;}#mermaid-svg-LPnyDPOngsldK7Df .node.clickable{cursor:pointer;}#mermaid-svg-LPnyDPOngsldK7Df .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LPnyDPOngsldK7Df .arrowheadPath{fill:#333333;}#mermaid-svg-LPnyDPOngsldK7Df .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LPnyDPOngsldK7Df .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LPnyDPOngsldK7Df .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LPnyDPOngsldK7Df .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LPnyDPOngsldK7Df .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LPnyDPOngsldK7Df .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LPnyDPOngsldK7Df .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LPnyDPOngsldK7Df .cluster text{fill:#333;}#mermaid-svg-LPnyDPOngsldK7Df .cluster span{color:#333;}#mermaid-svg-LPnyDPOngsldK7Df div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LPnyDPOngsldK7Df .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LPnyDPOngsldK7Df rect.text{fill:none;stroke-width:0;}#mermaid-svg-LPnyDPOngsldK7Df .icon-shape,#mermaid-svg-LPnyDPOngsldK7Df .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LPnyDPOngsldK7Df .icon-shape p,#mermaid-svg-LPnyDPOngsldK7Df .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LPnyDPOngsldK7Df .icon-shape .label rect,#mermaid-svg-LPnyDPOngsldK7Df .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LPnyDPOngsldK7Df .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LPnyDPOngsldK7Df .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LPnyDPOngsldK7Df :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
接收指令
命令类型检测
角色昵称匹配
构建LLM系统提示词
调用LLM生成叙事
提取角色对话
解析心情变化
计算亲密度变化
检测里程碑?
触发特殊剧情
角色日程移动
批量更新数据库
返回叙事结果
- 命令类型检测:支持"观察/查看"、"送礼给"等特殊命令
- 角色昵称匹配:内置8位角色别名映射(如"黛儿""林妹妹" -> 林黛玉)
- LLM叙事生成:构建LLM系统提示词(含地点、时段、在场角色、玩家身份、天气、记忆)
- 对话提取:正则提取"X道:「...」"格式的角色对话
- 心情解析:关键词在角色名字30字符范围内检测心情变化(10种心情,6级强度)
- 亲密度计算:基于叙事中的正/负信号(强+5、中+3、弱+1)+ 额外加成
- 亲密度里程碑:检测跨阈值事件,触发LLM生成特殊剧情
- 角色日程:时间流逝命令触发1-3个角色的加权随机移动
- DB更新:批量更新角色状态(心情、亲密度、在场状态、位置)
3.2.2 POST /api/game/start --- 开始新游戏
创建游戏会话,初始地点为荣国府大门,时段为"乾隆二十五年春",阶段为intro。根据起始地点的characters字段决定初始在场角色,初始亲密度50。
3.2.3 POST /api/game/event --- 随机事件
6种事件类型随机触发:character_appear(角色出现)、invitation(邀请)、rumor(谣言)、poetry_contest(诗词大会)、riddle_game(灯谜游戏)、其他。20%概率获得随机物品。
3.2.4 POST /api/game/schedule --- 时间推进
时段循环:晨 -> 午 -> 昏 -> 夜 -> 翠日晨。角色移动基于性格系统:mobility(偏离日程概率)、stayHomeProbability(留在原地概率)、preferredLocations(偏好地点)。时间段影响角色心情。
3.2.5 POST /api/game/save & /api/game/load --- 存读档
存档包含完整游戏状态(session + 角色状态 + 消息历史 + 地点数据)。读档采用多层回退策略:存档数据优先,DB数据兜底。
4 核心业务系统设计
4.1 LLM引擎系统 (llm-engine.ts)
4.1.1 双Provider支持
- z-ai SDK:智谱AI官方SDK,30s超时
- 自定义OpenAI兼容API :自动拼接
/v1/chat/completions端点
4.1.2 系统提示词架构
系统提示词包含以下内容:
- 世界观设定:清代乾隆年间,贾府大观园
- 叙事规则:文白夹杂,禁止现代用语
- 在场角色约束:硬性规则,仅在场角色可参与对话
- 玩家身份:性别影响NPC称呼
- 时辰氛围映射:10个时辰关键词对应不同氛围描述
- 角色记忆注入:格式化的近期互动记忆
4.1.3 分场景温度控制
- 对话(dialog):temperature=0.7
- 情节(plot):temperature=0.6
- 诗词(poetry):temperature=0.85
4.2 角色系统
4.2.1 角色定义
共8位经典角色,每位角色具备丰富的属性定义:
| 角色 | 居所 | 性格特质 | emoji |
|---|---|---|---|
| 林黛玉 | 潇湘馆 | 多愁善感、才情绝世 | 🌸 |
| 贾宝玉 | 怡红院 | 风流倚信、多情善感 | 🎭 |
| 薛宝钗 | 蘅芜苑 | 稳重端庄、学识渊博 | 🪐 |
| 王熙凤 | --- | 精明强干、张狂火爆 | 🔥 |
| 贾母 | --- | 慈祥和蔼、享受富贵 | 🌿 |
| 史湘云 | --- | 英豪阔大、心直口快 | 🐦 |
| 妙玉 | 栊翠庵 | 孤高如雪、才情出众 | 🏛 |
| 袭人 | --- | 温柔贤惠、小心翼翼 | 💡 |
4.2.2 角色日程系统 (character-schedule.ts)
按时辰(辰时到亥时)定义8个角色的位置与活动。服务端时间推进时,角色根据mobility、stayHomeProbability、preferredLocations等性格参数进行加权随机移动。
4.2.3 角色记忆系统 (character-memory.ts)
管理玩家与各角色的互动记忆,用于LLM上下文注入。每角色最多50条记忆,支持相对时间标签显示(刚刚/一刻钟前/半时辰前/一时辰前等)。
4.3 亲密度与关系系统
4.3.1 亲密度计算机制
- 基础范围:0-100,初始值50
- 叙事信号:强正面+5、中正面+3、弱正面+1;强负面-5、中负面-3、弱负面-1
- 额外加成:送礼+3~8、拜访+2、使用物品+5
4.3.2 亲密度等级映射
| 数值范围 | 中文标签 | 颜色 |
|---|---|---|
| 95+ | 至死不渝 | 中国红 |
| 80+ | 心心相印 | 翠绿 |
| 70+ | 亲密无间 | 绿色 |
| 60+ | 惺惺相惜 | 黄色 |
| 50+ | 友善相待 | 黄绿 |
| 40+ | 点头之交 | 褐色 |
| 30+ | 萍水相逢 | 灰色 |
| 20+ | 心存隔阂 | 深褐 |
| <20 | 形同陌路 | 暗红 |
4.3.3 关系事件系统 (relationship-events.ts)
8个角色 x 5个阈值级别 = 40个关系事件。当亲密度跨越特定阈值(30/50/70/80/95)时触发特殊剧情事件,每个事件含有叙事、选择项和奖励。
示例(林黛玉线):潇湘初访(30) -> 共读西厢(50) -> 黛玉葬花(70) -> 泪尽还泪(80) -> 木石前盟(95)
4.4 场景系统
4.4.1 场景定义
共10个大观园场景,每个场景含4个子场景,共计40个可探索地点:
| 场景 | 区域 | 子场景 |
|---|---|---|
| 怡红院 | 大观园 | 书房/卧室/后花园/小厨房 |
| 潇湘馆 | 大观园 | 琴房/竹轩/葬花台/书斋 |
| 蘅芜苑 | 大观园 | 暖阁/药房/花圃/客厅 |
| 稻香村 | 大观园 | 农舍/菜园/井台/碾坊 |
| 秋爽斋 | 大观园 | 画室/绣阁/书房/暖房 |
| 藕香榭 | 大观园 | 水榭/荷塘/画舫/曲廊 |
| 栊翠庵 | 大观园 | 佛堂/茶室/净室/竹林 |
| 紫菱洲 | 大观园 | 闺房/绣楼/水阁/花厅 |
| 暖香坞 | 大观园 | 暖房/书房/妆台/后院 |
| 省亲别墅 | 大观园 | 正殿/侧殿/戏台/庭院 |
4.4.2 天气系统
7种天气状态:sunny(晴)、cloudy(多云)、light_rain(小雨)、heavy_rain(大雨)、snow(雪)、fog(雾)、windy(风)。每种天气对应独特的粒子效果和环境音效。
4.5 任务系统 (quest-system.ts)
4.5.1 主线任务(8个)
- 初入贾府 -> 结缘黛玉 -> 海棠诗社 -> 元春省亲 -> 宝玉挨打 -> 黛玉葬花 -> 金玉良缘 -> 大观园主
4.5.2 支线任务(5个)
- 花间寻诗、灯谜高手、义结金兰、求签问卦、收藏鉴赏
4.5.3 每日任务
每天自动生成3个随机任务(吟诗/社交/探索),完成后获得奖励。
4.6 成就系统 (achievements.ts)
157+个成就,分6大类别,4级稀有度:
| 类别 | 示例成就 | 稀有度 |
|---|---|---|
| 探索 | 初入贾府、游遍大观园、足遍天下(50次移动) | common~legendary |
| 社交 | 初次交谈、知己难求(80+)、众星捧月(全员50+) | common~epic |
| 文采 | 诗词新秀(1首)、才高八斗(5首)、诗仙(10首) | common~legendary |
| 收藏 | 初获宝物、收藏家(5件)、珍宝如山(10件) | common~rare |
| 时间 | 朝花夕拾、百年修行、千古风流 | rare~legendary |
| 小游戏 | 投壶/围棋/古琴/花灯/七巧板/剪窗花等20+种 | common~legendary |
4.7 小游戏系统
共31个中国传统小游戏,涵盖多种类型:
| 类型 | 小游戏 |
|---|---|
| 投壶射箭 | 投壶、射覆、踢毽子 |
| 棋类对弈 | 围棋、象棋残局、华容道、打马图经 |
| 诗词文学 | 飞花令、对联、诗社、接龙诗、牙牌令、诗词多米诺 |
| 手工艺术 | 花灯制作、七巧板、扇面画、剪窗花、风筝制作 |
| 音乐开心 | 古琴、抖空竹 |
| 派对社交 | 划拳、藏钩、击鼓传花、摇骰行令 |
| 智力挑战 | 灯谜、九宫格灯谜、记忆宫殿、记忆翻牌、问答 |
| 其他 | 日运、茶道、梦境、礼物、信件、社交八卦、场景探索、草药制作、赛马、叶子戏 |
4.8 文化系统
4.8.1 茶道系统
- 12种名茶数据(龙井/碧螺春/铁观音/大红袍/普洱/白毫银针等)
- 茶与心情联动:8种茶对8个角色的心情效果矩阵
- 季节推荐:评分算法(季节匹配30分 + 角色偏好加分 + 最爱茶额外加分)
4.8.2 节日系统 (lunar-festivals.ts + event-calendar.ts)
- 8个传统节日:春节/元宵节/清明节/端午节/七夕节/中秋节/重阳节/除夕
- 24个季节事件(每季6个),包含节日、生辰、季节活动等
4.8.3 梦境系统 (dream-interpretation.ts)
- 10个红楼梦主题梦境,各有解锁条件
- 梦境心情:mysterious/romantic/melancholy/joyful/profound/tragic/elegant/lively
4.8.4 礼物系统 (gift-system.ts)
- 24种礼物,6大类别:文房雅器/珠宝玉器/茶道香道/绣品织物/花卉草木/实用器物
- 8个角色的礼物偏好矩阵(loved/liked/disliked/neutral)
- 效果计算:loved +15~20,liked +8~12,neutral +3~5,disliked -5~-10
4.9 物品合成系统 (item-crafting.ts)
10个合成配方,支持消耗材料合成新物品。包含传说级配方"红楼遗梦"(鹰鸢佩 + 金玉缘卷)。
4.10 知识库系统 (red-chamber-knowledge.ts)
红楼梦经典知识数据库,用于RAG增强LLM叙事质量。约24条知识条目,分5类:
- 诗词(8首):开篇诗、判词、葬花吟、题帕三绝、秋窗风雨夕等
- 名句(6条):宝黛初见、宝玉外貌、护官符、好了歌等
- 场景(5个):大观园、潇湘馆、怡红院、蘅芜苑、荣国府正堂
- 角色(5个):贾宝玉、林黛玉、薛宝钗、王熙凤、史湘云
- 事件(3个):元春省亲、黛玉葬花、宝玉挨打
根据地点/角色/关键词检索相关知识,最多返回5条,注入LLM上下文。
4.11 音效系统 (sound-manager.ts)
基于Web Audio API的纯程序化音效系统,无需音频文件。中国风五声音阶。
- 环境音:雨声、风声、晴天、雪天、雾天
- 交互音:点击、移动、成就、通知、打字机
- 背景音乐:随机五声音阶旋律,带颤音效果
5 前端界面设计
5.1 页面布局
游戏采用三栏布局设计,移动端自动切换为Tab导航:
| 区域 | 桌面端 | 移动端 | 内容 |
|---|---|---|---|
| 左侧栏 | 角色面板 | Tab1: 叙事 | CharacterPanel(角色卡片+关系雷达图) |
| 中央区域 | 叙事+输入 | Tab2: 角色 | NarrativePanel + CommandInput |
| 右侧栏 | 操作面板 | Tab3: 操作 | SidePanel(31个小游戏+功能入口) |
前端布局示意图
#mermaid-svg-EsaXrUaymVmNK2M7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EsaXrUaymVmNK2M7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EsaXrUaymVmNK2M7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EsaXrUaymVmNK2M7 .error-icon{fill:#552222;}#mermaid-svg-EsaXrUaymVmNK2M7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EsaXrUaymVmNK2M7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EsaXrUaymVmNK2M7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EsaXrUaymVmNK2M7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EsaXrUaymVmNK2M7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EsaXrUaymVmNK2M7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EsaXrUaymVmNK2M7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EsaXrUaymVmNK2M7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EsaXrUaymVmNK2M7 .marker.cross{stroke:#333333;}#mermaid-svg-EsaXrUaymVmNK2M7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EsaXrUaymVmNK2M7 p{margin:0;}#mermaid-svg-EsaXrUaymVmNK2M7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EsaXrUaymVmNK2M7 .cluster-label text{fill:#333;}#mermaid-svg-EsaXrUaymVmNK2M7 .cluster-label span{color:#333;}#mermaid-svg-EsaXrUaymVmNK2M7 .cluster-label span p{background-color:transparent;}#mermaid-svg-EsaXrUaymVmNK2M7 .label text,#mermaid-svg-EsaXrUaymVmNK2M7 span{fill:#333;color:#333;}#mermaid-svg-EsaXrUaymVmNK2M7 .node rect,#mermaid-svg-EsaXrUaymVmNK2M7 .node circle,#mermaid-svg-EsaXrUaymVmNK2M7 .node ellipse,#mermaid-svg-EsaXrUaymVmNK2M7 .node polygon,#mermaid-svg-EsaXrUaymVmNK2M7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EsaXrUaymVmNK2M7 .rough-node .label text,#mermaid-svg-EsaXrUaymVmNK2M7 .node .label text,#mermaid-svg-EsaXrUaymVmNK2M7 .image-shape .label,#mermaid-svg-EsaXrUaymVmNK2M7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-EsaXrUaymVmNK2M7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EsaXrUaymVmNK2M7 .rough-node .label,#mermaid-svg-EsaXrUaymVmNK2M7 .node .label,#mermaid-svg-EsaXrUaymVmNK2M7 .image-shape .label,#mermaid-svg-EsaXrUaymVmNK2M7 .icon-shape .label{text-align:center;}#mermaid-svg-EsaXrUaymVmNK2M7 .node.clickable{cursor:pointer;}#mermaid-svg-EsaXrUaymVmNK2M7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EsaXrUaymVmNK2M7 .arrowheadPath{fill:#333333;}#mermaid-svg-EsaXrUaymVmNK2M7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EsaXrUaymVmNK2M7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EsaXrUaymVmNK2M7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EsaXrUaymVmNK2M7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EsaXrUaymVmNK2M7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EsaXrUaymVmNK2M7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EsaXrUaymVmNK2M7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EsaXrUaymVmNK2M7 .cluster text{fill:#333;}#mermaid-svg-EsaXrUaymVmNK2M7 .cluster span{color:#333;}#mermaid-svg-EsaXrUaymVmNK2M7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EsaXrUaymVmNK2M7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EsaXrUaymVmNK2M7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-EsaXrUaymVmNK2M7 .icon-shape,#mermaid-svg-EsaXrUaymVmNK2M7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EsaXrUaymVmNK2M7 .icon-shape p,#mermaid-svg-EsaXrUaymVmNK2M7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EsaXrUaymVmNK2M7 .icon-shape .label rect,#mermaid-svg-EsaXrUaymVmNK2M7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EsaXrUaymVmNK2M7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EsaXrUaymVmNK2M7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EsaXrUaymVmNK2M7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 移动端布局
切换
切换
Tab1: 叙事
Tab2: 角色
Tab3: 操作
桌面端布局
左侧栏
角色面板
中央区域
叙事+输入
右侧栏
操作面板
5.2 核心组件设计
5.2.1 NarrativePanel(叙事面板)
- 消息类型:叙事、玩家、角色对话、系统消息、诗词
- 打字机效果:最新叙事逐字显示(50ms/字),可点击跳过
- Markdown渲染:自定义中文小说样式(首行缩进、行高、引用金色边框)
- 自动滚动到底部 + 新消息浮动按钮
5.2.2 CommandInput(命令输入)
- 命令别名系统:缩写自动展开("诗"->吟诗,"看"->观察四周)
- 命令检测与分发:move/visit/gift/give/use等类型自动识别
- 16个快捷命令按钮
- 自动补全 + 历史记录(上下箭头翻阅)
5.2.3 CharacterPanel(角色面板)
- CharacterCard(React.memo优化):头像+心情+性格标签+亲密度进度条
- 展开详情:个性描述、背景故事、亲密度趋势图、里程碑徽章
- RelationshipRadar:SVG雷达图展示全角色亲密度
5.2.4 SidePanel(操作面板)
集成了游戏的所有功能入口,包括:物品栏、游戏日志、关系图谱、统计面板、灯谜、记忆回溯、成就、任务、小地图、每日运势、时间线、结局,以及31个小游戏入口。
5.3 自定义Hooks
| Hook | 职责 | 返回值 |
|---|---|---|
| useGameActions | 游戏动作封装 | handlePoetry/handleRandomEvent/handleTimePassage/handleMoveToLocation |
| useSaveLoad | 存读档逻辑 | handleSaveGame/handleLoadGame/handleDeleteSave |
| useAchievementChecker | 成就检查(60+条件) | checkAchievements/checkAndUpdateQuests |
| useRelationshipEvents | 关系事件触发 | checkAndTriggerRelationshipEvents/handleRelEventChoice |
| useMobile | 移动端检测 | isMobile |
| useSwipe | 滑动手势 | 滑动方向检测 |
| useToast | Toast通知 | toast函数 |
5.4 CSS样式体系
9个CSS文件,共计21K+行:
| 文件 | 内容 |
|---|---|
| globals.css | 全局样式导入 |
| base.css | 基础样式变量 |
| components.css | 组件通用样式 |
| decorative.css | 装饰增强(22个装饰类) |
| animations.css | 动画定义(250+自定义动画) |
| game-specific.css | 游戏专属样式(~6900行) |
| responsive.css | 响应式布局 |
| seasonal.css | 季节主题 |
| weather.css | 天气粒子效果 |
| scrollbar.css | 自定义滚动条 |
CSS微交互系统:32个组件交互类 + 22个装饰增强 + 250+自定义动画。
6 配置与部署
6.1 配置中心
游戏提供完整的配置中心(ConfigCenter),包含6个子模块:
- ModelConfig:LLM模型选择(Provider、模型类型、API Key)
- ParameterTuning:分场景温度调节(对话/情节/诗词)
- CharacterManagement:角色启用/禁用管理
- KnowledgeBase:知识库管理
- AdvancedSettings:高级设置
6.2 部署架构
- 运行时:Bun
- Next.js输出模式:standalone(适合Docker/容器化)
- 反向代理:Caddy,监听端口81,代理到localhost:3000
- 开发模式:支持XTransformPort查询参数动态端口代理
- TypeScript构建:忽略类型错误(ignoreBuildErrors: true)
部署架构图
#mermaid-svg-xM20IQGFgPEyeZzR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xM20IQGFgPEyeZzR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xM20IQGFgPEyeZzR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xM20IQGFgPEyeZzR .error-icon{fill:#552222;}#mermaid-svg-xM20IQGFgPEyeZzR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xM20IQGFgPEyeZzR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xM20IQGFgPEyeZzR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xM20IQGFgPEyeZzR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xM20IQGFgPEyeZzR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xM20IQGFgPEyeZzR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xM20IQGFgPEyeZzR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xM20IQGFgPEyeZzR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xM20IQGFgPEyeZzR .marker.cross{stroke:#333333;}#mermaid-svg-xM20IQGFgPEyeZzR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xM20IQGFgPEyeZzR p{margin:0;}#mermaid-svg-xM20IQGFgPEyeZzR .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xM20IQGFgPEyeZzR .cluster-label text{fill:#333;}#mermaid-svg-xM20IQGFgPEyeZzR .cluster-label span{color:#333;}#mermaid-svg-xM20IQGFgPEyeZzR .cluster-label span p{background-color:transparent;}#mermaid-svg-xM20IQGFgPEyeZzR .label text,#mermaid-svg-xM20IQGFgPEyeZzR span{fill:#333;color:#333;}#mermaid-svg-xM20IQGFgPEyeZzR .node rect,#mermaid-svg-xM20IQGFgPEyeZzR .node circle,#mermaid-svg-xM20IQGFgPEyeZzR .node ellipse,#mermaid-svg-xM20IQGFgPEyeZzR .node polygon,#mermaid-svg-xM20IQGFgPEyeZzR .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xM20IQGFgPEyeZzR .rough-node .label text,#mermaid-svg-xM20IQGFgPEyeZzR .node .label text,#mermaid-svg-xM20IQGFgPEyeZzR .image-shape .label,#mermaid-svg-xM20IQGFgPEyeZzR .icon-shape .label{text-anchor:middle;}#mermaid-svg-xM20IQGFgPEyeZzR .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xM20IQGFgPEyeZzR .rough-node .label,#mermaid-svg-xM20IQGFgPEyeZzR .node .label,#mermaid-svg-xM20IQGFgPEyeZzR .image-shape .label,#mermaid-svg-xM20IQGFgPEyeZzR .icon-shape .label{text-align:center;}#mermaid-svg-xM20IQGFgPEyeZzR .node.clickable{cursor:pointer;}#mermaid-svg-xM20IQGFgPEyeZzR .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xM20IQGFgPEyeZzR .arrowheadPath{fill:#333333;}#mermaid-svg-xM20IQGFgPEyeZzR .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xM20IQGFgPEyeZzR .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xM20IQGFgPEyeZzR .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xM20IQGFgPEyeZzR .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xM20IQGFgPEyeZzR .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xM20IQGFgPEyeZzR .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xM20IQGFgPEyeZzR .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xM20IQGFgPEyeZzR .cluster text{fill:#333;}#mermaid-svg-xM20IQGFgPEyeZzR .cluster span{color:#333;}#mermaid-svg-xM20IQGFgPEyeZzR div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xM20IQGFgPEyeZzR .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xM20IQGFgPEyeZzR rect.text{fill:none;stroke-width:0;}#mermaid-svg-xM20IQGFgPEyeZzR .icon-shape,#mermaid-svg-xM20IQGFgPEyeZzR .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xM20IQGFgPEyeZzR .icon-shape p,#mermaid-svg-xM20IQGFgPEyeZzR .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xM20IQGFgPEyeZzR .icon-shape .label rect,#mermaid-svg-xM20IQGFgPEyeZzR .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xM20IQGFgPEyeZzR .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xM20IQGFgPEyeZzR .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xM20IQGFgPEyeZzR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 构建配置
服务器
81端口
代理请求
运行时
数据读写
调用
standalone输出
ignoreBuildErrors: true
用户
Caddy 反向代理
Next.js 应用
localhost:3000
Bun
SQLite数据库
智谱AI LLM API
Build Script
TypeScript 编译
7 已知问题与优化方向
| 问题 | 描述 | 建议优化方向 |
|---|---|---|
| 服务器内存压力 | 需NODE_OPTIONS="--max-old-space-size=4096" | 优化LLM调用和组件渲染性能 |
| 物品系统内存存储 | 服务器重启后物品丢失 | 迁移到Prisma DB持久化 |
| 成就检查不稳定 | 部分依赖字符串匹配 | 采用结构化事件触发机制 |
| game-specific.css过大 | 已增长至~6900行 | 拆分为小游戏独立样式文件 |
| Safari音效兼容性 | Web Audio API在Safari上可能有问题 | 添加兼容性检测和降级方案 |
| agent-browser OOM | 浏览器自动化存在内存溢出 | 优化内存使用策略 |
8 总结
"红楼梦·梦境漫游"是一个技术复杂度较高的AI驱动文字游戏项目,总代码量约88K行。项目将红楼梦的文学传统与现代AI技术深度融合,构建了一个包含角色系统、场景系统、任务系统、成就系统、小游戏系统、文化系统等多层次玩法的完整游戏世界。
核心亮点包括:LLM驱动的动态叙事、红楼梦知识库RAG增强、丰富的角色亲密度与关系事件系统、31个中国传统小游戏、以及精心设计的视觉效果(天气粒子、季节氛围、中国风音效)。