欢迎加入 CubeCity 开发团队!这是一款基于 Three.js 和 Vue 3 的 2.5D 卡通城市模拟经营游戏。本指南将帮助你快速上手项目开发,成为一名优秀的 CubeCity 开发者。
CubeCity 是一款 2.5D 卡通风格的城市建设模拟游戏,玩家通过放置、管理各类建筑,经营并扩展属于自己的城市。
- 🏗️ 3D 城市建设:基于 Three.js 的 3D 场景渲染
- 🎮 模拟经营:RCI & ESG 多维建筑体系
- 💾 本地存储:自动保存游戏进度
- 📱 响应式设计:支持桌面端和移动端
- 🌍 国际化:支持中英文切换
- 🎨 现代化 UI:基于 Vue 3 + Tailwind CSS
- 提供流畅的 3D 城市建设体验
- 实现复杂的建筑相互作用系统
- 构建可扩展的游戏架构
- 保持代码的可维护性和可读性
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue 3 | 3.x | 前端框架,负责 UI 层 |
| Three.js | 0.172.0 | 3D 图形渲染引擎 |
| Vite | 5.4.0 | 构建工具和开发服务器 |
| Pinia | 3.0.3 | 状态管理 |
| Tailwind CSS | 3.4.9 | CSS 框架 |
| 技术 | 用途 |
|---|---|
| GSAP | 动画库,用于 UI 和 3D 动画 |
| Cannon.js | 物理引擎 |
| Mitt | 事件总线,用于组件通信 |
| Vue I18n | 国际化支持 |
| Tweakpane | 调试面板 |
| 工具 | 用途 |
|---|---|
| ESLint | 代码质量检查 |
| Prettier | 代码格式化 |
| Playwright | 端到端测试 |
| Husky | Git hooks |
| Commitlint | 提交信息规范 |
- Node.js: >= 18.0.0
- 包管理器: pnpm (推荐) 或 npm
- 编辑器: VS Code (推荐)
- 浏览器: Chrome/Edge (支持 WebGL)
git clone https://github.com/your-username/CubeCity.git
cd CubeCity# 使用 pnpm (推荐)
pnpm install
# 或使用 npm
npm installpnpm dev
# 或
npm run dev访问 http://localhost:5173 查看项目。
# 构建生产版本
pnpm build
# 预览生产版本
pnpm preview
# 代码检查
pnpm lint
# 代码格式化
pnpm lint:fix
# 运行测试
pnpm test:chromeCubeCity/
├── docs/ # 项目文档
│ ├── PRD.md # 产品需求文档
│ ├── TD.md # 技术设计文档
│ └── 新手指南.md # 玩家指南
├── public/ # 静态资源
│ ├── models/ # 3D 模型文件
│ ├── textures/ # 纹理文件
│ └── fonts/ # 字体文件
├── src/ # 源代码
│ ├── components/ # Vue 组件
│ ├── js/ # Three.js 相关代码
│ │ ├── components/ # 3D 组件
│ │ ├── tools/ # 工具类
│ │ ├── utils/ # 工具函数
│ │ └── world/ # 世界管理
│ ├── shaders/ # GLSL 着色器
│ ├── stores/ # Pinia 状态管理
│ ├── assets/ # 资源文件
│ └── css/ # 样式文件
├── tests/ # 测试文件
├── package.json # 项目配置
└── vite.config.js # Vite 配置
App.vue: 主应用组件GameCanvas.vue: 游戏画布组件BuildingSidebar.vue: 建筑选择面板BuildingDetail.vue: 建筑详情面板
experience.js: 核心体验类,单例模式camera.js: 相机管理renderer.js: 渲染器管理world.js: 世界管理components/: 3D 组件tiles/: 地皮系统buildings/: 建筑系统effects/: 特效系统
useGameState.js: 游戏状态管理
// src/js/experience.js
export default class Experience {
constructor() {
if (Experience.instance) {
return Experience.instance
}
Experience.instance = this
// 初始化核心组件
this.scene = new THREE.Scene()
this.camera = new Camera()
this.renderer = new Renderer()
// ...
}
}- Vue 组件: 负责 UI 交互和状态展示
- Three.js 组件: 负责 3D 场景渲染和逻辑
- 状态管理: Pinia 统一管理游戏状态
// 使用 mitt 事件总线
import emitter from './utils/event-bus.js'
// Vue 组件发送事件
emitter.emit('ui:building-selected', { type: 'house' })
// Three.js 组件监听事件
emitter.on('ui:building-selected', (data) => {
// 处理建筑选择逻辑
})// 使用 Pinia 进行状态管理
import { useGameState } from '@/stores/useGameState.js'
const gameState = useGameState()
gameState.setMode('build')
gameState.setSelectedBuilding({ type: 'house', level: 1 })// src/js/sources.js - 定义资源
export default [
{
name: 'house',
type: 'gltfModel',
path: 'models/house_level1.glb'
}
]
// 使用资源
const houseModel = this.experience.resources.items.house// src/shaders/ - GLSL 着色器
import fragmentShader from '@/shaders/fragment.glsl'
import vertexShader from '@/shaders/vertex.glsl'
const material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
time: { value: 0 }
}
})- 使用 ES6+ 语法
- 类名使用大驼峰命名
- 方法名使用小驼峰命名
- 常量使用大写下划线
- 必须添加中文注释
- 组件名使用大驼峰命名
- 文件名与组件名一致
- 使用 Composition API
- 样式使用 Tailwind CSS
export default class YourComponent {
constructor() {
// 获取 Experience 单例实例
this.experience = new Experience()
// 获取需要的核心组件
this.scene = this.experience.scene
this.resources = this.experience.resources
this.debug = this.experience.debug
// 初始化组件
this.init()
}
init() {
// 组件初始化逻辑
}
update() {
// 更新逻辑
}
resize() {
// 响应式调整
}
}src/js/components/
├── tiles/ # 地皮相关组件
├── buildings/ # 建筑相关组件
├── effects/ # 特效组件
└── ui/ # UI 相关组件
src/js/utils/
├── debug.js # 调试工具
├── event-bus.js # 事件总线
├── resources.js # 资源管理
└── sizes.js # 尺寸管理
- 组件文件:
PascalCase.js - 工具文件:
kebab-case.js - 着色器文件:
kebab-case.glsl
// 常量
const BUILDING_TYPES = ['house', 'factory', 'shop']
// 类名
class BuildingFactory {}
// 方法名
function createBuilding() {}
// 私有属性
this._privateProperty = value/**
* 建筑工厂类
* 负责创建和管理不同类型的建筑实例
*/
export default class BuildingFactory {
/**
* 创建建筑实例
* @param {string} type - 建筑类型
* @param {number} level - 建筑等级
* @returns {Building} 建筑实例
*/
createBuilding(type, level) {
// 根据类型创建对应建筑
switch (type) {
case 'house':
return new House(level)
case 'factory':
return new Factory(level)
default:
throw new Error(`未知建筑类型: ${type}`)
}
}
}// src/constants/constants.js
export const BUILDING_DATA = {
newBuilding: {
name: { zh: '新建筑', en: 'New Building' },
type: 'newBuilding',
icon: '🏢',
category: 'commercial',
levels: {
1: {
displayName: { zh: '基础新建筑', en: 'Basic New Building' },
cost: 500,
coinOutput: 25,
powerUsage: 10,
pollution: 2,
upgradeCost: 1000,
nextLevel: 2,
visible: true
}
}
}
}// src/js/components/buildings/new-building.js
import Building from './building.js'
export default class NewBuilding extends Building {
constructor(level = 1) {
super('newBuilding', level)
// 加载模型
this.model = this.resources.items.newBuilding
this.scene.add(this.model)
}
// 重写特定方法
getCoinOutput() {
return this.buildingData.coinOutput * this.level
}
}// src/js/sources.js
export default [
// ... 其他资源
{
name: 'newBuilding',
type: 'gltfModel',
path: 'models/new-building.glb'
}
]<!-- src/components/BuildingSidebar.vue -->
<template>
<div class="building-card" @click="selectBuilding('newBuilding')">
<div class="building-icon">
🏢
</div>
<div class="building-name">
{{ t('newBuilding.name') }}
</div>
<div class="building-cost">
{{ getBuildingCost('newBuilding') }}
</div>
</div>
</template>// src/constants/building-interactions.js
export const BUILDING_INTERACTIONS = {
house: {
park: {
distance: 1,
effect: 'maxPopulation',
multiplier: 0.1, // +10%
description: '相邻公园增加人口容量'
},
factory: {
distance: 1,
effect: 'maxPopulation',
multiplier: -0.15, // -15%
description: '相邻工厂减少人口容量'
}
}
}// src/js/components/buildings/building.js
export default class Building {
calculateInteractions() {
const neighbors = this.getNeighborTiles()
let bonus = 1.0
neighbors.forEach((tile) => {
if (tile.building) {
const interaction = BUILDING_INTERACTIONS[this.type]?.[tile.building.type]
if (interaction) {
bonus += interaction.multiplier
}
}
})
return Math.max(0.5, bonus) // 最低 50% 效率
}
}// src/shaders/effects/new-effect/fragment.glsl
uniform float time;
uniform vec3 color;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
float wave = sin(uv.x * 10.0 + time) * 0.5 + 0.5;
gl_FragColor = vec4(color * wave, 1.0);
}// src/js/components/effects/new-effect.js
import fragmentShader from '@/shaders/effects/new-effect/fragment.glsl'
import vertexShader from '@/shaders/effects/new-effect/vertex.glsl'
export default class NewEffect {
constructor() {
this.material = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms: {
time: { value: 0 },
color: { value: new THREE.Color(0x00FF00) }
},
transparent: true
})
}
update() {
this.material.uniforms.time.value += 0.01
}
}<!-- src/components/NewFeature.vue -->
<script setup>
import { ref } from 'vue'
const props = defineProps({
title: {
type: String,
required: true
}
})
</script>
<template>
<div class="new-feature">
<h3>{{ title }}</h3>
<div class="content">
<slot />
</div>
</div>
</template>
<style scoped>
.new-feature {
@apply p-4 bg-white rounded-lg shadow-md;
}
</style><!-- src/App.vue -->
<script setup>
import NewFeature from '@/components/NewFeature.vue'
</script>
<template>
<div id="app">
<!-- 现有内容 -->
<NewFeature title="新功能" />
</div>
</template>- Console: 查看日志和错误
- Network: 检查资源加载
- Performance: 分析性能问题
- Application: 查看本地存储
// 启用调试面板
if (this.debug.active) {
this.debugFolder = this.debug.ui.addFolder({
title: '组件名称',
expanded: true
})
this.debugFolder.addBinding(this.object, 'position', {
label: '位置'
})
}- 安装 Vue DevTools 浏览器扩展
- 查看组件状态和事件
- 调试 Pinia 状态管理
// 使用对象池
class ObjectPool {
constructor(createFn) {
this.pool = []
this.createFn = createFn
}
get() {
return this.pool.pop() || this.createFn()
}
release(obj) {
this.pool.push(obj)
}
}
// 使用 LOD (Level of Detail)
const lod = new THREE.LOD()
lod.addLevel(highDetailMesh, 0)
lod.addLevel(mediumDetailMesh, 50)
lod.addLevel(lowDetailMesh, 100)// 正确释放资源
dispose() {
// 释放几何体
this.geometry.dispose()
// 释放材质
this.material.dispose()
// 从场景中移除
this.scene.remove(this.mesh)
// 清空纹理
this.texture.dispose()
}import BuildingCard from '@/components/BuildingCard.vue'
import { mount } from '@vue/test-utils'
// tests/components.test.js
import { describe, expect, it } from 'vitest'
describe('BuildingCard', () => {
it('显示正确的建筑信息', () => {
const wrapper = mount(BuildingCard, {
props: {
building: {
name: '住宅',
cost: 300
}
}
})
expect(wrapper.text()).toContain('住宅')
expect(wrapper.text()).toContain('300')
})
})// tests/e2e/game.test.js
import { expect, test } from '@playwright/test'
test('建筑放置功能', async ({ page }) => {
await page.goto('/')
// 选择建筑
await page.click('[data-testid="building-house"]')
// 点击地皮放置
await page.click('[data-testid="tile-0-0"]')
// 验证建筑已放置
await expect(page.locator('[data-testid="building-instance"]')).toBeVisible()
})# 构建
pnpm build
# 预览构建结果
pnpm preview// vite.config.js
export default defineConfig({
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
rollupOptions: {
output: {
manualChunks: {
three: ['three'],
vendor: ['vue', 'pinia']
}
}
}
}
})- 启用 Gzip 压缩
- 使用 CDN 加速
- 配置缓存策略
- 优化图片和模型资源
A: 建议按以下顺序学习:
- 阅读
docs/PRD.md了解产品需求 - 查看
docs/TD.md理解技术设计 - 从
src/js/experience.js开始,理解核心架构 - 查看
src/components/App.vue了解 UI 结构
A: 参考 添加新建筑类型 章节,需要:
- 在
constants.js中定义建筑数据 - 创建对应的 3D 组件
- 添加模型资源
- 更新 UI 组件
A: 使用以下方法:
- 启用调试面板查看对象属性
- 使用浏览器开发者工具的 Console
- 检查 Three.js 的 WebGL 错误
- 使用
console.log输出关键数据
A: 常见优化方法:
- 使用对象池减少内存分配
- 实现 LOD 系统
- 合理使用纹理压缩
- 优化渲染循环
A: 使用 Vue I18n:
- 在
src/assets/i18n/中添加语言文件 - 使用
$t()函数包装文本 - 通过
useI18n()切换语言
A: 遵循项目规范:
- 使用
git add暂存更改 - 使用
git commit提交,遵循 commitizen 规范 - 提交信息格式:
feat: 添加新建筑类型 (#issue)
- Fork 项目到个人仓库
- 创建功能分支:
git checkout -b feature/new-feature - 提交更改:
git commit -m "feat: 添加新功能" - 推送到分支:
git push origin feature/new-feature - 创建 Pull Request
- 所有代码变更需要经过审查
- 确保代码符合项目规范
- 添加必要的测试用例
- 更新相关文档
- 使用 GitHub Issues 报告 Bug
- 提供详细的复现步骤
- 包含环境信息和错误日志
- 建议解决方案
- 项目地址: GitHub Repository
- 问题反馈: GitHub Issues
- 讨论区: GitHub Discussions
恭喜你完成了 CubeCity 新手开发指南的学习!现在你已经具备了开始开发的基础知识。
记住:
- 🎯 保持学习心态:技术栈在不断发展,持续学习新知识
- 🤝 团队协作:与团队成员保持良好的沟通和协作
- 📝 文档优先:及时更新文档,帮助其他开发者
- 🐛 质量第一:注重代码质量和用户体验
- 🚀 勇于创新:在现有基础上提出改进建议
祝你在 CubeCity 项目中开发愉快,创造出优秀的作品!🌟
最后更新:2024年12月