关于cocos2dx中的action源码分析

前端之家收集整理的这篇文章主要介绍了关于cocos2dx中的action源码分析前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

action是cocos2dx扮演中很重要的角色,很多特殊的效果,都是通过他来实现,而且通过他可以方便的产生很多效果

而不需要太多的相关知识储备、以及实现技巧。借着学习的思路,我们走一下cocos2dx中action的流程分析,大家共勉

吧。


【ActionManager篇】


一般action的入口在:


Action * Node::runAction(Action* action)
{
    CCASSERT( action != nullptr,"Argument must be non-nil");
    _actionManager->addAction(action,this,!_running);
    return action;
}

他是node一个借口,而node是cocos2dx中绝大部分类的根类。所以意味着基本所有的cocos2dx具体类,都可以调用这个接口,

也意味着基本都可以为其添加特殊效果


里面_actionManager成员,在Node::Node()构造函数里赋值,如下

 _actionManager = director->getActionManager();

从director类获取到了,而director类是cocosdx里的最基本的功能类,在其init方法里,
_actionManager = new ActionManager();
// 初始化完,马上就注册了update更新
_scheduler->scheduleUpdate(_actionManager,Scheduler::PRIORITY_SYSTEM,false);

在其初始化后,就注册了update更新事件,所以ActionManager的update方法在每帧都会调用了,达到可以更新action的作用。

接着分析ActionManager::addAction,看action是如何加入ActionManager

void ActionManager::addAction(Action *action,Node *target,bool paused)
{
  CCASSERT(action != nullptr,"");
  CCASSERT(target != nullptr,"");




  tHashElement *element = nullptr;
  // we should convert it to Ref*,because we save it as Ref*
  Ref *tmp = target;
<span style="white-space:pre">	</span>// 根据目标来查找对应的数据(tHashElement)
  HASH_FIND_PTR(_targets,&tmp,element);
  // 没有找到
<span style="white-space:pre">	</span>if (! element)
  {
<span style="white-space:pre">		</span>// 构造元素tHashElement空间
    element = (tHashElement*)calloc(sizeof(*element),1);
    element->paused = paused;
    // 这里引用了target,为了防止在更新action,其宿主释放,这里retain一下
<span style="white-space:pre">		</span>target->retain();
    element->target = target;
    // 添加至_targets,以target的指针为key
<span style="white-space:pre">		</span>HASH_ADD_PTR(_targets,target,element);
  }




<span style="white-space:pre">	</span>// 分配element里的成员空间
  actionAllocWithHashElement(element);

<span style="white-space:pre">	</span>//
  CCASSERT(! ccArrayContainsObject(element->actions,action),"");
  // 将action放入element->actions
<span style="white-space:pre">	</span>ccArrayAppendObject(element->actions,action);


<span style="white-space:pre">	</span>// 这里设置一下action的对象
  action->startWithTarget(target);
}

里面涉及的方法如下:
// 分配tHashElement成员空间
void ActionManager::actionAllocWithHashElement(tHashElement *element)
{
	// 分配action的存储空间,默认是4个
    // 4 actions per Node by default
    if (element->actions == nullptr)
    {
        element->actions = ccArrayNew(4);
    }else 
    if (element->actions->num == element->actions->max) // action满了,空间翻倍
    {
        ccArrayDoubleCapacity(element->actions);
    }
}


// 将action放入element
void ccArrayAppendObject(ccArray *arr,Ref* object)
{
    CCASSERT(object != nullptr,"Invalid parameter!");
    // 这里将action retain了一下
	object->retain();
	// 放置action序列末尾
	arr->arr[arr->num] = object;
	arr->num++;
}

// 设置action的对象
void Action::startWithTarget(Node *aTarget)
{
    _originalTarget = _target = aTarget;
}

好了,到此,我们的action已经加入ActionManager中去了,现在由cocos2dx框架来驱动action了,前面已经分析了,ActionManager::update

会每帧调用,我们下面分析update

void ActionManager::update(float dt)
{
	// 遍历_targets
    for (tHashElement *elt = _targets; elt != nullptr; )
    {
		// 
        _currentTarget = elt;
        _currentTargetSalvaged = false;

	// 该对象没有暂停
        if (! _currentTarget->paused)
        {
	<span style="white-space:pre">	</span>// 遍历该对象的所有actions
		// 英文提示该actions可能在循环里改变
            // The 'actions' MutableArray may change while inside this loop.
            for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;
                _currentTarget->actionIndex++)
            {
		// 当前action
                _currentTarget->currentAction = (Action*)_currentTarget->actions->arr[_currentTarget->actionIndex];
                if (_currentTarget->currentAction == nullptr)
                {
                    continue;
                }

                _currentTarget->currentActionSalvaged = false;

		// 回调action::step
                _currentTarget->currentAction->step(dt);

                if (_currentTarget->currentActionSalvaged)
                {
			// 这里action::release了,记得前面我们分析,在
			// void ccArrayAppendObject(ccArray *arr,Ref* object) retain了一下
					
                    // The currentAction told the node to remove it. To prevent the action from
                    // accidentally deallocating itself before finishing its step,we retained
                    // it. Now that step is done,it's safe to release it.
                    _currentTarget->currentAction->release();
                } else
                if (_currentTarget->currentAction->isDone()) // action完成
                {
			// 回调action::stop
                    _currentTarget->currentAction->stop();
			// 移除该action
                    Action *action = _currentTarget->currentAction;
                    // Make currentAction nil to prevent removeAction from salvaging it.
                    _currentTarget->currentAction = nullptr;
                    
			// 移除该action,其会改变_currentTargetSalvaged的标志,
			// 也会改变_currentTarget->actions->num
			removeAction(action);
                }
		// 清理一下当前action,_currentTarget->currentAction = nullptr;
            }
        }

        // elt,at this moment,is still valid
        // so it is safe to ask this here (issue #490)
        elt = (tHashElement*)(elt->hh.next);

	// 如果该actions没有action,并且_currentTargetSalvaged为真
        // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
        if (_currentTargetSalvaged && _currentTarget->actions->num == 0)
        {
            deleteHashElement(_currentTarget);
        }
    }

    // issue #635
    _currentTarget = nullptr;
}

简单来说,update的工作,就是遍历_targets,换句话说,就是遍历所有调用了runAction方法的Node对象,执行其Action的step方法,传入的是每帧

逝去的时间,在处理完后,看这个action是不是完成了,完成了的话,就移除该action,在最后,如果该tHashElement的actions都没有action,就移除

该tHashElement,其中里面涉及的方法分析如下:

void ActionManager::removeAction(Action *action)
{
    // explicit null handling
    if (action == nullptr)
    {
        return;
    }

	// 找到该action对应的tHashElement
    tHashElement *element = nullptr;
    Ref *target = action->getOriginalTarget();
    HASH_FIND_PTR(_targets,&target,element);
    if (element)
    {
	// 获得该action在actions的索引
        auto i = ccArrayGetIndexOfObject(element->actions,action);
        if (i != CC_INVALID_INDEX)
        {
	 // 移除该索引位的action
            removeActionAtIndex(i,element);
        }
    }
    else
    {
        CCLOG("cocos2d: removeAction: Target not found");
    }
}

里面涉及的方法如下:
void ActionManager::removeActionAtIndex(ssize_t index,tHashElement *element)
{
    Action *action = (Action*)element->actions->arr[index];

	// 如果该action是当前处理的action,将该action retain一下,并设置
	// currentActionSalvaged 标志
    if (action == element->currentAction && (! element->currentActionSalvaged))
    {
        element->currentAction->retain();
        element->currentActionSalvaged = true;
    }

	// 这里释放该索引位置的action,并拼合剩下action的位置关系
	// 注意最后的参数传了true,说明要清理对象
    ccArrayRemoveObjectAtIndex(element->actions,index,true);

	// 当前索引并移除,后面索引前移来填充,所以循环索引要回退。
	// 仅仅也只有等于的情形吧
    // update actionIndex in case we are in tick. looping over the actions
    if (element->actionIndex >= index)
    {
        element->actionIndex--;
    }

	// 移除该索引后,actions里没有action里
    if (element->actions->num == 0)
    {
	// 当前处理的element,就是移除action所属的tHashElement
	// 意味这个tHashElement没有action了,标志其要移除出_targets
        if (_currentTarget == element)
        {
		// 是当前处理的tHashElement,暂缓移除,标志一下
            _currentTargetSalvaged = true;
        }
        else
        {
		// 如果element不是当前处理的tHashElement,就直接移除
            deleteHashElement(element);
        }
    }
}

再接着:
void ccArrayRemoveObjectAtIndex(ccArray *arr,ssize_t index,bool releaSEObj/* = true*/)
{
	// 要不要清理该对象
    CCASSERT(arr && arr->num > 0 && index>=0 && index < arr->num,"Invalid index. Out of bounds");
	if (releaSEObj)
    {
        CC_SAFE_RELEASE(arr->arr[index]);
    }
    // actions个数减一
	arr->num--;
	// 该位置移除,后面的填上
	ssize_t remaining = arr->num - index;
	if(remaining>0)
    {
		memmove((void *)&arr->arr[index],(void *)&arr->arr[index+1],remaining * sizeof(Ref*));
    }
}

至此,ActionManager部分,就是Aciton的框架部分,分析至此,脉络已经出来了,就是Scheduler驱动着这一切,我们只要调用runAction,

将Action交给ActionManager就好了,


【Action篇】

下面分析一下action的体系,action的怎样实现,造就了如此丰富的action效果

下面是我重新摘除重点的action类定义

class CC_DLL Action : public Ref,public Clonable
{
public:
	/** returns a clone of action */
	virtual Action* clone() const = 0;
	/** returns a new action that performs the exactly the reverse action */
	virtual Action* reverse() const = 0;
	//! called before the action start. It will also set the target.
	virtual void startWithTarget(Node *target);
	//called after the action has finished. It will set the 'target' to nil.
	virtual void stop();
	//! called every frame with it's delta time. DON'T override unless you know what you are doing.
    virtual void step(float dt);
	For example: 
    - 0 means that the action just started
    - 0.5 means that the action is in the middle
    - 1 means that the action is over
    */
    virtual void update(float time);
protected:
	 Node    *_originalTarget;
    /** The "target".
    The target will be set with the 'startWithTarget' method.
    When the 'stop' method is called,target will be set to nil.
    The target is 'assigned',it is not 'retained'.
    */
    Node    *_target;
    /** The action tag. An identifier of the action */
    int     _tag;
}

透露了几个信息

1、该类继承自Clonable

class CC_DLL Clonable
{
public:
    /** returns a copy of the Ref */
    virtual Clonable* clone() const = 0;
    /**
     * @js NA
     * @lua NA
     */
    virtual ~Clonable() {};	
}

就是定了克隆的接口,由于action的使用很频繁,所有有克隆是一个很重要的特性,

2、有reverse接口,说明reverse也是一个常备的特性,一个动作常常提供反转动作,但是也不是都会实现这个反转。

3、step是框架调用的,自己实现要慎重,后面会分析到,这个接口基本可以不用重载。

4、action的成员变量,就是有一个原始目标,还有一个目前目标,而且注明该目标是简单的赋值,不负责维护引用


这个action是个抽象类,规定了一些接口,用来为ActionManager提供操作接口。

下面看看最常用的有限时间动作,我摘除重要的部分如下:

class CC_DLL FiniteTimeAction : public Action
{
protected:
    //! duration in seconds
    float _duration;
}

其实就是增加了一个时间段。而且还是一个抽象类,没有什么具体作用,下面看看他的具体应用,区间动作(ActionInterval)

class CC_DLL ActionInterval : public FiniteTimeAction
{
public:
<span style="white-space:pre">	</span>// 实现了完成的条件,就是逝去时间大于动作区间
    virtual bool isDone(void) const override;
    // 这里需要分析下,他是reverse实现的基础
	virtual void step(float dt) override;
   
protected:
    float _elapsed;
    bool   _firstTick;
};

这里要注意下step的实现
void ActionInterval::step(float dt)
{
	// 第一次调用,初始化下
    if (_firstTick)
    {
        _firstTick = false;
        // 逝去时间初始化
		_elapsed = 0;
    }
    else
    {
		// 记录逝去的时间和
        _elapsed += dt;
    }
    
	// 这个表达式表达就是,update的参数,就是逝去的时间在整个动作
	// 时间的比例,而不是时间间隔了。
	// _elapsed = 0,就是update(0)
	// _elapsed = _duration 就是update(1)
    this->update(MAX (0,// needed for rewind. elapsed could be negative
                      MIN(1,_elapsed /
                          MAX(_duration,FLT_EPSILON)   // division by 0
                          )
                      )
                 );
}

下面分析个实例吧,Repeat的实现 Repeat的辅助数据如下:
protected:
	// 需要重复次数
    unsigned int _times;
	// 已重复次数
    unsigned int _total;
	// 重复动作时间占比,用于统计_total,
    float _nextDt;
	// 标记该动作是不是瞬时动作
    bool _actionInstant;
    /** Inner action */
	// 重复的动作
    FiniteTimeAction *_innerAction;

具体说明都在注释上
void Repeat::startWithTarget(Node *target)
{
	// 初始化已重复次数
    _total = 0;
	// 本动作在总时间的占比
    _nextDt = _innerAction->getDuration()/_duration;
    ActionInterval::startWithTarget(target);
	// 内部动作也初始化下对象
    _innerAction->startWithTarget(target);
}

核心实现update,如下:
void Repeat::update(float dt)
{
	// 当前时间比例,已经超过一次动作的时间比例
    if (dt >= _nextDt)
    {
	// 出现这种情况,只有卡的情况吧,
	// 时间比例超了动作时间占比,而次数又没有到目标
        while (dt > _nextDt && _total < _times)
        {
	<span style="white-space:pre">	</span>// 目标动作直接更新完成
            _innerAction->update(1.0f);
            // 已重复次数增加
		_total++;

		// 动作停止接着又开始,保证目标动作的回调函数调用到
            _innerAction->stop();
            _innerAction->startWithTarget(_target);
		// 重算下次目标占比
            _nextDt = _innerAction->getDuration()/_duration * (_total+1);
        }

	// 总时间占比完成,但是次数没有达到,一般是临界情况
        // fix for issue #1288,incorrect end value of repeat
        if(dt >= 1.0f && _total < _times) 
        {
            _total++;
        }

        // don't set an instant action back or update it,it has no use because it has no duration
        if (!_actionInstant)
        {
            if (_total == _times)
            {
                _innerAction->update(1);
                _innerAction->stop();
            }
            else // 最后一帧让他执行完
            {
                // issue #390 prevent jerk,use right update
                _innerAction->update(dt - (_nextDt - _innerAction->getDuration()/_duration));
            }
        }
    }
    else
    {
	// 目标动作执行update(单动作的时间占比),
        _innerAction->update(fmodf(dt * _times,1.0f));
    }
}
以上,就是action的一点点分析,希望对大家有点帮助。

猜你在找的Cocos2d-x相关文章