简介这份资源是Android平台3D游戏《摩天大楼》的完整源代码面向具备Java与Android基础、希望深入理解3D游戏开发的中级开发者可用于学习OpenGL ES渲染、游戏架构与场景管理等实战技能。压缩包共108个文件约2.99MB以24个java源文件与33个class字节码为核心辅以29个png、8个jpg纹理贴图3个mp3与2个mid音频以及apk、dex、xml配置等运行与构建文件结构紧凑便于逐模块研读。已有473人学习下载。源码覆盖OpenGL ES绘制建筑模型与光照、MVC式逻辑视图分离、3D对象与场景组织、碰撞检测、动画与物理模拟、触屏交互与自定义View、资源加载释放、游戏状态管理及性能优化等关键环节读者可据此掌握Android 3D游戏从渲染到交互的完整实现思路并借鉴实际项目中的问题解决策略。1. 摩天大楼源码一份能直接跑起来的建筑可视化工程第一次拿到「摩天大楼源码」这个资源时我以为是某个塔防小游戏解压后才发现是一套完整的建筑可视化演示工程——用代码把一栋摩天大楼从地基到塔冠逐层“搭”出来带楼层堆叠、立面幕墙、灯光夜景和相机漫游。它适合两类人一类是想学三维场景搭建但不想从零写渲染循环的前端或图形方向开发者另一类是需要一个可改参数、可换风格的建筑展示 Demo 做二次开发的人。源码结构不复杂但把“参数化建模 场景组织 交互控制”这条链路走通了拿来做课程作业、作品集或者内部演示都够用。下面按“能干什么 → 怎么跑 → 怎么改”的顺序拆。2. 工程结构与技术选型从入口文件到场景装配2.1 目录骨架与各文件职责这套源码的组织方式很典型一个入口页负责挂载画布一个主脚本负责场景初始化若干模块分别管建筑生成、材质灯光和相机控制。解压后大致是这样一层结构不同版本文件名可能略有出入以实际为准skyscraper-src/ ├── index.html # 入口页挂载 canvas 容器 ├── src/ │ ├── main.js # 启动入口初始化渲染器、场景、循环 │ ├── building.js # 摩天大楼几何生成楼层、核心筒、塔冠 │ ├── facade.js # 立面幕墙窗格、玻璃材质、发光贴图 │ ├── environment.js # 天空、地面、环境光与雾效 │ ├── controls.js # 相机漫游与鼠标交互 │ └── config.js # 全局参数楼层数、层高、楼宽、配色 ├── assets/ │ ├── textures/ # 玻璃、混凝土、夜景贴图 │ └── models/ # 可选的外部模型如有 └── package.json # 依赖与启动脚本config.js是整个工程的中枢楼层数、层高、楼体宽度、幕墙颜色都从这里读改一个数字整栋楼就变。building.js只负责“按参数堆几何”facade.js只负责“给几何贴皮”职责切得比较干净这也是它好改的原因。常见做法是把配置和生成逻辑分开这里就是这么做的二次开发时不用去几何代码里翻常量。2.2 技术栈判断与依赖说明从入口页和主脚本的写法看这套工程走的是浏览器端三维渲染路线核心依赖是 Three.js 这一类 WebGL 封装库构建工具大概率是 Vite 或原生 ES Module 直接跑。判断依据有三点入口页只有一个 canvas 容器、主脚本里是Scene / Camera / Renderer三段式初始化、几何体用BoxGeometry和InstancedMesh这类 API 拼出来。如果你本地没有node_modules先看package.json里的dependencies和scripts通常长这样{ name: skyscraper-demo, type: module, scripts: { dev: vite, build: vite build, preview: vite preview }, dependencies: { three: ^0.160.0 }, devDependencies: { vite: ^5.0.0 } }type: module说明源码用的是 ES Module 语法dev脚本起本地开发服务器build出静态产物。版本号以你手上文件为准这里只是说明字段含义three是渲染核心vite只负责开发和打包不参与运行时。如果package.json里没有这些字段说明是更早的原生脚本版本那就用一个静态服务器打开index.html即可不需要构建。2.3 启动链路一个场景是怎么被装配出来的主脚本的职责是把渲染器、场景、相机、灯光、建筑、控制器依次装好再启动渲染循环。核心代码大致如下逐段说明// src/main.js import * as THREE from three; import { buildSkyscraper } from ./building.js; import { setupEnvironment } from ./environment.js; import { setupControls } from ./controls.js; import { CONFIG } from ./config.js; // 1. 渲染器绑定 canvas开启抗锯齿和阴影 const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 限制像素比防止高分屏掉帧 renderer.shadowMap.enabled true; document.getElementById(app).appendChild(renderer.domElement); // 2. 场景与相机 const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera( 45, // 视场角建筑展示常用 40~50 window.innerWidth / window.innerHeight, // 宽高比 0.1, // 近裁剪面 2000 // 远裁剪面要能装下整栋楼 ); // 3. 环境天空、地面、雾 setupEnvironment(scene); // 4. 建筑主体按 CONFIG 生成楼层与幕墙 const building buildSkyscraper(CONFIG); scene.add(building); // 5. 相机控制轨道或漫游 const controls setupControls(camera, renderer.domElement); camera.position.set(80, 60, 120); controls.target.set(0, CONFIG.floorHeight * CONFIG.floorCount * 0.5, 0); // 6. 渲染循环 function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate(); // 7. 窗口自适应 window.addEventListener(resize, () { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });这段代码的关键点有三个。第一setPixelRatio做了上限限制这是血泪经验——不限制的话 4K 屏上像素比到 3几何一多帧率直接腰斩。第二相机的远裁剪面设到 2000因为摩天大楼高度可能上百米裁剪面太小会把塔冠切掉画面里楼顶“消失”多半是这个参数的问题。第三controls.target设在楼体高度的一半保证旋转时围绕楼身而不是地面原点观感差别很大。参数都在CONFIG里改楼层数不用动这段。3. 核心模块拆解楼层生成、幕墙与相机控制3.1 楼层堆叠用循环和实例化把楼“长”出来建筑生成模块是整个工程最值得看的部分。它没有用外部模型而是用参数化方式一层层堆盒子核心思路是“标准层重复 顶部收分 底部裙楼”。简化后的实现如下// src/building.js import * as THREE from three; export function buildSkyscraper(config) { const group new THREE.Group(); const { floorCount, floorHeight, width, depth } config; // 标准层用 InstancedMesh 批量绘制避免几百个独立 Mesh 拖慢渲染 const floorGeo new THREE.BoxGeometry(width, floorHeight * 0.92, depth); const floorMat new THREE.MeshStandardMaterial({ color: 0x8899aa, roughness: 0.6 }); const floors new THREE.InstancedMesh(floorGeo, floorMat, floorCount); const matrix new THREE.Matrix4(); for (let i 0; i floorCount; i) { // 每层之间留 8% 缝隙形成楼层分隔线 const y i * floorHeight floorHeight * 0.5; matrix.makeTranslation(0, y, 0); floors.setMatrixAt(i, matrix); } floors.instanceMatrix.needsUpdate true; group.add(floors); // 核心筒一根贯穿全楼的细长柱体增加结构感 const coreGeo new THREE.BoxGeometry(width * 0.3, floorCount * floorHeight, depth * 0.3); const core new THREE.Mesh(coreGeo, new THREE.MeshStandardMaterial({ color: 0x556677 })); core.position.y (floorCount * floorHeight) / 2; group.add(core); // 塔冠顶部收分用逐层缩小的盒子模拟 const crownLayers 5; for (let i 0; i crownLayers; i) { const scale 1 - i * 0.15; const crownGeo new THREE.BoxGeometry(width * scale, floorHeight, depth * scale); const crown new THREE.Mesh(crownGeo, floorMat); crown.position.y floorCount * floorHeight i * floorHeight floorHeight * 0.5; group.add(crown); } return group; }逻辑说明标准层用InstancedMesh一次提交所有楼层这是性能关键——如果每层都new THREE.Mesh一百层就是一百次 draw call加上幕墙和灯光很容易卡。floorHeight * 0.92留出的缝隙就是楼层分隔线比贴图省事。核心筒用一根柱体贯穿视觉上让楼“有骨头”。塔冠用循环缩小盒子scale每次减 0.15五层收完改crownLayers就能换塔冠高度。参数方面floorCount控制总层数floorHeight控制层高width / depth控制平面尺寸这三个在config.js里改整栋楼的比例立刻变。3.2 幕墙与夜景材质和发光贴图怎么配立面模块负责给楼体贴玻璃质感。常见做法是用MeshPhysicalMaterial开透明和反射再叠一张窗格贴图控制哪些格子亮灯。核心片段// src/facade.js import * as THREE from three; export function applyFacade(mesh, textureLoader, config) { const glassMap textureLoader.load(assets/textures/glass_grid.png); const emissiveMap textureLoader.load(assets/textures/window_lights.png); const material new THREE.MeshPhysicalMaterial({ map: glassMap, // 基础窗格纹理 emissiveMap: emissiveMap, // 夜景亮灯贴图 emissive: new THREE.Color(0xffcc88), emissiveIntensity: config.nightMode ? 1.2 : 0.0, // 夜景模式才发光 metalness: 0.4, roughness: 0.15, transparent: true, opacity: 0.85, envMapIntensity: 1.0 }); mesh.material material; return material; }emissiveMap是夜景的灵魂贴图里亮色像素对应亮灯的窗户暗色像素不发光emissiveIntensity控制整体亮度。nightMode开关在config.js里切到true整栋楼就亮起来。注意transparent: true加opacity会让渲染顺序变复杂如果出现窗户前后穿插的“鬼影”把depthWrite设为false或者干脆关掉透明、只靠贴图表现玻璃这是踩过的坑。metalness和roughness决定玻璃反光强弱白天场景调高金属度更通透夜景则要压低粗糙度让灯光有反射。3.3 相机漫游与交互轨道控制和自动巡航控制模块决定用户怎么看这栋楼。源码里通常是轨道控制加一个可选的自动巡航核心是限制俯仰角和距离防止相机钻到地下或飞出场景// src/controls.js import { OrbitControls } from three/addons/controls/OrbitControls.js; export function setupControls(camera, domElement) { const controls new OrbitControls(camera, domElement); controls.enableDamping true; // 惯性阻尼手感更顺 controls.dampingFactor 0.08; controls.minDistance 30; // 最近距离防止穿模进楼 controls.maxDistance 400; // 最远距离防止楼变成一个小点 controls.maxPolarAngle Math.PI * 0.49; // 限制俯仰防止相机钻到地面以下 controls.autoRotate false; // 自动巡航开关 controls.autoRotateSpeed 0.6; return controls; }maxPolarAngle设成略小于 90 度相机就永远在地面以上这个参数不设的话用户一拖就能看到楼底“穿帮”。enableDamping让松手后有缓停观感提升明显但要在渲染循环里每帧调controls.update()否则阻尼不生效——这是新手最容易漏的一步。autoRotate打开就是自动巡航适合做展示大屏。4. 跑起来与避坑本地启动、参数调试和常见故障4.1 本地启动的完整步骤拿到源码后按这个顺序走基本不会卡住# 1. 进入项目目录 cd skyscraper-src # 2. 安装依赖有 package.json 的情况 npm install # 3. 启动开发服务器 npm run dev # 终端会输出类似 http://localhost:5173 的地址浏览器打开即可 # 4. 如果要出静态包 npm run build # 产物在 dist/ 目录用任意静态服务器托管如果没有package.json说明是纯静态版本用npx serve .或任意静态服务器打开index.html就行但注意不能直接双击文件用file://打开——ES Module 和贴图加载都会被浏览器的同源策略拦掉控制台会报 CORS 错误。这是第一个高频翻车点。4.2 参数调试改哪几个数字最见效想快速看出效果优先动config.js里这几个值参数作用建议范围改动效果floorCount总楼层数20~120楼体高矮超过 100 注意性能floorHeight单层层高3~5楼层疏密影响整体比例width / depth平面尺寸10~40楼体胖瘦太大会显得笨重nightMode夜景开关true / false切换白天与亮灯模式autoRotate自动巡航true / false展示大屏用改完保存开发服务器会热更新不用重启。如果改了没反应先确认改的是src/config.js而不是dist里的旧产物。4.3 常见问题排查五个真实踩坑记录现象一页面全黑控制台报Failed to resolve module specifier three。原因是没有安装依赖或用了file://直接打开浏览器不认识裸模块名。解决走npm installnpm run dev或者用 importmap 把three映射到 CDN 地址。现象二楼体只有一半塔冠不见了。原因是相机远裁剪面太小塔冠超出可视范围被裁掉。解决把PerspectiveCamera第四个参数从默认的 1000 调到 2000 以上或者按楼高动态计算。现象三帧率很低风扇狂转。原因多半是楼层用了独立 Mesh 而不是InstancedMeshdraw call 爆炸或者setPixelRatio没设上限。解决确认楼层走实例化像素比限制在 2 以内阴影贴图分辨率别开太高。现象四玻璃窗户前后闪烁、穿插。原因是透明材质渲染顺序问题。解决给幕墙材质设depthWrite: false或者放弃透明、只用贴图表现玻璃夜景靠emissiveMap就够。现象五鼠标拖动后相机钻到地面以下看到楼底穿帮。原因是没限制俯仰角。解决controls.maxPolarAngle Math.PI * 0.49同时设minDistance防止穿模进楼。提示改任何参数前先备份config.js这个文件是整栋楼的“总开关”改乱了很难回退。5. 二次开发换风格、加数据、做展示页的改造思路这套源码最大的价值不是它现在长什么样而是它的参数化结构让你能快速换一栋“别的楼”。我一般从三个方向改。第一个方向是换建筑风格。把building.js里的标准层从方盒子换成带切角的八边形或者给塔冠加旋转收分改的是几何生成那几十行config.js加一个style字段做分支即可。幕墙那边换一张窗格贴图、调metalness和emissiveIntensity白天玻璃幕墙和夜景灯光楼就是两套观感。验证方法很简单改完跑npm run dev用自动巡航转一圈看有没有穿模和闪烁。第二个方向是接真实数据。很多展示需求是“按楼层显示不同信息”比如每层亮灯率、入驻企业色块。做法是在config.js里加一个floorData数组building.js生成楼层时按索引读数据用InstancedMesh的setColorAt给每层上不同颜色// 按楼层数据着色替代统一材质色 const color new THREE.Color(); for (let i 0; i floorCount; i) { const rate config.floorData?.[i]?.occupancy ?? 0.5; color.setHSL(0.55, 0.6, 0.3 rate * 0.4); // 入驻率越高越亮 floors.setColorAt(i, color); } floors.instanceColor.needsUpdate true;这样一栋楼就变成了数据可视化载体比纯展示有说服力。注意setColorAt要求材质开vertexColors或者用支持实例颜色的材质否则颜色不生效。第三个方向是做展示页。把autoRotate打开、隐藏控制面板、加一个标题层就是一个能挂在大屏上循环播放的建筑展示页。如果要嵌到已有页面里把renderer.setSize改成读容器尺寸而不是window.innerWidth再在resize里同步更新避免画布溢出。改造时我有个习惯每改一个模块就先单独跑一遍确认几何、材质、控制三块各自正常再合起来不然出了问题分不清是哪一层引入的。从那以后我每次动building.js的循环边界都强制先把floorCount调到 5 跑一遍小样确认塔冠和核心筒位置对得上再放大到一百层——这个习惯帮我省了不少排查时间。希望帮到你。本文还有配套的精品资源点击获取