携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情
连着写了一个月文章,思路多多少少有点枯竭了,于是我开始翻自己的github,找到了8年前刚刚参加工作的时候写的代码,一个3D推箱子的小游戏,回想起当年,真的是精力旺盛啊,明明是初出茅庐的前端新手,明明当时的“考核任务”就是做一个普通的推箱子(也就是为了考察一下JS的基本用法),我却为了能更引人注目一些,专门去学习了threejs,做了这么一个3D版的推箱子小游戏。现如今再看它时,仍为之感到自豪,并慨叹那样充满朝气与活力的日子早已不再……
好了,回归正题,来讲讲这个小游戏是如何通过threejs来实现的。
得益于注释的详尽,现在重新看懂这段代码基本没有什么困难,这个游戏主要由四个部分实现:
- 方块生成:用来生成箱子墙等基本方块、人物的可控制方块以及需要进行判定的终点方块
- 操作监听:监听键盘的方向键,调整人物的位置
- 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物体,你需要三个要素和一个工具:相机、场景、光照以及渲染器:
相机,顾名思义,就是我们观看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
//=============================================