WebXR教学 06 项目4 跳跃小游戏
WebXR教学 06 项目4 跳跃小游戏(1)介绍
准备
npm install vite three
代码
vite.config.js
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
{"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
<!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
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
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(); // 启动游戏循环