原文请猛戳:
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
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。