一次游戏开发尝试
谁能想到闲来无事做的零玩家游戏,居然成了组内浏览器性能测试工具?这是怎么回事呢?这事儿还得从我在github上瞎逛,发现了一个用JScript配合Canvas实现康威生命游戏的网页小动画说起。
那个时候还不了解康威生命游戏是个什么鬼,第一次看见的时候只觉得这玩意儿有点神奇啊,居然能自主进化成不同的形态。加上被原作者一句“这玩意儿很容易添加别的动画规则”给引诱了。
It would not be hard to add other rule sets so it can be used to run other automata. It's my platform for learning and playing with some web technologies.
虽然前半句说不难添加新规则,但后半句中却提到了「learning」,果然他的代码风格一言难尽啊。
肝了几天才把原始代码按逻辑重新进行了拆分和封装。在这过程中,也顺手尝试了好几种不同的动画效果,来看看历代的变化吧。
效果
初代-康威生命游戏
[图片上传失败...(image-6eca62-1649043941395)]
设置好初始图形(点、线)后,系统可以自动进行演进,无论初始图形是什么样的,最终得到的图形一定是固定的几种情况。
一代-自绘画
[图片上传失败...(image-62e19a-1649043941396)]
只需要绘制任意初始图形(点、线、面)初始图形颜色为红色,系统就可自行开始演进(方向随机)。演进过程中可以人为进行干预(使用白色),最终会得到相对稳定的色块图形。
二代-碰撞
[图片上传失败...(image-d6f4b1-1649043941396)]
每次会随机出现数量不等的小绿点,每个绿点向随机方向(上下左右)运行。若两个小点相撞,则会抵消其中的一个。在运行过程中,点击或滑动屏幕可再加入白色小点。白色小点运行规则与绿色小点完全一致。
三代-病毒
[图片上传失败...(image-e4e492-1649043941396)]
这是在上一代的基础上做的演进。每个小点代表一个细胞,用不同的颜色(绿色-健康细胞、黄色-已感染细胞、红色-携带病毒细胞、白色-免疫细胞)代表不同的状态。并且增加了更复杂的相遇规则:
红色一定能将绿色转换为黄色黄色有一定率将绿色转换为黄色黄色运行14步之后,较大几率转换为红色白色可将红色变为黄色白色可将黄色变为绿色每个小点会保持向一个方向运行10步,之后随机改变方向
每次运行前会随机生产数量不定的红色小点(已感染)与绿色小点(健康)。因为红色具有较高几率的转换能力(高传染性),因此能很快发现绿色小点被快速变为黄色,即便他们初始位置间隔很远。此时可通过点击屏幕或滑动屏幕,向游戏中加入白色免疫细胞。但是由于运行方向的不确定性,在白细胞数量没达到临界值之前,基本上无法挽回细胞由绿转红的趋势。实测,白细胞需要数倍于病毒细胞的数量才可平衡病毒的传染。
所以这个游戏也告诉我们<font color=red>疫情期间,做好防护,不要瞎聚集</font>。
战争
[图片上传失败...(image-d4ca0d-1649043941396)]
在上一个病毒版本基础上继续修改规则,最终变成类似地图战争的游戏,当然规则也发生了变化。
运行前可在屏幕上绘制任意图形(点、线、面),点击运行后。系统会根据图形像素点的个数按比例在地图随机放置若干不同的军事单位,不同的军事单位权重不一样,从小到大分别是:
机动部队(MO)<坦克部队(TK)< 空军部队(AF)<基地(BS)
而最开始所绘制的图形,会给每个点自动标记上不一样的颜色(每个颜色代表一个国家)。
每个色块都会向四周随机扩张,若扩张的方向是空白区域,则可占领该区域。
如果某色块占领的空白区域刚好有一个军事单位,那么这个军事单位就变为这个色块所拥有,此时它就可以去攻击别的色块了(战争爆发)。
战争双方均有军事单位时,则会依据攻击发起方的权重,以一定的比例判定攻击是否成功。如果攻击成功则占领该色块,并获取一定的战利品(军事单位升级),若攻击失败则军事单位降级。
动画原理
定义一个N x M的视图区域(view),并且对应一个N x M 的二维数组(data)。如果该二维数组的某一个元素有值,则在对应的视图区域绘制一个小点,当数组中有值的元素发生变化,视图区域也会随之变化,从而表现为动画效果。
数据
定义一个二维数组,用于保存原始数据。原始数据可以是数字,也可以是HashMap,根据需要进行设计即可。
function Grid(width, height) { if (isNaN(Number(width)) || isNaN(Number(height))) { throw new Error( "Grid constructor requires two numerical dimensions"); } this.width= width; this.height = height; this.length = width; let x; for (x = 0; x < width; ++x) { this = new Array(height); }}视图
利用canvas在可视范围内绘制 N x M 大小的矩形,该矩形由N x M个小方块组成。N 与 M 根据屏幕可视区域动态计算。
this.autoConfigSize =function(divid) { if(typeof divid !== 'undefined'){ this.divid = divid; } let left = $(this.divid).get(0).offsetLeft; let top= $(this.divid).get(0).offsetTop; this.config.width = Math.floor($(window).width() - (left * 2)); this.config.height = Math.floor($(window).height() - (top * 2)); this.model.setSize(this.config.width, this.config.height);};
这样在移动设备上运行的时候,可以自适应不同的屏幕大小,也可以自适应横竖屏变化。
绘制的时候根据设计的原始数据格式转换一下,即可绘制。因为表现为色块,所以关键信息为该色块当前应为什么颜色。
this.drawCell = function(x, y) { let d = this.draw; let offset = this.config.cellSize + this.config.borderWidth; let px = this.config.borderWidth + x * offset; let py = this.config.borderWidth + y * offset; let grade = undefined; if(this.model.cell(x, y) != undefined){ d.fillStyle = this.model.cell(x, y, 'color'); grade = this.model.cell(x, y, "grade"); }else{ d.fillStyle = this.config.color.all; } d.fillRect(px, py, this.config.cellSize, this.config.cellSize); if(this.gradDraw != undefined && grade != undefined){ this.gradDraw(d, px, py, this.config.cellSize, this.config.cellSize, grade); }};定时器
封装setInterval,实现一个简单的定时器,通过定时回调实现界面的自动刷新。
function Clock(func, period) { this.timer = null; this.func = func; this.period = period; this.setPeriod = function (period) { this.period = period; if (this.timer !== null) { this.stop(); this.start(); } }; this.start = function () { if (this.timer === null) { this.timer = setInterval(this.func, this.period); } }; this.stop = function () { if (this.timer !== null) { clearTimeout(this.timer); this.timer = null; } };}
界面刷新使用统一的接口nextGeneration实现,在这里就可以自定义的任意规则。接口只需要对N x M 的二维数组更新就可以了。
性能测试
从原理上可以看出,如果视图区域(view)比较大,那么对应的数组大小、上色的区域都比较大,加上代码并为针对不同的浏览器做优化,导致在不的浏览器上显示效果不一样。最明显的就是Ubuntu下的FireFox 和 Chrome。
[图片上传失败...(image-a432c1-1649043941396)]
移动端上的表现也各有不同,看来前端开发确实挺难的呢。
源码已在Gitee上开源。
页:
[1]