使用three.js开发3D推箱子游戏:从数组定义到交互实现

作者:拾光

时间:2026-10-23

浏览:351

本文详细讲解如何利用three.js框架开发一个3D推箱子小游戏。内容涵盖关卡数据的数组定义、场景元素(地面、墙壁、箱子、目标点)的初始化渲染,以及基于FirstPersonControls的相机控制与鼠标点击交互逻辑。通过代码分析,展示如何判断移动合法性及游戏胜利条件,适合希望尝试WebGL游戏开发的开发者参考。

文章目录

3D推箱子游戏开发概述

近期,许多读者对分享的three.js小型项目表现出浓厚兴趣。本文旨在展示一个基于3D引擎的推箱子游戏实现。尽管WebGL生态中存在更专业的游戏引擎,但利用three.js进行轻量级小游戏开发仍具有探索价值。在线演示案例请访问:

游戏开发核心步骤

构建推箱子游戏主要包含以下四个核心步骤。

  1. 首先,需定义四组关键数组:箱子初始位置数组、目标位置数组、地面区域数组以及墙壁位置数组。通过这四组数据即可构建出一个完整的关卡布局。
  2. 其次,依据上述数组初始化场景中的地面、墙壁、箱子模型以及目标点标识物。
  3. 接着,引入FirstPersonControls控制器以管理相机视角的移动,并根据地面、箱子和墙壁的数据计算玩家的可移动区域。
  4. 最后,实现交互逻辑:当相机正对箱子时,通过鼠标点击触发箱子移动,并进行移动合法性和游戏胜利条件的校验。

代码实现与逻辑分析

接下来,我们将通过具体代码深入分析实现细节。

第一步:定义核心数据数组。

这四组数组分别存储了墙壁坐标、地面范围、箱子初始坐标以及目标点坐标,共同构成了关卡的基础数据结构。

关卡数据定义


wallArr = [[0, 0], [1, 0], [2, 0], [3, 0], [3, 1], [4, 1], [4, 2], [4, 3], [5, 3], [5, 4], [5, 5], [5, 6], [4, 6], [3, 6], [2, 6], [1, 6], [0, 6], [0, 5], [0, 4], [0, 3], [0, 2], [0, 1]]
scopeArr = [[1, 1], [2, 1], [1, 2], [2, 2], [3, 2], [1, 3], [2, 3], [1, 4], [4, 4], [1, 5], [2, 5], [3, 5], [4, 5]];
boxArr = [[3, 3], [2, 4], [3, 4]];
targetArr = [[2, 2], [1, 3], [2, 3]]; 

第二步:根据箱子初始位置数组初始化箱子模型。

箱子模型初始化


initBox() {
  var textureBox = new THREE.TextureLoader().load("/static/images/base/crate.png");
  if (boxGroup) {
    scene.remove(boxGroup)
  }
  boxGroup = new THREE.Group();
  boxGroup.name = 'box_group'
  boxArr.forEach(d => {
    var boxGeom = new THREE.BoxGeometry(40, 40, 40);
    var boxMate = [];
    boxGeom.faces.forEach(d => boxMate.push(new THREE.MeshBasicMaterial({ map: textureBox })))
    var boxMesh = new THREE.Mesh(boxGeom, boxMate);
    boxMesh.position.set(d[0] * 40 - 20, 20, d[1] * 40 - 20);
    boxMesh.name = 'box';
    boxGroup.add(boxMesh);
  })
  scene.add(boxGroup);
  //判断是否赢得比赛
  this.isWinner(boxArr, targetArr)
}

第三步:根据地面数组初始化地面网格。

地面网格渲染


initGround() {
  var textureGround = new THREE.TextureLoader().load("/static/images/wall/plaster.jpg", () => {this.loaded_num --});
  var textureGroundNormal = new THREE.TextureLoader().load("/static/images/wall/plaster-normal.jpg", () => {this.loaded_num --});
  var textureGroundSpecular = new THREE.TextureLoader().load("/static/images/wall/plaster-diffuse.jpg", () => {this.loaded_num --});
  textureGround.wrapS = textureGround.wrapT = THREE.RepeatWrapping;
  textureGround.repeat.set(50, 50);
  textureGroundNormal.wrapS = textureGroundNormal.wrapT = THREE.RepeatWrapping;
  textureGroundNormal.repeat.set(50, 50);
  var materialGround = new THREE.MeshPhongMaterial({
    map: textureGround
  })
  materialGround.normalMap = textureGroundNormal;
  materialGround.specularMap = textureGroundSpecular;
  var ground = new THREE.Mesh(new THREE.PlaneGeometry(1000, 1000, 1, 1), materialGround);
  ground.rotation.x = - Math.PI / 2;
  scene.add(ground);
}

第四步:根据墙壁数组初始化墙壁模型。

墙壁模型构建


initWall() {
  var normal = new THREE.TextureLoader().load("/static/images/wall/stone.jpg", () => {this.loaded_num --});
  var bump = new THREE.TextureLoader().load("/static/images/wall/stone-bump.jpg", () => {this.loaded_num --});
  wallArr.forEach(d => {
    var wallBox = new THREE.BoxGeometry(40, 40, 40);
    var material = new THREE.MeshPhongMaterial({
      map: normal,
      bumpMap: bump,
      bumpScale: 1
    })
    var wall = new THREE.Mesh(wallBox, material);
    wall.position.x = d[0] * 40 - 20;
    wall.position.y = 20;
    wall.position.z = d[1] * 40 - 20;
    scene.add(wall);
  })
}

第五步:根据目标数组初始化目标点标识物。

目标点标识加载


initTarget() {
  let objLoader = new OBJLoader();
  objLoader.setPath("/static/images/texture/hongqi/");
  objLoader.load('hongqi.obj', (object) => {
    this.loaded_num --;
    let hongqi = object.children[0];
    targetArr.forEach(d => {
      hongqi.position.set(d[0] * 40 - 20, -50, d[1] * 40 - 20)
      hongqi.scale.set(0.12, 0.12, 0.12)
      hongqi.material = new THREE.MeshNormalMaterial({ side: THREE.DoubleSide });
      scene.add(hongqi.clone())
    })
  })
}

第六步:监听箱子的点击事件以处理交互。

每次点击操作都会触发computeMove方法,该方法负责判断当前移动是否符合游戏规则(如是否可移动、是否撞墙等)。

交互逻辑处理


initEventListener() {
  raycaster = new THREE.Raycaster();
  document.addEventListener('mousemove', function (event) {
    event.preventDefault();
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
  }, false)
  document.addEventListener('click', () => {
    if (scene.children && scene.getObjectByName('box')) {
      raycaster.setFromCamera(mouse, camera);
      let intersects = raycaster.intersectObjects(scene.getObjectByName('box_group').children);
      if (intersects[0] && intersects[0].object.name == 'box') {
        this.computeMove(intersects[0].object, camera.position);
      }
    }
  })
}

第七步:监听游戏胜利状态。

当检测到所有箱子均到达目标位置时,判定游戏成功,并弹出相应的提示消息。

胜利条件判定


isWinner(arr1, arr2) {
  let boo = true; //true为赢
  arr1.forEach(d => {
    let res = arr2.some(dd => {
      return d[0] == dd[0] && d[1] == dd[1]
    })
    if(!res) {
      boo = false;
    }
  })
  if(boo) {
    setTimeout(() => {
      alert('恭喜你赢了!')
    },100)
  }
}

由于该项目开发初期由初学者完成,代码中较少使用three.js的高级辅助方法,敬请谅解。

以上即为使用three.js制作3D推箱子游戏的完整流程与代码解析。如需了解更多关于three.js制作推箱子游戏的相关内容,请持续关注本站后续文章。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章