游戏设计中,动作是不可缺少的,Cocos2d-x中所有的动作都继承自Action类,而Action类继承自Ref和Clonable类,整个动作类继承体系如图:

FiniteTimeAction是所有瞬时动作和延时动作的父类,Follow跟随一个节点的动作,Speed改变一个动作的时间。,其中FiniteTimeAction的两个子类以及这两个子类的子类是重点。

瞬时性动作类

<ActionInstant.h>中的类是瞬时动作类,它的子类有:

//... 显示一个节点 setVisible(true)
class Show : public ActionInstant { }; //... 隐藏一个节点 setVisible(false)
class Hide : public ActionInstant { }; //... 切换节点的可视属性 setVisible(!_target->isVisible())
class ToggleVisibility : public ActionInstant {
}; //... 移除自己
class RemoveSelf : public ActionInstant { }; // 水平翻转精灵
class FlipX : public ActionInstant { }; // 垂直翻转精灵
class FlipY : public ActionInstant { }; // 将节点放置到某个位置
class Place : public ActionInstant { }; // 设置动作的回调函数为 std::function<void()>
class CallFunc : public ActionInstant {
public :
static CallFunc * create(const std::function<void()>& func);
}; // 设置动作的回调函数为 std::function<void(Node*)>
class CallFuncN : public CallFunc {
public :
static CallFuncN * create(const std::function<void(Node*)>& func);
};

延时性动作类

<ActionInterval.h>中的类是瞬时动作类,它的子类有:

// 创建序列动画
class Sequence : public ActionInterval {
public :
// 这种方式创建序列动画最后需要加nullptr
// 比如: Sequence::create(action1, action2, nullptr);
static Sequence* create(FiniteTimeAction *action1, ...); // 根据一个动作vector来创建
static Sequence* create(const Vector<FiniteTimeAction*>& arrayOfActions); // 创建两个动作
static Sequence* createWithTwoActions(FiniteTimeAction *actionOne, FiniteTimeAction *actionTwo); // 根据变长动作数组创建序列动作
static Sequence* createWithVariableList(FiniteTimeAction *action1, va_list args);
}; // 重复一个动作(一次)
class Repeat : public ActionInterval {
public :
// 创建一个FiniteTimeAction动作
static Repeat* create(FiniteTimeAction *action, unsigned int times);
}; // 创建不断重复的动作
class RepeatForever : public ActionInterval {
public :
// 由一个延时动作ActionInterval而创建
static RepeatForever* create(ActionInterval *action);
}; // 创建同时执行的动作
class Spawn : public ActionInterval {
public :
// 同序列式动作, 最后需要添加nullptr
static Spawn* create(FiniteTimeAction *action1, ...); static Spawn* createWithVariableList(FiniteTimeAction *action1, va_list args); static Spawn* create(const Vector<FiniteTimeAction*>& arrayOfActions); static Spawn* createWithTwoActions(FiniteTimeAction *action1, FiniteTimeAction *action2);
}; // 旋转动作 旋转到某个角度
class RotateTo : public ActionInterval { }; // 旋转动作 旋转一定角度
class CC_DLL RotateBy : public ActionInterval { }; // 移动一定距离
class MoveBy : public ActionInterval { }; // 移动到某个点
class MoveTo : public MoveBy { }; /*---------- 这个动作By版本继承自To版本 ----------*/
// 使某个倾斜到某个角度
class SkewTo : public ActionInterval { }; // 倾斜一定角度
class SkewBy : public SkewTo { }; // 跳跃一定距离
class JumpBy : public ActionInterval { }; // 跳跃到某个点
class JumpTo : public JumpBy { }; // 贝塞尔曲线
class BezierBy : public ActionInterval { }; //
class BezierTo : public BezierBy { }; // 缩放
class ScaleTo : public ActionInterval { }; class ScaleBy : public ScaleTo { }; // 闪烁
class Blink : public ActionInterval { }; // 设置透明度
class FadeTo : public ActionInterval // 淡入
class FadeIn : public FadeTo { }; // 淡出
class FadeOut : public FadeTo { }; // 延时动作
class DelayTime : public ActionInterval { }; // 动画
class Animate : public ActionInterval { };

在所有延时动作里:

  • SkewBy继承自SkewTo, ScaleBy继承自ScaleTo
  • RotateBy和RotateTo分别继承自ActionInterval

    TinkTo和TinkBy分别继承自ActionInterval
  • BesizerTo继承自BezierBy

    MoveTo继承自MoveBy 

    JumpTo继承自JumpBy

动作管理

所有的动作执行都由动作管理类ActionManager对象_actionManager来管理动作,_actionManager将所有的动作添加到执行序列中,然后该对象定时刷新自己的update函数,然后调用动作序列中每个动作的step函数,这些step再根据自身的update或结束动作:

假设先添加一个延时性动作:

tihs->runAction(action);

然后由ActionManager来添加到动作队列中:

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

看看addAction发生了什么:

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;
HASH_FIND_PTR(_targets, &tmp, element);
if (! element)
{
element = (tHashElement*)calloc(sizeof(*element), 1);
element->paused = paused;
target->retain();
element->target = target;
HASH_ADD_PTR(_targets, target, element);
} actionAllocWithHashElement(element); CCASSERT(! ccArrayContainsObject(element->actions, action), "");
ccArrayAppendObject(element->actions, action); // 此处将action添加到动作列表里 action->startWithTarget(target); // 然后绑定该动作的执行节点
}

添加了新的动作之后, 在帧刷新时会调用ActionManager的update函数,然后会遍历每个动作并执行相应的step函数:

void ActionManager::update(float dt)
{
for (tHashElement *elt = _targets; elt != nullptr; )
{
_currentTarget = elt;
_currentTargetSalvaged = false; if (! _currentTarget->paused)
{
// The 'actions' MutableArray may change while inside this loop.
for (_currentTarget->actionIndex = 0; _currentTarget->actionIndex < _currentTarget->actions->num;
_currentTarget->actionIndex++)
{
_currentTarget->currentAction = (Action*)_currentTarget->actions->arr[_currentTarget->actionIndex];
if (_currentTarget->currentAction == nullptr)
{
continue;
} _currentTarget->currentActionSalvaged = false; _currentTarget->currentAction->step(dt); // 对于延时性动作而言,执行ActionInterval::step(dt); if (_currentTarget->currentActionSalvaged)
{
// 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()) // 动作执行结束后,isDone函数返回true,然后将动作停止并移除
{
_currentTarget->currentAction->stop(); Action *action = _currentTarget->currentAction;
// Make currentAction nil to prevent removeAction from salvaging it.
_currentTarget->currentAction = nullptr;
removeAction(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); // 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;
}

对于延时性动作ActionInterval来说, 只有RepeatForever重写了基类ActionInterval的step函数,其他默认使用基类的版本:

void ActionInterval::step(float dt)
{
if (_firstTick)
{
_firstTick = false;
_elapsed = 0;
}
else
{
_elapsed += dt;
}
// 该调用会遍历每个ActionInterval的子类的update函数,从而执行相应的动作
this->update(MAX (0, // needed for rewind. elapsed could be negative
MIN(1, _elapsed /
MAX(_duration, FLT_EPSILON) // division by 0
)
)
);
}

在每一帧结束后, ActionMagener的update会根据函数isDone()来检测动作是否完成,如果完成,就执行stop函数将动作停止,并且执行 removeAction函数将动作移除。

【Cocos2d-x 3.x】 动作类Action源码分析的更多相关文章

  1. JDK中String类的源码分析(二)

    1.startsWith(String prefix, int toffset)方法 包括startsWith(*),endsWith(*)方法,都是调用上述一个方法 public boolean s ...

  2. String类的源码分析

    之前面试的时候被问到有没有看过String类的源码,楼主当时就慌了,回来赶紧补一课. 1.构造器(构造方法) String类提供了很多不同的构造器,分别对应了不同的字符串初始化方法,此处从源码中摘录如 ...

  3. Spring-MongoDB 关键类的源码分析

    本文分析的是 spring-data-mongodb-1.9.2.RELEASE.jar 和 mongodb-driver-core-3.2.2.jar. 一.UML Class Diagram 核心 ...

  4. Set集合架构和常用实现类的源码分析以及实例应用

    说明:Set的实现类都是基于Map来实现的(HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的). (01) Set 是继承于Collection的接口.它是一个不允许 ...

  5. Mybatis Mapper接口是如何找到实现类的-源码分析

    KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理,Proxy.newProxyInstance,Mapper 映射,Map ...

  6. java类uuid源码分析

    通用唯一识别码(英语:Universally Unique Identifier,简称UUID)是一种软件建构的标准,亦为自由软件基金会组织在分散式计算环境领域的一部份.UUID的目的,是让分散式系统 ...

  7. JDK中String类的源码分析(一)

    1.String类是final的,不允许被继承 /** The value is used for character storage. */ private final char value[]; ...

  8. java Thread 类的源码阅读(oracle jdk1.8)

    java线程类的源码分析阅读技巧: 首先阅读thread类重点关注一下几个问题: 1.start() ,启动一个线程是如何实现的? 2.java线程状态机的变化过程以及如何实现的? 3. 1.star ...

  9. Java并发编程笔记之Unsafe类和LockSupport类源码分析

    一.Unsafe类的源码分析 JDK的rt.jar包中的Unsafe类提供了硬件级别的原子操作,Unsafe里面的方法都是native方法,通过使用JNI的方式来访问本地C++实现库. rt.jar ...

随机推荐

  1. Java 设计模式学习

    看完headfirst设计模式,简单总结一下. 写在最前面:设计模式的关心的问题为"弹性.易于维护.易于扩展",通过对模式的应用,让自己的代码能够得到良好的可塑性.但是个人认为不能 ...

  2. 例子:Basic Lens sample

    本例演示了如何自己扩展一个Camera Lens. 1. UI界面是一个MediaViewer <controls:MediaViewer x:Name="MediaViewer&qu ...

  3. dos2unix

    dos2unix 实际上就是把文本文件里面的^M删除,我一般懒得用这个命令,vi里面用一个命令(:%s/^m//g)删除即可. 用法举例: dos2unix a.txt b.txt 要把一批文件都do ...

  4. MVC 支持同名路由,不同命名空间

    有时候我们会碰到两个项目合在一起,那么必然会碰到两个同名的controller,其实MVC在注册路由,添加Route的时候可以指定当前规则解析那个命名空间下的所有Controller. 注:Contr ...

  5. 写简单游戏,学编程语言-python篇:大鱼吃小鱼

    很常见的游戏之一,实现原理并不复杂,并且参考了几个相关的代码.这边主要还是以学习编程语言和学习编程思路为重点记录一下吧.最近时间有点吃紧,只能匆忙记录一下.用pygame做的大鱼吃小鱼的游戏截图如下: ...

  6. JAVA代理模式与动态代理模式

    1.代理模式 所谓代理,就是一个人或者一个机构代表另一个人或者另一个机构采取行动.在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之前起到中介的作用.代理模式给某 ...

  7. docker基本操作

    centos 7 安装docker 目前,CentOS 仅发行版本中的内核支持 Docker. Docker 运行在 CentOS 7 上,要求系统为64位.系统内核版本为 3.10 以上. Dock ...

  8. [转]使用scrapy进行大规模抓取

    原文:http://www.yakergong.net/blog/archives/500 使用scrapy有大概半年了,算是有些经验吧,在这里跟大家讨论一下使用scrapy作为爬虫进行大规模抓取可能 ...

  9. [Java Basics3] XML, Unit testing

    What's the difference between DOM and SAX? DOM creates tree-like representation of the XML document ...

  10. Hibernate的面试题

    1.Hibernate工作原理和为什么要用? 原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Session 4.创建事务Transation 5.持 ...