cocos2dx - lua 自定义控件及动画控制(2) ---- 基于Action实现循环滚动效果

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

动画效果

猜你喜欢

转载自blog.csdn.net/XIANG__jiangsu/article/details/78589746