WebXR教学 07 项目5 贪吃蛇小游戏
index.html
html
<!DOCTYPE html>
<html>
<head>
<title>3D贪吃蛇小游戏</title>
<style>
body { margin: 0; }
canvas { display: block; }
#score {
position: absolute;
top: 20px;
left: 20px;
color: white;
font-size: 24px;
}
</style>
</head>
<body>
<div id="score">Score: 0</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="game.js"></script>
</body>
</html>
game.js
javascript
/**
* 场景相关变量定义
*/
// Three.js 场景对象
let scene, camera, renderer;
// 游戏主要对象:蛇和食物
let snake, food;
// 游戏得分
let score = 0;
// 游戏场地大小(网格单位)
const gridSize = 20;
// 蛇身和食物的单位大小(立方体边长)
const cubeSize = 1;
// 游戏循环时间控制
let lastTime = 0;
// 蛇移动的时间间隔(毫秒)
const moveInterval = 200;
// 游戏运行状态标志
let gameLoop = false;
/**
* 初始化游戏场景
* 创建THREE.js场景、相机、渲染器等基本组件
*/
function init() {
// 创建THREE.js场景
scene = new THREE.Scene();
// 设置透视相机:视角75度,屏幕宽高比,近裁剪面0.1,远裁剪面1000
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 创建WebGL渲染器
renderer = new THREE.WebGLRenderer();
// 设置渲染器尺寸为窗口大小
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 设置相机位置和视角
camera.position.set(10, 15, 20);
camera.lookAt(0, 0, 0);
// 初始化游戏场景组件
createFloor(); // 创建地板
snake = new Snake(); // 创建蛇
food = createFood(); // 创建第一个食物
// 添加键盘事件监听
document.addEventListener('keydown', onKeyPress);
// 启动游戏循环
gameLoop = true;
requestAnimationFrame(animate);
}
/**
* 贪吃蛇类定义
* 包含蛇的属性和行为
*/
class Snake {
constructor() {
// 存储蛇身体段的数组
this.body = [];
// 初始移动方向
this.direction = 'right';
// 创建蛇头(绿色立方体)
const geometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const head = new THREE.Mesh(geometry, material);
this.body.push(head);
scene.add(head);
}
/**
* 蛇的移动逻辑
* 包含移动、碰撞检测和生长机制
*/
move() {
const head = this.body[0];
const newHead = head.clone();
// 根据当前方向更新头部位置
switch(this.direction) {
case 'right': newHead.position.x += cubeSize; break;
case 'left': newHead.position.x -= cubeSize; break;
case 'up': newHead.position.z -= cubeSize; break;
case 'down': newHead.position.z += cubeSize; break;
}
// 边界碰撞检测
const halfGrid = (gridSize * cubeSize) / 2 - cubeSize/2;
if (newHead.position.x > halfGrid ||
newHead.position.x < -halfGrid ||
newHead.position.z > halfGrid ||
newHead.position.z < -halfGrid) {
gameOver("离开地板范围!");
return;
}
// 自身碰撞检测
if (this.checkSelfCollision(newHead)) {
gameOver("撞到自己了!");
return;
}
// 食物碰撞检测及处理
if (this.checkCollisionWithFood(newHead)) {
scene.remove(food);
food = createFood();
score += 10;
document.getElementById('score').textContent = `Score: ${score}`;
} else {
// 如果没有吃到食物,移除尾部
scene.remove(this.body.pop());
}
// 添加新头部
this.body.unshift(newHead);
scene.add(newHead);
}
/**
* 检测蛇头是否与身体碰撞
* @param {THREE.Mesh} head - 新的蛇头位置
* @returns {boolean} - 是否发生碰撞
*/
checkSelfCollision(head) {
return this.body.some((segment, index) => {
if (index <= 0) return false;
const collision = Math.abs(head.position.x - segment.position.x) < 0.5 &&
Math.abs(head.position.z - segment.position.z) < 0.5;
return collision;
});
}
/**
* 检测是否吃到食物
* @param {THREE.Mesh} head - 蛇头位置
* @returns {boolean} - 是否吃到食物
*/
checkCollisionWithFood(head) {
return Math.abs(head.position.x - food.position.x) < 0.1 &&
Math.abs(head.position.z - food.position.z) < 0.1;
}
}
/**
* 创建食物
* 随机位置生成一个红色立方体
* @returns {THREE.Mesh} - 食物对象
*/
function createFood() {
const geometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const food = new THREE.Mesh(geometry, material);
// 计算食物可生成的范围
const maxRange = (gridSize / 2) - 1;
// 随机生成食物位置
food.position.x = (Math.floor(Math.random() * (maxRange * 2 + 1)) - maxRange) * cubeSize;
food.position.z = (Math.floor(Math.random() * (maxRange * 2 + 1)) - maxRange) * cubeSize;
// 确保食物不会出现在蛇身上
while (snake.body.some(segment =>
Math.abs(segment.position.x - food.position.x) < 0.1 &&
Math.abs(segment.position.z - food.position.z) < 0.1
)) {
food.position.x = (Math.floor(Math.random() * (maxRange * 2 + 1)) - maxRange) * cubeSize;
food.position.z = (Math.floor(Math.random() * (maxRange * 2 + 1)) - maxRange) * cubeSize;
}
scene.add(food);
return food;
}
/**
* 创建游戏地板
* 创建一个灰色平面作为游戏场地
*/
function createFloor() {
const geometry = new THREE.PlaneGeometry(gridSize * cubeSize, gridSize * cubeSize);
const material = new THREE.MeshBasicMaterial({
color: 0x808080,
side: THREE.DoubleSide
});
const floor = new THREE.Mesh(geometry, material);
floor.rotation.x = Math.PI / 2;
scene.add(floor);
}
/**
* 键盘按键处理
* 处理方向键控制蛇的移动方向
* @param {KeyboardEvent} event - 键盘事件对象
*/
function onKeyPress(event) {
switch(event.key) {
case 'ArrowRight': snake.direction = 'right'; break;
case 'ArrowLeft': snake.direction = 'left'; break;
case 'ArrowUp': snake.direction = 'up'; break;
case 'ArrowDown': snake.direction = 'down'; break;
}
}
/**
* 游戏结束处理
* @param {string} reason - 游戏结束原因
*/
function gameOver(reason) {
gameLoop = false;
alert(`游戏结束!${reason}\n最终得分:${score}`);
location.reload(); // 重置游戏
}
/**
* 游戏动画循环
* @param {number} currentTime - 当前时间戳
*/
function animate(currentTime) {
if (!gameLoop) return;
if (!lastTime) lastTime = currentTime;
const deltaTime = currentTime - lastTime;
// 控制蛇的移动速度
if (deltaTime >= moveInterval) {
snake.move();
lastTime = currentTime;
}
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
// 启动游戏
init();
