原文请猛戳:
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