cocos2d-x v3.9 与ActionInterval的孩子们之间的对话(3)

原创 2015年11月23日 12:47:23

我:今天咱们继续聊,还~有~谁~?
TargetedAction:这儿呢,这儿呢。我能让一个精灵待执行的动作在另一个精灵的动作执行完成之后执行。这可不同于Sequence,Sequence是让同一个精灵的多个动作顺序执行,而我是让另一个精灵的动作插个队。说了这么多可能听着还是有点儿绕,还是拿例子说话吧,

auto sprite1 = Sprite::create("image1.png");
auto sprite2 = Sprite::create("image2.png");
sprite1->setPosition(100, 100);
sprite2->setPosition(200, 200);
this->addChild(sprite1);
this->addChild(sprite2);
auto jumpby1 = JumpBy::create(3, Vec2(0, 0), 100, 3);
auto jumpby2 = jumpby1->clone();
sprite1->runAction(Sequence::create(jumpby1, TargetedAction::create(sprite2, jumpby2), nullptr));    // sprite1跳3下之后sprite2才会跳。

实现上也很简单,就是在runAction()调用我的startWithTarget()时,我去调用您指定动作的startWithTarget();在ActionManager调用我的update()时我去调用您指定动作的update()。
我:看起来就像是个帮别人插队的代理。


我:接下来ActionFloat,你的功能是什么?
ActionFloat:我可以创建一个自定义起始和终止状态的动作,

auto actionfloat = ActionFloat::create(3, 2, 5, [this](float value) {
    _tamara->setScale(value);
});    // 让执行动作的sprite在3秒内从2倍大小放大到5倍。

我接收规定的时间、动作的起始值、动作的终止值以及一个回调函数。回调函数的参数我会传递根据当前时间进度百分比,动作应被设定的值。以上面的例子为例,比如规定的时间过去一半时,我会向回调函数中传递2 + (5 - 2) * 0.5 = 3.5,所以此时执行动作的sprite应被放大到原尺寸的3.5倍。实现上同样很简单,首先在startWithTarget()中计算出终止值与起始值之差_delta,然后在update()中计算出当前动作应被设定的值,以此值调用回调函数。

// 当前动作应被设定的值。实现上用的是5 - (5 - 2) * 0.5 = 3.5,一样。
float value = _to - _delta * (1 - delta);

if (_callback)
{
    _callback(value);
}

我:你的功能有些奇怪,看起来就像是个快捷方式,省去了在执行动作之前设置动作初始值的步骤。


我:最后就剩你了,Animate。
Animate:我能够播放一段动画,您需要提供给我一段创建好的动画,这个是由动画制作组那帮家伙负责的。规定的时间就不用了,因为创建好的动画信息中已经包含了时间的相关的信息。

auto animate = Animate::create(animation);    // animation是创建好的动画。

我:关于动画你做个简单的说明。
Animate:好的。动画其实就是多张图片快速的连续播放的效果,比如您想让一个精灵跳舞,那您可以把这个精灵所做的每一个舞蹈动作制作成一张图片,然后将这些图片交给动画制作组。他们会处理图片的播放顺序、设置每张图片播放的时间等等,最终将这些图片组成一个可以播放的动画。而我就像是一个动画播放器,拿着动画制作组制作出来的这个动画文件,我就可以播放动画了。
我:你需要用到动画文件中的什么信息?
Animate:我需要用到动画播放一次的持续时间、动画播放的次数、按播放顺序排列好的图片(这里暂时先这么说,实际上是制作好的动画帧)、每一帧动画占用多少个时间片、每一个时间片占用多长时间、对于每一帧需要传递的用户数据。
我:时间片?
Animate:对,比如您的精灵所跳的这段舞蹈中有一个动作很优美,您想多看一会儿,那么您就可以给这张图片分配更多的时间片。图片拥有的时间片越多,它在动画中停留的时间就越长。
我:用户数据?
Animate:您可以理解为您对与每一个动画帧的备注,在播放动画时如果您需要,我可以把这些信息发送给您。
我:哦,好。基本的概念大致了解了,现在说说你的实现吧。
Animate:实现相比于TargetedAction和ActionFloat稍微复杂一点,还是对着源码说更清楚一些。首先是在创建我的时候,会进入我的initWithAnimation(),

bool Animate::initWithAnimation(Animation* animation)
{
    CCASSERT( animation!=nullptr, "Animate: argument Animation must be non-nullptr");

    // 动画播放一次的持续时间。
    float singleDuration = animation->getDuration();

    // 上报:动画播放一次的持续时间 × 动画播放的次数 = 整个动画播放完成的持续时间。
    if ( ActionInterval::initWithDuration(singleDuration * animation->getLoops() ) )
    {
        _nextFrame = 0;    // 下一次要播放哪一帧动画(暂时可以理解为哪一张图片),初始化为第一帧。
        setAnimation(animation);    // 存储动画。
        _origFrame = nullptr;    // 精灵在播放动画前原本的样子。可以设置动画播放完成后精灵回到原本的样子。
        _executedLoops = 0;    // 当前是在第几次播放动画。

        // _splitTimes存储每一帧动画在动画播放一次的持续时间中的百分之多少时播放。
        _splitTimes->reserve(animation->getFrames().size());

        float accumUnitsOfTime = 0;    // 此帧动画前用了多少个时间片,初始化为0。
        /* 动画中每一个时间片占用多长时间 = 动画播放一次的持续时间 / 动画一共使用了多少个时间片。
         * 其实直接使用Animation::getDelayPerUnit()不就好了。
         */
        float newUnitOfTimeValue = singleDuration / animation->getTotalDelayUnits();

        auto& frames = animation->getFrames();

        for (auto& frame : frames)    // 依次获取每一帧动画。
        {
        /* (此帧动画前用了多少个时间片 × 每一个时间片占用多长时间) / 动画播放一次的持续时间 
         * = 每一帧动画在动画播放一次的持续时间中的百分之多少时播放。
         */
            float value = (accumUnitsOfTime * newUnitOfTimeValue) / singleDuration;
            accumUnitsOfTime += frame->getDelayUnits();    // 此帧动画用了多少个时间片。
            _splitTimes->push_back(value);
        }
        return true;
    }
    return false;
}

接着在runAction()是会调用我的startWithTarget(),

void Animate::startWithTarget(Node *target)
{
    ActionInterval::startWithTarget(target);
    Sprite *sprite = static_cast<Sprite*>(target);

    CC_SAFE_RELEASE(_origFrame);

    if (_animation->getRestoreOriginalFrame())    // 整个动画播放完成之后是否需要恢复sprite本来的样子。
    {
        _origFrame = sprite->getSpriteFrame();    // 先把sprite原本的样子记住。
        _origFrame->retain();
    }
    _nextFrame = 0;    // 下一次要播放哪一帧动画,初始化为第一帧。
    _executedLoops = 0;    // 当前是在第几次播放动画,初始化为第0次。
}

最后在动作实际运行起来后,ActionManager会调用我的update(),

void Animate::update(float t)
{
    // if t==1, ignore. Animation should finish with t==1
    if( t < 1.0f ) {
        t *= _animation->getLoops();

        // ((unsigned int)(t * 动画播放次数)) --> 当前正在第几次播放动画。
        unsigned int loopNumber = (unsigned int)t;
        if( loopNumber > _executedLoops ) {    // 如果是进入了新的一次播放。
            _nextFrame = 0;    // 下一次要播放哪一帧动画,初始化为第一帧。
            _executedLoops++;    // 当前第几次播放动画+1。
        }

        t = fmodf(t, 1.0f);    // 转换为当次动画播放中的时间进度百分比。
    }

    auto& frames = _animation->getFrames();
    auto numberOfFrames = frames.size();    // 动画一共有多少帧。
    SpriteFrame *frameToDisplay = nullptr;    // 将要播放的动画帧。

    /* _nextFrame是下一次要播放的动画帧。
     * 每次的update()走到这里是从下一次要播放的动画帧开始判断的,而不是从第一个动画帧。
     * 这里用for()是因为怕程序的延时过长,
     * t传进来的时候已经是跳过了多个动画帧后的时间进度百分比。
     * 用for()可以更新到当前时间应该正确被播放的动画帧。
     */    
    for( int i=_nextFrame; i < numberOfFrames; i++ ) {
        // 获取下一个要播放的动画帧在播放一次动画中的时间进度百分比。
        float splitTime = _splitTimes->at(i);

        if( splitTime <= t ) {    // 如果下一个要播放的动画帧应该被播放。
            _currFrameIndex = i;    //  当前正在播放的动画帧索引。
            AnimationFrame* frame = frames.at(_currFrameIndex);    // 获取下一个要播放的动画帧。
            frameToDisplay = frame->getSpriteFrame();    // 从动画帧中取出精灵要显示的图片帧。
            static_cast<Sprite*>(_target)->setSpriteFrame(frameToDisplay);    // 让精灵显示该图片帧。

            const ValueMap& dict = frame->getUserInfo();    // 动画帧的用户数据。
            if ( !dict.empty() )    // 如果用户数据存在的话。
            {
                if (_frameDisplayedEvent == nullptr)
                    _frameDisplayedEvent = new (std::nothrow) EventCustom(AnimationFrameDisplayedNotification);    // 创建用户自定义事件。

                /* _frameDisplayedEventInfo是AnimationFrame中定义的一种结构体,
                 * 用户自定义事件以这种结构体的形式向用户发送用户数据。
                 */
                _frameDisplayedEventInfo.target = _target;
                _frameDisplayedEventInfo.userInfo = &dict;
                _frameDisplayedEvent->setUserData(&_frameDisplayedEventInfo);    // 填写用户自定义事件的信息。
                Director::getInstance()->getEventDispatcher()->dispatchEvent(_frameDisplayedEvent);    // 发送用户自定义事件。
            }
            _nextFrame = i+1;    // 下一次要播放的动画帧+1。
        }
        // Issue 1438. Could be more than one frame per tick, due to low frame rate or frame delta < 1/FPS
        else {    // 如果下一个要播放的动画帧不应该被播放。
            break;
        }
    }
}

我:你的实现还真是稍复杂了一些,看来要想比较好的理解你还需要找动画制作组聊聊。

版权声明:本文为博主原创文章,未经博主允许不得转载。

cocos2d基础学习--自定义对话框实现

在游戏开发中,为了获得更好的交互效果,经常使用对话框,cocos实现对话框也比较容易,说到底对话框也是一个Layer。我们在添加Layer之前首先对对话框进行一定的设置,之后在Layer的OnEnte...
  • smbroe
  • smbroe
  • 2014年12月27日 17:19
  • 782

Cocos2d-x 手游聊天系统需求分析

手游聊天系统需求分析转载请注明:IT_xiao小巫移动开发狂热者群:299402133策划需求图  参考系统:刀塔传奇点击这个,然后弹出下面的...
  • wwj_748
  • wwj_748
  • 2014年08月05日 16:51
  • 8723

用ClippingNode实现文字AVG游戏的对话字幕效果

玩文字AVG游戏主要的行为就是阅读j

【COCOS2DX-游戏开发之二】 模态对话框

需求:用cocos2d-x开发游戏时 可能需要模态对话框 拦截下层消息  比如:购买等待模态对话框  我们点击购买按钮后不希望玩家再次点击购买 这时候我们最幸福的就是拥有一个模态对话框了 不知...

《游戏脚本的设计与开发》-(RPG部分)3.3 加入多个人物以及对话实现

上一节中 给地图加入了遮挡功能,尝试着加入了一个可以控制的测试人物,并且实现了人物行走时的各个动作变换的控制。本节中接下来要做的事情就是把之前的工作全部脚本化,并且使用游戏脚本加入多个人物角色。另外,...

Cocos2d-x 3.0 开发(十五)使用UILayout布局,制作对话界面

编辑器中能设计静态展示的UIScrollView,而通常我们都需要在程序中动态增加信息。插入元素的位置怎么确定?在3.0中UILayout已经实现了基本的布局,有图为证.........
  • fansongy
  • fansongy
  • 2013年12月17日 19:20
  • 24465

cocos2d-x v3.9 与MoveBy和MoveTo之间的对话

我:能做什么? MoveBy:我能移动您指定的物体。 我:需要什么? MoveBy:需要您规定个以秒为单位的时间,再告诉我移动的方向和距离,最后再告诉我要移动谁。 我:方向和距离? Move...

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

Delphi7高级应用开发随书源码

  • 2003年04月30日 00:00
  • 676KB
  • 下载

【cocos2dx 3.2】一个都不能死2 人物层

分析: 人物对象看作是一个Sprite类
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:cocos2d-x v3.9 与ActionInterval的孩子们之间的对话(3)
举报原因:
原因补充:

(最多只允许输入30个字)