当前位置: 代码迷 >> .NET Framework >> bbframework入门之路【6】常用的动作和动画
  详细解决方案

bbframework入门之路【6】常用的动作和动画

热度:175   发布时间:2016-05-01 23:26:06.0
bbframework入门之路【六】常用的动作和动画

【正文】

        今天我们来介绍下游戏开发中必不可少的东西,那就是动作和动画。bbframework除了支持Cocos2D-X里面提供的动作之外,我们自己也根据实际项目需求往框架里面新增了不少的动作,所以这部分的内容比较多,我们简单的列举几个常用的动作和一些常用的和动作相关的函数,剩下的大家可以自行参考框架的“UAction.lua”文件,UAction里面包含了bbframework提供的所有动作,包括每个动作的案例代码和详细的注释。

 

        话不多说,我们直接进入主题。我们bbframework的动作是通过table(表)的形式组成一个类似于动作配置的结构体,里面包含动作的名称、执行的事件以及其他一些执行动作所必须的信息,我们权且称这个数值为“动作信息配置表”吧。我们将动作的信息存放在表里面,然后把表当做参数传递给bbframework特定的几个函数,他们分别是:“A.one()”、“A.line()”、“A.union()”和“A.cycle()”。这些函数的参数都是一个表,或者我们称之为数组。第一个函数A.one()接收的是一个一维的数组,里面有且仅包含一个简单动作的信息,剩下的三个都接收一个二维的数组,二维数组中包含一个或者多个的动作信息配置表。这些函数在接收到配置信息表的时候会将其解析,然后创建一个CCAction对象返回。当然这里的动作信息配置表也可以以具体的动作对象来代替,这也同样是可行的。

 

        首先我们先来写个例子,假设我们游戏里面有个精灵位于坐标(100, 100)的位置,我们想要控制它移动到坐标(700,450)的位置,移动的时间是三秒钟,其实现代码如下:

 

------------------------ 结点渲染------------------------[[--视图渲染,处理视图结点加载、事件绑定等相关操作]]function M:onRender()    M.super.onRender(self)    local node = D.img("node.png"):p(100, 100):to(self)    A.one({"moveTo", 3, ccp(700, 450)}):at(node)end 

 

        如上,我们在渲染器里面创建了一个精灵,然后写了一个动作信息配置表,将其传递到A.one()函数里面,它会返回一个动作对象,我们在调用动作对象的“:at()”函数,at()函数接收一个节点对象作为动作的执行者,我们把我们的精灵传递进去,然后这个精灵就自动开始执行我们指定的动作了。那么我们现在来看下这个动作信息配置表具体包含哪些内容。

 

        我们的动作信息配置表是:“{"moveTo", 3, ccp(700, 450)}”,其中第一个成员固定是一个字符串,表示动作的名称;第二个参数是数值型的,表示动作的执行时间;第三个参数是一个坐标,表示要移动到哪个位置。那么我们先来看下我们常用的一些动作信息配置表的结构说明:

 

    -- 移动    {"moveTo", 持续时间, 移动的最终位置},    {"moveBy", 持续时间, 位移量(位置变化量)}    -- 例如:    -- 经过3秒,移动到坐标(700450)的位置。    {"moveTo", 3, ccp(700, 450)},    -- 经过3秒,从当前位置往x轴正方向移动20个像素,同时往y轴正方向移动30个像素。    {"moveBy", 5, ccp(20, 30)},    -- 缩放【一】    {"scaleTo", 持续时间, 缩放比例},    {"scaleBy", 持续时间, 缩放比例变化量},    -- 例如:    -- 经过1秒钟,宽高都缩放为原来的0.5倍大小。    {"scaleTo", 1, 0.5},    -- 经过2秒钟,从当前大小宽高都缩小0.2倍大小。    {"scaleBy", 2, -0.2},    -- 缩放【二】    {"scaleTo", 持续时间, X轴缩放比例, Y轴缩放比例},    {"scaleBy", 持续时间, X轴缩放比例变化量, Y轴缩放比例变化量},    -- 例如:    -- 经过1秒钟,宽度缩放为原来的0.5倍大小,高度缩放到原来的2倍大小。    {"scaleTo", 1, 0.5, 2.0},    -- 经过2秒钟,从当前大小宽度缩小0.2倍大小,高度放大1倍(变为原先的2倍了)。    {"scaleBy", 2, -0.2, 1.0},    -- 旋转    {"rotateTo", 持续时间, 最终的角度},    {"rotateBy", 持续时间, 角度变化量},    -- 例如:    -- 经过1.5秒,顺时针旋转到90度。    {"rotateTo", 1.5, 90},    -- 经过3秒钟,逆时针旋转到-90度。    {"rotateTo", 3, -90},    -- 经过2秒,从当前角度顺时针旋转45度。    {"rotateBy", 2, 45},    -- 经过2秒,从当前角度逆时针旋转90度。    {"rotateBy", 2, -90},    -- 跳跃    {"jumpTo", 持续时间, 跳跃的最终位置, 跳跃高度, 到达目标位置的总跳跃次数},    {"jumpBy", 持续时间, 位移量(位置变化量), 跳跃高度, 总跳跃次数},    -- 例如:    -- 经过3秒,跳跃到坐标(700100)的位置,跳跃的高度是200,跳跃2次到达目的地。    {"jumpTo", 3, ccp(700, 100), 200, 2},    -- 经过3秒,从当前位置水平向右跳跃200个像素,竖直方向跳跃0个像素,跳跃的高度为300,跳跃1次。    {"jumpBy", 3, ccp(200, 0), 300, 1},    -- 渐隐渐现[透明度]    {"fadeTo", 持续时间, 目标透明度(0~255)},    {"fadeBy", 持续时间, 透明度变化量},    -- 例如:    -- 经过两秒,透明度降为0(全透明)    {"fadeTo", 2, 0},    -- 经过2秒,透明度下降50【注意:透明度只能介于0到255之间的整数,溢出会导致意料之外的Bug。】    {"fadeBy", 2, -50},    -- 函数    {"fn", function()        -- body    end},    {"fn", handler(函数调用者, 函数对象)},    -- 例如:    -- 通过动作调用匿名函数打印“Hello Action!”    {"fn", function()        print("Hello Action!")    end},    -- 通过函数调用节点node1的remove()函数    {"fn", handler(node1, node1.remove)},    -- 延迟    {"delay", 延迟的时间},    -- 例如:    -- 动作延迟3秒。    {"delay", 3},    -- 波形运动    {"SinTo", 持续时间, 目标位置, 振幅, 周期数,},    -- 例如:    -- 经过3秒,运动到坐标(700400)的位置,振幅是100,周期是3个周期。    {"sinTo", 3.0, ccp(700, 400), 100, 3}    -- 自由落体    {"freeFallTo", 目标位置},    -- 例如:    -- 自由落体到坐标(00)的位置    {"freeFallTo", ccp(0, 0)},    -- 其他动作 ... 

 

        除了上面列举出来的动作,我们强大的bbframework还支持比如ease的缓冲回弹效果、水波动作效果、3D震荡、螺旋运动、椭圆轨迹运动(圆周运动)、闪烁等。具体的函数和案例测试代码都在UAction.lua文件里面,有兴趣或者有需要的都可以去UAction里面查看相关代码注释。

 

        介绍完动作信息配置表,我们接下来看下A.one()、A.line()、A.union()和A.cycle()这四个函数的作用。

 

        首先,A.one()正如上面所说的,它可以包装一个动作,是我们用来执行单一动作的不二之选。

 

    local node = D.img("node.png"):p(100, 100):to(self)    A.one({"jumpTo", 3, ccp(700, 100), 200, 1}):at(node) 

 

        但是更多的时候我们游戏里面要执行的肯定不是单一的动作,更多的是多个动作的组合。比如我们有5个动作,我们希望它们按先后顺序执行,那么我们就需要用到A.line()这个函数。我们将我们所有要顺序执行的的动作对象或者动作信息配置表都放到一个table里面去,这样这个table就是一个二维的动作信息配置表,然后我们将这个配置表当成A.line()的参数,这样,A.line()就会将我们的动作全部解析成一个复杂的复合动作(序列动作),然后再调用“:to()”函数,我们的动作就会按顺序一个接一个的执行了。

 

    local node = D.img("node.png"):p(100, 100):to(self)    -- 先移动到(700, 450)的位置,然后缩放为原来的一半。    A.line({        {"moveTo", 3, ccp(700, 450)},        {"scaleTo", 1, 0.5},    }):at(node)

 

 

        同样的,有时候我们可能有两个动作需要同时开始执行,那么我们就需要将这两个动作放到一个表里面,再将这个表当成A.union()函数的参数,那么A.union()就会将这两个动作包装成一个复杂的复合动作(联合动作)。

 

    local node = D.img("node.png"):p(100, 100):to(self)    -- 一边顺时针旋转360度,一边缩放到大小为0。    A.line({        {"rotateBy", 3, 360},        {"scaleTo", 3, 0},    }):at(node)

 

        通过A.one()、A.line()和A.union()包装的动作只会执行一次,但是我们有时候希望我们的动作能够一直循环执行或者循环执行一定的次数,那这时候就需要用到A.cycle()函数了。A.cycle(actions, loopNum)函数有两个参数,第一个参数actions跟A.line()、A.union()的参数一样,是一个动作集合,第二个参数loopNum是一个整数,知道我们的actions动作集合循环执行几次。并且,第二个参数是可缺省的,如果我们不传第二个参数的话,bbframework默认我们的动作无限循环的执行下去。另外值得注意的一点是,这里的actions集合中的动作是按照先后顺序固定执行的。

 

    local node1 = D.img("node.png"):p(100, 100):to(self)    local node2 = D.img("node.png"):p(100, 100):to(self)    -- 每秒逆时针旋转120度,一直旋转下去。    A.cycle({        {"rotateBy", 1, -120},    }):at(node)    -- 3秒钟顺时针旋转360度,循环5次。    A.cycle({        {"rotateBy", 3, 360},    }, 5):at(node)

 

        动作除了调用动作对象的to(动作执行对象)函数来执行外,我们的节点也提供了“:runAction(动作对象)”函数来执行指定的动作,比如:

 

    local node1 = D.img("node.png"):p(100, 100):to(self)    -- 每秒逆时针旋转120度,一直旋转下去。    local actionObj = A.cycle({            {"rotateBy", 1, -120},        })    -- 由node1来执行动作actionObj    node1:runAction(actionObj)

 

        另外,正如前面所说的,我们的复合动作对象除了使用动作信息配置表之外,还可以直接使用动作对象作为动作集合的成员之一,如下:

 

    local node     = D.img("node.png"):p(100, 100):to(self)    -- 创建一个动作对象    local actionObj = A.one({"moveTo", 2, ccp(700, 500)})    -- 创建序列动作并执行    A.line({        {"rotateBy", 1, 45},        -- 这里直接将动作对象添加到二维表中。        actionObj,        {"scaleTo", 1, 0.5},    }):at(node)

 

        有的时候,我们甚至想要让一个动作倒着执行,或者说反过来执行。那么久需要用到“A.reverse()”函数,这个函数接收一个动作对象,然后它会将其解析成一个相反的动作来执行。

 

    local node             = D.img("node.png"):p(100, 100):to(self)        -- 反向执行动作    A.reverse({"moveTo", 2, ccp(700, 500)}):at(node)

 

        除了以上这些动作之外,我们再介绍下一个特殊的动作,震动。为什么说这个动作呢?有时候我们在进行一些游戏逻辑处理的时候希望能够有画面震动的效果。那就需要使用到bbframework的“A.shake()”函数。这个函数同样接收一个动作信息配置表,其使用说明如下:

 

    local nodeObj             = D.img("node.png"):p(100, 100):to(self)        -- 震动    A.shake({         node        = nodeObj,        time        = 3.0,        strength    = 30,    })

 

        其参数对象是一个table,table中key为“node”的值是要执行这个动作的节点对象,key为“time”的值是该动作持续的时间,key为“strength”的值表示的是震动的强度。但是有的时候我们想要震动在竖直跟水平两个方向的强度不同,那么我们就用到了“strengthX”和“strengthY”这两个参数,例如:

 

    local nodeObj             = D.img("node.png"):p(100, 100):to(self)    -- 震动    A.shake({        node        = nodeObj,        time        = 3.0,        strengthX      = 30,        strengthY   = 60,    })

 

        除了让执行动作的对象震动之外,这个动作还会调用硬件的震动马达,就是手机来电震动的那个效果。但是值得注意的一点是这个功能一般只有手机才有,平板一般没有配备震动马达。所以在平板上只能看到节点震动,而没有设备震动的效果。

 

        除了动作之外,游戏里面还有至关重要的一环是动画,游戏里面的动物之类的精灵总是要动的吧,那就涉及到了帧动画。我们的bbframework将动画也是以动作的形式实现的,同样是通过一个特定的动作信息配置表生成对应的动作对象,然后执行,其信息配置说明如下:

 

    -- 动画    {"image", 动画文件路径前缀, 动画帧数, 每帧间隔时间},    -- 例如:    {"image", "gameplay/img_", 10, 0.3},

 

        上面那个配置表可以播放一个位于“gameplay”文件夹里面,文件名称以“img_”开头(如:img_1.png,img_2.png,...)的动画。总共10帧,每帧显示0.3秒。但是这个函数有个毛病,就是它播放的动画必须后缀从1开始。但是有时候我们希望动画的图片顺序可以更加的自由,这时候我们需要将要播放的动画帧放到table里面,然后根据这个table来播放。比如:

 

    -- 动画    {"filenames", 动画帧集合, 每帧间隔时间},    -- 例如:    local nodeObj  = D.img("node.png"):p(100, 100):to(self)    local imgs     = {"logo.png", "gameplay/img_7.png", "node.png", "gameplay/img_3.png",}    -- 播放动画    A.one({"filenames", imgs, 0.3}):at(node)

 

        这个时候我们无限指定动画帧数,因为帧数是根据表imgs的成员个数来定的,也没有什么所谓的帧文件名称前缀,因为每帧的图片文件路径都在表里面了。而且是按照表里面的顺序播放的。

 

        但是我个人通常是使用“image”这个动作来播放的,除非真的遇到顺序打乱掉的动画才会使用“filenames”。之所以我喜欢image的方式是因为我们有的时候为了节省资源大小,将动画图片打包成“.plist”+“.png”的方式来使用,这个时候它不但支持单张图片的帧动画播放,而且也支持plist加大图的动画播放。但是其配置信息表的第二个参数(帧文件名前缀)的写法略微有所改变。它不需要写文件的完整路径,只需要在文件名的前缀前面加个“#”即可,这是cocos2D-X这个游戏引擎读取plist大图的读取方式。

 

        了解了怎么让节点执行动作,那么动作应该怎么停止呢?bbframework框架给我们的节点对象提供了“:stopAction()”和“stopAllActions()”两个函数来停止动作。stopAction()函数接收一个动作对象,节点执行了这个函数后,就会停止指定的动作的执行。而第二个函数不需要参数,因为它默认停止该节点的所有动作。

 

    -- 例如:    local node         = D.img("node.png"):p(100, 100):to(self)    -- 动画帧集合    local imgs         = {"logo.png", "gameplay/img_7.png", "node.png", "gameplay/img_3.png",}    -- 动作对象    local actionObj = A.one({"filenames", imgs, 0.3})    -- 执行动作    node:runAction(actionObj)    -- 马上停止动作    node:stopAction(actionObj)

 

        或者我们可以停止节点的所有动作:

 

    -- 例如:    local node         = D.img("node.png"):p(100, 100):to(self)    -- 动画帧集合    local imgs         = {"logo.png", "gameplay/img_7.png", "node.png", "gameplay/img_3.png",}    -- 动作对象    local actionObj = A.one({"filenames", imgs, 0.3})    -- 执行动作    node:runAction(actionObj)    -- 马上停止动作    node:stopAllAction()

 

        关于bbframework的动作和动画的介绍就先到这里了,还有什么不懂的或者想要了解更多和动画、动作有关的东西,建议你们去浏览下“UAction.lua”文件,里面AC写了非常详细的注释,还加上了案例代码。有了动作和动画,为了更加复杂的逻辑需求,下一次我们来讲一下bbframework延时器的开启和关闭,以及延时函数(F.delayCall())的使用。由于那个内容不多,我们顺便讲一下目前框架对广告的添加和移除。

 

【脚注】

        宝宝巴士-快乐童年!

 

  相关解决方案