1. lua层简化版动画系统
cocos2dx中的Action系统能实现一般性的动画控制。但是对于一些特殊的动画需求,如何处理?一种方法是在C++层派生新的Action子类,继承原有机制,使用方法与一般Action无异。但很多时候修改或增加C++层代码并不方便,能否直接在lua层定制属于自己的Action?答案是肯定的,但是lua层的实现不能简单继承c++层的Action,因为它无法修改Action的核心函数:时间线step()以及进度线update()。这意味着我们首先需搭建一个简易版动画系统,该动画系统框架与cocos2dx中的动画系统框架基本保持一致。
框架将包含三部分:1)动画管理器:用于统一绑定目标与执行动画;2)动画基类;3)动画具体派生类,如移动动画与缓动动画。
-- 动画管理器
ccActionManager = class("ccActionManager")
--[[
ccActionManager.targetActionsMap = {
target1 = {action1,action2},
target2 = {action1,action2,action3},
}]]
function ccActionManager:ctor()
self.targetActionsMap = {} -- 目标节点与其动画映射表
end
function ccActionManager:runAction( action,target ) -- 绑定并执行动画
assert(action~=nil and target~=nil,"action and target must be non-nil")
action:startWithTarget(target)
if not self.targetActionsMap[target] then
self.targetActionsMap[target] = {}
end
table.insert(self.targetActionsMap[target],action)
-- 目标移除场景时 清空对应动作
target:onNodeEvent("exit",function ()
self.targetActionsMap[target] = nil
end)
-- 启动主循环
local scheduler = cc.Director:getInstance():getScheduler()
if not self.mainScheduleId then
self.mainScheduleId = scheduler:scheduleScriptFunc(function (dt)
self:update(dt)
end, 0, false)
end
end
function ccActionManager:update( dt )
local hasAction = false
for target,actionLst in pairs(self.targetActionsMap) do
for key,action in ipairs(actionLst) do
action:step(dt)
hasAction = true
if action:isDone() then
actionLst[key] = nil -- 清除已完成动画
end
end
end
if not hasAction then
if self.mainScheduleId then
local scheduler = cc.Director:getInstance():getScheduler()
scheduler:unscheduleScriptEntry(self.mainScheduleId)
self.mainScheduleId = nil
end
end
end
g_ccActionManager = ccActionManager:create()
local Action = class("Action") -- 动作基类
--[[
Action.m_target = nil -- 动画的目标节点
]]
function Action:ctor()
end
function Action:startWithTarget( target )
self.m_target = target
end
function Action:getTarget()
return self.m_target
end
function Action:initWithDuration( duration )
self.m_elapsed = 0 -- 已执行时间
self.m_duration = duration -- 总时间
end
function Action:getDuration()
return self.m_duration
end
function Action:step( dt ) -- 将动作执行时间线转换为进度线
self.m_elapsed = self.m_elapsed + dt
local percent = math.max(0,math.min(self.m_elapsed/self.m_duration,1))
self:update(percent)
end
function Action:isDone()
return self.m_elapsed > self.m_duration
end
function Action:update( percent )
end
-- 实现一个在周期区间内循环移动的动作
ccMoveToPeriod = class("ccMoveToPeriod",Action)
function ccMoveToPeriod:ctor( duration,endPos,periodWidth,periodHeight )
-- endPos: period*n + pos
self:initWithDuration(duration)
self.m_endPos = endPos
self.m_periodWidth = periodWidth
self.m_periodHeight = periodHeight
end
function ccMoveToPeriod:startWithTarget( target )
self.super.startWithTarget(self,target)
self.m_startPos = cc.p(target:getPositionX(),target:getPositionY())
self.m_deltaLen = cc.pSub(self.m_endPos,self.m_startPos)
end
function ccMoveToPeriod:update( percent )
-- 位置转换至一个周期区间内
local curPosX = self.m_periodWidth and (self.m_startPos.x + self.m_deltaLen.x * percent) % self.m_periodWidth or self.m_startPos.x
local curPosY = self.m_periodHeight and (self.m_startPos.y + self.m_deltaLen.y * percent) % self.m_periodHeight or self.m_startPos.y
self.m_target:setPosition(cc.p(curPosX,curPosY))
-- print("ccMoveToPeriod:update curPosX:",curPosX,"curPosY:",curPosY,"startPosY:",self.m_startPos.y)
end
-- 缓动动画 与cocos基本一致
ccEaseInOut = class("ccEaseInOut",Action)
function ccEaseInOut:ctor( action,rate )
self.m_action = action
self.m_rate = rate
self:initWithDuration(action:getDuration())
end
function ccEaseInOut:startWithTarget( target )
self.super.startWithTarget(self,target)
self.m_action:startWithTarget(target)
end
function ccEaseInOut:update( percent )
percent = self:easeProcess(percent)
self.m_action:update(percent)
end
function ccEaseInOut:easeProcess(percent) -- 可替换为任意缓动函数
-- sineEaseInOut
return -0.5 * (math.cos(math.pi * percent) - 1) -- 三角函数简单变换
end
2. 具体实例
实现一个动画,将列表中的所有节点循环移动,且移动具备先加速、后减速的特质。
ccMoveToPeriod负责实现节点的循环移动,ccEaseInOut负责实现移动的加减速效果。
local UIMoveToMenu = class("UIMoveToMenu",_class.UIBase)
function UIMoveToMenu:ctor()
_class.UIBase.ctor(self)
local cs = cc.Director:getInstance():getWinSize()
self:setContentSize(cs)
end
function UIMoveToMenu:onEnter()
cclog("UIMoveToMenu:onEnter")
-- title
local label = cc.Label:createWithTTF("I LOVE TT", "fonts/arial.ttf", 32)
self:addChild(label)
label:setAnchorPoint(cc.p(0.5, 0.5))
label:setPosition( cc.p(VisibleRect:center().x, VisibleRect:top().y - 50) )
-- index label
label = cc.Label:createWithTTF("", "fonts/arial.ttf", 22)
label:setColor(cc.c3b(255,0,0))
self:addChild(label)
label:setAnchorPoint(cc.p(0.5, 0.5))
label:setPosition( cc.p(VisibleRect:center().x, VisibleRect:top().y - 80) )
self.label = label
local clipcs = self:getContentSize()
self.clipperNode = cc.ClippingNode:create()
self.clipperNode:setPosition(cc.p(0,0)) -- cc.p(clipcs.width/2,clipcs.height/2)
self:addChild(self.clipperNode)
local stenSize = cc.size(clipcs.width,clipcs.height)
self.stencilLayer = cc.Layer:create()--ccui.Layout:create()
self.stencilLayer:setContentSize(stenSize)
self.clipperNode:addChild(self.stencilLayer)
self.clipperNode:setStencil(self.stencilLayer)
local paddwidth = 10
self.imgLst = {}
local ball1 = cc.Sprite:create("Images/moke1.jpg")
ball1:setName("moke1")
ball1:setAnchorPoint(cc.p(0,0.5))
ball1:setPosition(cc.p(5,clipcs.height/2))
-- ball1:setScale(0.6)
self.clipperNode:addChild(ball1)
table.insert(self.imgLst,ball1)
local ball2 = cc.Sprite:create("Images/moke2.jpg")
ball2:setName("moke2")
ball2:setAnchorPoint(cc.p(0,0.5))
ball2:setPosition(cc.p(ball1:getPositionX()+ball1:getContentSize().width+paddwidth,clipcs.height/2))
-- ball2:setScale(0.6)
self.clipperNode:addChild(ball2)
table.insert(self.imgLst,ball2)
local ball3 = cc.Sprite:create("Images/moke3.jpg")
ball3:setName("moke3")
ball3:setAnchorPoint(cc.p(0,0.5))
ball3:setPosition(cc.p(ball2:getPositionX()+ball2:getContentSize().width+paddwidth,clipcs.height/2))
-- ball3:setScale(0.6)
self.clipperNode:addChild(ball3)
table.insert(self.imgLst,ball3)
local ball4 = cc.Sprite:create("Images/moke4.jpg")
ball4:setName("moke4")
ball4:setAnchorPoint(cc.p(0,0.5))
ball4:setPosition(cc.p(ball3:getPositionX()+ball3:getContentSize().width+paddwidth,clipcs.height/2))
-- ball4:setScale(0.6)
self.clipperNode:addChild(ball4)
table.insert(self.imgLst,ball4)
local colorLayer = cc.LayerColor:create(cc.c4b(255,0,0,0))
local stenSize = self.stencilLayer:getContentSize()
local ballHeight = ball2:getContentSize().height
local ballWidht = ball2:getContentSize().width
stenSize.width = ballWidht+paddwidth
colorLayer:setContentSize(stenSize)
colorLayer:setPosition(cc.p(ball2:getPositionX(),ball2:getPositionY()-ballHeight/2))
self.stencilLayer:addChild(colorLayer) -- 必须在layer中添加layerColor才能正常显示下面的内容
-- 滑动按钮
local scrollBtn = ccui.Button:create("cocosui/animationbuttonnormal.png", "cocosui/animationbuttonpressed.png")
scrollBtn:setPosition(cc.p(clipcs.width/2,50))
scrollBtn:addClickEventListener(function(sender)
print("scrollBtn:addClickEventListener")
self:scrollImgs()
end)
scrollBtn:setTitleText("滑动按钮")
self:addChild(scrollBtn)
end
function UIMoveToMenu:scrollImgs()
local duration = 20 -- 动画持续时间
local periodNum = 10
local periodWidth = self:getContentSize().width
local easeRate = 3
for i,v in ipairs(self.imgLst) do
-- 终点位置:起点加上n个移动周期
local endPos = cc.p(v:getPositionX()+periodWidth*periodNum,v:getPositionY())
-- y垂直方向上静止 在x水平方向上周期性移动
local moveto = ccMoveToPeriod:create(duration,endPos,periodWidth,nil)
local easeInOut = ccEaseInOut:create(moveto,easeRate)
g_ccActionManager:runAction(easeInOut,v)
end
end
function UIMoveToMenu:onExit()
end
function UIMoveToMenuMain()
cclog("UIMoveToMenuMain")
local scene = cc.Scene:create()
local rotateMenu = UIMoveToMenu:create()
scene:addChild(rotateMenu)
scene:addChild(CreateBackMenuItem())
return scene
end