quick-cocos2d-x 学习系列之十三 触摸

 

现在智能机基本都是触摸屏,除了键盘爱好者们耍键盘。我们要通过这小小的触摸屏上完成整个游戏逻辑的控制,需要对这巴掌大地方进行详细控制了。

 

1.  单点触摸测试

创建精灵函数

function createTouchableSprite(p)

    local sprite = display.newScale9Sprite(p.image)

    sprite:setContentSize(p.size)

 

    local cs = sprite:getContentSize()

    local label = cc.ui.UILabel.new({

            UILabelType = 2,

            text = p.label,

            color = p.labelColor})

    label:align(display.CENTER)

    label:setPosition(cs.width / 2, label:getContentSize().height)

    sprite:addChild(label)

    sprite.label = label

 

    return sprite

end

划BOX框

function drawBoundingBox(parent, target, color)

    local cbb = target:getCascadeBoundingBox()

    local left, bottom, width, height = cbb.origin.x, cbb.origin.y, cbb.size.width, cbb.size.height

    local points = {

        {left, bottom},

        {left + width, bottom},

        {left + width, bottom + height},

        {left, bottom + height},

        {left, bottom},

    }

    local box = display.newPolygon(points, {borderColor = color})

    parent:addChild(box, 1000)

end

1.1             响应触摸事件

调用该函数:

self.sprite = createTouchableSprite({

            image = "WhiteButton.png",

            size = cc.size(500, 300),

            label = "TOUCH ME !",

            labelColor = cc.c3b(255, 0, 0)})

        :pos(display.cx, display.cy)

        :addTo(self)

    drawBoundingBox(self, self.sprite, cc.c4f(0, 1.0, 0, 1.0))

-- 启用触摸

self.sprite:setTouchEnabled(true)

self.sprite:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        -- event.name 是触摸事件的状态:began, moved, ended, cancelled

        -- event.x, event.y 是触摸点当前位置

        -- event.prevX, event.prevY 是触摸点之前的位置

        local label = string.format("sprite: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.sprite.label:setString(label)

 

        -- 返回 true 表示要响应该触摸事件,并继续接收该触摸事件的状态变化

        return true

    end)

单点触摸是最直接的使用方式了。

 

1.2             事件穿透和事件捕获

--创建底层触摸层

self.parentButton = createTouchableSprite({

            image = "WhiteButton.png",

            size = cc.size(600, 500),

            label = "TOUCH ME !",

            labelColor = cc.c3b(255, 0, 0)})

        :pos(display.cx, display.cy)

        :addTo(self)

    self.parentButton.name = "parentButton"

    drawBoundingBox(self, self.parentButton, cc.c4f(0, 1.0, 0, 1.0))

self.parentButton:setTouchEnabled(true)

--给该触摸层增加监听

self.parentButton:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("parentButton: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.parentButton.label:setString(label)

        return true

    end)

 

 

--在底层创建button1, button1 响应触摸后,会吞噬掉触摸事件

    self.button1 = createTouchableSprite({

            image = "GreenButton.png",

            size = cc.size(400, 160),

            label = "TOUCH ME !"})

        :pos(300, 400)

        :addTo(self.parentButton)

 

    cc.ui.UILabel.new({text = "SWALLOW = YES\n事件在当前对象处理后被吞噬", size = 24})

        :align(display.CENTER, 200, 90)

        :addTo(self.button1)

    drawBoundingBox(self, self.button1, cc.c4f(1.0, 0, 0, 1.0))

 

self.button1:setTouchEnabled(true)

self.button1:setTouchSwallowEnabled(true)-- 是否吞噬事件,默认值为 true

    self.button1:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("button1: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.button1.label:setString(label)

        return true

    end)

 

 

 

-- 在底层创建button2, 响应触摸后,不会吞噬掉触摸事件

    self.button2 = createTouchableSprite({

            image = "PinkButton.png",

            size = cc.size(400, 160),

            label = "TOUCH ME !"})

        :pos(300, 200)

        :addTo(self.parentButton)

    cc.ui.UILabel.new({text = "SWALLOW = NO\n事件会传递到下层对象", size = 24})

        :align(display.CENTER, 200, 90)

        :addTo(self.button2)

    drawBoundingBox(self, self.button2, cc.c4f(0, 0, 1.0, 1.0))

 

    self.button2:setTouchEnabled(true)

    self.button2:setTouchSwallowEnabled(false) -- 当不吞噬事件时,触摸事件会从上层对象往下层对象传递,称为穿透

    self.button2:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("button1: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.button2.label:setString(label)

        return true

    end)

 

事件穿透通过函数setTouchSwallowEnabled来实现。

如下函数设置是否捕捉触摸

self.parentButton:setTouchCaptureEnabled(button:isButtonSelected())

 

1.3             在事件捕获阶段决定是否接受事件

-- 这个标志变量用于在触摸事件捕获阶段决定是否接受事件

    self.isTouchCaptureEnabled_ = true

 

    --parentButton button1 的父节点

    self.parentButton = createTouchableSprite({

            image = "WhiteButton.png",

            size = cc.size(600, 500),

            label = "TOUCH ME !",

            labelColor = cc.c3b(255, 0, 0)})

        :pos(display.cx, display.cy)

        :addTo(self)

    drawBoundingBox(self, self.parentButton, cc.c4f(0, 1.0, 0, 1.0))

 

    self.parentButton.label2 = cc.ui.UILabel.new({text = "", size = 24, color = cc.c3b(0, 0, 255)})

        :align(display.CENTER, 300, 60)

        :addTo(self.parentButton)

 

    self.parentButton:setTouchEnabled(true)

    self.parentButton:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("parentButton: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.parentButton.label:setString(label)

        printf("%s %s [TARGETING]", "parentButton", event.name)

        if event.name == "ended" or event.name == "cancelled" then

            print("-----------------------------")

        else

            print("")

        end

        return true

    end)

 

    -- 可以动态捕获触摸事件,并在捕获触摸事件开始时决定是否接受此次事件

    self.parentButton:addNodeEventListener(cc.NODE_TOUCH_CAPTURE_EVENT,function(event)

        if event.name == "began" then

            print("-----------------------------")

        end

 

        local label = string.format("parentButton CAPTURE: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.parentButton.label2:setString(label)

        printf("%s %s [CAPTURING]", "parentButton", event.name)

        if event.name == "began" or event.name == "moved" then

            return self.isTouchCaptureEnabled_

        end

    end)

 

    -- button1响应触摸后,会吞噬掉触摸事件

    self.button1 = createTouchableSprite({

            image = "GreenButton.png",

            size = cc.size(400, 160),

            label = "TOUCH ME !"})

        :pos(300, 400)

        :addTo(self.parentButton)

    cc.ui.UILabel.new({text = "SWALLOW = YES\n事件在当前对象处理后被吞噬", size = 24})

        :align(display.CENTER, 200, 90)

        :addTo(self.button1)

    drawBoundingBox(self, self.button1, cc.c4f(1.0, 0, 0, 1.0))

 

    self.button1:setTouchEnabled(true)

    self.button1:setTouchSwallowEnabled(true) -- 是否吞噬事件,默认值为 true

    self.button1:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("button1: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.button1.label:setString(label)

        printf("%s %s [TARGETING]", "button1", event.name)

        if event.name == "ended" or event.name == "cancelled" then

            print("-----------------------------")

        else

            print("")

        end

        return true

    end)

 

    -- button2响应触摸后,不会吞噬掉触摸事件

    self.button2 = createTouchableSprite({

            image = "PinkButton.png",

            size = cc.size(400, 160),

            label = "TOUCH ME !"})

        :pos(300, 200)

        :addTo(self.parentButton)

    cc.ui.UILabel.new({text = "SWALLOW = NO\n事件会传递到下层对象", size = 24})

        :align(display.CENTER, 200, 90)

        :addTo(self.button2)

    drawBoundingBox(self, self.button2, cc.c4f(0, 0, 1.0, 1.0))

 

    self.button2:setTouchEnabled(true)

    self.button2:setTouchSwallowEnabled(false) -- 当不吞噬事件时,触摸事件会从上层对象往下层对象传递,称为穿透

    self.button2:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("button1: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.button2.label:setString(label)

        printf("%s %s [TARGETING]", "button2", event.name)

        return true

    end)

 

    -- 即便父对象在捕获阶段阻止响应事件,但子对象仍然可以捕获到事件,只是不会触发事件

    self.button2:addNodeEventListener(cc.NODE_TOUCH_CAPTURE_EVENT,function(event)

        printf("%s %s [CAPTURING]", "button2", event.name)

        return true

    end)

 

    -- 放置一个开关按钮在屏幕上

    local labels = {}

    labels[true] = "父对象【可以】捕获触摸事件"

    labels[false] = "父对象【不能】捕获触摸事件"

    local images = {on = "CheckBoxButton2On.png", off = "CheckBoxButton2Off.png"}

    self.captureEnabledButton = cc.ui.UICheckBoxButton.new(images)

        :setButtonLabel(cc.ui.UILabel.new({text= labels[true], size = 24}))

        :setButtonLabelOffset(40, 0)

        :setButtonSelected(true)

        :onButtonStateChanged(function(event)

            local button = event.target

            button:setButtonLabelString(labels[button:isButtonSelected()])

        end)

        :onButtonClicked(function(event)

            local button = event.target

            self.isTouchCaptureEnabled_ = button:isButtonSelected()

        end)

        :pos(display.cx - 160, display.top- 80)

        :addTo(self)

 

    cc.ui.UILabel.new({

        text = "事件处理流程:\n1. 【捕获】阶段:从父到子\n2. 【目标】阶段\n3. 【传递】阶段:尝试传递给下层对象",

        size= 24})

        :align(display.CENTER_TOP, display.cx,display.top - 120)

        :addTo(self)

 

 

其中NODE_TOUCH_EVENT和 NODE_TOUCH_CAPTURE_EVENT 表示两种事件。

在NODE_TOUCH_CAPTURE_EVENT的处理函数中返回真假,然后决定是否调用NODE_TOUCH_EVENT的处理函数。

1.4             容器的触摸区域由子对象决定

创建一个node,在node上增加几个精灵,精灵的局域决定的触摸的范围。

 

-- touchableNode 是启用触摸的 Node

    self.touchableNode = display.newNode()

    self.touchableNode:setPosition(display.cx, display.cy)

    self:addChild(self.touchableNode)

 

    -- touchableNode 中加入一些 sprite

    local count = math.random(3, 8)

    local images = {"WhiteButton.png", "BlueButton.png", "GreenButton.png", "PinkButton.png"}

    for i = 1, count do

        local sprite = display.newScale9Sprite(images[math.random(1, 4)])

        sprite:setContentSize(cc.size(math.random(100, 200), math.random(100, 200)))

        sprite:setPosition(math.random(-200, 200), math.random(-200, 200))

        self.touchableNode:addChild(sprite)

    end

 

    self.stateLabel = cc.ui.UILabel.new({text = ""})

    self.stateLabel:align(display.CENTER, display.cx,display.top - 100)

    self:addChild(self.stateLabel)

 

    -- 启用触摸

    self.touchableNode:setTouchEnabled(true)

    -- 添加触摸事件处理函数

    self.touchableNode:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("touchableNode: %s x,y: %0.2f, %0.2f", event.name, event.x, event.y)

        self.stateLabel:setString(label)

        return true

    end)

    drawBoundingBox(self, self.touchableNode, cc.c4f(0, 1.0, 0, 1.0))

 

 

2.  多点触摸

 

2.1         响应触摸事件

 

 

 

-- createTouchableSprite() 定义在 includes/functions.lua

    self.sprite = createTouchableSprite({

            image = "WhiteButton.png",

            size = cc.size(500, 600),

            label = "TOUCH ME !",

            labelColor = cc.c3b(255, 0, 0)})

        :pos(display.cx, display.cy)

        :addTo(self)

    drawBoundingBox(self, self.sprite, cc.c4f(0, 1.0, 0, 1.0))

 

    local labelPoints = cc.ui.UILabel.new({text = "", size = 24})

        :align(display.CENTER_TOP, display.cx,display.top - 120)

        :addTo(self)

 

    -- 启用触摸

    self.sprite:setTouchEnabled(true)

    -- 设置触摸模式

    self.sprite:setTouchMode(cc.TOUCH_MODE_ALL_AT_ONCE)-- 多点

    --self.sprite:setTouchMode(cc.TOUCH_MODE_ONE_BY_ONE) -- 单点(默认模式)

    -- 添加触摸事件处理函数

    self.sprite:addNodeEventListener(cc.NODE_TOUCH_EVENT, function(event)

        -- event.name 是触摸事件的状态:began, moved, ended, cancelled, added(仅限多点触摸), removed(仅限多点触摸)

        -- event.points 包含所有触摸点,按照 events.point[id] = {x = ?, y = ?} 的结构组织

        local str = {}

        for id, point in pairs(event.points) do

            str[#str + 1] = string.format("id: %s, x: %0.2f, y: %0.2f", point.id, point.x, point.y)

        end

        local pointsCount = #str

        table.sort(str)

        labelPoints:setString(table.concat(str, "\n"))

 

        if event.name == "began" or event.name == "added" then

            self.touchIndex = self.touchIndex + 1

            for id, point in pairs(event.points) do

                local cursor = display.newSprite("Cursor.png")

                    :pos(point.x, point.y)

                    :scale(1.2)

                    :addTo(self)

                self.cursors[id] = cursor

            end

        elseif event.name == "moved" then

            for id, point in pairs(event.points) do

                local cursor = self.cursors[id]

                local rect = self.sprite:getBoundingBox()

                if cc.rectContainsPoint(rect, cc.p(point.x, point.y)) then

                    -- 检查触摸点的位置是否在矩形内

                    cursor:setPosition(point.x, point.y)

                    cursor:setVisible(true)

                else

                    cursor:setVisible(false)

                end

            end

        elseif event.name == "removed" then

            for id, point in pairs(event.points) do

                self.cursors[id]:removeSelf()

                self.cursors[id] = nil

            end

        else

            for _, cursor in pairs(self.cursors) do

                cursor:removeSelf()

            end

            self.cursors = {}

        end

 

        local label = string.format("sprite: %s , count = %d, index = %d", event.name, pointsCount, self.touchIndex)

        self.sprite.label:setString(label)

 

        if event.name == "ended" or event.name == "cancelled" then

           self.sprite.label:setString("")

            labelPoints:setString("")

        end

 

        -- 返回 true 表示要响应该触摸事件,并继续接收该触摸事件的状态变化

        return true

    end)

 

    cc.ui.UILabel.new({

        text = "注册多点触摸后,目标将收到所有触摸点的数据\nadded removed 指示触摸点的加入和移除",

        size= 24})

        :align(display.CENTER, display.cx,display.top - 80)

        :addTo(self)

 

2.2         在事件捕获阶段决定是否接受事件

-- 这个标志变量用于在触摸事件捕获阶段决定是否接受事件

    self.isTouchCaptureEnabled_ = true

 

    --parentButton button1 的父节点

    self.parentButton = createTouchableSprite({

            image = "WhiteButton.png",

            size = cc.size(600, 500),

            label = "TOUCH ME !",

            labelColor = cc.c3b(255, 0, 0)})

        :pos(display.cx, display.cy)

        :addTo(self)

    drawBoundingBox(self, self.parentButton, cc.c4f(0, 1.0, 0, 1.0))

 

    self.parentButton.label2 = cc.ui.UILabel.new({text = "", size = 24, color = cc.c3b(0, 0, 255)})

        :align(display.CENTER, 300, 60)

        :addTo(self.parentButton)

 

    self.parentButton:setTouchEnabled(true)

    self.parentButton:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("parentButton: %s", event.name)

        self.parentButton.label:setString(label)

        printf("%s %s [TARGETING]", "parentButton", event.name)

        if event.name == "ended" or event.name == "cancelled" then

            print("-----------------------------")

        else

            print("")

        end

        return true

    end)

 

    -- 可以动态捕获触摸事件,并在捕获触摸事件开始时决定是否接受此次事件

    self.parentButton:addNodeEventListener(cc.NODE_TOUCH_CAPTURE_EVENT,function(event)

        if event.name == "began" then

            print("-----------------------------")

        end

 

        local label = string.format("parentButton CAPTURE: %s", event.name)

        self.parentButton.label2:setString(label)

        printf("%s %s [CAPTURING]", "parentButton", event.name)

        if event.name == "began" or event.name == "moved" then

            return self.isTouchCaptureEnabled_

        end

    end)

 

    -- button1响应触摸后,会吞噬掉触摸事件

    self.button1 = createTouchableSprite({

            image = "GreenButton.png",

            size = cc.size(400, 160),

            label = "TOUCH ME !"})

        :pos(300, 400)

        :addTo(self.parentButton)

    cc.ui.UILabel.new({text = "SWALLOW = YES\n事件在当前对象处理后被吞噬", size = 24})

        :align(display.CENTER, 200, 90)

        :addTo(self.button1)

    drawBoundingBox(self, self.button1, cc.c4f(1.0, 0, 0, 1.0))

 

    self.button1:setTouchEnabled(true)

    self.button1:setTouchMode(cc.TOUCH_MODE_ALL_AT_ONCE)-- 多点

    self.button1:setTouchSwallowEnabled(true) -- 是否吞噬事件,默认值为 true

    self.button1:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("button1: %s count: %d", event.name, table.nums(event.points))

        self.button1.label:setString(label)

        printf("%s %s [TARGETING]", "button1", event.name)

        if event.name == "ended" or event.name == "cancelled" then

            print("-----------------------------")

        else

            print("")

        end

        return true

    end)

 

    -- button2响应触摸后,不会吞噬掉触摸事件

    self.button2 = createTouchableSprite({

            image = "PinkButton.png",

            size = cc.size(400, 160),

            label = "TOUCH ME !"})

        :pos(300, 200)

        :addTo(self.parentButton)

    cc.ui.UILabel.new({text = "SWALLOW = NO\n事件会传递到下层对象", size = 24})

        :align(display.CENTER, 200, 90)

        :addTo(self.button2)

    drawBoundingBox(self, self.button2, cc.c4f(0, 0, 1.0, 1.0))

 

    self.button2:setTouchEnabled(true)

    self.button2:setTouchMode(cc.TOUCH_MODE_ALL_AT_ONCE)-- 多点

    self.button2:setTouchSwallowEnabled(false) -- 当不吞噬事件时,触摸事件会从上层对象往下层对象传递,称为穿透

    self.button2:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local label = string.format("button1: %s count: %d", event.name, table.nums(event.points))

        self.button2.label:setString(label)

        printf("%s %s [TARGETING]", "button2", event.name)

        return true

    end)

 

    -- 即便父对象在捕获阶段阻止响应事件,但子对象仍然可以捕获到事件,只是不会触发事件

    self.button2:addNodeEventListener(cc.NODE_TOUCH_CAPTURE_EVENT,function(event)

        printf("%s %s [CAPTURING]", "button2", event.name)

        return true

    end)

 

    -- 放置一个开关按钮在屏幕上

    local labels = {}

    labels[true] = "父对象【可以】捕获触摸事件"

    labels[false] = "父对象【不能】捕获触摸事件"

    local images = {on = "CheckBoxButton2On.png", off = "CheckBoxButton2Off.png"}

    self.captureEnabledButton = cc.ui.UICheckBoxButton.new(images)

        :setButtonLabel(cc.ui.UILabel.new({text= labels[true], size = 24}))

        :setButtonLabelOffset(40, 0)

        :setButtonSelected(true)

        :onButtonStateChanged(function(event)

            local button = event.target

            button:setButtonLabelString(labels[button:isButtonSelected()])

        end)

        :onButtonClicked(function(event)

            local button = event.target

            self.isTouchCaptureEnabled_ = button:isButtonSelected()

        end)

        :pos(display.cx - 160, display.top- 80)

        :addTo(self)

 

    cc.ui.UILabel.new({

        text = "事件处理流程:\n1. 【捕获】阶段:从父到子\n2. 【目标】阶段\n3. 【传递】阶段:尝试传递给下层对象",

        size= 24})

        :align(display.CENTER_TOP, display.cx,display.top - 120)

        :addTo(self)

 

 

 

 

2.3         容器的触摸区域由子对象决定

-- touchableNode 是启用触摸的 Node

    self.touchableNode = display.newNode()

    self.touchableNode:setPosition(display.cx, display.cy)

    self:addChild(self.touchableNode)

 

    -- touchableNode 中加入一些 sprite

    local count = math.random(3, 8)

    local images = {"WhiteButton.png", "BlueButton.png", "GreenButton.png", "PinkButton.png"}

    for i = 1, count do

        local sprite = display.newScale9Sprite(images[math.random(1, 4)])

        sprite:setContentSize(cc.size(math.random(100, 200), math.random(100, 200)))

        sprite:setPosition(math.random(-200, 200), math.random(-200, 200))

        self.touchableNode:addChild(sprite)

    end

 

    self.stateLabel = cc.ui.UILabel.new({text = ""})

    self.stateLabel:align(display.CENTER, display.cx,display.top - 100)

    self:addChild(self.stateLabel)

 

    -- 启用触摸

    self.touchableNode:setTouchEnabled(true)

    self.touchableNode:setTouchMode(cc.TOUCH_MODE_ALL_AT_ONCE)-- 多点

   -- 添加触摸事件处理函数

    self.touchableNode:addNodeEventListener(cc.NODE_TOUCH_EVENT,function(event)

        local str = {}

        for id, point in pairs(event.points) do

            str[#str + 1] = string.format("id: %s, x: %0.2f, y: %0.2f", point.id, point.x, point.y)

        end

        self.stateLabel:setString(table.concat(str, "\n"))

        return true

    end)

    drawBoundingBox(self, self.touchableNode, cc.c4f(0, 1.0, 0, 1.0))

 

    --

    app:createNextButton(self)

    app:createTitle(self, "多点触摸测试 - 容器的触摸区域由子对象决定")