mixi小六 2019-06-20
原文请猛戳:
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, 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, event) local scene = display.getRunningScene() scene:performWithDelay(MyPackage.callbackWrapper({scene}, 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
进入场景而且过渡动画结束时候触发。提示 GameScene场景中的继承于节点,这些生命周期事件根本上是从Node继承而来。事实上所有Node对象都有这些事件,具体实现代码与GameScene场景类似。
进入场景而且过渡动画结束时候触发。提示 GameScene场景中的继承于节点,这些生命周期事件根本上是从Node继承而来。事实上所有Node对象都有这些事件,具体实现代码与GameScene场景类似。