WebXR教学 07 项目5 贪吃蛇小游戏

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();
相关推荐
jqq6664 分钟前
Vue3脚手架实现(七、渲染eslint配置)
前端·javascript·vue.js
Mintopia5 分钟前
BVH:光线追踪里的空间管家
前端·javascript·计算机图形学
Mintopia11 分钟前
Three.js 射线拾取原理:像素世界的侦探故事
前端·javascript·计算机图形学
掘金安东尼30 分钟前
前端周刊第421期(2025年7月1日–7月6日)
前端·面试·github
摸鱼仙人~33 分钟前
深入理解 classnames:React 动态类名管理的最佳实践
前端·react.js·前端框架
未来之窗软件服务35 分钟前
chrome webdrive异常处理-session not created falled opening key——仙盟创梦IDE
前端·人工智能·chrome·仙盟创梦ide·东方仙盟·数据调式
kymjs张涛35 分钟前
零一开源|前沿技术周报 #6
前端·ios·harmonyos
玲小珑39 分钟前
Next.js 教程系列(十)getStaticPaths 与动态路由的静态生成
前端·next.js
天天鸭1 小时前
写个vite插件自动处理系统权限,降低99%重复工作
前端·javascript·vite
蓝婷儿1 小时前
每天一个前端小知识 Day 23 - PWA 渐进式 Web 应用开发
前端