浅析Cocos2d-x引擎的动作类Action

我用是3.14.1的版本,不过总体上应该大同小异,然而即使有大的区别我目前也不知道,因为我只看了这个版本的?。

直接上图,大致的Action类架构就是这样,如果有看过Cocos源码的朋友都知道它实际上并不复杂的,这里我就按照自己的理解,从整体设计到具体的局部实现上简单阐述重要的几点:

一、Action类由ActionManager类通过结构体tHashElement(_hashElement)去管理,ActionManager在Schedule类中注册了一个优先级最低的,每帧都会调用其update(dt)的定时器。

二、ActionManger类的update(dt)函数中,会遍历所有tHashElement,若其中持有的每个Node在当前具有非暂停状态的Action,则执行其对应的step(dt)函数,该函数在之后的子类中被覆写,以支持多种类型的动作执行,如瞬时动作ActionInstant、需要时间间隔的动作ActionInterval、加速Speed等。

三、瞬时动作ActionInstant与间隔动作ActionInterval以在step(dt)函数中调用update(dt)的方式,在每帧修改其绑定的Node节点的数据,从而实现动画的效果。如Show与Hide动作修改Node的可见性isVisible,Move动作修改坐标Postion,Rotate修改节点旋转使用的四元数Quaternion等。

 

这里提一下比较特殊的两个组合动作Sequence与Spawn的实现,当时看源码并没有研究地很清楚,不过在《我所理解的Cocos2d-x》一书中找到了不错的解释,再次上图:

Sequence内部保存一个指针数组,内含两个Action指针,配合两个动作的间隔时间点split和用于记录上一个动作的last变量,以深度遍历二叉树的形式实现多个动作的有序执行:

Spawn负责多个动作同时执行,可想而知只需要两个Action指针即可实现(偷懒用手机拍照了,马赛克见谅):

 剩下的Repeat动作的实现就留给各位看官思考啦(并不是我懒得写哈哈哈哈)。

 

接下来自问自答几个可能会令人疑惑的问题,因小弟才疏学浅不可面面俱到,各位大可留言讨论一波:

Q1:ActionManager中的_hashElement到底是个什么玩意??为什么我管个Action还要通过它?

A1:tHashElement是个结构体,用于关联每个Node和它所持有的所有Action,并提供标志位以便于Manager类管理,如当前动作索引actionIndex,是否暂停paused以及是否需要销毁currentActionSalvaged。它的内部还有一个结构体UT_hash_handle,它的具体解析可见这位大佬的链接https://blog.csdn.net/beautyleaf/article/details/51792467

Q2:不同类型的动作的step(dt)函数的处理细节?

A2:加速动作Speed将每帧传入的时间间隔参数乘以系数_speed,这样在一帧内就能控制动作的快慢;瞬时动作ActionInstant由于不需要间隔作为参数,因此直接执行update()函数;ActionInterval则将时间间隔dt转换为从动作执行开始所经过的时间_elapsed所占动作总时长_duration的百分比,然后将该百分比作为参数传入具体动作的update()函数,从而计算每一帧对Node属性修改的增量(插值计算)。

Q3:如果我想自己设计一个Action,我该怎么做?

A3:①确定你想实现的动作属于哪一类(讲道理应该都找得到的,Cocos2d-x的Action设计者考虑过的情况总不会比你少吧)。

        ②继承相应的动作基类,实现属于你的update()函数,对Node用于变换的属性(标量、矢量、四元数等)以计算增量的方式进行修改。

        ③别忘了实现Action的创建、与Node节点的绑定,以及Action自身的clone()和reverse()等基础操作。

Q4:如果我想再进一步地研究Action所修改的那些Node属性是如何最后在游戏中呈现给玩家看的,我应该去学习什么内容?

A4:去看一下Director导演类的mainLoop()函数,里面的drawScene()方法中会通过调用visit()递归绘制所有Node,其中涉及到OpenGL引擎,即图形学相关知识,有个神奇的类Mat4,这就触及到我的知识盲区了?,不过我截了一段它的注释:

 额。。你们自由发挥吧!我就写到这,实验室的同学都走光了。。刚好夜宵出摊了,我撤啦哈哈哈哈哈哈哈哈哈。

 

--------------------------------久违地更新一下-----------------------------------------

       因为近期需要做一个变(加减)速旋转动画的需求,看到Cocos动画系统里面已经提供了相关接口,也就是ActionEase,它专用于变速动画的制作。

       ActionEase在原先的ActionInterval(带时间间隔的动画)基础上多加了一层,通过基于各类数学公式改变ActionInterval在每一帧计算出的"已经流逝的时间占动画总持续时间的百分比(详情可参考前文中粉色加粗部分)",以控制一个ActionInterval动画的播放速度,以下是Cocos2d-X用于实现变速动画的核心代码:

#define EASE_TEMPLATE_IMPL(CLASSNAME, TWEEN_FUNC, REVERSE_CLASSNAME) \
CLASSNAME* CLASSNAME::create(cocos2d::ActionInterval *action) \
{ \
    CLASSNAME *ease = new (std::nothrow) CLASSNAME(); \
    if (ease) \
    { \
        if (ease->initWithAction(action)) \
            ease->autorelease(); \
        else \
            CC_SAFE_RELEASE_NULL(ease); \
    } \
    return ease; \
} \
CLASSNAME* CLASSNAME::clone() const \
{ \
    if(_inner) return CLASSNAME::create(_inner->clone()); \
    return nullptr; \
} \
void CLASSNAME::update(float time) { \
    _inner->update(TWEEN_FUNC(time)); \
} \
ActionEase* CLASSNAME::reverse() const { \
    return REVERSE_CLASSNAME::create(_inner->reverse()); \
}

EASE_TEMPLATE_IMPL(EaseExponentialIn, tweenfunc::expoEaseIn, EaseExponentialOut);
EASE_TEMPLATE_IMPL(EaseExponentialOut, tweenfunc::expoEaseOut, EaseExponentialIn);
EASE_TEMPLATE_IMPL(EaseExponentialInOut, tweenfunc::expoEaseInOut, EaseExponentialInOut);
EASE_TEMPLATE_IMPL(EaseSineIn, tweenfunc::sineEaseIn, EaseSineOut);
EASE_TEMPLATE_IMPL(EaseSineOut, tweenfunc::sineEaseOut, EaseSineIn);
EASE_TEMPLATE_IMPL(EaseSineInOut, tweenfunc::sineEaseInOut, EaseSineInOut);
EASE_TEMPLATE_IMPL(EaseBounceIn, tweenfunc::bounceEaseIn, EaseBounceOut);
EASE_TEMPLATE_IMPL(EaseBounceOut, tweenfunc::bounceEaseOut, EaseBounceIn);
EASE_TEMPLATE_IMPL(EaseBounceInOut, tweenfunc::bounceEaseInOut, EaseBounceInOut);
EASE_TEMPLATE_IMPL(EaseBackIn, tweenfunc::backEaseIn, EaseBackOut);
EASE_TEMPLATE_IMPL(EaseBackOut, tweenfunc::backEaseOut, EaseBackIn);
EASE_TEMPLATE_IMPL(EaseBackInOut, tweenfunc::backEaseInOut, EaseBackInOut);

      可以看到真的没啥,就一个宏定义然后套了一堆公式,下面列举几个:


// Expo Ease
float expoEaseIn(float time)
{
    return time == 0 ? 0 : powf(2, 10 * (time/1 - 1)) - 1 * 0.001f;
}
float expoEaseOut(float time)
{
    return time == 1 ? 1 : (-powf(2, -10 * time / 1) + 1);
}
float expoEaseInOut(float time)
{
    time /= 0.5f;
    if (time < 1)
    {
        time = 0.5f * powf(2, 10 * (time - 1));
    }
    else
    {
        time = 0.5f * (-powf(2, -10 * (time - 1)) + 2);
    }

    return time;
}

      大家可以随便挑一个函数画一个曲线,就能看出大致的变速趋势。

      实现这类变速动画的需求并不难,但是我踩到一个恶心的坑,半天也没反应过来(还是自己太菜了):

      以上指数型动画(EaseExponentialIn)所采用的函数expoEaseIn内用到的变速公式,在time==1时,也就是动画结束时返回的值不为1。也就是说,假如你用它实现了一个"幅度较大"的动画(比如移动距离或转动角度很大(通常是10的3次方以上),或是持续时间很长),最后动画停止时精灵的状态会与你预期的不符。

      是的,就是因为这个原因,导致在我用它实现变速的转动动画的时候,最后的旋转角度总会差上那么3-5度,精灵总会看上去好像提前停止了一样,而实际上整个动画的播放时间是不变的(我当时真的是一脸懵逼)。

      我的解决方案比较简单粗暴,直接换用另外一个变速动画的接口就完事了?。

      不过之后毕竟还是心有不甘,参照网上的一些案例以及现有项目的代码,我又舍弃了使用Cocos2d提供的这些接口,自己实现了一下变速动画的效果,也就是在每一帧通过一个分段函数算出一个转动速度,然后基于该速度直接修改精灵当前的旋转角度(Rotation),各位同学也可以自己尝试一下,这里我就不贴出来啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值