133 lines
7.3 KiB
Markdown
133 lines
7.3 KiB
Markdown
|
||
|
||
# 🏗️ 《Project Vibe: Ship It!》技术架构与关键路径文档
|
||
|
||
## 一、 技术栈选型 (Tech Stack)
|
||
*强烈建议在 Prompt 开头向 AI 明确这些技术栈,防止它自由发挥引入不兼容的库。*
|
||
|
||
* **前端框架:** React 18 + Vite (纯前端单页应用,无需复杂后端)。
|
||
* **开发语言:** TypeScript (*极其重要!严格的类型定义是 VibeCoding 不翻车的绝对保障*)。
|
||
* **状态管理:** **Zustand** (*极其推荐!比 Redux 轻量,比 Context 性能好,非常适合做游戏的状态机更新*)。
|
||
* **样式方案:** Tailwind CSS (*方便 AI 快速生成好看的 UI 卡片*)。
|
||
* **拖拽与动画:** `framer-motion` (处理卡牌平滑移动和翻转动画) + 原生 Pointer Events (处理自由桌面坐标 x,y,**不要用**基于网格的 `dnd-kit`,因为我们要的是类似操作系统的无网格自由堆叠)。
|
||
|
||
---
|
||
|
||
## 二、 核心数据模型 (Data Models - TypeScript Interfaces)
|
||
*这是整个系统的灵魂。第一步请先让 AI 生成并完善这些类型定义文件 `types.ts`。有了它,AI 后续写的逻辑就不会乱。*
|
||
|
||
```typescript
|
||
// 1. 卡牌基础模型
|
||
export interface Card {
|
||
id: string; // 唯一实例ID (uuid)
|
||
templateId: string; // 对应配置表中的模板ID (如 'dirty_code', 'developer')
|
||
name: string;
|
||
type: 'resource' | 'actor' | 'module' | 'crisis' | 'warning';
|
||
tags: string[]; // 用于危机判定 (如 ['need_test', 'human'])
|
||
position: { x: number; y: number }; // 桌面绝对坐标
|
||
status: 'idle' | 'dragging' | 'processing' | 'locked';
|
||
processEndTime?: number; // 如果在合成中/危机倒计时中,记录结束的绝对时间戳
|
||
}
|
||
|
||
// 2. 合成配方模型 (静态配置)
|
||
export interface Recipe {
|
||
id: string;
|
||
inputs: string[]; // 需要堆叠在一起的 templateId 列表
|
||
timeMs: number; // 合成所需时间 (毫秒)
|
||
outputs: Array<{ templateId: string; chance: number }>; // 产物及概率
|
||
}
|
||
|
||
// 3. 全局状态模型 (Zustand Store)
|
||
export interface GameState {
|
||
// --- 资源状态 ---
|
||
funds: number; // 项目资金
|
||
sprintTimeRemaining: number; // 当前 Sprint 倒计时
|
||
sprintNumber: number; // 当前处于第几个 Sprint
|
||
|
||
// --- 实体状态 ---
|
||
cards: Card[]; // 桌面上的所有卡牌
|
||
|
||
// --- 核心方法 (Actions) ---
|
||
moveCard: (id: string, x: number, y: number) => void;
|
||
checkCraftingOverlap: (cardId: string) => void; // 碰撞检测核心逻辑
|
||
tick: () => void; // 游戏主循环 (Game Loop),每帧/每秒更新状态
|
||
triggerCrisis: (crisisData: any) => void; // LLM 返回后触发危机
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 三、 系统架构设计图 (单向数据流)
|
||
|
||
游戏的架构必须是**数据驱动**的。UI 层只负责渲染 `cards` 数组和捕获鼠标事件。
|
||
|
||
```text
|
||
[ 玩家操作 (拖拽/松开) ]
|
||
│
|
||
▼
|
||
[ 操作意图 (Pointer Events) ] ──发起请求──> [ LLM 危机生成器 (异步 Hook) ]
|
||
│ │ (返回JSON/超时Fallback)
|
||
▼ ▼
|
||
[ 碰撞检测算法 (Overlap Check) ] [ 触发预警卡/危机卡生成 ]
|
||
│ │
|
||
▼ (匹配 Recipe) │
|
||
[ 状态机引擎 (Zustand Store) ] <─────────────────┘
|
||
├─ 更新卡牌坐标 (x, y)
|
||
├─ 锁定卡牌状态 (status: 'processing')
|
||
└─ 结算资金/时间 (funds, sprintTime)
|
||
│
|
||
▼ (响应式数据)
|
||
[ React 渲染层 (UI Components) ]
|
||
├─ <DesktopBoard /> (渲染桌面)
|
||
├─ <CardItem /> (渲染实体卡、倒计时进度条)
|
||
└─ <HudPanel /> (渲染顶部资金与时间)
|
||
```
|
||
|
||
---
|
||
|
||
## 四、 VibeCoding 关键开发路径 (Critical Paths)
|
||
|
||
千万不要让 AI “一口气写一个游戏”。请严格按照以下 4 个阶段向 AI 提需求 (Prompting),每完成一个阶段就运行测试,确保无误后再进入下一阶段。
|
||
|
||
### 🚩 Path 1: 静态桌面与全局状态 (The Dumb Board)
|
||
* **目标:** 能在屏幕上看到几张卡牌,资金数值能够显示。
|
||
* **AI 任务:**
|
||
1. 初始化 Vite + React + TS 项目,安装 Zustand 和 Tailwind。
|
||
2. 创建 `types.ts`(使用上面提供的模型)。
|
||
3. 创建 `useGameStore.ts` (Zustand),初始化几个假数据卡牌 (Mock Data)。
|
||
4. 编写 `<DesktopBoard>` 和 `<CardItem>` 组件。卡牌通过绝对定位 `absolute` 渲染在对应的 `x, y` 坐标上。
|
||
|
||
### 🚩 Path 2: 自由拖拽与物理碰撞 (Drag & Collision)
|
||
* **目标:** 卡牌可以被鼠标拖拽,松开鼠标时,能判断两张卡是否重叠。
|
||
* **AI 任务:**
|
||
1. 给 `<CardItem>` 添加 `onPointerDown`, `onPointerMove`, `onPointerUp` 事件。
|
||
2. 拖拽时,调用 Store 的 `moveCard` 更新坐标。
|
||
3. **核心算法要求:** 松开鼠标时,写一个 `checkOverlap(cardA, cardB)` 函数(通过判断两个矩形的边界 bounding box 是否相交)。如果重叠率超过 50%,则判定为“堆叠成功”。
|
||
|
||
### 🚩 Path 3: 核心主循环与合成引擎 (Game Loop & Crafting)
|
||
* **目标:** 两张配方卡重叠 -> 出现进度条 -> 读条结束产出新卡。
|
||
* **AI 任务:**
|
||
1. 创建一个静态的 `recipes.json`(包含 2-3 个基础配方,如 开发+脏代码=模块)。
|
||
2. 在 Store 中创建一个 `requestAnimationFrame` 或 `setInterval(..., 100)` 驱动的 `tick()` 函数(这就是游戏的心跳)。
|
||
3. **合成逻辑:** 当卡牌重叠,匹配 JSON 成功后,将这两张卡的 status 改为 `processing`,并设置 `processEndTime = Date.now() + timeMs`。
|
||
4. **心跳检测:** `tick()` 函数不断检查当前时间是否超过 `processEndTime`。如果超过,删除旧卡,在原坐标 `push` 新产物卡。
|
||
|
||
### 🚩 Path 4: LLM 异步危机总线 (The AI Director)
|
||
* **目标:** 每隔一段时间,根据桌面状态调用大模型,生成危机并处理延迟。
|
||
* **AI 任务:**
|
||
1. 编写一个独立的服务 `llmService.ts`。
|
||
2. 编写 `useCrisisDirector` 自定义 Hook。内部设置一个定时器(如每 30 秒触发一次)。
|
||
3. **时序逻辑编写:**
|
||
* 触发时:立刻在 Store 中推入一张 status 为 `processing` 的【预警卡】。
|
||
* 发起 `fetch` 请求调用 OpenAI/DeepSeek API,附带当前桌面状态。
|
||
* 使用 `Promise.race` 实现 5 秒超时控制。
|
||
* 请求 Resolve (或超时 Fallback) 时:将那张【预警卡】的数据替换为真实的【危机卡】JSON 数据,并开始倒计时。
|
||
|
||
---
|
||
|
||
## 💡 给开发者的 VibeCoding 避坑指南 (Tips)
|
||
|
||
1. 这是一个自由摆放的桌面,没有列表,只需要纯粹的 x,y 坐标更新和基于宽高的 AABB (Axis-Aligned Bounding Box) 碰撞检测。”
|
||
2. 在 Zustand store 里统一管理所有状态。**不要**在 `<CardItem>` 组件内部用 `useState` 去管理倒计时!所有的进度条渲染都应该基于 `processEndTime - Date.now()` 实时计算得出,这样即使组件卸载/重新渲染,进度也不会丢失。
|
||
3. **Mock First:** 在写 LLM 接入时,用 `setTimeout` 模拟一个假的大模型返回。把游戏跑通了,最后再填入真实的 API Key 和 `fetch` 请求。
|