19、Cocos2dx 3.0游戏开发找小三之Action:流动的水没有形状,漂流的风找不到踪迹、、、
重开发人员的劳动成果。转载的时候请务必注明出处:http://blog.csdn.net/haomengzhu/article/details/30478985
流动的水没有形状。漂流的风找不到踪迹、、、
直睡的陵迁谷变。石烂松枯,斗转星移,整个宇宙在不停的运动着、、、
前面具体介绍了游戏的基本组成元素--场景、 层、 精灵和渲染树等。 也具体介绍了 Node 提供的定时器。
为了让整个世界运动起来,让每个精灵运动。能够利用定时器。不断改动节点的属性。实现简单的动态效果。
然而,这样的方法会导致为了实现简单的动态效果,十分烦 琐地维护一批定时器。
Cocos2d-x 为了解决问题。引入了动作机制。
Action是动作类的基类
全部的动作都派生自这个类,它创建的一个对象代表了一个动作。
动作作用于Node,因此。 不论什么一个动作都须要由 Node 对象来运行。
下面代码实现了一个精灵用 10秒钟的时间移动到了点(100, 100):
auto sprite = Sprite::create("sprite.png"); auto action = MoveTo::create(1.0f, Point(0, 0)); sprite->runAction(action);
须要注意的是,一个 Action 仅仅能使用一次,
这是由于动作对象不仅描写叙述了动作,还保存了这个动作持续过程中不断改变的一些中间參数。
对于须要重复使用的动作对象,能够通过 copy 方法复制使用。
Action 作为一个基类,事实上质是一个接口(即抽象类),
由它派生的实现类(如运动和转动等)才是我们实际使用的动作。
Action 的绝大多数实现类都派生自 FiniteTimeAction。这个类定义了在有限时间内能够完毕的动作。
FiniteTimeAction定义了 reverse 方法。通过这种方法能够获得一个与原动作相反的动作(称作逆动作),比如隐藏一个精灵后。用逆转动作再显示出来。
当然,并不是全部的动作都有相应的逆动作,
比如类似"放大到"等设置属性为常量的动作不存在逆动作。而设置属性为相对值的动作则往往存在对应的逆动作。
由 FiniteTimeAction 派生出的两个主要类各自是瞬时动作(ActionInstant)和持续性动作(ActionInterval),
这两类动作与复合动作配合使用,能得到复杂生动的动作效果。
瞬时动作
瞬时动作是指能立马完毕的动作。是 FiniteTimeAction 中动作持续时间为 0 的特例。
更准确地说。这类动作是在下一帧会立马运行并完毕的动作,如设定位置、设定缩放等。
这些动作原本能够通过简单地对 Node 赋值完毕,可是把它们包装
为动作后,能够方便地与其它动作类组合为复杂动作。
一些经常使用的瞬时动作:
Place
该动作用于将节点放置到某个指定位置,其作用与改动节点的 Position 属性同样。
比如。将精灵放置到屏幕坐标(100, 100) 处。再运行曲线运动 curveMove
的代码例如以下:
FiniteTimeAction* placeAction = Place::create(Point(100, 100)); Action* action = Sequence::create(placeAction, curveMove, NULL);
当中 Sequence 又称为动作序列。是一种复合动作。它在初始化时。会接受多个动作,当它被运行时,这些动作会按顺序逐个运行,形成有序的一列动作。
FlipX 和 和 FlipY
这两个动作分别用于将精灵沿 X 和 Y 轴反向显示,其作用与设置精灵的 FlipX 和 FlipY 属性同样。
将其包装为动作类也是为了便于与其它动作进行组合。
比如。我们想使精灵从屏幕的一端游动到还有一端,然后按原路返回。
为了更自然一点,我们设置一个序列。精灵先运行移动的动作,在精灵到达还有一端时反向显示,然后再运行移动回起点的动 作,相关代码例如以下:
FiniteTimeAction* flipXAction = FlipX::create(true); Action* action = Sequence::create(curveMove, flipXAction, curveMove->reverse(), NULL);
当中 reverse 的作用是取得原动作的逆动作。在这个样例中,精灵沿 X 轴翻转后将会沿原路返回起点。
Show 和 和 Hide
这两个动作分别用于显示和隐藏节点,其作用与设置节点的 Visible 属性的作用一样。
比如,为了使精灵完毕运动之后隐藏 起来。我们使用例如以下代码:
FiniteTimeAction* hideAction = Hide::create(); Action* action = Sequence::create(curveMove, hideAction, NULL);
CallFunc
CallFunc 系列动作包含 CallFunc、CallFuncN、__CCCallFuncND,以及 __CCCallFuncO四个动作,
用来在动作中进行方法的调用(之所以不是函数调用,是由于它们仅仅能调用某个类中的实例方法。而不能调用普通的
C 函数)。
当某个对象 运行 CallFunc 系列动作时,就会调用一个先前被设置好的方法,以完毕某些特别的功能。
在CallFunc 系列动作的 4 个类中:
CallFunc 调用的方法不包括參数。
CallFuncN 调用的方法包括一个 Node*类型的參数,
表示运行动作的对象。
__CCCallFuncND调用的方法包括两个參数,
不仅有一个节点參数, 另一个自己定义參数 (Node*与 void*)。
__CCCallFuncO调用的方法则仅仅包括一个 Ref*类型的參数。
实际上,CallFunc 系列动作的后缀"N"表示 Node 參数,指的是运行动作的对象,
"D"表示 Data 參数。指的是用户自己定义的数据,"O"表示对象。指的是一个用户自己定义的
Ref參数。
在不同的情况下。我们能够依据不同的需求来选择不同的 CallFunc 动作。
考虑一种情况,我们创建了很多会在屏幕中移动的精灵,希望精灵在移动结束之后就从游戏中删除。
为了实现这个效果,我们能够创建一系列动作:首先让精灵移动。然后调用一个 removeSelf(Node* nodeToRemove)方法来删除
nodeToRemove 对象。在 removeSelf 方法中须要訪问运行此动作的精灵,因此我们就採用 CallFuncN 来调用removeSelf 方法。
持续性动作
持续性动作是在持续的一段时间里逐渐完毕的动作。如精灵从一个点连续地移动到还有一个点。
与瞬时动作相比,持续性动作的种类更丰富。
因为这些动作将持续一段时间,所以大多数的持续性动作都会带有一个用于控制动作运行时间的实型參数duration。
每一种持续性动作通常都存在两个不同的变种动作。分别具有 To 和 By 后缀:
后缀为 To 的动作描写叙述了节点属性值的绝对变化。比如
MoveTo 将对象移动到一个特定的位置。
而后缀为 By 的动作则描写叙述了属性值相对的变化,如 MoveBy 将对象移动一段相对位移。
依据作用效果不同,能够将持续性动作划分为下面 4 大类:
位置变化动作
属性变化动作
视觉特效动作
控制动作
位置变化动作
针对位置(position)这一属性,引擎为我们提供了 3 种位置变化动作类型。
MoveTo 和 MoveBy:用于使节点做直线运动。设置了动作时间和终点位置后,节点就会在规定时间内。从当前位置直线移动到设置的终点位置。它们的初始化方法分别为:
static MoveTo* create(float duration, const Point& position); static MoveBy* create(float duration, const Point& deltaPosition);
当中。duration 參数表示动作持续的时间。position 參数表示移动的终点或距离。
对于 MoveTo,节点会被移动到 position相应的 位置;
对于 MoveBy,节点会相对之前的位置移动 position的距离。
JumpTo 和 JumpBy:使节点以一定的轨迹跳跃到指定位置。
它们的初始化方法例如以下:
static JumpTo* create(float duration, const Point& position, float height, int jumps); static JumpBy* create(float duration, const Point& position, float height, int jumps);
当中 position 表示跳跃的终点或距离。height
表示最大高度,jumps 表示跳跃次数。
BezierTo 和 BezierBy: 使节点进行曲线运动,
运动的轨迹由贝塞尔曲线描写叙述。
static BezierTo* create(float t, const ccBezierConfig& c); static BezierBy* create(float t, const ccBezierConfig& c);
贝塞尔曲线是描写叙述随意曲线的有力工具,
在很多软件(如 Adobe Photoshop)中。钢笔工具就是贝塞尔曲线的应用。
每一条贝塞尔曲线都包括一个起点和一个终点。
在一条曲线中。起点和终点都各自包括一个控制点。而控制点到端点的连线称作控制线。
控制线决定了从端点发出的曲线的形状,包括角度和长度两个參数:角度决定了它所控制的曲线的方向,
即这段曲线在这一控制点的切线方向;长度控制曲线的曲率。
控制线越长,它所控制的曲线离控制线越近。
使用时我们要先创建 ccBezierConfig 结构体, 设置好终点 endPosition 以及两个控制点controlPoint_1 和controlPoint_2后,再把结构体传入
BezierTo 或 BezierBy 的初始化方法中:
ccBezierConfig bezier; bezier.controlPoint_1 = Point(20, 150); bezier.controlPoint_2 = Point(200, 30); bezier.endPosition = Point(160, 30); FiniteTimeAction * beizerAction = BezierTo::create(actualDuration / 4, bezier);
属性变化动作
属性变化动作的特点是通过属性值的逐渐变化来实现动画效果。
比如, 以下要介绍的第一个动作 ScaleTo。 它会在一段时间内不断地改变游戏元素的
scale 属性。
使属性值平滑地变化到一个新值,从而使游戏元素产生缩放的动画 效果。
ScaleTo 和 ScaleBy:产生缩放效果,使节点的缩放系数随时间线性变化。
相应的初始化方法为:
static ScaleTo* create(float duration, float s); static ScaleBy* create(float duration, float s);
当中,s 为缩放系数的终于值或变化量。
RotateTo 和 RotateBy:产生旋转效果。相应的初始化方法为:
static RotateTo* create(float duration, float deltaAngle); static RotateBy* create(float duration, float deltaAngle);
当中 deltaAngle的单位是角度,正方向为顺时针方向。
FadeIn 和 FadeOut:产生淡入淡出效果,当中前者实现了淡入效果,后者实现了淡出效果。相应的初始化方法为:
static FadeIn* create(float d); static FadeOut* create(float d);
下面介绍的几个类似的动作。
FadeTo:用于设置一段时间内透明度的变化效果。
其初始化方法为:
static FadeTo* create(float duration, GLubyte opacity);
參数中的 Glubyte 是 8 位无符号整数,因此。opacity 可取 0 至 255 中的随意整数。
与透明度相关的动作仅仅能应用在精灵(Sprite)上,且子节点不会受到父节点的影响。
TintTo 和 TintBy:设置色调变化。这个动作较为少用,其初始化方法为:
static TintTo* create(float duration, GLubyte red, GLubyte green, GLubyte blue); static TintBy* create(float duration, GLshort deltaRed, GLshort deltaGreen, GLshort deltaBlue);
与 FadeTo 类似。red、green 和 blue的取值范围也为
0~255。
视觉特效动作
用于实现一些特殊的视觉效果
Blink:使目标节点闪烁。其初始化方法为:
static Blink* create(float duration, int blinks);
当中,blinks是闪烁次数。
Animation:播放帧动画。用帧动画的形式实现动画效果。
控制动作
控制动作是一类特殊的动作,用于对一些列动作进行精细控制。
利用这一类动作能够实现一些有用的功能。因此它们是十分经常使用的。
这类动作包含 DelayTime、 Repeat 和RepeatForever 等。
DelayTime能够将动作延时一定的时间,
Repeat能够把现有的动作反复一定次数,
RepeateForever能够使一个动作不断反复下去。
其实,控制动作与复合动作息息相关。
复合动作
简单动作显然不足以满足游戏开发的要求。 在这些动作的基础上。
Cocos2d-x 为我们提供了一套动作的复合机制。同意我们组合各种基本动作。产生更为复杂和生动的动作效果。
复合动作是一类特殊的动作。因此它也须要使用 Node 的runAction
方法运行。
而它的特殊之处在于,作为动作容器,复合动作能够把很多动作组合成一个复杂的动作。
因此,我们一般会使用一个或多个动作来创建复合动作,再把动作交给节点运行。
复合动作十分灵活,这是因为复合动作本身也是动作,因此也能够作为一个普通的动作嵌套在其它复合动作中。
反复( Repeat/RepeatForever )
有的情况下。动作仅仅须要运行一次就可以。但我们还经常遇到一个动作重复运行的情况。
对于一些反复的动作。我们能够通过
Repeat 与 RepeatForever 这两个方式反复运行:
static Repeat* create(FiniteTimeAction *action, unsigned int times); static RepeatForever* create(ActionInterval *action);
当中,action參数表示须要反复的动作,第一个方法同意指定动作的反复次数,第二个方法使节点一直反复该动
作直到动作被停止。
并列( Spawn )
指的是使一批动作同一时候运行。Spawn 从 ActionInterval 派生而来的。它提供了两个工厂方法:
static Spawn* create(FiniteTimeAction *action1, ...) CC_REQUIRES_NULL_TERMINATION; static Spawn* createWithTwoActions(FiniteTimeAction *action1, FiniteTimeAction *action2);
当中第一个静态方法能够将多个动作同一时候并列运行。參数表中最后一个动作后须要紧跟 NULL 表示结束。
第二个则仅仅能指定两个动作复合。 不须要在最后一个动作后紧跟 NULL。
此外, 运行的动作必须是可以同一时候运行的、 继承自 FiniteTimeAction的动作。
组合后。Spawn 动作的终于完毕时间由其成员中最大运行时间的动作来决定。
序列( Sequence )
除了让动作同一时候并列运行。我们更常遇到的情况是顺序运行一系列动作。
Sequence 提供了一个动作队列,它会顺序运行一系列动作。
Sequence 相同派生自 ActionInterval。
与 Spawn 一样。Squence 也提供了两个工厂方法:
static Sequence* create(FiniteTimeAction *action1, ...) CC_REQUIRES_NULL_TERMINATION; static Sequence* createWithTwoActions(FiniteTimeAction *actionOne, FiniteTimeAction *actionTwo);
它们的作用各自是建立多个和两个动作的顺序运行的动作序列。
相同要注意复合动作的使用条件,部分的非延时动作(如RepeatForever)并不被支持。
在实现 Sequence 和 Spawn 两个组合动作类时,有一个很有趣的细节:
成员变量中并未定义一个可变长的容器来容纳每个动作系列,
而是定义了m_pOne和m_pTwo两个动作成员变量。
假设我们创建了两个动作的组合,
那么m_pOne与m_pTwo就各自是这两个动作本身;
当我们创建很多其它动作的组合时。引擎会把动作分解为两部分来看待。
当中后一部分仅仅包括最后一个动作,而前一部分包括它之前的全部动作,
引擎把 m_pTwo 设置为后一部分的动作。把 m_pOne 设置为其余全部动作的组合。
比如。
语句
sequence = Sequence::create(action1, action2, action3, action4, NULL);
就等价于:
Sequence s1 = Sequence::createWithTwoActions(action1, action2); Sequence s2 = Sequence::createWithTwoActions(s1, action3); sequence = Sequence::createWithTwoActions(s2, action4);
Spawn 与 Sequence 所採用的机制类似,在此就不再赘述了。
採用这样的递归的方式,而不是直接使用容器来定义组合动作,实际上为编程带来了极大的便利。
维护多个动作的组合是一个复杂的问题,如今我们仅仅须要考虑两个动作组合的情况就能够了。
以下是 Spawn 的一个初始化方法,就是利用递归的思想简化了编程的复杂度:
Spawn* Spawn::create(const Vector<FiniteTimeAction*>& arrayOfActions) { Spawn* ret = nullptr; do { auto count = arrayOfActions.size(); CC_BREAK_IF(count == 0); auto prev = arrayOfActions.at(0); if (count > 1) { for (int i = 1; i < arrayOfActions.size(); ++i) { prev = createWithTwoActions(prev, arrayOfActions.at(i)); } } else { // If only one action is added to Spawn, make up a Spawn by adding a simplest finite time action. prev = createWithTwoActions(prev, ExtraAction::create()); } ret = static_cast<Spawn*>(prev); }while (0); return ret; }
众所周知,递归往往会牺牲一些效率,但能换来代码的简洁。
在这两个复合动作中。细节处理得十分优雅,
全部的操作都仅仅须要针对两个动作实施,多个动作的组合会被自己主动变换为递归
void Spawn::update(float time) { if (_one) { _one->update(time); } if (_two) { _two->update(time); } } Spawn* Spawn::reverse() const { return Spawn::createWithTwoActions(_one->reverse(), _two->reverse()); }
延时( DelayTime )
DelayTime 是一个"什么都不做"的动作。类似于音乐中的休止符,
用来表示动作序列里一段空白期。通过占位的方式将不同的动作段串接在一起。
实际上。这与一个定时期实现的延迟没有差别,
但相比之下,使用 DelayTime 动作来延时就可以方便地利用动作序列把一套动作连接在一起。
DelayTime 仅仅提供了一个project方法,例如以下所看到的:
static DelayTime* create(float d);
当中仅包括一个实型參数d。表示动作占用的时间。
变速动作
大部分动作的变化过程是与时间成线性关系的。即一个动作经过同样时间产生的变化同样,
比如,MoveBy 会使节点在同样长的时间内经过相同的位移。
这是由于 Cocos2d-x 把动作的速度变化控制抽离了出来,形成一个独立的机制。
普通动作配合 变速动作,能够构造出非常有趣的动作效果。
与复合动作类似,变速动作也是一种特殊的动作,它能够把不论什么一种动作依照改变后的速度运行。
因此,在初始化变速动作时,须要传入一个动作。
变速动作包含 Speed 动作与 Ease 系列动作。以下来具体介绍这些动作。
Speed
Speed 用于线性地改变某个动作的速度。因此,能够实现成倍地快放或慢放功能。
static Speed* create(ActionInterval* action, float speed);
为了改变一个动作的速度,首先须要将
目标动作包装到 Speed 动作中:
RepeatForever* repeat = RepeatForever::create(animation); Speed* speed = Speed::create(repeat, 1.0f); speed->setTag(action_speed_tag); sprite->runAction(speed);
在上面的代码中, 我们创建了一个 animation 动作的 CCRepeatForever 复合动作 repeat, 使动画被不断地反复运行。
然后,我们又使用 repeat 动作创建了一个 CCSpeed 变速动作。
create 初始化方法中的两个參数分别为目标动作与变速比率。
设置变速比率为
1,目标动作的速度将不会改变。
最后,我们为 speed 动作设置了一个 tag 属性,并把动作交给精灵,让精灵运行变速动作。
此处设置的 tag 属性与 Node 的 tag 属性类似,用于从节点中方便地查找动作。
接下来。在须要改变速度的地方,我们通过改动变速动作的 speed 属性来改变动作速度。
以下的代码将会把上面设置的动画速度变为原来的两倍:
Speed * speed = sprite->getActionByTag(action_speed_tag); speed->setSpeed(2.0f);
ActionEase
尽管使用 Speed 可以改变动作的速度,然而它仅仅能按比例改变目标动作的速度。
假设我们要实现动作由快到慢、速度随时间改变的变速运动,
须要不停地改动它的speed属性才干实现, 显然这是一个非常烦琐的方法。
以下将要介绍的ActionEase 系列动作通过使用内置的多种自己主动速度变化来解决这一问题。
ActionEase 系列包括 15 个动作,
它们能够被概括为 5 类动作:指数缓冲、Sine 缓冲、弹性缓冲、跳跃缓冲和回震缓冲。
每一类动作都有 3 个不同一时候期的变换:In、Out 和 InOut。
以下使用时间变换图像表示每组 ActionEase 动作的作用效果,
当中横坐标表示实际动画时间,纵坐标表示变换后的动画时间。
因此,线性动作的图像应该是一条自左下角到右上角的直线。
ActionEase 的用法与 Speed 类似。
以 Sine 缓冲为例。下面代码实现了 InSine 变速运动:
EaseSineIn* sineIn = EaseSineIn::create(action); sineIn->setTag(action_sine_in_tag); sprite->runAction(sineIn);
创建自己定义动作
为了抽象出独立的旋转跟踪动作。依据精灵的移动路径设置合适的旋转角度。
Action 包括两个重要的方法:step 与 update。
step 方法会在每一帧动作更新时触发,该方法接受一个表示调用时间间隔的參数 dt,dt 的积累即为动作执行的总时间。
引擎利用积累时间来计算动作执行的进度(一个从 0 到 1 的实数)。并调用 update 方法更新动作。
update 方法是 Action 的核心。它由 step 方法调用,接受一个表示动作进度的參数。
每个动作都须要利用进度值改变目标节点的属性或运行其它指令。
自己定义动作仅仅须要从这两个方法入手就可以,我们通常仅仅须要修改 update 方法就能够实现简单的动作。
Action的step和update方法定义:
//! called every frame with it's delta time. DON'T override unless you know what you are doing. virtual void step(float dt); /** called once per frame. time a value between 0 and 1 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);
让动作更平滑流畅
怎样让动作看起来更加自然并优雅,实际上。这是一个涉及玩家注意力的问题。
对于新出现的变化效果,玩家须要时间转移注意力适应这个变化,
而后假设效果持续稳定、变化不明显,则会减少玩家的注意力。使玩家感觉疲惫。
在这样的情况下。一个冗长的匀速动作效果就会造成游戏不自然不优雅。
最好还是为动作加入一些变速效果,将玩家有限的注意力集中到我们希望玩家关注的效果上。
进场动作:由快到慢,高速进入后缓慢停下,在停止前给玩家足够的视觉时间分辨清楚进入的图像。
出场动作:先慢后快。展示了出场趋势和方向后高速移出屏幕,不拖泥带水。
这个变速效果就非常自然地交给前面提到的 Ease 系列动作实现了。
针对详细的需求。我们选择了 EaseExponential 动作来实现变速效果。
以暂停游戏时弹出的菜单为例:
点击暂停游戏后,菜单从屏幕顶端向下滑出;
点击恢复游戏后,菜单向上收起。
弹出菜单的代码例如以下:
Menu* menu = Menu::create(item0, item1, item2, item3, NULL); menu->alignItemsVerticallyWithPading(5.0f); menu->setPosition(ccp(size.width/2, size.height)); menu->setTag(menu_pause_tag); this->addChild(menu, 5); MoveTo* move = MoveTo::create(0.5f, Point(size.width/2, size.height/2)); Action* action = EaseExponentialOut::create(move); menu->runAction(action);
收起菜单的代码例如以下:
Size size = Director::getInstance()->getWinSize(); Menu* menu = (Menu*)this->getChildByTag(menu_pause_tag); Point point = Point (size.width/2, size.height + menu->getContentSize().height/2); MoveTo* move = MoveTo::create(0.5f, point); Action* action = EaseExponentialIn::create(move); menu->runAction(action);
郝萌主友情提示:
优雅自然的动作,能加强游戏的表现性,能吸引很多其它的玩家、、、
posted on 2017-05-06 13:54 yjbjingcha 阅读(369) 评论(0) 编辑 收藏 举报