一个简单的threejs3D推箱子小游戏(3)——交互操作

1,039 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第33天,点击查看活动详情

上一篇文章中讲到了这个推箱子小游戏的初场景生成,今天就来实现实际的游玩移动操作了。

项目源码:github.com/zhzhch335/s…

监听键盘事件

//监听键盘事件
function addKeyboardEvent() {
  document.onkeydown = function(event) {
    var choice = ghostdirection(event.key);
    if (choice == "↑" || choice == "↓" || choice == "←" || choice == "→") {
      document.getElementById("answer1").style.visibility = "hidden";
      document.getElementById("answer2").style.visibility = "hidden";
      document.getElementById("answer3").style.visibility = "hidden";
      ghostmove(choice);
      document.getElementById("num").innerHTML = step.length;
    }
  };
}
addKeyboardEvent();

游戏中使用的是键盘的wasd四个按键,但是由于使用了一个OrbitControls视角控制插件,可以通过鼠标随意调整视角,因此wasd需要根据视角来判断是要往哪个方向移动,所以首先调用了ghostdirection这个方法:

function ghostdirection(eventkey) {
  var x = camera.position.x;
  var z = camera.position.z;
  var tmepele; //用于改变数组顺序的变量
  var choice;
  var choiceArray = ["↑", "←", "↓", "→"];
  if (z < 100 - x) {
    if (z > x) {
      choiceArray = ["→", "↑", "←", "↓"];
    } else {
      choiceArray = ["↓", "→", "↑", "←"];
    }
  } else {
    if (z > x) {
      choiceArray = ["↑", "←", "↓", "→"];
    } else {
      choiceArray = ["←", "↓", "→", "↑"];
    }
  }
  switch (eventkey) {
    case "w":
      choice = choiceArray[0];
      break;
    case "a":
      choice = choiceArray[1];
      break;
    case "s":
      choice = choiceArray[2];
      break;
    case "d":
      choice = choiceArray[3];
      break;
  }
  return choice;
}

判断运动方向主要看当前相机视角的x轴和z轴的关系,根据不同的视角生成wasd对应的方向。

人物移动

方向判断完成之后,就可以开始做人物运动的判断,调用ghostmove方法:

function ghostmove(choice) {
  switch (choice) {
    case "↑":
      ghost.cube.rotation.z = Math.PI;
      if (ghost.isavailable("w")) {
        step.push(choice); //更新步数
        move(ghost.cube, "↑"); //人物移动
        cubes[ghost.row][ghost.column].state = 0; //更新原人物位置状态信息
        var state = cubes[ghost.row - 1][ghost.column].state;
        if (state == 2) {
          stepflag.push(true);
          var box = cubes[ghost.row - 1][ghost.column].cube;
          move(box, "↑"); //箱子移动
          cubes[ghost.row - 2][ghost.column].state = 2; //更新数组中箱子信息
          cubes[ghost.row - 2][ghost.column].cube = box;
          if (descube.checkcube(ghost.row - 2, ghost.column)) {
            setTimeout(function() {
              box.scale.set(0.5, 0.5, 0.5);
            }, 500);
          } else {
            setTimeout(function() {
              box.scale.set(1, 1, 1);
            }, 500);
          }
        } else {
          stepflag.push(false);
        }
        cubes[ghost.row - 1][ghost.column].state = 3; //更新数组中人物信息
        ghost.row -= 1; //更新人物位置信息
        checkdesall();
      }
      break;
    case "↓":
      ghost.cube.rotation.z = 0;
      if (ghost.isavailable("s")) {
        step.push(choice); //更新步数
        move(ghost.cube, "↓"); //人物移动
        cubes[ghost.row][ghost.column].state = 0; //更新原人物位置状态信息        
        var state = cubes[ghost.row + 1][ghost.column].state;
        if (state == 2) {
          stepflag.push(true);
          var box = cubes[ghost.row + 1][ghost.column].cube;
          move(box, "↓"); //箱子移动
          cubes[ghost.row + 2][ghost.column].state = 2; //更新数组中箱子信息
          cubes[ghost.row + 2][ghost.column].cube = box;
          if (descube.checkcube(ghost.row + 2, ghost.column)) {
            setTimeout(function() {
              box.scale.set(0.5, 0.5, 0.5);
            }, 500);
          } else {
            setTimeout(function() {
              box.scale.set(1, 1, 1);
            }, 500);
          }
        } else {
          stepflag.push(false);
        }
        cubes[ghost.row + 1][ghost.column].state = 3; //更新数组中人物信息
        ghost.row += 1; //更新人物位置信息
        checkdesall();
      }
      break;
    case "←":
      ghost.cube.rotation.z = -Math.PI / 2;
      if (ghost.isavailable("a")) {
        step.push(choice); //更新步数
        move(ghost.cube, "←"); //人物移动
        cubes[ghost.row][ghost.column].state = 0; //更新原人物位置状态信息
        var state = cubes[ghost.row][ghost.column - 1].state;
        if (state == 2) {
          stepflag.push(true);
          var box = cubes[ghost.row][ghost.column - 1].cube;
          move(box, "←"); //箱子移动
          cubes[ghost.row][ghost.column - 2].state = 2; //更新数组中箱子信息
          cubes[ghost.row][ghost.column - 2].cube = box;
          if (descube.checkcube(ghost.row, ghost.column - 2)) {
            setTimeout(function() {
              box.scale.set(0.5, 0.5, 0.5);
            }, 500);
          } else {
            setTimeout(function() {
              box.scale.set(1, 1, 1);
            }, 500);
          }
        } else {
          stepflag.push(false);
        }
        cubes[ghost.row][ghost.column - 1].state = 3; //更新数组中人物信息
        ghost.column -= 1; //更新人物位置信息
        checkdesall();
      }
      break;
    case "→":
      ghost.cube.rotation.z = Math.PI / 2;
      if (ghost.isavailable("d")) {
        step.push(choice); //更新步数
        move(ghost.cube, "→"); //人物移动
        cubes[ghost.row][ghost.column].state = 0; //更新原人物位置状态信息
        var state = cubes[ghost.row][ghost.column + 1].state;
        if (state == 2) {
          stepflag.push(true);
          var box = cubes[ghost.row][ghost.column + 1].cube;
          move(box, "→"); //箱子移动
          cubes[ghost.row][ghost.column + 2].state = 2; //更新数组中箱子信息
          cubes[ghost.row][ghost.column + 2].cube = box;
          if (descube.checkcube(ghost.row, ghost.column + 2)) {
            setTimeout(function() {
              box.scale.set(0.5, 0.5, 0.5);
            }, 500);
          } else {
            setTimeout(function() {
              box.scale.set(1, 1, 1);
            }, 500);
          }
        } else {
          stepflag.push(false);
        }
        cubes[ghost.row][ghost.column + 1].state = 3; //更新数组中人物信息
        ghost.column += 1; //更新人物位置信息
        checkdesall();
      }
      break;
    default:
      break;
  }
}

当时的代码结构还有些复杂,实际上是可以简化一部分的,人物的移动首先是将人物按照移动方向进行转向,之后对是否可以移动进行判断,这个判断方法在上一篇方块生成的文章中有介绍过。如果可以移动,则开始记录步骤,值得一提的是,记录步骤的时候使用了另一个数组stepflag来记录实际推动了箱子的步骤,以便在步骤回退的时候跳过没有移动箱子的步骤,直接退回到上一次改变箱子位置的步骤上去。之后,对箱子和人物的当前位置都进行记录以便为下一次判断做好准备;调用move方法开启移动动画;对箱子是否达到终点进行判断,如果到达终点则改变其scale属性调整大小,最后调用checkdesall方法判定是否通关。

其中大部分的方法都在上一篇文章中讲过了,或者实现起来比较简答,移动动画的生成着重说两句。

移动动画

人物和箱子的移动实际上就是对其坐标进行重新设置,但是由于是3D的视觉,如果没有过渡动画的话,会显得很突兀,因此使用了setInterval实现了一个动画的效果:

function move(obj, opt) {
    var mover;
    switch (opt) {
        case "↑":
            if (mover) {
                clearInterval(mover);
            }
            var count = 0;
            mover = setInterval(function() {
                if (count == 50) {
                    clearInterval(mover);
                } else {
                    obj.position.z -= .2;
                    count++;
                }
            }, 5);
            break;
        case "↓":
            if (mover) {
                clearInterval(mover);
            }
            var count = 0;
            mover = setInterval(function() {
                if (count == 50) {
                    clearInterval(mover);
                } else {
                    obj.position.z += .2;
                    count++;
                }
            }, 5);
            break;
        case "←":
            if (mover) {
                clearInterval(mover);
            }
            var count = 0;
            mover = setInterval(function() {
                if (count == 50) {
                    clearInterval(mover);
                } else {
                    obj.position.x -= .2;
                    count++;
                }
            }, 5);
            break;
        case "→":
            if (mover) {
                clearInterval(mover);
            }
            var count = 0;
            mover = setInterval(function() {
                if (count == 50) {
                    clearInterval(mover);
                } else {
                    obj.position.x += .2;
                    count++;
                }
            }, 5);
            break;
        default:
            return;
    }
    renderer.render(scene, camera);
}

这样,这个小游戏就完成了!