原文请猛戳:
http://galoisplusplus.coding.me/blog/2016/01/16/tips-in-cocos2d-x-game/
这次分享一个简单的小功能,用cocos2d-x实现tips效果,作为之前一篇博文的后续。tips的行为很简单:点击某个node(我们不妨称它为target_node
)触发,当点击区域在target_node
范围时出现tips
,否则隐藏tips
(有些情况需要指定有效点击范围不在某些node中,我们把这些node称为exclude_nodes
);当target_node
位于屏幕左半边时,tips
出现在target_node
右侧;否则tips
就出现在target_node
左侧,tips
和target_node
有一个固定的水平间距(我们不妨定义为DEFAULT_TIPS_DIST
);tips
和target_node
底部对齐,但tips
不能超过屏幕范围。
不废话,先上代码:
- function setupTips(params)
- local targetNode = params.target_node
- local tips = params.tips
- local excludeNodes = params.exclude_nodes or {}
- local DEFAULT_TIPS_DIST = 10
- local TIPS_ZORDER = 1000
- if tolua.isnull(targetNode) or tolua.isnull(tips) then
- return
- end
- tips:setVisible(false)
- targetNode:setTouchEnabled(false)
- display.getRunningScene():addChild(tips,TIPS_ZORDER)
- targetNode:addNodeEventListener(cc.NODE_EVENT,function(event)
- if event.name == "exit" then
- local scene = display.getRunningScene()
- scene:performWithDelay(MyPackage.callbackWrapper({scene},function()
- if not tolua.isnull(tips) then
- tips:setVisible(false)
- end
- end),0)
- local eventDispatcher = display.getRunningScene():getEventDispatcher()
- eventDispatcher:removeEventListenersForTarget(targetNode)
- end
- end)
- ------------------------------------------------------------
- local function setTipsPosition()
- local leftBottomPos = MyPackage.getPositionOfNode(targetNode,display.LEFT_BOTTOM)
- local targetNodePos = display.getRunningScene():convertToNodeSpace(targetNode:getParent():convertToWorldSpace(leftBottomPos))
- local targetNodeAnchorPoint = targetNode:getAnchorPoint()
- local tipsPos = targetNodePos
- local tipsAnchorPoint = cc.p(0,0)
- local director = cc.Director:getInstance()
- local glView = director:getOpenGLView()
- local frameSize = glView:getFrameSize()
- local viewSize = director:getVisibleSize()
- if targetNodePos.x <= frameSize.width * 0.5 / glView:getScaleX() then
- -- show tips on the right of the targetNode if the targetNode is on the left screen
- tipsPos.x = tipsPos.x + targetNode:getContentSize().width + DEFAULT_TIPS_DIST
- else
- -- show tips on the left of the targetNode otherwises
- tipsPos.x = tipsPos.x - DEFAULT_TIPS_DIST
- tipsAnchorPoint.x = 1
- end
- if targetNodePos.y + tips:getContentSize().height > viewSize.height then
- tipsPos.y = viewSize.height
- tipsAnchorPoint.y = 1
- end
- if targetNodePos.y < 0 then
- targetNodePos.y = 0
- end
- tips:ignoreAnchorPointForPosition(false)
- tips:setAnchorPoint(tipsAnchorPoint)
- tips:setPosition(tipsPos)
- end
- ------------------------------------------------------------
- local function activeFunc()
- local scene = display.getRunningScene()
- -- NOTE: delay util the next frame in order to get the correct WorldSpace position
- scene:performWithDelay(MyPackage.callbackWrapper({scene,tips},function()
- setTipsPosition()
- tips:setVisible(true)
- end),0)
- end
- local function inactiveFunc()
- if not tolua.isnull(tips) then
- tips:setVisible(false)
- end
- end
- local function isTouchInNode(touch,node)
- if tolua.isnull(node) or tolua.isnull(touch) then
- return false
- end
- local localLocation = node:convertToNodeSpace(touch:getLocation())
- local width = node:getContentSize().width
- local height = node:getContentSize().height
- local rect = cc.rect(0,width,height)
- return getCascadeVisibility(node) and node:isRunning() and cc.rectContainsPoint(rect,localLocation)
- end
- local function isActive(touch)
- local isExcluded = false
- for _,excludeNode in ipairs(excludeNodes) do
- isExcluded = isExcluded or isTouchInNode(touch,excludeNode)
- end
- return isTouchInNode(touch,targetNode) and not isExcluded
- end
- local function onTouchBegan(touch,event)
- if isActive(touch) then
- activeFunc()
- return true
- else
- return false
- end
- end
- local function onTouchMoved(touch,event)
- local scene = display.getRunningScene()
- scene:performWithDelay(MyPackage.callbackWrapper({scene},function()
- if isActive(touch) then
- activeFunc()
- else
- inactiveFunc()
- end
- end),0)
- end
- local function onTouchEnded(touch,function()
- inactiveFunc()
- end),0)
- end
- local listener = cc.EventListenerTouchOneByOne:create()
- listener:registerScriptHandler(onTouchBegan,cc.Handler.EVENT_TOUCH_BEGAN)
- listener:registerScriptHandler(onTouchMoved,cc.Handler.EVENT_TOUCH_MOVED)
- listener:registerScriptHandler(onTouchEnded,cc.Handler.EVENT_TOUCH_ENDED)
- listener:registerScriptHandler(onTouchEnded,cc.Handler.EVENT_TOUCH_CANCELLED)
- local eventDispatcher = display.getRunningScene():getEventDispatcher()
- eventDispatcher:removeEventListenersForTarget(targetNode)
- eventDispatcher:addEventListenerWithSceneGraPHPriority(listener,targetNode)
- end
关于MyPackage.getPositionOfNode
、MyPackage.callbackWrapper
等helper functions请参见之前某篇博文。
上面的代码基本是很简单的,除了有几点需要额外说明一下:
1.几处调用到performWithDelay
的地方。这是因为我们用target_node
的WorldSpace坐标来确定tips
的位置,当target_node
位于某个可滚动的node(如ScrollView
)中时,需要延迟到下一帧才能拿到它正确的WorldSpace坐标,所以我们用了quickx定义的Node:performWithDelay
来做延时。之所以用这个函数而不用scheduler,是因为它在Node的生命周期中,我们不需要担心如何去安全销毁scheduler所产生的handler。事实上我们只要看一下quickx定义在NodeEx.lua中的Node:performWithDelay
就一目了然了:
- function Node:performWithDelay(callback,delay)
- local action = transition.sequence({
- cc.DelayTime:create(delay),cc.CallFunc:create(callback),})
- self:runAction(action)
- return action
- end
2.我们指定target_node
在收到exit
事件时隐藏tips
,这是因为target_node
可能在某些ClippingNode
(如ScrollView
)中,当它超出区域不再显示时,tips
也不应该被显示。
3.当同一个位置有多个具有tips
行为的target_node
时,需要判断当前的target_node
是否有显示,这需要回溯看父节点的visibility
,getCascadeVisibility
定义如下:
- function getCascadeVisibility(node)
- if tolua.isnull(node) then
- return true
- end
- local visibility = node:isVisible()
- if visibility then
- local parent = node:getParent()
- visibility = visibility and getCascadeVisibility(parent)
- end
- return visibility
- end