WebXR教学 06 项目4 跳跃小游戏

WebXR教学 06 项目4 跳跃小游戏(1)介绍

准备

bash 复制代码
npm install vite three

代码

vite.config.js

javascript 复制代码
import { defineConfig } from 'vite';

export default defineConfig({
    build: {
        emptyOutDir: false,
    },
    server: {
        middleware: (req, res, next) => {
            if (req.url === '/favicon.ico') {
                res.statusCode = 404;
                res.end();
                return;
            }
            next();
        },
    },
});

package.json

json 复制代码
{
  "name": "three-vite",
  "version": "0.0.1",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "three": "^0.174.0",
    "vite": "^6.2.1"
  }
}

index.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>跳跃小游戏</title>
    <link rel="stylesheet" href="./style.css">
</head>
<body>
    <div id="score">得分:0</div>
    <div id="game-over" class="hidden">游戏结束!得分:0</div>
    <canvas id="gameCanvas"></canvas>
    <script type="module" src="./main.js"></script>
</body>
</html>

style.css

css 复制代码
body {
    margin: 0;
    overflow: hidden;
    background-color: white;
}

#canvas {
    display: block;
}

#score {
    position: absolute;
    left: 50%;
    top: 10px;
    transform: translateX(-50%);
    font-size: 30px;
}

#game-over {
    position: absolute;
    left: 50%;
    top: 50%;
    font-size: 30px;
    transform: translate(-50%, -50%);
}

.hidden {
    display: none;
}

main.js

javascript 复制代码
import * as THREE from 'three';
import { distance } from 'three/tsl';

let mario; // 玩家(马里奥)
let gameOver = false; // 游戏是否结束
let velocity = 0;   // 玩家垂直速度
let isJumping = false; // 跳跃状态
let jumpVelocity = 0.7; // 初始跳跃速度
let score = 0; // 分数

const gravity = 0.03;  // 重力加速度
const moveSpeed = 0.1; // 移动速度
const coinScore = 1; // 吃掉金币增加分数
const coinRate = 0.005; // 金币生成概率
const enemyRate = 0.005; // 敌人生成概率
const enemySpeed = 0.05; // 敌人移动速度
const enemyScore = 1; // 敌人死亡增加分数
const enemies = []; // 敌人数组
const coins = [];   // 金币数组

const keys = { left: false, right: false, jump: false }; // 按键状态

// ========================
// 场景初始化
// ========================
// 创建基础场景元素
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas: document.getElementById('gameCanvas') });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xe0e0e0); // 设置背景色
camera.position.set(0, 5, 15); // 设置相机初始位置

// ========================
// 游戏对象创建
// ========================
// 创建地面对象
function createGround() {
    const groundGeometry = new THREE.PlaneGeometry(20, 1);
    const groundMaterial = new THREE.MeshBasicMaterial({ color: 0x89ffff });
    const ground = new THREE.Mesh(groundGeometry, groundMaterial);
    ground.rotation.x = -Math.PI / 2;
    ground.position.y = 0;
    scene.add(ground);
}

// 创建玩家角色
function createPlayer(){
    const marioGeometry = new THREE.BoxGeometry(1, 2, 1);
    const marioMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
    const mario = new THREE.Mesh(marioGeometry, marioMaterial);
    mario.position.set(-9, 1, 0);
    scene.add(mario);

    return mario;
}

// 金币生成逻辑
function spawnCoin() {
    const coinGeometry = new THREE.SphereGeometry(0.5, 16, 16);
    const coinMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
    const coin = new THREE.Mesh(coinGeometry, coinMaterial);
    coin.position.set(
        Math.random() * 18 - 9, // X轴随机位置
        6, // Y轴在地面以上
        0
    );
    scene.add(coin);
    coins.push(coin);
}

// 敌人生成逻辑
function spawnEnemy() {
    const enemyGeometry = new THREE.BoxGeometry(1, 1, 1);
    const enemyMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
    const enemy = new THREE.Mesh(enemyGeometry, enemyMaterial);
    enemy.position.set(10, 0.5, 0); // 从右侧生成
    scene.add(enemy);
    enemies.push(enemy);
}

// 游戏结束处理
function endGame() {
    gameOver = true;
    document.getElementById('game-over').textContent = `游戏结束!得分:${score}`;
    document.getElementById('game-over').classList.remove('hidden');
}

// 碰撞检测系统
function checkCollisions() {
    enemies.forEach((enemy, index) => {
        const dx = mario.position.x - enemy.position.x;
        const dy = mario.position.y - enemy.position.y;

        // 新增踩踏敌人判断(玩家在敌人正上方且在下落)
        if (
            Math.abs(dx) < 0.8 && // X轴接近
            dy > 0.5 &&          // 玩家Y坐标高于敌人
            dy < 1.5 &&          // 有效踩踏高度范围
            velocity < 0         // 玩家处于下落状态
        ) {
            scene.remove(enemy);
            enemies.splice(index, 1);
            updateScore(1);
            return; // 结束当前敌人的检测
        }

        // 侧面碰撞(玩家y坐标在敌人范围内)
        if (
            Math.abs(dx) < 0.8 &&
            Math.abs(dy) < 1.5
        ) {
            endGame();
        }
    });
}

// 更新分数
function updateScore(value) {
    score += value;
    document.getElementById('score').textContent = `得分:${score}`;
}

// 窗口自适应逻辑
function handleResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

// ========================
// 监听事件
// ========================
function listenKey() {
    // 键盘按下事件
    document.addEventListener('keydown', (e) => {
        if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = true;
        if (e.key === 'ArrowRight' || e.key === 'd') keys.right = true;
        if (e.key === ' ') keys.jump = true;
    });
    
    // 键盘释放事件
    document.addEventListener('keyup', (e) => {
        if (e.key === 'ArrowLeft' || e.key === 'a') keys.left = false;
        if (e.key === 'ArrowRight' || e.key === 'd') keys.right = false;
        if (e.key === ' ') keys.jump = false;
    });

    // 窗口自适应逻辑
    window.addEventListener('resize', handleResize);
}

// ========================
// 游戏主循环
// ========================
// 初始化游戏
function init() {
    createGround();
    mario = createPlayer();
    listenKey();
}

function animate() {
    requestAnimationFrame(animate);
    if (gameOver) return;

    // 玩家移动
    if (keys.left && mario.position.x > -9) {
        mario.position.x -= moveSpeed;
    }
    if (keys.right && mario.position.x < 9) {
        mario.position.x += moveSpeed;
    }

    // 跳跃逻辑
    if (keys.jump && !isJumping && mario.position.y <= 1) {
        velocity = jumpVelocity;
        isJumping = true;
    }

    // 重力
    velocity -= gravity;
    mario.position.y += velocity;
    if (mario.position.y <= 1) {
        mario.position.y = 1;
        velocity = 0;
        isJumping = false;
    }

    // 生成金币
    if (Math.random() < coinRate) {
        spawnCoin();
    }

    // 更新金币位置
    coins.forEach((coin, index) => {
        let distance = mario.position.distanceTo(coin.position);
        if (distance < 1) {
            scene.remove(coin);
            coins.splice(index, 1);
            updateScore(coinScore);//加分
        }
    });

    // 生成敌人
    if (Math.random() < enemyRate) {
        spawnEnemy();
    }

    // 更新敌人位置
    enemies.forEach((enemy, index) => {
        enemy.position.x -= enemySpeed; // 向左移动
        if (enemy.position.x < -10) {
            scene.remove(enemy);
            enemies.splice(index, 1);
        }
    }); 

    // 碰撞检测
    checkCollisions();
    
    // 渲染
    renderer.render(scene, camera);
}



// ========================
// 游戏启动
// ========================
init(); // 初始化游戏
animate(); // 启动游戏循环
相关推荐
硅谷秋水18 分钟前
基于视觉的自动驾驶 3D 占据预测:回顾与展望
人工智能·机器学习·计算机视觉·3d·自动驾驶
pixle028 分钟前
Vue3 Echarts 3D立方体柱状图实现教程
3d·信息可视化·echarts
越来越无动于衷4 小时前
java web 过滤器
java·开发语言·servlet·web
SoaringPigeon5 小时前
Locate 3D:Meta出品自监督学习3D定位方法
人工智能·学习·3d
EQ-雪梨蛋花汤20 小时前
【3D基础】顶点法线与平面法线在光照与PBR中的区别与影响
平面·3d
layneyao1 天前
神经辐射场(NeRF)技术解析:3D重建与虚拟世界的未来
人工智能·3d
韩曙亮1 天前
【3D 地图】无人机测绘制作 3D 地图流程 ( 无人机采集数据 | 地图原始数据处理原理 | 数据处理软件 | 无人机测绘完整解决方案 )
3d·无人机·3d地图·无人机测绘·地图测绘·测绘方案·地图数据采集
pixle01 天前
Vue3 Echarts 3D圆形柱状图实现教程以及封装一个可复用的组件
前端·3d·vue·echarts
SEO-狼术1 天前
Aspose.Total for .NET Crack,3D modeling and rendering software
3d