您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息
免费发信息
三六零分类信息网 > 崇左分类信息网,免费分类信息发布

HTML5游戏开发:套圈圈丨前端开发者

2024/1/27 22:31:57发布21次查看
by xiaobai·
前端开发者丨html5
希望能给诸位读者带来的启发技术选型 整体代码布局 难点及解决思路 优化点技术选型一个项目用什么技术来实现,权衡的因素有许多。
其中时间是必须优先考虑的,毕竟效果可以减,但上线时间是死的。
本项目预研时间一周,真正排期时间只有两周。
虽然由项目特点来看比较适合走 3d 方案,但时间明显是不够的。
最后保守起见,决定采用 2d 方案尽量逼近真实立体的游戏效果。
从游戏复杂度来考虑,无须用到 egret 或 cocos 这些“牛刀”,而轻量、易上手、团队内部也有深厚沉淀的 createjs 则成为了渲染框架的首选。
另外需要考虑的是是否需要引入物理引擎,这点需要从游戏的特点去考虑。
本游戏涉及重力、碰撞、施力等因素,引入物理引擎对开发效率的提高要大于学习使用物理引擎的成本。
因此权衡再三,我引入了同事们已经玩得挺溜的 matter.js。
( matter.js 文档清晰、案例丰富,是切入学习 web 游戏引擎的一个不错的框架)整体代码布局在代码组织上,我选择了面向对象的手法,对整个游戏做一个封装,抛出一些控制接口给其他逻辑层调用。
伪代码: 1 2 3 // game.js /** * 游戏对象 */ class waterful { // 初始化函数 init () {} // createjs tick,游戏操作等事件的绑定放到游戏对象内 eventbinding () {} // 暴露的一些方法 score () {} restart () {} pause () {} resume () {} // 技能 skillx () {} } /** * 环对象 */ class ring { // 于每一个 createjs tick 都调用环自身的 update 函数 update () {} // 进针后的逻辑 aftercollision () {} } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 // game.js /** * 游戏对象 */ class waterful { // 初始化函数 init ( ) { } // createjs tick,游戏操作等事件的绑定放到游戏对象内 eventbinding ( ) { } // 暴露的一些方法 score ( ) { } restart ( ) { } pause ( ) { } resume ( ) { } // 技能 skillx ( ) { } } /** * 环对象 */ class ring { // 于每一个 createjs tick 都调用环自身的 update 函数 update ( ) { } // 进针后的逻辑 aftercollision ( ) { } }// main.js // 根据业务逻辑初始化游戏,调用游戏的各种接口 const waterful = new waterful() waterful.init({…}) 1 2 3 4 // main.js // 根据业务逻辑初始化游戏,调用游戏的各种接口 const waterful = new waterful ( ) waterful . init ( { . . . } )初始化游戏的初始化接口主要做了4件事情:参数初始化 createjs 显示元素(display object)的布局 matter.js 刚体(rigid body)的布局 事件的绑定下面主要聊聊游戏场景里各种元素的创建与布局,即第二、第三点。
前端开发者丨html5
https://rokub
一、createjs 结合 matter.js阅读 matter.js 的 demo 案例,都是用其自带的渲染引擎 matter.render。
但是由于某些原因(后面会说到),我们需要使用 createjs 去渲染每个环的贴图。
不像 laya 配有和 matter.js 自身用法一致的 render,createjs 需要单独创建一个贴图层,然后在每个 tick 里把贴图层的坐标同步为 matter.js 刚体的当前坐标。
伪代码:createjs.ticker.addeventlistener(‘tick’, e => { 环贴图的坐标 = 环刚体的坐标 }) 1 2 3 createjs . ticker . addeventlistener ( ‘tick’ , e = > { 环贴图的坐标 = 环刚体的坐标 } )使用 createjs 去渲染后,要单独调试 matter.js 的刚体是非常不便的。
建议写一个调试模式专门使用 matter.js 的 render 去渲染,以便跟踪刚体的运动轨迹。
二、环本游戏的难点是要以 2d 去模拟 3d,环是一点,进针的效果是一点,先说环。
环由一个圆形的刚体,和半径稍大一些的贴图层所组成。
如下图,蓝色部分为刚体:伪代码:class ring { constructor () { // 贴图 this.texture = new createjs.sprite(…) // 刚体 this.body = matter.bodies.circle(…) } } 1 2 3 4 5 6 7 8 class ring { constructor ( ) { // 贴图 this . texture = new createjs . sprite ( . . . ) // 刚体 this . body = matter . bodies . circle ( . . . ) } }三、刚体为什么把刚体半径做得稍小呢,这也是受这篇文章 推金币 里金币的做法所启发。
推金币游戏中,为了达到金币间的堆叠效果,作者很聪明地把刚体做得比贴图小,这样当刚体挤在一起时,贴图间就会层叠起来。
所以这样做是为了使环之间稍微有点重叠效果,更重要的也是当两个紧贴的环不会因翻转角度太接近而显得留白太多。
如图:为了模拟环在水中运动的效果,可以选择给环加一些空气摩擦力。
另外在实物游戏里,环是塑料做成的,碰撞后动能消耗较大,因此可以把环的 restitution 值调得稍微小一些。
需要注意 matter.js 中因为各种物理参数都是没有单位的,一些物理公式很可能用不上,只能基于其默认值慢慢进行微调。
下面的 frictionair 和 restitution 值就是我慢慢凭感觉调整出来的:this.body = matter.bodies.circle(x, y, r, { frictionair: 0.02, restitution: 0.15 }) 1 2 3 4 this . body = matter . bodies . circle ( x , y , r , { frictionair : 0.02 , restitution : 0.15 } )四、贴图环在现实世界中的旋转是三维的,而 createjs 只能控制元素在二维平面上的旋转。
对于一个环来说,二维平面的旋转是没有任何意义的,无论如何旋转,都只会是同一个样子。
想要达到环绕 x 轴旋转的效果,一开始想到的是使用 rotation + scaley。
虽然这样能在视觉上达到目的,但是 scaley 会导致环有被压扁的感觉,图片会失真:显然这样的效果是不能接受的,最后我采取了逐帧图的方式,最接近地还原了环的旋转姿态:注意在每个 tick 里需要去判断环是否静止,若非静止则继续播放,并将贴图的 rotation 值赋值为刚体的旋转角度。
如果是停止状态,则暂停逐帧图的播放:// 贴图与刚体位置的小数点后几位有点不一样,需要降低精度 const x1 = math.round(texture.x) const x2 = math.round(body.position.x) const y1 = math.round(texture.y) const y2 = math.round(body.position.y) if (x1 !== x2 || y1 !== y2) { texture.paused && texture.play() texture.rotation = body.angle * 180 / math.pi } else { !texture.paused && texture.stop() } texture.x = body.position.x texture.y = body.position.y 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // 贴图与刚体位置的小数点后几位有点不一样,需要降低精度 const x1 = math . round ( texture . x ) const x2 = math . round ( body . position . x ) const y1 = math . round ( texture . y ) const y2 = math . round ( body . position . y ) if ( x1 !== x2 || y1 !== y2 ) { texture . paused && texture . play ( ) texture . rotation = body . angle * 180 / math . pi } else { ! texture . paused && texture . stop ( ) } texture . x = body . position . x texture . y = body . position . y五、舞台舞台需要主要由物理世界、背景图,墙壁,针所组成。
1. 物理世界为了模拟真实世界环在水中的向下加速度,可以把 y 方向的 g 值调小:engine.world.gravity.y = 0.2 1 engine . world . gravity . y = 0.2左右重力感应对环的加速度影响同样可以通过改变 x 方向的 g 值达到:// 最大倾斜角度为 70 度,让用户不需要过分倾斜手机 // 0.4 为灵敏度值,根据具体情况调整 window.addeventlistener(‘deviceorientation’, e => { let gamma = e.gamma if (gamma 70) gamma = 70 this.engine.world.gravity.x = (e.gamma / 70) * 0.4 }) 1 2 3 4 5 6 7 8 // 最大倾斜角度为 70 度,让用户不需要过分倾斜手机 // 0.4 为灵敏度值,根据具体情况调整 window . addeventlistener ( ‘deviceorientation’ , e = > { let gamma = e . gamma if ( gamma 70 ) gamma = 70 this . engine . world . gravity . x = ( e . gamma / 70 ) * 0.4 } )2. 背景图本游戏布景为游戏机及海底世界,两者可以作为父容器的背景图,把 canvas 的位置定位到游戏机内即可。
canvas 覆盖范围为下图的蓝色蒙层:3. 墙壁因为环的刚体半径比贴图半径小,因此墙壁刚体需要有一些提前位移,环贴图才不会溢出,位移量为 r – r(下图红线为墙壁刚体的一部分):4. 针为了模拟针的边缘轮廓,针的刚体由一个矩形与一个圆形所组成。
下图红线描绘了针的刚体:为什么针边缘没有像墙壁一样有一些提前量呢?这是因为进针效果要求针顶的平台区域尽量地窄。
作为补偿,可以把环刚体的半径尽可能地调得更大,这样在视觉上环与针的重叠也就不那么明显了。
进针进针是整个游戏的核心部分,也是最难模拟的地方。
进针后两个二维平面的物体交错是不能产生“穿过”效果的:除非把环分成前后两部分,这样层级关系才能得到解决。
但是由于环贴图是逐帧图,分两部分的做法并不合适。
最后找到的解决办法是利用视觉错位来达到“穿过”效果:具体做法是,当环被判定成功进针时,把环刚体去掉,环的逐帧图逐渐播放到平放的那一帧,rotation 值也逐渐变为 0。
同时利用 createjs 的 tween 动画把环平移到针底。
进针后需要去掉环刚体,平移环贴图,这就是上文为什么环的贴图必须由 createjs 负责渲染的答案。
伪代码:/ object ring aftercollision (waterful) { // 平移到针底部 createjs.tween.get(this.texture) .to({y: y}, duration) // 消去刚体 matter.world.remove(waterful.engine.world, this.body) this.body = null // 接下来每一 tick 的更新逻辑改变如下 this.update = function () { const texture = this.texture if 当前环贴图就是第 0 帧(环平放的那一帧){ texture.gotoandstop(0) } else { 每 5 个 tick 往前播放一帧(相隔多少 tick 切换一帧可以凭感觉调整,主要是为了使切换到平放状态的过程不显得太突兀) } // 使针大概在环中央位置穿过 if (texture.x 213 && texture.x 462) –texture.x if (texture.x > 400 && texture.x < 448) ++texture.x // 把环贴图尽快旋转到水平状态 let rotation = math.round(texture.rotation) % 180 if (rotation 0 && rotation 90 && rotation 400 && texture . x < 448 ) ++ texture . x // 把环贴图尽快旋转到水平状态 let rotation = math . round ( texture . rotation ) % 180 if ( rotation 0 && rotation 90 && rotation { if (e.target.y { if ( e . target . y < 400 ) { e . target . gotoandplay ( ‘short’ ) } else { e . target . gotoandplay ( ‘normal’ ) } } )3. rotation 值同理,为了使得环与针相垂直,rotation 值不能太接近 90 度。
经试验后规定 0下图这种过大的倾角逻辑上是不能进针成功的:初探一开始我想的是把三维的进针做成二维的“圆球进桶”,进针的判断也就归到物理事件上面去,不需要再去考虑。
具体做法如下图,红线为针壁,当环刚体(蓝球)掉入桶内且与 sensor (绿线)相碰,则判断进针成功。
为了使游戏难度不至于太大,环刚体必须设置得较小,而且针壁间距离要比环刚体直径稍大。
这种模拟其实已经能�...
崇左分类信息网,免费分类信息发布

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录