迷宫生成器:递归回溯算法打造奇幻3D迷宫

阅读量:17
更新时间:2025-04-13 23:39:39

效果预览:效果预览
源码下载:关注公众号【程序员新视界】,回复【算法4】可获取源码

核心原理

🌀 迷宫生成算法

js 复制代码
function carve(x, y) {
  // 随机方向探索
  const directions = [[0,-2],[2,0],[0,2],[-2,0]].sort(()=>Math.random()-0.5);
  
  directions.forEach(([dx, dy]) => {
    const nx = x + dx
    const ny = y + dy
    // 边界检测与挖墙逻辑
    if(nx >=0 && ny >=0 && maze[ny][nx] === 1){
      maze[y+dy/2][x+dx/2] = 0 // 打通当前墙
      maze[ny][nx] = 0         // 标记为通路
      carve(nx, ny)            // 递归探索
    }
  })
}

算法在初始化全墙矩阵后,从起点(1,1)开始随机方向深度优先搜索,每次打通两堵墙生成路径,最终形成枝杈分明的迷宫结构。

🎥 3D渲染流程

✨场景搭建

  • 创建Phong材质金属地板
  • 设置透视摄像机(空中俯视视角)
  • 添加聚光灯+环境光组合照明

✨迷宫实体化

js 复制代码
unction initCube(){
 const geometry = new THREE.BoxGeometry(1, 2, 1)
 // 批量创建带阴影的立方体墙
 maze.forEach((row, i) => {
   row.forEach((cell, j) => {
     if(cell === 1){
       const cube = new THREE.Mesh(geometry, material)
       cube.castShadow = true
       cube.position.set(i - size/2, 1, j - size/2)
       scene.add(cube)
     }
   })
 })

如何运行体验

✨安装依赖:npm install three @types/three

✨将代码嵌入Vue3项目

✨调整size变量生成不同规模迷宫(建议保持奇数值)

✨点击按钮见证新迷宫诞生!

进阶玩法指南

✨修改material.color定制墙面颜色

✨调整聚光灯angle参数改变投影锐度

✨在initCube中随机化墙面高度创造破碎效果

✨添加玩家角色实现第一人称解谜

完整代码

html 复制代码
<template>
  <div class="maze">
    <div class="action">
      <el-button @click="back">返回</el-button>
      <el-button type="primary" @click="startMaze">生成新迷宫</el-button>
    </div>
    <div id="container"></div>
  </div>
</template>

<script lang="ts" setup>
import * as THREE from 'three'; 
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; 
import { onMounted, ref } from 'vue'; 
import { useRouter } from 'vue-router';
 
let scene:THREE.Scene; 
let camera:THREE.PerspectiveCamera; 
let renderer:THREE.WebGLRenderer;  
let controls:OrbitControls;
//
let maze = ref<number[][] >([]);
let size = ref<number>(51); 
onMounted(() => {
  init();
});

function init() {  
  generateMaze();
  initScene();
  initCamera();
  initRenderer(); 
  initControls();
  initPlane(); 
  initCube();
  initSpotLight();
  render();
  animation();
}

function initScene(){
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x000000);
}

function initCamera(){
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
  camera.position.set(0, 30, 50);
}

function initRenderer(){
  renderer = new THREE.WebGLRenderer({
    antialias: true
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = true;
  document.body.appendChild(renderer.domElement);
}

function initControls(){
  controls = new OrbitControls(camera, renderer.domElement); 
  controls.dampingFactor = 15; 
  controls.addEventListener('change', render);
}

function initPlane(){
  let planeGeometry = new THREE.PlaneGeometry(size.value + 6, size.value + 6, 1, 1); 
  let planeMaterial = new THREE.MeshPhongMaterial({
    color: 0x555555,
    dithering: true
  });
  let plane = new THREE.Mesh(planeGeometry, planeMaterial); 
  // 接受阴影
  plane.receiveShadow = true;
  plane.rotation.x = -Math.PI * 0.5;
  scene.add(plane);
}
 
// 聚光灯
function initSpotLight() {
  const light = new THREE.SpotLight(0xffffff, 1);
  // 投射阴影
  light.castShadow = true;
  // 光照最大范围
  light.angle = 0.5;
  // 半影衰减百分比
  light.penumbra = 0.2;
  // 沿着光照距离的衰减量
  light.decay = 0.5;
  // 光照最大距离
  light.distance = 70;
  // 位置
  light.position.set(-30, 40, -10);
  scene.add(light); 
  // 环境光
  const ambientLight = new THREE.AmbientLight(0xffffff);
  scene.add(ambientLight);
}

function animation(){ 
  requestAnimationFrame(animation);
}

function render(){
  if(!renderer || !scene || !camera){
    return;
  } 
  renderer.render(scene, camera);
}  

function initCube(){
  const geometry = new THREE.BoxGeometry(1, 2, 1);
  const material = new THREE.MeshPhongMaterial({ color: '#a65b00' });
  let cube = new THREE.Mesh(geometry, material);  
  for (let i = 0;i < maze.value.length;i++){
    for (let j = 0;j < maze.value[i].length;j++){
      if(maze.value[i][j] === 1){  
        let newCube = cube.clone();
        newCube.castShadow = true;
        newCube.receiveShadow = true;
        newCube.name = `cube cube${i}${j}`;
        newCube.position.x = i - size.value / 2 + 0.5;
        newCube.position.z = j - size.value / 2 + 0.5; 
        newCube.position.y = 1; 
        scene.add(newCube); 
      }
    }
  } 
}
// 生成迷宫逻辑
function generateMaze() { 
  size.value = size.value % 2 === 0 ? size.value + 1 : size.value; // 确保奇数
                    
  // 初始化迷宫
  maze.value = Array.from({ length: size.value }, () => 
    Array(size.value).fill(1)
  );
  // 递归回溯算法
  function carve(x:number, y:number) {
    const directions = [[0, -2], [2, 0], [0, 2], [-2, 0]].sort(() => Math.random() - 0.5);
    for (const [dx, dy] of directions) {
      const nx = x + dx;
      const ny = y + dy;
      if (nx >= 0 && nx < size.value && ny >= 0 && ny < size.value && maze.value[ny][nx] === 1) {
        maze.value[y + dy / 2][x + dx / 2] = 0;
        maze.value[ny][nx] = 0;
        carve(nx, ny);
      }
    }
  }
  // 设置起点并开始生成
  maze.value[1][1] = 0;
  carve(1, 1);

  // 设置出入口
  maze.value[0][1] = 0;
  maze.value[size.value - 1][size.value - 2] = 0;
}

// 生成新迷宫
function startMaze(){ 
  generateMaze();
  scene.children
    .filter((obj) => obj.name.startsWith('cube'))
    .forEach((cube) => { 
      let c = cube as THREE.Mesh; 
      scene.remove(c); 
      c.geometry.dispose(); 
      (c.material as THREE.MeshBasicMaterial).dispose(); 
    }); 
  initCube();
  render();
}
const router = useRouter();
const back = () => {
  router.push('/');
};
</script>

<style lang="scss" scoped>
.action{
    position: fixed;
    background-color: #000;
    padding:15px;
}
</style>