一个简单的threejs3D推箱子小游戏(1)——初始化

445 阅读4分钟

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

连着写了一个月文章,思路多多少少有点枯竭了,于是我开始翻自己的github,找到了8年前刚刚参加工作的时候写的代码,一个3D推箱子的小游戏,回想起当年,真的是精力旺盛啊,明明是初出茅庐的前端新手,明明当时的“考核任务”就是做一个普通的推箱子(也就是为了考察一下JS的基本用法),我却为了能更引人注目一些,专门去学习了threejs,做了这么一个3D版的推箱子小游戏。现如今再看它时,仍为之感到自豪,并慨叹那样充满朝气与活力的日子早已不再……

好了,回归正题,来讲讲这个小游戏是如何通过threejs来实现的。

image.png

得益于注释的详尽,现在重新看懂这段代码基本没有什么困难,这个游戏主要由四个部分实现:

  • 方块生成:用来生成箱子墙等基本方块、人物的可控制方块以及需要进行判定的终点方块
  • 操作监听:监听键盘的方向键,调整人物的位置
  • UI:页面按钮的交互
  • 动画:人物和物体移动的过渡动画

threejs组件初始化

首先对threejs组件进行初始化:

function initthreejs() {
  //渲染器
  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
  });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);
  //相机
  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 5000);
  // camera.lookAt(new THREE.Vector3(50, 0, 50));
  camera.position.set(50.06421484100598,219.95842897735875,-33.78512665221608);
  //场景
  scene = new THREE.Scene();
  //辅助线
  // var axishelper = new THREE.AxisHelper(100);
  // scene.add(axishelper);
  // var boxhelper;
  //移轴组件
  control = new THREE.OrbitControls(camera);
  control.target.x = 50;
  control.target.z = 50;
  control.enableKeys = false;
  control.autoRotate = true;
  control.autoRotateSpeed = 7;
  control.update();
  //自然光
  light = new THREE.AmbientLight(0xffffff);
  scene.add(light);
}

要想让你的threejs代码出现3D物体,你需要三个要素和一个工具:相机、场景、光照以及渲染器:

image.png

相机,顾名思义,就是我们观看3D对象的视角,就像是玩FPS游戏时你的游玩视角一样。

场景则是在其中渲染的物体,他可以是一个纯色立体几何图形(比如我这里的网格),外部加载的模型(比如我这里的人物)或是加了贴图看起来“很立体”的几何图形(比如我这里的箱子和墙壁)

而光照,是一开始写代码发现全屏黑掉的重要因素,不同角度,强度、类型的光源会让整个场景起到完全不一样的样貌。

渲染器设定了整个场景的基本参数,比如大小、渲染位置、alpha通道等,并在每一次变化时来触发渲染让画面动起来。

游戏循环

想必很多人打游戏的时候,右上角都会打开FPS的监控,它代表这每秒你画面渲染的帧数,也就是说,在cpu和显卡的共同努力下,你的画面每秒钟都要进行着相当多次的渲染,也就是说,每秒需要调用很多次渲染方法来使得画面维护着最新的状态,这个过程就叫做游戏循环

white(true) {
    render()
}

然而,当我们使用JS来实现游戏循环的时候,会遇到一点问题:

JS是单线程的

这就意味着我们不能使用类似while(true)的方式来持续调用渲染函数,否则,在渲染函数的执行期间,你的JS做不了任何事。所以,一个选择是使用setInterval:

setInterval(() => {
    render()
}, 100)

这样设定后每100毫秒执行一次渲染函数,相当于你的FPS为1000/100=10,过于卡顿了,所以说,如果要达到60FPS,我们将间隔时间设定为16.66667,实际开发中,为了更好的利用浏览器渲染,可以使用函数requestAnimationFrame()代替setInterval()函数,requestAnimationFrame()setInterval()一样都是浏览器window对象的方法,它的参数是结束一次渲染之后要调用的方法,因此一般使用requestAnimationFrame()来实现游戏循环我们都会使用一个永不结束的递归:

function gameloop() {
  stats.update();// FPS监视器更新
  if (control) {
    control.update();
  }
  renderer.render(scene, camera); // 场景渲染
  requestAnimationFrame(gameloop);
}

requestAnimationFrame()的渲染频率由浏览器决定,一般会维持在60FPS左右,随着软硬件资源的当前状态也会适时调整,关于requestAnimationFrame()的详细内容,可以看一下这篇文章

完成了初始化,接下来就可以向场景中添加几何图形,加载模型,设置位置让场景动起来了,这些内容我们下篇文章再说。

最后代码结尾发现了当时留的一个彩蛋,年轻的时候玩的真花啊:

//                   _ooOoo_
//                  o8888888o
//                  88" . "88
//                  (| -_- |)
//                  O\  =  /O
//               ____/`---'\____
//             .'  \\|     |//  '.
//             /  \\||  :  ||//  \
//            /  \\|||  :  |||//  \
//           /  _||||| -:- |||||_  \
//           |   | \\\  -  /// |   |
//           | \_|  ''\---/''  |_/ |
//           \  .-\__  `-`  __/-.  /
///         ___`. .'  /--.--\  `. . ___
//      ."" '<  `.___\_<|>_/___.'  >' "".
//     | | :  `- \`.;`\ _ /`;.`/ - ` : | |
//     \  \ `-.   \_ __\ /__ _/   .-` /  /
///======`-.____`-.___\_____/___.-`____.-'======
//                   `=---='
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//         佛祖保佑       永无BUG
//  本模块已经经过开光处理,绝无可能再产生bug
//=============================================