文章目录
浏览器里的3D世界:用HTML5+Three.js打造你的第一款小游戏
一、阶段1:零基础入门(环境搭建与核心概念)
目标:搭建开发环境,理解Three.js基本原理,创建第一个3D场景。
核心知识点
-
开发环境准备
- HTML5基础:
canvas元素(3D渲染容器)、基本DOM操作 - Three.js引入:通过CDN(
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>)或npm安装 - 开发工具:VS Code(推荐插件:Live Server实时预览)
- HTML5基础:
-
Three.js核心组件
- 场景(Scene):3D世界的容器,所有物体都需要添加到场景中
- 相机(Camera):模拟人眼视角,常用
PerspectiveCamera(透视相机,有近大远小效果) - 渲染器(Renderer):将3D场景渲染到
canvas上 - 物体(Mesh):由几何体(Geometry)和材质(Material)组成
实战代码:第一个3D场景(旋转立方体)
<!DOCTYPE html>
<html>
<head>
<title>我的第一个Three.js游戏</title>
<!-- 引入Three.js -->
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<style>
body { margin: 0; } /* 移除默认边距 */
canvas { display: block; } /* 全屏显示canvas */
</style>
</head>
<body>
<script>
// 1. 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xeeeeee); // 浅灰色背景
// 2. 创建相机(视野角度75°,宽高比=窗口宽高,近平面0.1,远平面1000)
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5; // 相机位置(沿z轴远离原点)
// 3. 创建渲染器并添加到页面
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染尺寸
document.body.appendChild(renderer.domElement); // 将canvas添加到页面
// 4. 创建物体:立方体(几何体+材质)
const geometry = new THREE.BoxGeometry(1, 1, 1); // 1x1x1的立方体
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00, // 绿色
wireframe: true // 线框模式(便于观察3D结构)
});
const cube = new THREE.Mesh(geometry, material); // 组合几何体和材质
scene.add(cube); // 将立方体添加到场景
// 5. 窗口大小变化时调整渲染器和相机
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); // 更新相机投影矩阵
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 6. 动画循环(每帧执行)
function animate() {
requestAnimationFrame(animate); // 浏览器原生动画API,确保流畅渲染
// 让立方体旋转
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera); // 渲染场景
}
animate(); // 启动动画
</script>
</body>
</html>
最佳实践与注意事项
- 相机位置:初始位置需确保物体在相机视野内(如
z=5可看到原点附近的物体) - 渲染循环:必须使用
requestAnimationFrame而非setInterval,前者会根据浏览器刷新率自动调整(通常60帧/秒) - 窗口适配:监听
resize事件并更新相机和渲染器,避免窗口缩放后画面变形
二、阶段2:3D物体与场景构建(游戏世界基础)
目标:掌握3D物体创建、材质纹理应用、灯光与阴影,构建游戏场景。
核心知识点
-
几何体与物体
- 内置几何体:
BoxGeometry(立方体)、SphereGeometry(球体)、CylinderGeometry(圆柱体)等 - 自定义几何体:通过顶点坐标创建复杂形状(进阶)
- 物体操作:位置(
position)、旋转(rotation)、缩放(scale)
- 内置几何体:
-
材质与纹理
- 基础材质:
MeshBasicMaterial(不受灯光影响)、MeshLambertMaterial(漫反射,受灯光影响) - 纹理映射:加载图片作为材质(
TextureLoader),实现物体表面细节 - 透明与混合:
transparent: true+opacity控制透明度
- 基础材质:
-
灯光与阴影
- 常用灯光:
AmbientLight(环境光,无方向)、DirectionalLight(平行光,如太阳光) - 阴影开启:需同时设置渲染器、灯光、物体的阴影属性
- 常用灯光:
实战代码:带纹理和阴影的3D场景
<!DOCTYPE html>
<html>
<head>
<title>3D场景与纹理</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<style> body { margin: 0; } </style>
</head>
<body>
<script>
// 1. 场景、相机、渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 10;
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // 启用阴影渲染
document.body.appendChild(renderer.domElement);
// 2. 灯光
// 环境光(基础照明,无阴影)
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// 平行光(产生阴影)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 5, 5); // 灯光位置(斜上方)
directionalLight.castShadow = true; // 灯光产生阴影
scene.add(directionalLight);
// 3. 地面(接收阴影)
const groundGeometry = new THREE.PlaneGeometry(20, 20);
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0xcccccc });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2; // 旋转90度使其水平
ground.position.y = -3;
ground.receiveShadow = true; // 地面接收阴影
scene.add(ground);
// 4. 带纹理的球体
const textureLoader = new THREE.TextureLoader();
// 加载地球纹理(使用picsum的图片模拟)
textureLoader.load('https://picsum.photos/id/237/512/512', (texture) => {
const sphereGeometry = new THREE.SphereGeometry(2, 32, 32); // 球体,32段细分
const sphereMaterial = new THREE.MeshLambertMaterial({ map: texture });
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.castShadow = true; // 球体产生阴影
scene.add(sphere);
// 动画:球体旋转
function animate() {
requestAnimationFrame(animate);
sphere.rotation.y += 0.005;
renderer.render(scene, camera);
}
animate();
});
// 窗口适配
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
最佳实践与注意事项
- 纹理优化:纹理图片尺寸建议为2的幂次方(如256x256、512x512),加载更快且渲染更稳定
- 阴影性能:阴影计算消耗性能,复杂场景可降低阴影分辨率(
directionalLight.shadow.mapSize.set(1024, 1024)) - 几何体细分:细分段数(如球体的
32,32)越高,物体越光滑,但性能消耗越大,需平衡画质与性能
三、阶段3:交互与物理(游戏核心机制)
目标:实现玩家交互(鼠标/键盘控制)、碰撞检测、物理效果,让游戏“可玩”。
核心知识点
-
用户交互
- 射线检测(
Raycaster):检测鼠标点击的3D物体(如拾取物品、点击按钮) - 键盘控制:监听
keydown/keyup事件,控制角色移动 - 触摸适配:移动端触摸事件(
touchstart/touchmove)处理
- 射线检测(
-
物理引擎集成
- 常用引擎:Cannon.js(轻量)、Ammo.js(基于Bullet,功能强)
- 核心概念:刚体(RigidBody)、碰撞体(Shape)、世界(World)、重力(Gravity)
实战代码:鼠标交互与物理碰撞
<!DOCTYPE html>
<html>
<head>
<title>交互与物理效果</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<!-- 引入Cannon.js物理引擎 -->
<script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script>
<style> body { margin: 0; } </style>
</head>
<body>
<script>
// 1. 基础组件
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 5, 15);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 2. 灯光
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(5, 10, 7);
scene.add(dirLight);
// 3. 物理世界(Cannon.js)
const world = new CANNON.World();
world.gravity.set(0, -9.82, 0); // 重力(y轴向下)
world.broadphase = new CANNON.NaiveBroadphase(); // 碰撞检测算法
// 4. 地面(物理+渲染)
// 渲染用地面
const groundGeo = new THREE.PlaneGeometry(20, 20);
const groundMat = new THREE.MeshLambertMaterial({ color: 0x999999 });
const groundMesh = new THREE.Mesh(groundGeo, groundMat);
groundMesh.rotation.x = -Math.PI / 2;
scene.add(groundMesh);
// 物理用地面
const groundShape = new CANNON.Plane();
const groundBody = new CANNON.Body({ mass: 0 }); // 质量0=静止物体
groundBody.addShape(groundShape);
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1, 0, 0), -Math.PI / 2);
world.addBody(groundBody);
// 5. 可点击的立方体(物理+渲染)
const boxes = []; // 存储所有立方体(渲染+物理)
function createBox(x, y, z) {
// 渲染用立方体
const geo = new THREE.BoxGeometry(1, 1, 1);
const mat = new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff });
const mesh = new THREE.Mesh(geo, mat);
mesh.position.set(x, y, z);
mesh.castShadow = true;
scene.add(mesh);
// 物理用立方体
const shape = new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)); // 半边长
const body = new CANNON.Body({ mass: 1 }); // 质量1=动态物体
body.addShape(shape);
body.position.set(x, y, z);
world.addBody(body);
boxes.push({ mesh, body });
}
// 初始创建3个立方体
createBox(-3, 5, 0);
createBox(0, 5, 0);
createBox(3, 5, 0);
// 6. 鼠标点击交互(射线检测)
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
window.addEventListener('click', (event) => {
// 将鼠标坐标转换为Three.js标准坐标(-1到1)
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 更新射线(从相机到鼠标点)
raycaster.setFromCamera(mouse, camera);
// 检测与立方体的碰撞
const intersects = raycaster.intersectObjects(boxes.map(b => b.mesh));
if (intersects.length > 0) {
// 点击到立方体:施加向上的力
const clickedBox = boxes.find(b => b.mesh === intersects[0].object);
clickedBox.body.applyImpulse(
new CANNON.Vec3(0, 5, 0), // 力的方向和大小
clickedBox.body.position // 力的作用点
);
}
});
// 7. 动画循环(同步物理与渲染)
const timeStep = 1 / 60; // 物理模拟帧率
function animate() {
requestAnimationFrame(animate);
// 更新物理世界
world.step(timeStep);
// 同步物理位置到渲染物体
boxes.forEach(box => {
box.mesh.position.copy(box.body.position);
box.mesh.quaternion.copy(box.body.quaternion);
});
renderer.render(scene, camera);
}
animate();
// 窗口适配
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
最佳实践与注意事项
- 射线检测性能:
intersectObjects方法传入需要检测的物体列表(而非整个场景),减少计算量 - 物理引擎优化:
- 质量为0的物体(如地面)设为静态,减少计算
- 复杂场景使用
Cannon.BVHBroadphase替代NaiveBroadphase,碰撞检测更快
- 交互反馈:点击物体时添加视觉反馈(如颜色变化、粒子效果),提升用户体验
四、阶段4:游戏功能开发(核心玩法与逻辑)
目标:整合前面知识,实现游戏核心玩法(如收集、闯关、计分)、UI界面、状态管理。
核心知识点
-
游戏场景管理
- 多场景切换:菜单场景、游戏场景、结束场景
- 资源预加载:使用
LoadingManager加载纹理、模型等资源,显示加载进度
-
核心玩法实现
- 碰撞事件:检测玩家与目标/障碍物的碰撞(如收集物品加分)
- 角色控制:第一/第三人称视角移动(结合键盘
WASD或方向键) - 计分与状态:使用变量记录分数、生命值,实现游戏胜利/失败逻辑
-
2D UI集成
- HTML叠加层:用普通HTML/CSS创建计分板、按钮(简单高效)
- 纹理UI:用Three.js的
CanvasTexture创建3D空间中的UI元素
实战代码:简易收集类游戏(核心玩法)
<!DOCTYPE html>
<html>
<head>
<title>收集星星游戏</title>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script>
<style>
body { margin: 0; }
#scoreboard {
position: fixed;
top: 20px;
left: 20px;
font-family: Arial;
font-size: 24px;
color: white;
background: rgba(0,0,0,0.5);
padding: 10px 20px;
border-radius: 5px;
}
#startScreen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: white;
font-family: Arial;
}
#startButton {
padding: 15px 30px;
font-size: 20px;
margin-top: 20px;
cursor: pointer;
background: #4CAF50;
border: none;
color: white;
border-radius: 5px;
}
</style>
</head>
<body>
<!-- 游戏UI -->
<div id="scoreboard">分数: 0</div>
<div id="startScreen">
<h1>收集星星</h1>
<p>使用方向键移动,收集黄色星星得分!</p>
<button id="startButton">开始游戏</button>
</div>
<script>
// 游戏状态
let score = 0;
let isPlaying = false;
// 1. 基础组件
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111133); // 深色背景
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 2. 灯光
scene.add(new THREE.AmbientLight(0xffffff, 0.3));
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(10, 10, 5);
scene.add(dirLight);
// 3. 物理世界
const world = new CANNON.World();
world.gravity.set(0, -20, 0);
world.broadphase = new CANNON.SAPBroadphase(world);
// 4. 地面
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(50, 50),
new THREE.MeshLambertMaterial({ color: 0x333333 })
);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
const groundBody = new CANNON.Body({ mass: 0 });
groundBody.addShape(new CANNON.Plane());
groundBody.quaternion.setFromAxisAngle(new CANNON.Vec3(1,0,0), -Math.PI/2);
world.addBody(groundBody);
// 5. 玩家(立方体)
const playerGeo = new THREE.BoxGeometry(1, 2, 1);
const playerMat = new THREE.MeshLambertMaterial({ color: 0x4287f5 });
const playerMesh = new THREE.Mesh(playerGeo, playerMat);
playerMesh.position.y = 1; // 底部与地面对齐
scene.add(playerMesh);
// 物理玩家
const playerShape = new CANNON.Box(new CANNON.Vec3(0.5, 1, 0.5));
const playerBody = new CANNON.Body({ mass: 5 });
playerBody.addShape(playerShape);
playerBody.position.set(0, 1, 0);
playerBody.fixedRotation = true; // 防止旋转
world.addBody(playerBody);
// 6. 星星(收集目标)
const stars = [];
function createStar() {
// 随机位置(x: -20~20, z: -20~20)
const x = (Math.random() - 0.5) * 40;
const z = (Math.random() - 0.5) * 40;
// 星星几何体(用八面体模拟)
const starGeo = new THREE.OctahedronGeometry(0.5);
const starMat = new THREE.MeshLambertMaterial({ color: 0xffff00 });
const starMesh = new THREE.Mesh(starGeo, starMat);
starMesh.position.set(x, 0.5, z);
scene.add(starMesh);
// 物理星星(传感器,不产生碰撞力)
const starShape = new CANNON.Sphere(0.5);
const starBody = new CANNON.Body({ mass: 0, type: CANNON.Body.KINEMATIC });
starBody.addShape(starShape);
starBody.position.set(x, 0.5, z);
world.addBody(starBody);
stars.push({ mesh: starMesh, body: starBody });
}
// 初始创建10个星星
for (let i = 0; i < 10; i++) createStar();
// 7. 碰撞检测(玩家与星星)
world.addEventListener('beginContact', (event) => {
if (!isPlaying) return;
const bodyA = event.bodyA;
const bodyB = event.bodyB;
// 判断是否是玩家与星星碰撞
const isPlayerStar = (bodyA === playerBody && stars.some(s => s.body === bodyB)) ||
(bodyB === playerBody && stars.some(s => s.body === bodyA));
if (isPlayerStar) {
// 找到被碰撞的星星
const star = stars.find(s => s.body === bodyA || s.body === bodyB);
if (star) {
// 移除星星
scene.remove(star.mesh);
world.removeBody(star.body);
stars.splice(stars.indexOf(star), 1);
// 加分
score += 10;
document.getElementById('scoreboard').textContent = `分数: ${score}`;
// 每收集3个星星新增1个
if (score % 30 === 0) createStar();
}
}
});
// 8. 玩家控制(键盘)
const keys = { left: false, right: false, forward: false, backward: false };
window.addEventListener('keydown', (e) => {
if (!isPlaying) return;
switch(e.key) {
case 'ArrowLeft': keys.left = true; break;
case 'ArrowRight': keys.right = true; break;
case 'ArrowUp': keys.forward = true; break;
case 'ArrowDown': keys.backward = true; break;
}
});
window.addEventListener('keyup', (e) => {
switch(e.key) {
case 'ArrowLeft': keys.left = false; break;
case 'ArrowRight': keys.right = false; break;
case 'ArrowUp': keys.forward = false; break;
case 'ArrowDown': keys.backward = false; break;
}
});
// 9. 游戏开始
document.getElementById('startButton').addEventListener('click', () => {
document.getElementById('startScreen').style.display = 'none';
isPlaying = true;
});
// 10. 动画循环
const timeStep = 1 / 60;
function animate() {
requestAnimationFrame(animate);
if (isPlaying) {
// 玩家移动
const speed = 10;
const direction = new CANNON.Vec3(0, 0, 0);
if (keys.forward) direction.z -= speed;
if (keys.backward) direction.z += speed;
if (keys.left) direction.x -= speed;
if (keys.right) direction.x += speed;
playerBody.velocity.x = direction.x;
playerBody.velocity.z = direction.z;
// 相机跟随玩家
camera.position.x = playerBody.position.x;
camera.position.z = playerBody.position.z + 10;
camera.position.y = playerBody.position.y + 5;
camera.lookAt(playerBody.position.x, playerBody.position.y + 1, playerBody.position.z);
}
// 更新物理和渲染
world.step(timeStep);
playerMesh.position.copy(playerBody.position);
renderer.render(scene, camera);
}
animate();
// 窗口适配
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
最佳实践与注意事项
- 游戏状态管理:用变量(如
isPlaying)清晰区分游戏状态(未开始/进行中/结束),避免逻辑混乱 - 性能控制:限制同时存在的物体数量(如星星数量),复杂场景使用对象池(Object Pool)复用物体
- 移动适配:为移动端添加虚拟摇杆(用HTML/CSS实现),映射到方向键控制逻辑
五、阶段5:优化、测试与发布(上线准备)
目标:优化游戏性能,适配多设备,最终打包发布。
核心知识点
-
性能优化
- 几何体合并:
BufferGeometryUtils.mergeBufferGeometries合并静态物体,减少绘制调用 - 层级细节(LOD):远处物体使用低多边形模型
- 纹理压缩:使用
basisu等工具压缩纹理,减少内存占用
- 几何体合并:
-
兼容性与适配
- 浏览器兼容:检测WebGL支持(
renderer.capabilities.isWebGL2),提供降级提示 - 响应式设计:根据设备性能自动调整画质(如移动端降低阴影质量)
- 浏览器兼容:检测WebGL支持(
-
打包与发布
- 资源压缩:用
terser压缩JS,gulp/webpack打包项目 - 发布平台:部署到静态服务器(如Netlify、Vercel)或游戏平台(如itch.io)
- 资源压缩:用
最佳实践与注意事项
- 性能测试:用Chrome DevTools的Performance面板分析帧率,找出瓶颈(如频繁GC、复杂碰撞)
- 错误监控:添加全局错误捕获(
window.addEventListener('error', ...)),记录玩家遇到的问题 - 加载策略:大资源(如3D模型)分阶段加载,优先加载核心游戏资源,提升首屏加载速度
总结:从零基础到发布的全路径
- 基础入门:环境搭建→Three.js核心组件→第一个3D场景
- 场景构建:几何体与材质→纹理与灯光→3D世界搭建
- 交互与物理:鼠标/键盘控制→射线检测→物理引擎集成
- 游戏开发:核心玩法实现→UI界面→状态管理与逻辑
- 优化发布:性能优化→多设备适配→打包上线
Three.js降低了3D开发的门槛,即使零基础也能通过循序渐进的学习,从创建简单立方体到开发完整小游戏。关键是多实践,理解“场景-相机-渲染器”的核心逻辑,再逐步叠加交互和物理效果,最终实现自己的3D游戏创意。


被折叠的 条评论
为什么被折叠?



