学习threejs,生成复杂3D迷宫游戏

👨‍⚕️ 主页: gis分享者

👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅!

👨‍⚕️ 收录于专栏:threejs gis工程师


文章目录

  • 一、🍀前言
    • [1.1 ☘️cannon-es物理引擎](#1.1 ☘️cannon-es物理引擎)
      • [1.1.1 ☘️核心特性](#1.1.1 ☘️核心特性)
      • [1.1.2 ☘️安装](#1.1.2 ☘️安装)
      • [1.1.3 ☘️使用示例](#1.1.3 ☘️使用示例)
  • 二、🍀生成复杂3D迷宫游戏
    • [1. ☘️实现思路](#1. ☘️实现思路)
    • [2. ☘️代码样例](#2. ☘️代码样例)

一、🍀前言

本文详细介绍如何基于threejs在三维场景中生成复杂3D迷宫游戏。亲测可用。希望能帮助到您。一起学习,加油!加油!

1.1 ☘️cannon-es物理引擎

cannon-es 是一个轻量级的 JavaScript 3D 物理引擎,专为网页端设计,能够高效模拟现实世界中的物理效果,如碰撞检测、重力、刚体动力学和约束等。作为经典物理引擎 cannon.js 的现代分支,cannon-es 在保留核心功能的同时,通过优化代码结构、支持模块化(ESM/CJS)和树摇(Tree Shaking),提升了性能和兼容性,更适合现代前端开发环境。

cannon-es 官网

1.1.1 ☘️核心特性

物理模拟

支持重力、碰撞检测、刚体动力学等基础物理效果。

刚体类型

提供动态刚体(受力和碰撞影响)、静态刚体(固定不动)和运动学刚体(通过速度控制,不受力影响)。

碰撞形状

支持多种几何形状,如球体(Sphere)、立方体(Box)、平面(Plane)、凸多面体(ConvexPolyhedron)、粒子(Particle)、高度场(Heightfield)和三角网格(Trimesh)。

约束系统

支持固定约束、距离约束、点对点约束等,用于限制物体间的相对运动。

材质系统

通过定义摩擦系数和反弹系数,模拟不同材质间的交互效果。

性能优化

支持时间步进控制(fixedStep/step),适合需要精确控制的场景。

1.1.2 ☘️安装

通过 npm 或 yarn 安装 cannon-es:

javascript 复制代码
npm install cannon-es
# 或
yarn add cannon-es

1.1.3 ☘️使用示例

完整示例代码:

javascript 复制代码
import * as CANNON from 'cannon-es';

// 创建物理世界
const world = new CANNON.World({
  gravity: new CANNON.Vec3(0, -9.82, 0),
});

// 创建动态球体
const sphereShape = new CANNON.Sphere(1);
const sphereBody = new CANNON.Body({
  mass: 5,
  shape: sphereShape,
  position: new CANNON.Vec3(0, 10, 0),
});
world.addBody(sphereBody);

// 创建静态地面
const groundShape = new CANNON.Plane();
const groundBody = new CANNON.Body({
  type: CANNON.Body.STATIC,
  shape: groundShape,
});
groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
world.addBody(groundBody);

// 动画循环
function animate() {
  requestAnimationFrame(animate);
  world.fixedStep();
  console.log(`球体Y坐标: ${sphereBody.position.y}`);
}

animate();

结合 Three.js 渲染:

javascript 复制代码
import * as THREE from 'three';

// 创建Three.js场景、相机和渲染器
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建Three.js球体和地面(与cannon-es中的刚体对应)
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const sphereMesh = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphereMesh);

const groundGeometry = new THREE.PlaneGeometry(20, 20);
const groundMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, side: THREE.DoubleSide });
const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial);
groundMesh.rotation.x = -Math.PI / 2;
scene.add(groundMesh);

camera.position.z = 15;

// 动画循环中更新Three.js物体位置
function animate() {
  requestAnimationFrame(animate);

  // 推进物理模拟
  world.fixedStep();

  // 更新Three.js球体位置
  sphereMesh.position.copy(sphereBody.position);
  sphereMesh.quaternion.copy(sphereBody.quaternion);

  renderer.render(scene, camera);
}

animate();

二、🍀生成复杂3D迷宫游戏

1. ☘️实现思路

集成了 Three.js(渲染)、Cannon-es(物理引擎)以及一个自动生成的迷宫算法。具体代码参考代码样例。可以直接运行。

2. ☘️代码样例

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>三维迷宫</title>
    <style>
        body { margin: 0; overflow: hidden; background-color: #1a1a1a; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
        #game-ui {
            position: absolute;
            top: 0; left: 0; width: 100%; height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-direction: column;
            background: rgba(0,0,0,0.7);
            color: white;
            z-index: 10;
            pointer-events: auto;
        }
        h1 { font-size: 3rem; margin-bottom: 10px; text-shadow: 0 0 10px #00d2ff; }
        p { font-size: 1.2rem; color: #ccc; }
        #start-btn {
            margin-top: 20px;
            padding: 15px 40px;
            font-size: 1.5rem;
            background: #00d2ff;
            border: none;
            border-radius: 30px;
            cursor: pointer;
            color: #000;
            font-weight: bold;
            transition: transform 0.1s;
        }
        #start-btn:hover { transform: scale(1.05); background: #5be0ff; }
        #instructions {
            position: absolute;
            bottom: 20px;
            left: 20px;
            color: rgba(255,255,255,0.5);
            pointer-events: none;
        }
    </style>

    <!-- 配置 Import Map 以从 CDN 加载模块 -->
    <script type="importmap">
    {
        "imports": {
            "three": "https://esm.sh/three@0.160.0",
            "three/addons/": "https://esm.sh/three@0.160.0/examples/jsm/",
            "cannon-es": "https://esm.sh/cannon-es@0.20.0"
        }
    }
    </script>
</head>
<body>
<!-- UI 界面 -->
<div id="game-ui">
    <h1>物理迷宫</h1>
    <p>使用 W A S D 或 方向键 控制小球到达绿色区域</p>
    <button id="start-btn">开始游戏</button>
</div>
<div id="instructions">WASD 移动 | R 重置</div>
<script type="module">
  import * as THREE from 'three';
  import * as CANNON from 'cannon-es';
  // --- 全局变量 ---
  let scene, camera, renderer;
  let world;
  let ballMesh, ballBody;
  let lastCallTime;
  let mazeWidth = 15; // 迷宫宽度 (必须是奇数)
  let mazeHeight = 15; // 迷宫高度 (必须是奇数)
  const wallSize = 2;
  const input = { w: false, a: false, s: false, d: false };
  let isGameRunning = false;
  let goalPosition = new THREE.Vector3();
  // --- 1. 迷宫生成算法 (深度优先搜索) ---
  function generateMaze(width, height) {
    // 初始化全为墙 (1)
    const map = Array(height).fill().map(() => Array(width).fill(1));

    function carve(x, y) {
      const directions = [
        [0, -2], [0, 2], [-2, 0], [2, 0] // 上下左右
      ].sort(() => Math.random() - 0.5); // 随机打乱方向
      directions.forEach(([dx, dy]) => {
        const nx = x + dx;
        const ny = y + dy;
        if (nx > 0 && nx < width && ny > 0 && ny < height && map[ny][nx] === 1) {
          map[y + dy/2][x + dx/2] = 0; // 打通中间的墙
          map[ny][nx] = 0; // 打通目标点
          carve(nx, ny);
        }
      });
    }
    // 起点
    map[1][1] = 0;
    carve(1, 1);

    // 确保终点附近是空的
    map[height-2][width-2] = 0;
    return map;
  }
  // --- 2. 初始化 Three.js 和 Cannon.js ---
  function init() {
    // 渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    document.body.appendChild(renderer.domElement);
    // 场景
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x202025);
    scene.fog = new THREE.Fog(0x202025, 10, 50);
    // 摄像机
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
    camera.position.set(0, 20, 10);
    // 灯光
    const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
    scene.add(ambientLight);
    const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
    dirLight.position.set(10, 20, 10);
    dirLight.castShadow = true;
    dirLight.shadow.camera.top = 30;
    dirLight.shadow.camera.bottom = -30;
    dirLight.shadow.camera.left = -30;
    dirLight.shadow.camera.right = 30;
    dirLight.shadow.mapSize.width = 2048;
    dirLight.shadow.mapSize.height = 2048;
    scene.add(dirLight);
    // 物理世界
    world = new CANNON.World();
    world.gravity.set(0, -20, 0); // 增加重力让手感更沉稳

    // 物理材质
    const defaultMaterial = new CANNON.Material('default');
    const defaultContact = new CANNON.ContactMaterial(defaultMaterial, defaultMaterial, {
      friction: 0.4,
      restitution: 0.2, // 低反弹
    });
    world.addContactMaterial(defaultContact);
    // 地板
    const floorGeo = new THREE.PlaneGeometry(100, 100);
    const floorMat = new THREE.MeshStandardMaterial({ color: 0x333333 });
    const floorMesh = new THREE.Mesh(floorGeo, floorMat);
    floorMesh.rotation.x = -Math.PI / 2;
    floorMesh.receiveShadow = true;
    scene.add(floorMesh);
    const floorBody = new CANNON.Body({
      mass: 0,
      shape: new CANNON.Plane(),
      material: defaultMaterial
    });
    floorBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0);
    world.addBody(floorBody);
    // 生成迷宫
    createLevel();
    // 事件监听
    window.addEventListener('resize', onWindowResize);
    window.addEventListener('keydown', (e) => handleKey(e, true));
    window.addEventListener('keyup', (e) => handleKey(e, false));

    // 开始循环
    requestAnimationFrame(animate);
  }
  function createLevel() {
    // 清理旧物体 (如果需要重置逻辑,这里可以扩展)

    const map = generateMaze(mazeWidth, mazeHeight);

    // 墙壁材质
    const wallGeo = new THREE.BoxGeometry(wallSize, wallSize * 1.5, wallSize);
    const wallMat = new THREE.MeshStandardMaterial({ color: 0x4a90e2 });

    // 物理墙壁形状
    const wallShape = new CANNON.Box(new CANNON.Vec3(wallSize/2, (wallSize*1.5)/2, wallSize/2));
    const offsetX = -(mazeWidth * wallSize) / 2;
    const offsetZ = -(mazeHeight * wallSize) / 2;
    for(let z=0; z<mazeHeight; z++) {
      for(let x=0; x<mazeWidth; x++) {
        const posX = x * wallSize + offsetX;
        const posZ = z * wallSize + offsetZ;
        if(map[z][x] === 1) {
          // 1. Three.js Mesh
          const mesh = new THREE.Mesh(wallGeo, wallMat);
          mesh.position.set(posX, wallSize * 0.75, posZ);
          mesh.castShadow = true;
          mesh.receiveShadow = true;
          scene.add(mesh);
          // 2. Cannon Body
          const body = new CANNON.Body({ mass: 0 });
          body.addShape(wallShape);
          body.position.set(posX, wallSize * 0.75, posZ);
          world.addBody(body);
        }
      }
    }
    // 创建终点区域 (视觉效果)
    const goalX = (mazeWidth - 2) * wallSize + offsetX;
    const goalZ = (mazeHeight - 2) * wallSize + offsetZ;
    goalPosition.set(goalX, 0, goalZ);
    const goalGeo = new THREE.BoxGeometry(wallSize, 0.1, wallSize);
    const goalMat = new THREE.MeshStandardMaterial({ color: 0x00ff00, emissive: 0x004400 });
    const goalMesh = new THREE.Mesh(goalGeo, goalMat);
    goalMesh.position.set(goalX, 0.05, goalZ);
    scene.add(goalMesh);

    // 放置光柱在终点
    const light = new THREE.PointLight(0x00ff00, 1, 10);
    light.position.set(goalX, 2, goalZ);
    scene.add(light);
    // 创建玩家小球
    createPlayer((1 * wallSize) + offsetX, (1 * wallSize) + offsetZ);
  }
  function createPlayer(x, z) {
    const radius = 0.6;

    // Visual
    const ballGeo = new THREE.SphereGeometry(radius, 32, 32);
    const ballMat = new THREE.MeshStandardMaterial({
      color: 0xff3333,
      roughness: 0.4,
      metalness: 0.3
    });
    ballMesh = new THREE.Mesh(ballGeo, ballMat);
    ballMesh.castShadow = true;
    scene.add(ballMesh);
    // Physics
    const shape = new CANNON.Sphere(radius);
    ballBody = new CANNON.Body({
      mass: 5,
      shape: shape,
      position: new CANNON.Vec3(x, 5, z),
      linearDamping: 0.3, // 阻尼,防止无限滚动
      angularDamping: 0.3
    });
    world.addBody(ballBody);
  }
  // --- 3. 控制与逻辑 ---
  function handleKey(e, pressed) {
    if(e.key.toLowerCase() === 'w' || e.key === 'ArrowUp') input.w = pressed;
    if(e.key.toLowerCase() === 'a' || e.key === 'ArrowLeft') input.a = pressed;
    if(e.key.toLowerCase() === 's' || e.key === 'ArrowDown') input.s = pressed;
    if(e.key.toLowerCase() === 'd' || e.key === 'ArrowRight') input.d = pressed;
    if(e.key.toLowerCase() === 'r' && pressed) location.reload(); // 简单的重置
  }
  function updatePhysics() {
    if (!ballBody) return;
    // 施加力
    const force = 40;
    const vec = new CANNON.Vec3(0,0,0);

    // 相机是斜着的,所以需要稍微转换一下输入方向,这里简化为直接对应世界坐标
    if (input.w) vec.z -= force;
    if (input.s) vec.z += force;
    if (input.a) vec.x -= force;
    if (input.d) vec.x += force;
    ballBody.applyForce(vec, ballBody.position);
    // 检查是否掉落
    if (ballBody.position.y < -10) {
      resetBall();
    }
    // 检查胜利
    const dist = ballBody.position.distanceTo(new CANNON.Vec3(goalPosition.x, goalPosition.y, goalPosition.z));
    if (dist < 1.5) {
      showWin();
    }
  }
  function resetBall() {
    ballBody.position.set(goalPosition.x * -1 + 4, 5, goalPosition.z * -1 + 4); // 简易回到起点附近
    ballBody.velocity.set(0,0,0);
    ballBody.angularVelocity.set(0,0,0);
  }
  function showWin() {
    if(!isGameRunning) return;
    isGameRunning = false;
    document.getElementById('game-ui').style.display = 'flex';
    document.querySelector('h1').innerText = "你赢了!";
    document.getElementById('start-btn').innerText = "再玩一次";
  }
  function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  }
  // --- 4. 游戏循环 ---
  function animate(time) {
    requestAnimationFrame(animate);
    if (!isGameRunning) {
      // 暂停时也可以渲染,但不更新物理
      renderer.render(scene, camera);
      return;
    }
    const dt = lastCallTime ? (time - lastCallTime) / 1000 : 1/60;
    lastCallTime = time;
    // 物理步进 (固定时间步长以保持稳定性)
    world.step(1 / 60);
    updatePhysics();
    // 同步 Mesh 和 Body
    if (ballMesh && ballBody) {
      ballMesh.position.copy(ballBody.position);
      ballMesh.quaternion.copy(ballBody.quaternion);
      // 摄像机平滑跟随
      const offset = new THREE.Vector3(0, 18, 12);
      const targetPos = new THREE.Vector3(ballMesh.position.x, 0, ballMesh.position.z).add(offset);
      camera.position.lerp(targetPos, 0.1);
      camera.lookAt(ballMesh.position);
    }
    renderer.render(scene, camera);
  }
  // --- 5. UI 交互 ---
  const startBtn = document.getElementById('start-btn');
  startBtn.addEventListener('click', () => {
    document.getElementById('game-ui').style.display = 'none';
    if (document.querySelector('h1').innerText === "你赢了!") {
      location.reload();
    }
    isGameRunning = true;
    lastCallTime = performance.now();
  });
  // 启动初始化
  init();
</script>
</body>
</html>

效果如下

相关推荐
deardao5 小时前
【张量等变学习】张量学习与正交,洛伦兹和辛对称
人工智能·学习·自然语言处理
会编程的吕洞宾5 小时前
智能体学习记录一
人工智能·学习
zore_c5 小时前
【C语言】EasyX图形库——实现游戏音效(详解)(要游戏音效的看过来!!!)
c语言·开发语言·经验分享·笔记·游戏
啄缘之间7 小时前
10.基于 MARCH C+ 算法的SRAM BIST
经验分享·笔记·学习·verilog
石像鬼₧魂石14 小时前
如何配置Fail2Ban的Jail?
linux·学习·ubuntu
Nan_Shu_61416 小时前
学习:VueUse (1)
学习
Li.CQ16 小时前
SQL学习笔记(二)
笔记·sql·学习
Huangxy__16 小时前
指针的补充学习
学习