# Simcity threejs version > by:hexianWeb # Threejs 游戏基建结构 | 要素 | 作用 | 类比现实 | | ------------ | ---------------- | ------------------------------ | | **Scene** | 游戏世界的3D环境 | 就像游乐场的场地 | | **Game UI** | 用户界面和交互层 | 相当于游乐场的指示牌和售票处 | | **Metadata** | 游戏数据和逻辑 | 类似游乐场的运营规则和游客数据 | # Metadata 设计 ## BUILDING_DATA 数据结构说明 `BUILDING_DATA` 是一个包含所有建筑类型及其属性的常量对象,定义于 `src/constants/constants.js`。其结构如下: ```js export const BUILDING_DATA = { [buildingType]: { name: { zh: '中文名', en: 'English Name' }, // 建筑名称(多语言) type: 'buildingType', // 建筑类型唯一标识 icon: '🏠', // 图标(emoji或字符串) buildingType: { zh: '建筑类型', en: 'Type' }, // 建筑大类(多语言) category: 'residential' | 'industrial' | 'commercial' | 'environment' | 'governance' | 'social' | 'infrastructure', // 分类 levels: { [level]: { displayName: { zh: '中文名', en: 'English Name' }, // 等级显示名 cost: number, // 建造消耗金币 maxPopulation?: number, // 最大人口(住宅类) powerUsage?: number, // 用电量 powerOutput?: number, // 发电量(发电建筑) pollution: number, // 污染值(负数为减污) coinOutput?: number, // 金币产出(商业/工业) population?: number, // 提供就业/人口(商业/工业/设施) upgradeCost?: number, // 升级消耗金币 nextLevel?: number, // 下一级编号(无则为null) visible: boolean, // 是否在UI可见 }, // ...更多等级 } }, // ...更多建筑类型 } ``` ### 字段说明 - **name**:建筑的多语言名称,`zh` 为中文,`en` 为英文。 - **type**:建筑类型唯一标识(如 `house`, `factory`)。 - **icon**:建筑图标,通常为 emoji。 - **buildingType**:建筑大类(如“住宅建筑”、“工业建筑”),多语言。 - **category**:建筑所属分类,用于功能分组(如 `residential`、`industrial`)。 - **levels**:建筑的多级属性,key 为等级(1, 2, 3...),value 为该等级的详细属性对象。 #### levels 下的属性 - **displayName**:该等级的多语言显示名。 - **cost**:建造该等级建筑所需金币。 - **maxPopulation**:最大人口容量,仅住宅类有。 - **powerUsage**:用电量,部分建筑有。 - **powerOutput**:发电量,仅发电建筑有。 - **pollution**:污染值,负数表示减污(如公园)。 - **coinOutput**:金币产出,商业/工业建筑有。 - **population**:提供就业/人口,部分建筑有。 - **upgradeCost**:升级到下一级所需金币。 - **nextLevel**:下一级编号,无则为 null。 - **visible**:该等级是否在UI中可见。 ### 示例 以住宅(house)为例: ```js house: { name: { zh: '住宅', en: 'Residential' }, type: 'house', icon: '🏠', buildingType: { zh: '住宅建筑', en: 'Residential Building' }, category: 'residential', levels: { 1: { displayName: { zh: '普通住宅', en: 'Basic Residential' }, cost: 300, maxPopulation: 50, powerUsage: 10, pollution: 2, upgradeCost: 600, nextLevel: 2, visible: true, }, 2: { displayName: { zh: '高级住宅', en: 'Advanced Residential' }, cost: 600, maxPopulation: 100, powerUsage: 15, pollution: 3, upgradeCost: 1200, nextLevel: 3, visible: false, }, 3: { displayName: { zh: '豪华住宅', en: 'Luxury Residential' }, cost: 1200, maxPopulation: 200, powerUsage: 20, pollution: 5, upgradeCost: null, nextLevel: null, visible: false, }, }, } ``` --- ### 设计原则 - **多语言支持**:所有显示相关字段均为 `{ zh, en }` 结构,便于国际化。 - **多级建筑**:通过 `levels` 字段支持建筑升级,每级有独立属性。 - **灵活扩展**:可轻松添加新建筑类型或扩展属性。 - **UI 可控**:`visible` 字段控制各等级在UI的显示与否。 --- 如需进一步细化字段含义或扩展,请补充在此结构说明下方。 # SimObject 互动基类 > 提供了 mesh 管理、选中高亮、HTML 信息展示等通用交互能力。 - SimObject 提供了 mesh 管理、选中高亮、HTML 信息展示等通用交互能力。 - 只要是场景中可交互的对象(如 Tile、Building),都应继承 SimObject。 # building 类组件 ## 🧠 分析与设计思路 1. 单一职责原则 - 0多态与扩展性 - 不同类型建筑继承自 Building,重写各自的功能方法(如 getPopulation、getPower、getEconomy)。 - 便于后续扩展新建筑类型或功能。 3. 解耦与协作 - Tile 只持有 Building 的实例(如 this.buildingInstance),通过接口与其交互。 - Building 需要能访问 Experience、scene、resources 等核心实例。 ------ ## 推荐实现步骤 ### 1. 新建 building.js 基础类 - 负责加载建筑模型、通用属性(如 position、direction)、升级等。 - 提供通用接口(如 update、upgrade、get功能值等)。 ### 2. 新建具体建筑子类(如 house.js、factory.js、shop.js) - 继承 Building,重写/扩展功能方法。 ### 3. 修改 Tile 类 - Tile 只负责地皮表现,持有 Building 实例。 - 通过接口与 Building 交互(如升级、获取功能值等)。 ```mermaid classDiagram SimObject <|-- Tile Tile <|-- Building SimObject <|-- Building Building <|-- House Building <|-- Factory Building <|-- Shop ``` # Tile 地皮交互 ## 1. 方案梳理 ### 方案一:Tile 负责建筑实例 - 流程:射线检测命中 tile(地皮),直接调用 tile.userData.setBuilding('house', 0) 在 tile 内部生成建筑实例(如 House),并作为 tile 的子对象(mesh.add(buildingInstance))。 - 特点: - 建筑和地皮是父子关系,建筑始终附着在 tile 上。 - 交互、管理、拾取都通过 tile 进行。 - 删除/移动建筑时,直接操作 tile 实例。 ### 方案二:Tile 和 Building 分离 - 流程:射线检测命中 tile,获取其 position.x/z,随后在 buildingsGroup(独立 group)中创建建筑实例,建筑与 tile 仅通过坐标关联。 - 特点: - 地皮和建筑完全分离,建筑统一管理在 buildingsGroup。 - 需要额外的数据结构维护 tile 与 building 的映射关系。 - 移动/删除建筑时,需要先查找对应 tile,再操作 buildingsGroup。 ------ ## 2. 需求与扩展性分析 ### PRD 需求 - 建筑与地皮一一对应,每个 tile 最多一个建筑。 - 需要支持建筑的放置、移动、删除。 - 未来可能有 tile 升级、建筑升级、地皮扩展等需求。 ### 技术实现对比 | 维度 | 方案一(父子) | 方案二(分离) | | :--------- | :------------------------- | :--------------------------- | | 实现难度 | 简单,直接操作 tile | 复杂,需维护映射关系 | | 性能 | 高效,遍历 tile 即可 | 需遍历 buildingsGroup 或查表 | | 扩展性 | 易于扩展(如 tile 升级) | 灵活,但管理复杂 | | 交互逻辑 | 直观,所有交互聚焦 tile | 需同步 tile 与 building 状态 | | 数据一致性 | 易保证(父子结构天然一致) | 需手动同步,易出错 | | 未来扩展 | 支持 tile/建筑联动、升级等 | 支持建筑独立动画、批量操作等 | ### 代码风格与维护 - 你的框架强调通过 Experience 单例获取依赖,tile 作为地皮的核心单元,建筑作为 tile 的"内容"更符合直觉。 - 方案一更贴合"组合"思想,tile 作为容器,建筑作为内容,便于后续扩展 tile 的属性(如地皮类型、状态等)。 ------ ## 3. 推荐与理由 强烈推荐采用方案一(tile 负责建筑实例,建筑作为 tile 的子对象),理由如下: 1. 符合 PRD 需求:每个 tile 只允许一个建筑,tile 作为地皮的唯一管理者,建筑作为其内容,逻辑清晰。 1. 易于维护:所有操作(放置、删除、移动、升级)都只需操作 tile 实例,无需额外维护映射关系。 1. 高扩展性:未来如地皮扩展、建筑升级、tile 状态变化等,都可以在 tile 类中集中管理,便于统一调试和维护。 1. 性能优越:遍历 tile 即可获取所有建筑,无需遍历全场景或查找映射表。 1. 代码风格统一:符合你当前框架的单例与组件化设计,便于团队协作和后续开发。 ------ ## 4. 方案一的最佳实践建议 - tile.js 中的 setBuilding(type, direction) 方法负责创建/替换建筑实例,并将其作为 tile 的子对象。 - interactor.js 通过射线检测命中 tile 后,直接调用 tile.userData.setBuilding(...) 实现建筑放置。 - 建筑删除/移动:可在 tile 上实现 removeBuilding()、moveBuilding() 等方法,保持 tile 的唯一性和一致性。 - 建筑信息面板:通过 tile 统一获取建筑信息,便于展示和交互。 ------ ## 5. 方案二的适用场景 - 如果未来有建筑与 tile 多对多、建筑可跨 tile、建筑批量动画等需求,可以考虑方案二。但目前 PRD 明确是一一对应,方案一更优 # UI 交互 - UI 交互(如选择建筑、切换模式、点击地皮)目前只在前端 JS 层(index.js)和 HTML 层(index.html)生效。 - Three.js 场景中的建筑实际放置、删除、移动等操作由 tile/building 组件(如 tile.js、house.js)负责。 - 需要桥接:UI 事件 → Three.js 场景 & Three.js 场景 → UI 事件操作。 - 用户在左侧面板选择建筑类型(如"FACTORY")。 - 用户切换到"BUILD"模式。 - 用户点击画布(canvas)上的某个 tile,期望在该 tile 上放置所选建筑。 - 用户切换到"DEMOLISH"模式,点击建筑,期望删除该建筑。 - 用户点击建筑,右侧面板显示详细信息。 ### 方案:mitt js 事件驱动(推荐) - 全局状态:用 Experience 单例存储当前选中的建筑类型、操作模式。多用于 UI 事件 → Three.js 场景 ```js export default class Experience extends EventEmitter { constructor(canvas) { super() // ... this.currentMode = 'build' this.selectedBuilding = null this.credits = 12345 // ... } } ``` 随后在 Ray 射线检测相关逻辑中读取全局变量相应状态并做不同逻辑操作 ```js // interactor.js _onClick(_event) { if (this.focused) { if (this.experience.currentMode === 'build' && window.selectedBuilding) { this.focused.setBuilding(window.selectedBuilding) } else if (this.experience.currentMode === 'demolish') { this.focused.removeBuilding() // 可选:window.showToast('建筑已拆除') } else { // 显示信息面板 const html = this.focused.toHTML() document.getElementById('info-panel').innerHTML = html } } } ``` - 事件驱动:UI 事件只负责更新全局状态,Three.js 交互(如 Interactor)在射线命中 tile/building 时,读取全局状态并执行相应操作。Three.js 场景 → UI 事件 ```js // experience.js / interactor.js import { eventBus } from './event-bus' eventBus.emit('building:placed', { tile, type }) // index.js import { eventBus } from './event-bus' eventBus.on('building:placed', ({ tile, type }) => { showToast(`${type} 已放置在 (${tile.x}, ${tile.y})`, 'success') // ...刷新 UI }) ``` ## 典型需求场景 - 用户在左侧面板选择建筑类型(如"FACTORY")。 - 用户切换到"BUILD"模式。 - 用户点击画布(canvas)上的某个 tile,期望在该 tile 上放置所选建筑。 - 用户切换到"DEMOLISH"模式,点击建筑,期望删除该建筑。 - 用户点击建筑,右侧面板显示详细信息 ## 典型事件设计 ### ① 建筑相关 | 事件名 | 触发时机 | 事件参数结构 | | :---------------- | :--------------------- | :------------------------------- | | building:placed | 成功放置建筑后 | { tile, type, buildingInstance } | | building:removed | 拆除建筑后 | { tile, type } | | building:selected | 选中建筑(点击/hover) | { tile, type, buildingInstance } | | building:upgraded | 建筑升级后 | { tile, type, level } | ### ② 地皮/格子相关 | 事件名 | 触发时机 | 事件参数结构 | | :------------ | :--------- | :----------- | | tile:selected | 选中地皮 | { tile } | | tile:expanded | 地皮扩展后 | { newSize } | ### ③ 模式/状态相关 | 事件名 | 触发时机 | 事件参数结构 | | :--------------- | :------------------------- | :----------- | | mode:changed | 操作模式切换(build/move) | { mode } | | building:choosed | 选择建筑卡片 | { type } | ### ④ 资源/经济相关 | 事件名 | 触发时机 | 事件参数结构 | | :----------------- | :------- | :-------------------- | | credits:changed | 金币变化 | { credits, delta } | | population:changed | 人口变化 | { population, delta } | ### ⑤ UI 相关 | 事件名 | 触发时机 | 事件参数结构 | | :------------ | :------------- | :---------------- | | ui:toast | 需要弹出提示时 | { message, type } | | ui:panel:show | 显示信息面板 | { panel, data } | # 建筑四大模式 ## 1. SELECT(选择)模式 **核心功能**:信息查看与建筑升级 ```mermaid graph TD A[SELECT模式] --> B[鼠标悬停效果] A --> C[点击交互] A --> D[信息面板] A --> E[建筑升级] B --> B1[地皮:浅蓝色边框] B --> B2[建筑:轻微浮动动画] C --> C1[点击地皮:显示空地信息] C --> C2[点击建筑:显示建筑详情] D --> D1[右侧面板显示] D1 --> D11[建筑名称/类型] D1 --> D12[居民人数/状态] D1 --> D13[当前产出] D1 --> D14[升级选项] E --> E1[显示升级按钮] E1 --> E11[升级条件检查] E1 --> E12[扣除金币] E1 --> E13[更新建筑模型] ``` **实现要点**: - 视觉反馈: - 悬停地皮:浅蓝色半透明边框 - 悬停建筑:Y轴轻微浮动(0.2单位 gsap ease )+发光效果 - 信息面板内容: ```js // 建筑信息示例 { name: "高级公寓", type: "住宅", residents: "12/15", status: "正常", // 状态标签颜色:正常-绿色,拥挤-橙色,空置-灰色 output: "+5.2金币/秒", nextLevel: { cost: 1500, benefits: "+2居民容量" } } ``` - 交互限制: - 禁用放置/删除操作 - 升级按钮仅在满足条件时可用 ```mermaid graph TD A[用户鼠标点击/移动] --> B[Interactor 监听事件] B --> C[获取当前模式: SELECT] C --> D[获取射线焦点 Tile] D --> E[高亮/选中 Tile] E --> F[调用 _handleSelectMode] F --> G[更新 Pinia: selectBuilding/selectPosition] G --> H[UI 组件监听 Pinia 状态] H --> I[App.vue 及子组件响应, 展示详情/高亮] ``` ### upgrade, demolish 升级功能 ```mermaid sequenceDiagram participant User as 用户 participant Interactor as Interactor(射线交互器) participant Pinia as Pinia(GameState) participant App as App.vue/子组件 participant Building as buildingInstance User->>Interactor: 鼠标点击建筑 Interactor->>Interactor: 获取当前模式: SELECT Interactor->>Interactor: 获取射线焦点 Tile Interactor->>Interactor: 高亮/选中 Tile Interactor->>Interactor: 调用 _handleSelectMode Interactor->>Pinia: setSelectedBuilding(building.type) Interactor->>Pinia: setSelectedPosition(tile.position) Pinia-->>App: 状态变更通知 App->>App: 响应状态, 展示建筑详情/高亮 User->>App: 点击升级/拆除按钮 App->>App: 弹出 ConfirmDialog User->>App: 确认操作 App->>Interactor: eventBus.emit('ui:action-confirmed', action) Interactor->>Interactor: _onActionConfirmed(action) alt 升级 Interactor->>Interactor: _confirmUpgrade Interactor->>Building: building.upgrade() alt 可升级 Building-->>Interactor: 返回新 building 数据 Interactor->>Building: selected.setBuilding(newType, newDir, options) Interactor->>App: eventBus.emit('toast:add', '升级成功') else 已最高级 Interactor->>App: eventBus.emit('toast:add', '已达最高等级') end else 拆除 Interactor->>Interactor: _confirmDemolish Interactor->>Pinia: setTile, removeBuilding Interactor->>App: eventBus.emit('toast:add', '建筑移除') end App->>App: 响应状态, 刷新地图 ``` ## 2. BUILD(建造)模式 **核心流程**: ```mermaid sequenceDiagram participant UI as 左侧面板 participant Camera as 摄像机 participant Tile as 地皮系统 participant Economy as 经济系统 UI->>Camera: 选择建筑类型 Camera->>Tile: 显示放置预览(半透明模型) loop 地皮悬停检测 Tile-->>Camera: 可用地皮(绿色高亮)
不可用地皮(红色高亮) end Tile->>Economy: 点击放置时检查金币 alt 金币充足 Economy->>Tile: 扣除金币,生成建筑 Tile->>Camera: 播放放置特效 else 金币不足 Economy->>UI: 显示"金币不足"提示 end ``` **关键实现**: ```js // 在Interactor.js中的实现 _onClick() { if (this.experience.currentMode === 'build') { const cost = Building.getCost(selectedBuildingType); if (this.experience.credits >= cost) { this.focusedTile.setBuilding(selectedBuildingType); this.experience.credits -= cost; eventBus.emit('credits:changed', { credits: this.experience.credits, delta: -cost }); } else { eventBus.emit('ui:toast', { message: `金币不足!需要 ${cost} 金币`, type: 'error' }); } } } ``` **UI提示要素**: 1. 左侧面板: - 当前选中建筑卡片:金色边框+放大效果 - 建筑价格显示(红色标注不足金额) 2. 场景内: - 可用地皮:绿色网格高亮 - 不可用地皮:红色网格闪烁 - 建筑预览:50%透明度的3D模型 3. 状态栏: - 实时金币计数(放置时跳动减少) ## 3. RELOCATE(移动)模式 **状态机实现**: ```mermaid stateDiagram-v2 [*] --> 选择阶段 选择阶段 --> 放置阶段: 点击选中建筑 放置阶段 --> 选择阶段: 按ESC取消 放置阶段 --> 旋转调整: 按R键 旋转调整 --> 放置阶段: 自动返回 放置阶段 --> 确认放置: 点击空地 确认放置 --> [*]: 完成移动 state 放置阶段 { 原建筑: 半透明显示 目标位置: 绿色高亮框 建筑预览: 跟随鼠标位置 } state 旋转调整 { 方向指示器: 4方向箭头UI 当前朝向: 0°/90°/180°/270° } ``` **技术要点**: ```js // 在Experience.js中 startRelocation(building) { this.relocatingBuilding = building; this.originalTile = building.parentTile; this.originalTile.setBuilding(null, true); // 临时移除 // 创建预览模型 this.previewModel = building.clone(); this.previewModel.material.transparent = true; this.previewModel.material.opacity = 0.7; } // 旋转处理 rotatePreview(angle = 90) { this.previewRotation = (this.previewRotation + angle) % 360; this.previewModel.rotation.y = THREE.MathUtils.degToRad(this.previewRotation); } // 确认放置 confirmRelocation(targetTile) { targetTile.setBuildingInstance(this.relocatingBuilding); this.relocatingBuilding.setRotation(this.previewRotation); this.cleanupPreview(); } ``` **视觉反馈**: 1. 选中建筑:半透明化(opacity: 0.5) 2. 预览模型:70%透明度+发光轮廓 3. 有效目标地皮:脉动绿色光圈 4. 无效目标地皮:静态红色边框 5. 方向指示器:底部罗盘UI(显示当前朝向) ```mermaid graph TD A[用户鼠标点击/移动] --> B[Interactor 监听事件] B --> C[获取当前模式: RELOCATE] C --> D[获取射线焦点 Tile] D --> E[高亮/选中 Tile] E --> F{第几次点击?} F -- 第一次 --> G[记录 relocateFirst] F -- 第二次 --> H{canPlaceBuilding 合法性判断} H -- 合法且空地 --> I[事件总线 ui:confirm-action] I --> J[App.vue 监听, 弹出 ConfirmDialog] J --> K[用户确认] K --> L[事件总线 'ui:action-confirmed'] L --> M[Interactor._confirmRelocate] M --> N[Pinia: setTile, 交换建筑] N --> O[UI Toast: 搬迁成功] H -- 不合法/已占用 --> P[UI Toast: 无法搬迁] O & P --> Q[UI 组件监听 Pinia 状态] Q --> R[App.vue 及子组件响应, 刷新地图] ``` ## 4. DEMOLISH(拆除)模式 **安全交互流程**: ```mermaid graph LR A[点击建筑] --> B{确认对话框} B -->|确认| C[执行拆除] B -->|取消| D[返回DEMOLISH模式] C --> E[播放拆除动画] E --> F[返还部分资源] F --> G[更新经济系统] style A stroke:#f00 style C stroke:#090 ``` **视觉提示**: 1. 场景内: - 所有建筑:显示红色边框 - 悬停建筑:脉动红色警示效果 2. 光标变化: - 默认:红色禁止图标 - 悬停建筑:锤子图标 3. 确认对话框: - 半透明黑色蒙层 - 居中红色边框面板 - 拆除图标动画 ```mermaid graph TD A[用户鼠标点击/移动] --> B[Interactor 监听事件] B --> C[获取当前模式: DEMOLISH] C --> D[获取射线焦点 Tile] D --> E[高亮/选中 Tile] E --> F{Tile 是否有建筑} F -- 有建筑 --> G[事件总线 ui:confirm-action] G --> H[App.vue 监听, 弹出 ConfirmDialog] H --> I[用户确认] I --> J[事件总线 ui:action-confirmed] J --> K[已确认销毁 执行销毁逻辑] K --> L[Metadata 更新数据,Scene 删除地皮] L --> M[UI Toast: 建筑移除] F -- 无建筑 --> N[设置地皮为草地] M & N --> O[UI 组件监听 Pinia 状态] O --> P[App.vue 及子组件响应, 刷新地图] ``` # 产出系统 ## 1.人口资源 ### 1. 机制说明 - **住宅建筑**(如🏠、🏡)只决定"人口容量"上限(maxPopulation 字段)。 - **商业/工业建筑**(如🏭、🧪、🏬、🏢)提供就业岗位(population 字段),吸纳实际人口。 - **实际人口** = 所有商业/工业建筑的岗位总和,不能超过人口容量。 - **人口利用率** = 实际人口 / 人口容量。 - 当人口利用率 > 1.5 或 < 0.5 时,满意度下降。 - 满意度过低会导致人口流失、经济产出下降。 ### 2. 资源关系矩阵(新版) | 资源类型 | 来源建筑 | 吸纳/限制建筑 | 影响机制 | | ---------- | ------------------ | ---------------- | ------------------------------------------ | | 人口容量 | 住宅建筑 | - | 限制实际人口上限 | | 实际人口 | - | 商业/工业建筑 | 由商业/工业建筑需求决定,不能超过人口容量 | | 满意度 | 公园、医院、学校等 | 工厂、发电厂等 | 受人口利用率影响,过高/过低均会下降 | | 资金 | 所有建筑 | - | 受实际人口和满意度共同影响 | ### 3. 资源流向与满意度影响(Mermaid 图) ```mermaid graph TD A[住宅建筑🏠] -- 提供 --> B[人口容量] C[商业/工业建筑🏭/🏬] -- 吸纳 --> D[实际人口] B -- 限制 --> D D & B -- 计算比例 --> E[人口利用率] E -- 影响 --> F[满意度] F -- 影响 --> G[人口增长/流失] F -- 影响 --> H[经济产出] ``` ### 4. 满意度影响机制 - **人口利用率 = 实际人口 / 人口容量** - 当人口利用率 > 1.5 或 < 0.5 时,满意度每分钟下降X点 - 满意度过低时,人口增长变为负数(流失),经济产出下降 ### 5. 公式示例 ``` 实际人口 = min(人口容量, 商业/工业总岗位需求) 人口利用率 = 实际人口 / 人口容量 满意度变化 = if (人口利用率 > 1.5 || 人口利用率 < 0.5) 则满意度 -= Δ else 满意度 += 正常增长 ``` > 详见 @src/constants/constants.js 中 BUILDING_DATA 字段定义。 ## 2. 金币的产出 & 获取 💰 ### 机制说明 - 金币是城市建设与运营的核心资源。 - 金币主要通过商业(🏬、🏢)和工业(🏭、🧪、☢️)建筑按时间周期自动产出。 - 金币消耗主要用于新建筑的建造、升级、搬迁等。 - 产出与消耗数值详见下表。 ### 产出与消耗一览表 | 建筑类型 | 建筑名称 | 产出金币/周期 | 建造消耗金币 | | :--: | :--: | :--: | :--: | | 工业 | 工厂 🏭 | 20 | 500 | | 工业 | 化学工厂 🧪 | 30/45/60 | 1000/1500/2000 | | 工业 | 核电站 ☢️ | 50 | 5000 | | 商业 | 商店 🏬 | 25/40/60 | 400/800/1600 | | 商业 | 办公室 🏢 | 35/55/80 | 500/1000/2000 | > 说明: > - "产出金币/周期"为该建筑每个时间周期(如每分钟)自动产出的金币数量。 > - "建造消耗金币"为新建该建筑时需要消耗的金币数量。 > - 部分建筑有多级,表格中以"/"分隔不同等级。 > - 详细数据请参考 @src/constants/constants.js 中 BUILDING_DATA。 ### 机制细节 - 金币产出受市场需求、满意度等因素影响,详见"动态经济系统"章节。 - 拆除建筑可返还部分金币。 - 随着游戏进程推进,金币产出与消耗会动态调整。 # 未来功能 ## 1. 挑战与失败机制 ### 1..1 动态经济系统 ```js class Economy { constructor() { this.marketDemand = { residential: 1.0, commercial: 1.0, industrial: 1.0 } // 每10分钟重新计算市场需求 setInterval(() => this.calculateMarketDemand(), 600000) } calculateMarketDemand() { // 基于建筑比例调整需求 const totalBuildings = this.experience.buildings.length const resRatio = this.getBuildingRatio('residential') const comRatio = this.getBuildingRatio('commercial') // 住宅需求公式:商业比例越高,住宅需求越大 this.marketDemand.residential = 0.5 + comRatio * 1.5 // 商业需求公式:住宅比例越高,商业需求越大 this.marketDemand.commercial = 0.3 + resRatio * 2.0 // 工业需求随机波动 this.marketDemand.industrial = 0.8 + Math.random() * 0.4 } getBuildingOutput(building) { const baseOutput = building.baseOutput const demandFactor = this.marketDemand[building.type] const efficiency = this.getEfficiency(building) return baseOutput * demandFactor * efficiency } } ``` ### 1.2 失败条件 ```js class GameState { checkFailureConditions() { // 条件1: 连续负债超过5分钟 if (this.credits < 0) { this.debtTimer += delta if (this.debtTimer > 300) { this.triggerFailure('经济崩溃') } } else { this.debtTimer = 0 } // 条件2: 人口归零 if (this.population <= 0) { this.triggerFailure('城市荒废') } // 条件3: 污染爆表 if (this.pollution >= 100) { this.triggerFailure('生态灾难') } } triggerFailure(reason) { eventBus.emit('game:over', { reason }) // 保存分数到排行榜 this.saveScore() // 显示失败界面 this.showGameOverScreen(reason) } } ``` ## 2. 策略性建筑系统 ### 2.1 建筑相互依存关系 ```mermaid graph LR A[住宅] -->|提供| B[人口] B -->|需要| C[就业] C -->|由| D[提供 by 商业/工业] D -->|消耗| E[电力] E -->|由| F[提供 by 发电厂] F -->|产生| G[污染] G -->|降低| H[满意度] H -->|影响| A[住宅人口增长] I[公园] -->|减少| G[污染] I -->|提升| H[满意度] J[医院] -->|提升| H[满意度] J -->|减少| K[人口流失] L[消防站] -->|提升| M[工业安全性] N[警察局] -->|提升| O[商业安全性] P[垃圾站] -->|减少| G[污染] ``` ### 2.2 建筑状态指示器系统 #### 2.2.1 状态类型定义 基于 `statusConfig` 配置,建筑可显示以下状态: | 状态类型 | 触发条件 | 显示图标 | 效果类型 | |---------|---------|---------|---------| | `UPGRADE_AVAILABLE` | 建筑可升级且金币充足 | ⬆️ | upgrade | | `POWER_SHORTAGE` | 城市电力不足 | ⚡ | missPower | | `POPULATION_OVERLOAD` | 人口超负荷 | 👥 | missPopulation | | `POPULATION_SHORTAGE` | 人口不足 | 👥 | missPopulation | | `MISSING_ROAD` | 缺少道路连接 | 🛣️ | missRoad | | `HIGH_POLLUTION` | 污染过高 | 🌫️ | missPollution | | `PROVIDING_BUFF` | 为相邻建筑提供增益 | 🌟 | upgrade | #### 2.2.2 建筑互动配置 **住宅类建筑(house, house2)** ```js statusConfig: [ { statusType: 'UPGRADE_AVAILABLE', condition: (building, gs) => { const upgradeInfo = building.upgrade() return upgradeInfo && gs.credits >= building.getCost() }, effect: { type: 'upgrade' } }, { statusType: 'POPULATION_OVERLOAD', condition: (building, gs) => { return gs.population >= gs.maxPopulation }, effect: { type: 'humanBuff' } } ] ``` **工业类建筑(factory, chemistry-factory, nuke-factory)** ```js statusConfig: [ { statusType: 'POWER_SHORTAGE', condition: (building, gs) => { return gs.powerUsage > gs.powerOutput }, effect: { type: 'missPower' } }, { statusType: 'MISSING_ROAD', condition: (building, _gs) => { return !building.hasRoadConnection() }, effect: { type: 'missRoad' } }, { statusType: 'HIGH_POLLUTION', condition: (building, gs) => { return gs.pollution > 80 }, effect: { type: 'missPollution' } } ] ``` **基础设施类建筑** ```js // 公园 - 为住宅提供满意度加成 { statusType: 'PROVIDING_BUFF', condition: (building, gs) => { building.buffConfig = { targets: ['house', 'house2'], range: 2 } return building.checkForBuffTargets(gs) }, effect: { type: 'upgrade' } } // 医院 - 为住宅提供满意度加成 { statusType: 'PROVIDING_BUFF', condition: (building, gs) => { building.buffConfig = { targets: ['house', 'house2'], range: 3 } return building.checkForBuffTargets(gs) }, effect: { type: 'upgrade' } } // 消防站 - 为工业建筑提供安全性 { statusType: 'PROVIDING_BUFF', condition: (building, gs) => { building.buffConfig = { targets: ['factory', 'chemistry-factory', 'nuke-factory'], range: 2 } return building.checkForBuffTargets(gs) }, effect: { type: 'upgrade' } } // 警察局 - 为商业建筑提供安全性 { statusType: 'PROVIDING_BUFF', condition: (building, gs) => { building.buffConfig = { targets: ['shop', 'office'], range: 2 } return building.checkForBuffTargets(gs) }, effect: { type: 'upgrade' } } // 垃圾站 - 为工业建筑减少污染 { statusType: 'PROVIDING_BUFF', condition: (building, gs) => { building.buffConfig = { targets: ['factory', 'chemistry-factory', 'nuke-factory'], range: 2 } return building.checkForBuffTargets(gs) }, effect: { type: 'missPollution' } } ``` ### 2.3 建筑升级系统 #### 2.3.1 升级条件 - 建筑达到当前等级上限 - 玩家金币充足 - 城市发展水平满足要求 #### 2.3.2 升级效果 - 人口容量提升(住宅类) - 金币产出增加(商业/工业类) - 电力产出提升(发电类) - 污染减少(基础设施类) ### 2.4 实现规范 #### 2.4.1 状态检测机制 ```js // 在建筑类中实现状态检测 checkStatus(gs) { for (const config of this.statusConfig) { if (config.condition(this, gs)) { return config } } return null } // 检查相邻建筑 checkForBuffTargets(gs) { const neighbors = this.getNeighborTiles(this.buffConfig.range) return neighbors.some(tile => tile.building && this.buffConfig.targets.includes(tile.building.type) ) } ``` #### 2.4.2 视觉效果实现 - 使用 `effects.js` 中的广告牌系统 - 状态图标位置:建筑顶部 0.5 单位高度 - 动画效果:缓入缓出 + 浮动动画 - 颜色编码:绿色(正常)、黄色(警告)、红色(危险) ## 3. 动态事件系统 **事件类型表**: | 事件类型 | 频率 | 影响 | 玩家应对策略 | | -------- | ---------- | ------------------- | ---------------------- | | 经济危机 | 10-15分钟 | 所有商业收入减少40% | 转向工业或缩减开支 | | 移民潮 | 随机 | 人口+20% | 快速建造住宅提供住所 | | 能源短缺 | 电力>90%时 | 电力产出-30% | 建造备用能源或减少消耗 | | 环保抗议 | 污染>60%时 | 满意度-25% | 建造公园或升级清洁技术 | | 技术突破 | 学校>3座时 | 所有升级费用-20% | 趁机升级关键建筑 | **事件实现代码**: ```js class EventSystem { constructor() { this.events = [ { id: 'economic_crisis', name: '全球经济危机', probability: 0.01, // 每分钟1%概率 condition: () => this.experience.playTime > 600, // 10分钟后可能发生 apply: () => { this.marketDemand.commercial *= 0.6 eventBus.emit('ui:toast', { message: '经济危机!商业收入减少40%', type: 'warning' }) }, duration: 120 // 持续2分钟 } ] } update(delta) { this.events.forEach((event) => { if (!event.active && Math.random() < event.probability * delta) { if (!event.condition || event.condition()) { this.activateEvent(event) } } if (event.active) { event.timeRemaining -= delta if (event.timeRemaining <= 0) { this.deactivateEvent(event) } } }) } } ``` ## 4. 进阶游戏机制 ### 5.1 区域规划加成 ```js class ZoneSystem { getEfficiencyBonus(building) { let bonus = 1.0 // 检查相邻格子 const neighbors = this.getNeighborTiles(building.tile) // 住宅相邻公园:满意度+10% if (building.type === 'residential') { const parkCount = neighbors.filter(t => t.building?.type === 'park').length bonus += parkCount * 0.1 } // 工厂远离住宅:污染-15% if (building.type === 'industrial') { const residentialCount = neighbors.filter(t => t.building?.type === 'residential').length bonus -= residentialCount * 0.15 } return Math.max(0.5, bonus) // 最低50%效率 } } ``` ### 5.2 科技树系统 ```mermaid graph TD A[基础技术] --> B[清洁能源] A --> C[智能电网] A --> D[绿色建筑] B --> E[太阳能电池板
+20%电力 -30%污染] B --> F[核聚变发电
+50%电力 高风险] C --> G[电力传输优化
-10%电力损耗] C --> H[智能负载均衡
+15%电力效率] D --> I[垂直农场
+人口 +满意度] D --> J[生态住宅
-维护费 +满意度] ``` ### 5.3 政策系统(二选一机制) ```js class PolicySystem { policies = [ { id: 'tax_policy', name: '税收政策', options: [ { label: '低税率吸引投资', effect: '+20%人口增长,-15%建筑收入' }, { label: '高税率增加收入', effect: '-10%人口增长,+25%建筑收入' } ] }, { id: 'environment_policy', name: '环境政策', options: [ { label: '工业优先', effect: '+30%工业产出,+0.2污染/分钟' }, { label: '环保优先', effect: '-50%污染,-20%工业产出' } ] } ] applyPolicy(policyId, optionIndex) { const policy = this.policies.find(p => p.id === policyId) const option = policy.options[optionIndex] // 应用效果 this.parseEffect(option.effect) // 锁定选择30分钟 this.policyCooldowns[policyId] = 1800 eventBus.emit('policy:enacted', { policy, option }) } } ``` ## 5. 平衡性调整 ### 5.1 资源转换公式 ``` 人口增长 = (基础增长率 + 满意度/100) × (1 - 污染/200) × (就业率^0.5) × 住宅容量使用率 电力需求 = Σ(工业建筑×1.2 + 商业建筑×0.8 + 住宅×0.2) 污染产生 = Σ(燃煤发电×0.8 + 工业建筑×0.5 - 公园×0.3) ``` ### 5.2 动态难度曲线 ```js class DifficultySystem { getDifficultyMultiplier() { const timeFactor = Math.min(1.0, this.playTime / 3600) // 1小时后达最大难度 const buildingFactor = Math.min(1.5, this.buildings.length / 50) // 50建筑后达上限 return 0.8 + (timeFactor * 0.5) + (buildingFactor * 0.7) } adjustGameParameters() { const multiplier = this.getDifficultyMultiplier() // 增加维护成本 this.maintenanceMultiplier = multiplier // 增加事件频率 this.eventProbabilityMultiplier = 0.5 + multiplier * 0.5 // 降低资源产出 this.resourceOutputMultiplier = 1.2 - multiplier * 0.4 } } ``` ## 6. 玩家进度与成就 ### 6.1 里程碑系统 ```js const milestones = [ { id: 'first_city', name: '初具规模', condition: state => state.population >= 100 }, { id: 'power_king', name: '电力大亨', condition: state => state.powerOutput >= 500 }, { id: 'eco_champion', name: '环保先锋', condition: state => state.pollution < 20 && state.playTime > 1800 }, { id: 'metropolis', name: '大都市', condition: state => state.population >= 5000 } ] ``` ### 6.2 保存进度设计 ```js class SaveSystem { saveGame() { const saveData = { version: 1.1, timestamp: Date.now(), resources: this.experience.resources, buildings: this.experience.buildings.map(b => ({ type: b.type, level: b.level, x: b.tile.x, z: b.tile.z, rotation: b.rotation })), stats: { playTime: this.playTime, totalEarnings: this.totalEarnings, buildingsConstructed: this.buildingsConstructed }, upgrades: this.unlockedUpgrades, policies: this.activePolicies } localStorage.setItem('city_save', JSON.stringify(saveData)) } loadGame() { const saveData = JSON.parse(localStorage.getItem('city_save')) // 重建游戏状态... } } ``` ## 实施路线图 1. **第一阶段:核心经济系统重构(1-2周)** - 实现电力、人口、满意度资源 - 添加建筑维护费 - 创建基本失败条件 2. **第二阶段:策略深度扩展(2-3周)** - 实现区域规划加成 - 添加基础事件系统 - 设计初始科技树 3. **第三阶段:内容丰富化(3-4周)** - 添加10+新建筑类型 - 实现政策系统 - 创建成就和里程碑 4. **第四阶段:平衡与优化(1-2周)** - 调整资源公式 - 优化新玩家引导 - 添加难度选项 这些改进将游戏从简单的"放置-等待"循环转变为需要持续决策和战略规划的城市模拟游戏。玩家现在需要: - 平衡多种相互冲突的资源 - 应对随机事件和危机 - 规划长期科技发展 - 在政策决策中权衡取舍 - 避免多个失败条件 同时保持了游戏的休闲本质,所有复杂系统都通过清晰的UI和渐进式引导呈现给玩家。 --- # useGameState.js 变量引用表 > 本文档记录了 `src/stores/useGameState.js` 中所有可用的变量、getters 和 actions,供开发时查询引用。 ## State 状态变量 | 变量名 | 类型 | 默认值 | 说明 | |--------|------|--------|------| | `currentMode` | string | `'build'` | 当前操作模式:`'build'`、`'demolish'`、`'relocate'`、`'select'` | | `selectedBuilding` | object/null | `null` | 当前选中建筑:`{type, level}` | | `selectedPosition` | object/null | `null` | 当前选中位置:`{x, z}` | | `toastQueue` | array | `[]` | Toast 消息队列 | | `gameDay` | number | `1` | 游戏天数 | | `credits` | number | `10000` | 金币数量 | | `territory` | number | `16` | 地皮数量 | | `cityLevel` | number | `1` | 城市等级 | | `cityName` | string | `'HeXian City'` | 城市名称 | | `citySize` | number | `16` | 城市大小 | | `language` | string | `'en'` | 语言设置:`'en'`、`'zh'` | | `metadata` | array | `17x17` 数组 | 地图元数据,每个元素包含:`{type, building, direction, level}` | | `showMapOverview` | boolean | `false` | 地图总览显示状态 | | `stability` | number | `100` | 城市稳定度 (0-100) | | `stabilityChangeRate` | number | `0` | 稳定度每秒变化率 | ## Getters 计算属性 | Getter名 | 返回类型 | 说明 | 计算公式 | |----------|----------|------|----------| | `dailyIncome` | number | 每日总收入 | 所有建筑的 `coinOutput` 总和,受相邻公园影响 | | `maxPopulation` | number | 总人口容量 | 住宅建筑的 `maxPopulation` 总和,受相邻公园影响 | | `totalJobs` | number | 总就业岗位 | 所有建筑的 `population` 总和 | | `population` | number | 实际人口 | `Math.min(maxPopulation * 1.5, totalJobs)` | | `maxPower` | number | 最大发电量 | 所有建筑的 `powerOutput` 总和 | | `power` | number | 总耗电量 | 所有建筑的 `powerUsage` 总和 | | `buildingCount` | number | 建筑总数 | 排除道路的建筑数量 | | `pollution` | number | 总污染值 | 所有建筑的 `pollution` 总和,工业建筑受相邻公园影响 | | `hospitalCount` | number | 医院数量 | 统计 `building === 'hospital'` 的数量 | | `policeStationCount` | number | 警察局数量 | 统计 `building === 'police'` 的数量 | | `fireStationCount` | number | 消防站数量 | 统计 `building === 'fire_station'` 的数量 | ## Actions 操作方法 | Action名 | 参数 | 说明 | |----------|------|------| | `updateStability()` | 无 | 更新稳定度变化率 | | `applyStabilityChange()` | 无 | 应用稳定度变化 | | `setMode(mode)` | string | 设置操作模式 | | `setSelectedBuilding(payload)` | object | 设置选中建筑 | | `setSelectedPosition(position)` | object | 设置选中位置 | | `setCredits(credits)` | number | 设置金币数量 | | `updateCredits(credits)` | number | 更新金币数量(增减) | | `setTerritory(territory)` | number | 设置地皮数量 | | `setCityLevel(cityLevel)` | number | 设置城市等级 | | `setCityName(cityName)` | string | 设置城市名称 | | `setCitySize(citySize)` | number | 设置城市大小 | | `addToast(message, type)` | string, string | 添加 Toast 消息 | | `setLanguage(lang)` | string | 设置语言 | | `removeToast(id)` | number | 移除 Toast 消息 | | `clearSelection()` | 无 | 清除选中状态 | | `setTile(x, y, patch)` | number, number, object | 设置地图瓦片 | | `updateTile(x, y, patch)` | number, number, object | 更新地图瓦片(同 setTile) | | `getTile(x, y)` | number, number | 获取地图瓦片 | | `setShowMapOverview(val)` | boolean | 设置地图总览显示 | | `nextDay()` | 无 | 进入下一天,更新金币 | | `resetAll()` | 无 | 重置所有状态 | ## 建筑状态检测中的变量引用 在建筑类的 `statusConfig` 中,`gs` 参数代表 `useGameState` 实例,可访问以下属性: ### 常用变量引用示例 ```js // 电力相关 gs.power // 总耗电量 (getter) gs.maxPower // 最大发电量 (getter) // 人口相关 gs.population // 实际人口 (getter) gs.maxPopulation // 总人口容量 (getter) gs.totalJobs // 总就业岗位 (getter) // 其他资源 gs.credits // 金币数量 (state) gs.pollution // 总污染值 (getter) gs.stability // 城市稳定度 (state) // 建筑统计 gs.buildingCount // 建筑总数 (getter) gs.hospitalCount // 医院数量 (getter) gs.policeStationCount // 警察局数量 (getter) gs.fireStationCount // 消防站数量 (getter) ``` ### 建筑状态配置示例 ```js // 电力短缺检测 { statusType: 'POWER_SHORTAGE', condition: (building, gs) => { return gs.power > gs.maxPower // 耗电量 > 发电量 }, effect: { type: 'missPower' } } // 人口超负荷检测 { statusType: 'POPULATION_OVERLOAD', condition: (building, gs) => { return gs.population >= gs.maxPopulation // 实际人口 >= 人口容量 }, effect: { type: 'missPopulation' } } // 可升级检测 { statusType: 'UPGRADE_AVAILABLE', condition: (building, gs) => { const upgradeInfo = building.upgrade() return upgradeInfo && gs.credits >= building.getCost() // 有下一级且金币充足 }, effect: { type: 'upgrade' } } ``` ## 注意事项 1. **State vs Getter**: - `state` 中的变量是直接存储的值 - `getter` 中的变量是计算得出的值,会实时更新 2. **变量命名一致性**: - 电力相关:`power` (耗电量) vs `maxPower` (发电量) - 人口相关:`population` (实际人口) vs `maxPopulation` (人口容量) 3. **地图访问**: - 使用 `gs.getTile(x, y)` 安全访问地图数据 - 使用 `gs.setTile(x, y, patch)` 更新地图数据 4. **持久化**: - 所有 state 变量都会自动持久化到 localStorage - 页面刷新后会自动恢复状态