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();
相关推荐
会功夫的李白2 分钟前
uniapp自动构建pages.json的vite插件
前端·uni-app·vite
一口一个橘子20 分钟前
[ctfshow web入门] web77
前端·web安全·网络安全
yyywoaini~37 分钟前
wordcount程序
前端·javascript·ajax
Yvonne爱编码1 小时前
CSS- 4.2 相对定位(position: relative)
前端·css·状态模式·html5·hbuilder
白小白灬1 小时前
Vue主题色切换实现方案(CSS 变量 + 类名切换)
前端·css·vue.js
江沉晚呤时1 小时前
.NET Core 中 Swagger 配置详解:常用配置与实战技巧
前端·.netcore
waterHBO1 小时前
chrome 浏览器插件 myTools, 日常小工具。
前端·chrome
哎呦你好1 小时前
HTML 颜色全解析:从命名规则到 RGBA/HSL 值,附透明度设置与场景应用指南
前端·css·html
多云的夏天2 小时前
前端:VUE-(0)-环境搭建和helloworld
前端·javascript·vue.js
Dontla2 小时前
BootCDN介绍(Bootstrap主导的前端开源项目免费CDN加速服务)
前端·开源·bootstrap