在游戏开发中,游戏速率控制一直是一个需求,官方提供了计时器的控制接口以及动作系统的 cc.speed,但是使用起来不是很方便且无法影响到 update 控制逻辑以及物理系统,那么如何实现这一需求呢?
CocosCreator 版本 2.3.4
想问题还是要去根上找,跑到源码里先看看官方实现的计时器控制逻辑是怎么样的?在 CCSchedule.js
中可以看到有这样一个方法:
setTimeScale: function (timeScale) {
this._timeScale = timeScale;
}
这个私有属性是如何控制速率的呢?一番寻找,是在 update 中进行了计算:
update: function (dt) {
this._updateHashLocked = true;
if(this._timeScale !== 1)
dt *= this._timeScale;
var i, list, len, entry;
//......
}
这样就明白了,实际上就是把被计时器控制的组件的 dt
时间给改了,那我们想实现全局的控制应该再往根源处寻找。
正常讲游戏循环是每秒 60 帧,那么每帧的主循环逻辑应该不是在 CCGame.js
就是在 CCDirector.js
中,果然在导演类中看到了 mainLoop
方法,而其中有这么一段代码(省略了无关代码):
// calculate "global" dt
this.calculateDeltaTime(now);
// Update
if (!this._paused) {
// before update
this.emit(cc.Director.EVENT_BEFORE_UPDATE);
// Call start for new added components
this._compScheduler.startPhase();
// Update for components
this._compScheduler.updatePhase(this._deltaTime);
// Engine update with scheduler
this._scheduler.update(this._deltaTime);
// Late update for components
this._compScheduler.lateUpdatePhase(this._deltaTime);
// After life-cycle executed
this._compScheduler.clearup();
// User can use this event to do things after update
this.emit(cc.Director.EVENT_AFTER_UPDATE);
// Destroy entities that have been removed recently
Obj._deferredDestroy();
}
在没暂停的情况,计算完 dt 后分发下去,那我们在 this.calculateDeltaTime(now)
方法里面把 this._deltaTime
给改了不就可以了,比如这样:
calculateDeltaTime: function (now) {
if (!now) now = performance.now();
this._deltaTime = now > this._lastUpdate ? (now - this._lastUpdate) / 1000 : 0;
if (CC_DEBUG && (this._deltaTime > 1))
this._deltaTime = 1 / 60.0;
this._lastUpdate = now;
// 乘以 2 实现倍数
this._deltaTime *= 2;
},
或者把这个乘以逻辑放在 calculateDeltaTime
调用的下面也可以:
// calculate "global" dt
this.calculateDeltaTime(now);
// 乘以 2 实现倍数
this._deltaTime *= 2;
试了试还真实现了,能够做到全局控制速率,但是这个方法要魔改下引擎,换项目或者引擎版本无法做到复用,有没有更好的办法呢?当然,是可以不改引擎还能改引擎的(怪怪的,嘿嘿)。其实就是在自己的代码里去更改引擎代码,但是又涉及一个顺序问题,要确保引擎的更改顺序早于你使用的逻辑。
如果你翻过文档,你会知道插件脚本就能实现这个需求,在 CocosCreator 中脚本执行顺序为:Cocos2d 引擎最先执行,然后是插件脚本(有多个的话按项目中的路径字母顺序依次加载),最后才是我们写的普通脚本(打包后只有一个文件,内部按 require 的依赖顺序依次初始化)。
那就写个引擎扩展脚本 k-cocos.js 去扩展引擎就行了,不用魔改引擎,完美!
接下来就是在这个插件脚本中修改一下引擎计算 dt 的方法,为了方便控制,可以引入一个变量,然后在计算后让时间乘以这个变量,变量默认为 1 代表正常速度,想倍数我们把变量改为 2 就可以了。导演类作为一个单例对象让这个实现更加简单,在 k-cocos.js
中写入:
// 游戏速率变量
cc.director._kSpeed = 1;
var _originCalculateDeltaTime = cc.Director.prototype.calculateDeltaTime;
cc.director.calculateDeltaTime = function (now) {
_originCalculateDeltaTime.call(this, now);
this._deltaTime *= this._kSpeed;
}
// 将方法挂到 cc 对象上
cc.kSpeed = function (speed) {
cc.director._kSpeed = speed;
}
因为是单例对象,直接为其声明 _kSpeed
这个私有变量,然后重写 calculateDeltaTime
方法,在调用保留后的原方法后进行一次乘法即可!(call 方法中的 this 换成 cc.director 也是一样的,如果用箭头函数记得改)
为了不与未来引擎的接口冲突,所有扩展的属性方法都加个 k 字母,这样 cc.kSpeed()
就诞生啦!可以在任何地方愉快的进行 cc.kSpeed(0.3)
这种写法了。
效果图:
实现了想要的效果,到这里文章其实应该结束了,但是谁让阔阔这么有奉献精神,独乐乐不如众乐乐,论坛那么多人问游戏倍速的问题,干脆开源吧!名称就叫 KCocos 扩展库,再给自己设计一个图标:
不仅开源,再写个文档,写就写的高大上点!
扩展脚本已经开源,还实现了全局触点数量控制、也扩展了节点的一些属性和方法,还有许多想法没加进去,比如 _hitTest 等!
GitHub地址:https://github.com/KuoKuo666/k-cocos
码云地址:https://gitee.com/kuokuo666/k-cocos
对应文档地址:https://kuokuo666.github.io
2020!我们一起进步!O(∩_∩)O~~