CocosCreator之控制游戏速率实现倍速与慢动作

摘要

在游戏开发中,游戏速率控制一直是一个需求,官方提供了计时器的控制接口以及动作系统的 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 去扩展引擎就行了,不用魔改引擎,完美!

cc.kSpeed()诞生

接下来就是在这个插件脚本中修改一下引擎计算 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 等!

欢迎大家提建议、给点个星星 Star,欢迎加入讨论 QQ 群:1085201157

GitHub地址: https://github.com/KuoKuo666/k-cocos

码云地址: https://gitee.com/kuokuo666/k-cocos

对应文档地址: https://kuokuo666.github.io

2020!我们一起进步!O(∩_∩)O~~

个人网站

内容也会同步 CSDN 与微信公众号哦!
www.kuokuo666.com

微信公众号

猜你喜欢

转载自blog.csdn.net/kuokuo666/article/details/109012832