精灵动画作用在精灵上,使精灵表现出动画效果。本文将详细说明如何创建一个简单的动作系统,暂时只有 4 中基本的动作——平移、旋转、缩放和 Alpha 变化,并且这些动作能够自由组合,组成串行动作或并行动作。下图是动作系统的类图:

  动作就是进行插值的过程,需要在每一帧被调用。FrameCall 顾名思义是一个帧调用对象,将 FrameCall 添加到帧调用管理器 FrameCallManager 中,FrameCall 的 Step( float frame_time ) 函数则在每一帧中被调用。

  如果将一个球从 P1 移动到 P2,只给出起点 P1 和终点 P2 的坐标,中间的坐标可以通过线性插值进行计算:

   t 是一个 0 到 1 范围的数,动作系统基于此进行设计。AbstractAction 是一个动作基类,继承于 FrameCall,通过重写 Step( float frame_time ) 函数,累计当前动作执行的累计时间。动作有一个执行总时间 DurationTime 表示经过这个时间长度后动作执行结束;还有一个动作执行的持续时间 ElapsedTime 表示动作执行了多长时间。此时 t = ElapsedTime / DurationTime,AbstractAction 主要进行 t 的计算:

  1. void AbstractAction::Step(float dt)
  2. {
  3. this->BeginAction();
  4.  
  5. fElapsedTime += dt;
  6. fElapsedTime = fminf(fElapsedTime, fDurationTime);
  7.  
  8. /* 缓动动画插值 */
  9. this->Update(Tween::tween(fElapsedTime, fDurationTime, tweenType));
  10.  
  11. if ( fElapsedTime >= fDurationTime ) {
  12. fElapsedTime = ;
  13. SigActionFinishedCallback(this);
  14.  
  15. this->EndAction();
  16. }
  17. }

  如果进行线性插值的话,小球的移动动作的速度是一直不变的,有些情况下,可能需要速度发生正弦变化、余弦变化或其他变化。Tween 中封装了了一些常用的缓动曲线,下面的动图是 Tween 缓动动画的演示:

  使用不同的缓动动画,小球会以不同的速度进行移动。将计算出来的 t 传递给 Update( float ratio ) 函数,由继承 AbstractAction 的子类重写 Update( float ratio ) 函数进行响应的插值工作。开始动作前需要调用 BeginAction( ) 函数进行相关的初始化工作,例如重新设置起始值、将 ElapsedTime 清零。

  1. void AbstractAction::BeginAction()
  2. {
  3. if ( bFirstTick == false ) return;
  4.  
  5. this->Reset();
  6.  
  7. bFirstTick = false;
  8. bActionFinished = false;
  9. }

  由于只需进行一次调用即可,所以设置一个开关,保证它只有第一次才会执行函数内的内容,其中 Reset( ) 函数由子类实现进行初始化工作。在动作结束后要停止 FrameCall 的 Step( ) 函数被调用,表示停止动作。

  1. void AbstractAction::EndAction()
  2. {
  3. bFirstTick = true;
  4. bActionFinished = true;
  5.  
  6. if ( HasFrameCallManager() ) {
  7. this->Stop();
  8. }
  9. }

  基本动作

  MoveTo 动作,使精灵从起始位置移动到目标位置。MoveTo 有起始位置和结束位置的属性:

  1. int nSrcx;
  2. int nSrcy;
  3.  
  4. int nDestx;
  5. int nDesty;

  在重写的 Reset( ) 函数中设置起始位置:

  1. void MoveTo::Reset()
  2. {
  3. AbstractAction::Reset();
  4.  
  5. nSrcx = pTarget->GetPosition().x;
  6. nSrcy = pTarget->GetPosition().y;
  7. }

  然后在 Update( ) 函数中对位置进行插值:

  1. void MoveTo::Update(float rate)
  2. {
  3. pTarget->SetPosition(nSrcx + (nDestx - nSrcx) * rate, nSrcy + (nDesty - nSrcy) * rate);
  4. }

  除了 MoveTo 动作,还有其他的 MoveBy、ScaleTo、ScaleBy、RotateTo、RotateBy、AlphaTo、AlphaBy 的动作,和 MoveTo 的实现类似。

  组合动作

  很多时候,并不是单纯的执行一个动作,而是一次执行一系列动作或同时执行一系列动作,称为串行动作或并行动作。要实现组合动作,需要一个容器来保存这些组合的动作。ActionGroup 继承于 AbstractAction,是串行动作容器和并行动作容器的父类。ActionGroup 单纯是一个容器,封装了添加动作的方法。但要组合的动作的数量是不确定的,需要一个合理的方法来添加这些动作。在 Signal-Slot 的文章中介绍过 C++ 可变参模板的使用,使用变参模板就可以接收任意数量的动作对象了。

  1. template<class... Args>
  2. void AppendAction(AbstractAction* head, Args... args)
  3. {
  4. vActions.push_back(head);
  5. AppendAction(args...);
  6. }
  7.  
  8. void AppendAction(AbstractAction* Action)
  9. {
  10. vActions.push_back(Action);
  11. }

  你可以使用方法 AppendAction 来添加动作,也可以在新建对象时添加动作

  1. template<class... Args>
  2. ActionGroup(Args... args) : AbstractAction()
  3. {
  4. this->AppendAction(args...);
  5. }

  

  SequenceActionGroup 是串行动作组对象,添加到该容器中的动作会被依次执行:

  1. void SequenceActionGroup::Step(float dt)
  2. {
  3. this->BeginAction();
  4.  
  5. auto Action = vActions[nCurrentActionIndex];
  6. Action->Step(dt);
  7.  
  8. if ( Action->IsFinishedAction() ) {
  9. nCurrentActionIndex++;
  10. }
  11.  
  12. if ( nCurrentActionIndex >= vActions.size() ) {
  13. this->EndAction();
  14. }
  15. }

  ParallelActionGroup 是并行动作组对象,添加到该容器中的动作会被同时执行:

  1. void ParallelActionGroup::Step(float dt)
  2. {
  3. this->BeginAction();
  4.  
  5. for ( int i = ; i < vActions.size(); i++ ) {
  6. if ( vFinishedIndices[i] ) continue;
  7.  
  8. auto action = vActions[i];
  9. action->Step(dt);
  10.  
  11. if ( action->IsFinishedAction() ) {
  12. vFinishedIndices[i] = true;
  13. nRemainActionCount--;
  14. }
  15. }
  16.  
  17. if ( nRemainActionCount == ) {
  18. for ( auto& ele : vFinishedIndices ) ele = false;
  19. this->EndAction();
  20. }
  21. }

  并行动作有个问题,就是这些动作的执行时长并不一致。有些时长短的动作在动作结束后,由于时长长的动作还没有结束,这就要求已经结束的动作的 Step( ) 不会被调用。所以还需要一个 bool 数组来标志哪些动作已经执行完,不需要调用 Step( )。

  其它动作

  除了那些基本的动作,还需要一些特殊用途的动作,类似于容器,通过包装基本动画实现例如重复动作、延时动作和空动作(什么都不干)的功能。

  这些的实现比较简单,重复动作 RepeatAction 记录动作的执行次数,有重复一定次数的,有无限重复的:

  1. void RepeatAction::Step(float dt)
  2. {
  3. this->BeginAction();
  4.  
  5. pAction->Step(dt);
  6.  
  7. if ( pAction->IsFinishedAction() ) {
  8. if ( nLoopCount == - ) {
  9. return;
  10. }
  11. nCurrentLoopCount--;
  12. }
  13.  
  14. if ( nLoopCount != - && nCurrentLoopCount <= ) {
  15. this->EndAction();
  16. }
  17. }

  延时动作 DelayAction 在一段时间后才进行 Step( ) 的调用:

  1. void DelayAction::Step(float dt)
  2. {
  3. this->BeginAction();
  4.  
  5. fElapsedTime += dt;
  6. if ( fElapsedTime <= fDelayTime ) return;
  7.  
  8. pAction->Step(dt);
  9.  
  10. if ( pAction->IsFinishedAction() ) {
  11. this->EndAction();
  12. }
  13. }

  而空动作 DummyAction 只继承与 AbstractAction 即可。

  Sprite 使用动作,调用 StartAction( ) 函数将开始动作:

  1. void RectTransform::StartAction(AbstractAction* action)
  2. {
  3. pAction = action;
  4. pAction->SetTarget(this);
  5. pAction->Start();
  6. }

  动作系统就结束了,下面给出一个动图,图中的正方形和圆形执行的都是组合动作:

  详细内容请参考源码。

  源码下载:Simple2D-20.rar

Simple2D-25 精灵动作的更多相关文章

  1. [原]quick2.25精灵变灰

    由于quick2.25没有导出shader相应的接口,所以2.25无法直接使用shader. 本文简单介绍如何导出相应接口,同时教大家使用shader 实现精灵变灰 一.编写静态函数,以供导出使用(直 ...

  2. [原]quick2.25让描边闪起来

    本文教大家如何使用shader让描边动起来.实质就是间隔一定时间改变描边的颜色.难点:如何通过程序把颜色传给shader.想在quick2.25里面尝试的朋友,参考quick2.25精灵变灰配置一下环 ...

  3. 精灵动画Animation对话框组成Idle动画的各精灵

    精灵动画Animation对话框组成Idle动画的各精灵 1.3  精灵动画 场景中已经添加了精灵,现在是时候让让它动起来了.读者也许已经从精灵图集中,各精灵的命名中看出来了,这个精灵一共有两种动画状 ...

  4. 【Java基础】【25多线程(下)&GUI】

    25.01_多线程(单例设计模式)(掌握) 单例设计模式:保证类在内存中只有一个对象. 如何保证类在内存中只有一个对象呢? (1)控制类的创建,不让其他类来创建本类的对象.private (2)在本类 ...

  5. <转>cocos2d-x学习笔记(五)仿真树叶飘落效果的实现(精灵旋转、翻转、钟摆运动等综合运用)

    转载自ufolr的博客 原文连接:http://blog.csdn.net/ufolr/article/details/7624851 最近项目中需要一个落叶的效果,本来想用粒子特效来实现,但是几经调 ...

  6. cocos2d-js使用plist执行自身动作

    首先需要将精灵动作帧动画图片使用TexturePacker创建plist,创建好后,将生成的plist和png图片(所有帧动画图片集成的一张大图): 百牛信息技术bainiu.ltd整理发布于博客园 ...

  7. Atitit 游戏的原理与概论attilax总结

    Atitit 游戏的原理与概论attilax总结 1. 游戏历史2 1.1.1. 盘点PC游戏史上最重要的50款游戏2 1.1.2. 回味人类文明进程 五款经典的历史游戏2 2. 游戏类型(主要分为6 ...

  8. 学习opengl第一步

    有两个地址一个是学习opengl基础知识的网站, 一个是博客园大牛分享的特别好的文章. 记录一下希望向坚持做俯卧撑一样坚持下去. 学习网站:http://learnopengl-cn.readthed ...

  9. laravel(三):larave基本使用

    1.基本视图显示 前文已经介绍如何创建控制器.动作和视图,下面我们来创建一些更实质的功能. 在此之前我们需要修改一些配置: app/config/app.php 文件中的 debug 选项设置为 tr ...

随机推荐

  1. greasemonkey修改网页url

    // ==UserScript== // @name JSHE_ModifyFunction // @namespace jshe // @include http://localhost/* // ...

  2. 【转】每天一个linux命令(25):linux文件属性详解

    原文网址:http://www.cnblogs.com/peida/archive/2012/11/23/2783762.html Linux 文件或目录的属性主要包括:文件或目录的节点.种类.权限模 ...

  3. weexpack 使用

    weexpack 的github地址:https://github.com/weexteam/weex-pack weex-toolkit: 初始化的项目是针对开发单个 Weex 页面而设计的,也就是 ...

  4. linux下编译GD(freetype+libjpeg+libpng+gd-devel)

    linux下编译GD(freetype+libjpeg+libpng+gd-devel) 1.检查freetype是否安装rpm -qa | grep freetype没有的话编译freetype 这 ...

  5. Servlet 串联过滤器

    1. 串联Servlet过滤器的工作流程 2. 创建两个过滤器 MyFilter1和MyFilter2 1) MyFilter1 package com.example.filter; import ...

  6. 大快DKhadoop开发环境安装常见问题与解决方案

    2018年度国内大数据公司排名50强本月初榜单发布,榜单上看到大快搜索跻身50强,再看看他们做的DKHadoop发行版,的确还是蛮厉害的吧!最起码这款DKHadoop用起来确实在易用性方面要更好!Dk ...

  7. ionic使用常见问题(八)——PHP无法获取$http的post数据

    一个简单的post请求 $http.post('do-submit.php',myData) .success(function(){ // some code });   可是,用angularjs ...

  8. WPF Demo7

    没有Path/Source的数据绑定 本地local资源用法 namespace Demo9 { public class Student { private string name; public ...

  9. java IO字符流

    字节流:因为内存中数据都是字节,二进制数据. 字符流:方便处理文本数据.字符流是基于字节流的. ascii 编码表,并且各国都有自己的编码表. unicode码表,世界码表.优化后 utf-8码表. ...

  10. 关于lidroid xUtils 开源项目

    最近搜了一些框架供初学者学习,比较了一下XUtils是目前git上比较活跃 功能比较完善的一个框架,是基于afinal开发的,比afinal稳定性提高了不少,下面是介绍: xUtils简介 xUtil ...