基于 three.js 开发的web版坦克大战
坦克大战 BATTLE CITY REMAKE
游戏截图
基础部分介绍
光源
在光源的选择上,使用方向灯+环境光做基础光源,发现要比聚光灯节约性能。本来想把炮弹击中墙壁和敌人的地方加点光源,但是对性能影响比较大,目前没有解决办法,先放弃了。
坐标
考虑到玩家和敌人的坦克处于一个正方形的场地中,并且要为未来自定义地图做准备,所以我将世界坐标轴x轴和y轴设为地图平面,z轴垂直向上,这样就可以用二维数组在前期做简单的测试地图啦。
履带印和爆炸灰烬
起初想通过decals做灰烬和履带印,后来发现多少有点影响性能,考虑到地面为平面,索性直接改为plane几何体+贴图材质。
波纹
水波波纹还是很好解决的,官网的例子里扒过来就好了,不得不说还挺逼真。后来看了源码发现这个水波纹包含折射反射的计算,如果场景中的mesh太多了,就会徒增性能的压力。最后就改为跟旗帜一样的函数做面动画+半透明颜色材质来解决了。
音效
使用THREE.Audio做游戏音效,开场音乐、射击、爆炸、击中墙壁和坦克这些音效。为了防止音效时间超过播放间隔,每次执行播放前,要将isplaying设成false,不过检测性能的时候发现listeners在每次播放音效都会只增不减,然后等待自动回收机制去清理,目前还没有想到有什么好的办法解决。
玩家初始护盾
最早设计的时候,我就画了个半透明的球在玩家坦克上边,后来看了属实跟水波纹这么‘高大上’的效果不匹配,于是就改成了更加真实一些的渐变色(外缘深中心浅)半透明球体+淡入淡出的效果,这样看起来才有保护罩的感觉嘛。最新版,又学习了一下shader着色器,对护盾的动画进行了优化。
地图元素
为致敬原版游戏,地图元素基本没做大的变动,命名的话,我就随便起了个名,分别为:普通地面,草(emmm,一种植物,坦克和炮弹都可穿过,但不可见),水(坦克不能穿过,但炮弹可以飞过),钢铁(炮弹打不穿的那个),砖块(炮弹可以轰的细碎那个),最新版又加了冰面(雪地),不过操作上暂时没有效果,只有外观的不同。
除此之外,还设有玩家大本营(中间的小旗子是会飘动的哟),坦克出生点的闸门以及四周的围墙。除水元素外,都是由网格模型+贴图组成的基础元素。
在后边,我会说一下我开发过程中遇到的核心问题——性能优化。
升级提示
吃到升级道具后的升级提示是用sprite做的,理由就是方向始终朝向相机。
坦克
主角来了,坦克大战的坦克是怎么做的呢。鄙人不才,不会用游戏模型制作软件,但在大学学过Proe建模,于是就用creo画了个简单的坦克,面数也不多,敌人和玩家就先用颜色来区分吧。
敌人逻辑
敌人的逻辑决定了游戏难度,难度可以通过两个点来设置:1.敌人击中玩家的积极程度;2.敌人击中大本营的积极程度。
随机移动逻辑:
- 1.如果前方没有障碍,则向前方运动一格
- 2.如果前方有敌人,则保持不动(或小概率随机转向,防止被玩家卡死)
- 3.如果前方有障碍,则敌人随机转向
随机转动逻辑:
- 1.随机转动概率:w-10%;a-30%;s-30%;d-30%;
随机射击逻辑:(*后期可设计为允许敌人连续射击)
- 1.如果可射击(一个敌人在地图中仅有一颗子弹),则射击。
玩法介绍
- 1.玩家通过键盘W、A、S、D、Space(或者移动端触屏按钮)控制墨绿色坦克的移动和射击
- 2.玩家坦克与敌人坦克的子弹对射可抵消
- 3.玩家击杀全部敌人,则判定胜利
- 4.敌人击杀玩家直至无法复活,或者大本营被摧毁,则判定失败
- 5.玩家击杀第6和第13个敌人时,将随机出现增益道具
- 6.玩家重生时,生成护盾若干秒
敌人种类
- 土黄色坦克——高速坦克
- 黄绿色坦克——高防坦克(HP × 4)
- 灰色坦克——普通坦克
道具种类
- 青色——冻结所有敌人若干秒
- 红色——摧毁场上所有敌人
- 绿色——玩家加一条命
- 黄色——若玩家有护盾,则移除护盾,新加护盾若干秒
- 白色——修复大本营围墙,并强化为金属元素若干秒
- 灰色——提升玩家坦克等级
性能优化
移动碰撞和检测击中
- 以检测击中为例,综合考虑有两种方向。一是使用TWEEN补间动画,定时获取mesh的位置查看是否碰撞;第二种方式是在每次渲染画面时,对mesh的位置进行更新。
- 前者以时间轴为基础,也就是说不管画面卡不卡,mesh都会按照给定时间匀速移动到指定位置。而后者是以画面为基础,在这种情况下,即使画面卡顿,也不会出现mesh跳过某个位置直接到下一位置的情况,也就是常见的卡穿墙了。
- 这两种情况各有利弊,先说前者,好处是不管画面卡不卡,都会按时间匀速运动到位,缺点是检测碰撞的函数由于卡顿延迟触发时,会出现未碰撞穿墙效果。后者呢,好处是不会出现穿墙的问题,但坏处是不依赖时间刷新位置,也就是即使画面卡住不动就真的不动了,如果做多人游戏,A、B处于不同屏幕画面,A卡了,这时候B给A干死了,A都不知道自己死了。
- 综上所述,我最后选择了前者,为未来多人游戏做准备。经测试我的苹果xr还可以 满帧,笔记本也还可以
满帧,但是像什么oppo啊vivo啊还有华为老款啊就不行了,平均帧率低点没事,怕的就是某一帧特别费时,导致穿墙。然而穿墙的问题怎么解决呢? - 我想了三条路避开穿墙bug,一是使用好点的硬件来运行软件,就不多说什么了;二是使用多线程处理,比方说webworker之类的,不过没什么思路;三是预计算结果,也就是在开炮的一瞬间就决定是否击中,不过这种方式会有很多中间判断,比方说A离得远先朝上开炮,检测击中墙,这时B离得近朝左开炮,就有可能打断A的飞行轨迹,检测击中炮弹抵消,这种判断方式应该会很复杂,不过应该挺好用的,每次有变数就检测,如果我有心力,未来可能会这么改吧。
Mesh合并之InstancedMesh与geometry.merge的选择
更新中……
画布渲染与其他计算的处理
更新中……
内存泄漏
更新中……
针对不同浏览器垃圾回收机制的处理办法
更新中……