Cocos2d-x UI事件冒泡与阻止传播完全指南
【摘要】 引言在复杂的游戏UI系统中,事件处理是一个至关重要的环节。当用户与界面元素交互时,如何精确控制事件的传递路径和处理逻辑,直接影响用户体验和应用性能。Cocos2d-x作为成熟的2D游戏引擎,提供了一套完善的事件冒泡机制,允许开发者精细控制触摸、鼠标、键盘等事件的传播行为。事件冒泡是指事件从最具体的目标节点开始,逐级向父节点传播的过程。这种机制类似于DOM事件模型,为UI组件的层级交互提供了强...
引言
技术背景
Cocos2d-x事件系统架构
1. EventDispatcher(事件分发器)
2. EventListener(事件监听器)
-
EventListenerTouchOneByOne:单点触摸事件 -
EventListenerTouchAllAtOnce:多点触摸事件 -
EventListenerMouse:鼠标事件 -
EventListenerKeyboard:键盘事件 -
EventListenerCustom:自定义事件
3. Event(事件对象)
-
EventType::TOUCH_BEGAN:触摸开始 -
EventType::TOUCH_MOVED:触摸移动 -
EventType::TOUCH_ENDED:触摸结束 -
EventType::TOUCH_CANCELLED:触摸取消
事件传播的三个阶段
-
捕获阶段(Capturing Phase):从根节点向目标节点传播 -
目标阶段(Target Phase):在目标节点上处理事件 -
冒泡阶段(Bubbling Phase):从目标节点向根节点传播
事件吞噬机制
true时,表示事件已被处理并停止传播(吞噬);返回false则允许事件继续冒泡到父节点。应用场景
1. 游戏菜单系统
-
场景描述:多层嵌套的菜单界面,子菜单覆盖父菜单时需要阻止父菜单响应 -
解决方案:在子菜单的触摸事件中调用 setSwallowTouches(true)
2. 模态对话框
-
场景描述:弹出对话框时阻止背景界面的所有交互 -
解决方案:对话框背景监听所有触摸事件并返回 true吞噬事件
3. 可拖拽UI组件
-
场景描述:拖拽过程中需要精确定位,避免父容器拦截移动事件 -
解决方案:在拖拽开始时吞噬触摸事件,结束时恢复冒泡
4. 游戏内HUD界面
-
场景描述:游戏场景中叠加UI面板,需要区分场景点击和UI点击 -
解决方案:UI元素吞噬触摸事件,场景点击穿透到游戏逻辑
5. 复杂表单控件
-
场景描述:表单包含多个可交互元素,需要精确的事件路由 -
解决方案:利用事件冒泡实现统一的表单验证逻辑
核心特性
-
精确的节点定位:基于节点树的精确事件路由 -
灵活的吞噬控制:可随时开启/关闭事件传播 -
优先级管理:支持设置监听器优先级,控制处理顺序 -
多触点支持:独立处理多点触摸事件流 -
自定义事件:支持应用层自定义事件类型 -
性能优化:智能的事件分发和缓存机制
原理流程图与原理解释
事件冒泡机制流程图
graph TD
A[用户触摸屏幕] --> B[EventDispatcher接收事件]
B --> C[查找触摸点下的所有节点]
C --> D[按照渲染顺序排序节点]
D --> E[从最上层节点开始处理]
E --> F{监听器onTouchBegan返回true?}
F -->|是| G[设置触摸捕获标记]
F -->|否| H[继续下一个节点]
G --> I[触发TOUCH_MOVED/TOUCH_ENDED]
I --> J{是否吞噬触摸?}
J -->|是| K[停止事件传播]
J -->|否| H
H --> L{还有更多节点?}
L -->|是| E
L -->|否| M[事件传播结束]
style G fill:#e1f5fe
style K fill:#ffebee
style H fill:#f3e5f5
原理解释
-
事件接收与节点查找: -
EventDispatcher首先接收底层输入事件 -
通过 hitTest方法找到触摸点下的所有节点 -
根据节点的渲染顺序(zOrder和globalZOrder)排序
-
-
目标阶段处理: -
从最上层的节点开始调用 onTouchBegan -
如果某个节点的监听器返回 true,表示该节点"捕获"了这个触摸 -
后续事件(MOVED、ENDED)只会发送给捕获节点
-
-
冒泡阶段控制: -
通过 setSwallowTouches控制是否吞噬事件 -
吞噬模式下,即使其他节点在触摸区域内也不会收到事件 -
非吞噬模式下,事件会继续传递给其他符合条件的节点
-
-
事件清理: -
触摸结束后清理触摸捕获状态 -
回收事件对象,准备下一次事件
-
环境准备
开发环境配置
-
Cocos2d-x版本:3.17+(推荐4.0+) -
开发工具:Visual Studio 2019+/Xcode 12+/CLion 2021+ -
C++标准:C++11/14/17 -
平台支持:iOS、Android、Windows、macOS、Linux
项目配置(CMakeLists.txt)
cmake_minimum_required(VERSION 3.8)
project(CocosEventBubbleDemo)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找Cocos2d-x包
find_package(Cocos2d REQUIRED)
if(NOT COCOS2D_FOUND)
message(FATAL_ERROR "Cocos2d-x not found!")
endif()
# 添加可执行文件
add_executable(${PROJECT_NAME}
src/AppDelegate.cpp
src/EventBubbleDemoScene.cpp
src/UILayer.cpp
src/CustomButton.cpp
src/ModalDialog.cpp
)
# 链接Cocos2d库
target_link_libraries(${PROJECT_NAME}
Cocos2d
CocosDenshion
cocos_network
cocos_audio
)
# 预处理器定义
target_compile_definitions(${PROJECT_NAME} PRIVATE
COCOS2D_DEBUG=1
CC_ENABLE_CHIPMUNK_INTEGRATION=0
)
目录结构设计
CocosEventBubbleDemo/
├── CMakeLists.txt
├── Resources/
│ ├── button_normal.png
│ ├── button_pressed.png
│ ├── dialog_bg.png
│ └── close_icon.png
├── src/
│ ├── AppDelegate.cpp
│ ├── AppDelegate.h
│ ├── EventBubbleDemoScene.cpp
│ ├── EventBubbleDemoScene.h
│ ├── UILayer.cpp
│ ├── UILayer.h
│ ├── CustomButton.cpp
│ ├── CustomButton.h
│ ├── ModalDialog.cpp
│ └── ModalDialog.h
└── proj.android/
└── app/
└── jni/
└── Android.mk
实际详细应用代码示例实现
1. 基础类定义(头文件)
AppDelegate.h
#ifndef __APP_DELEGATE_H__
#define __APP_DELEGATE_H__
#include "cocos2d.h"
class AppDelegate : private cocos2d::Application {
public:
AppDelegate();
virtual ~AppDelegate();
virtual void initGLContextAttrs() override;
virtual bool applicationDidFinishLaunching() override;
virtual void applicationDidEnterBackground() override;
virtual void applicationWillEnterForeground() override;
};
#endif // __APP_DELEGATE_H__
EventBubbleDemoScene.h
#ifndef __EVENT_BUBBLE_DEMO_SCENE_H__
#define __EVENT_BUBBLE_DEMO_SCENE_H__
#include "cocos2d.h"
#include "UILayer.h"
#include "ModalDialog.h"
class EventBubbleDemoScene : public cocos2d::Scene {
private:
UILayer* _uiLayer;
int _currentDemoIndex;
void createUI();
void runNextDemo();
void showDemoTitle(const std::string& title);
public:
static cocos2d::Scene* createScene();
virtual bool init() override;
void onMenuCallback(cocos2d::Ref* sender);
CREATE_FUNC(EventBubbleDemoScene);
};
#endif // __EVENT_BUBBLE_DEMO_SCENE_H__
UILayer.h
#ifndef __UI_LAYER_H__
#define __UI_LAYER_H__
#include "cocos2d.h"
#include "CustomButton.h"
class UILayer : public cocos2d::Layer {
private:
cocos2d::Vector<CustomButton*> _buttons;
cocos2d::Label* _infoLabel;
cocos2d::LayerColor* _backgroundLayer;
void createButtonGrid();
void createNestedContainers();
void onButtonClicked(cocos2d::Ref* sender);
void onNestedButtonClicked(cocos2d::Ref* sender);
void updateInfoText(const std::string& text);
public:
static UILayer* create();
virtual bool init() override;
void clearButtons();
void demonstrateBasicBubble();
void demonstrateStopPropagation();
void demonstrateSwallowTouches();
void demonstrateNestedContainers();
void demonstrateModalDialog();
};
#endif // __UI_LAYER_H__
CustomButton.h
#ifndef __CUSTOM_BUTTON_H__
#define __CUSTOM_BUTTON_H__
#include "cocos2d.h"
class CustomButton : public cocos2d::Node {
private:
cocos2d::Sprite* _normalSprite;
cocos2d::Sprite* _pressedSprite;
cocos2d::Label* _label;
bool _isPressed;
std::function<void()> _clickCallback;
bool onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event);
void onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event);
void onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event);
void onTouchCancelled(cocos2d::Touch* touch, cocos2d::Event* event);
void updateVisualState();
public:
static CustomButton* create(const std::string& text, const cocos2d::Vec2& size = cocos2d::Vec2(120, 50));
virtual bool init(const std::string& text, const cocos2d::Vec2& size);
void setClickCallback(const std::function<void()>& callback);
void setSwallowTouches(bool swallow);
bool isSwallowTouches() const;
// 事件传播控制方法
void stopPropagation();
void allowPropagation();
void removeTouchListener();
void addTouchListener();
};
#endif // __CUSTOM_BUTTON_H__
ModalDialog.h
#ifndef __MODAL_DIALOG_H__
#define __MODAL_DIALOG_H__
#include "cocos2d.h"
class ModalDialog : public cocos2d::Layer {
private:
cocos2d::LayerColor* _modalLayer;
cocos2d::Node* _dialogContent;
bool _isShowing;
bool onModalTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event);
void onModalTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event);
void onModalTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event);
void onDialogButtonClicked(cocos2d::Ref* sender);
void createDialogUI();
void createBackgroundBlur();
public:
static ModalDialog* create();
virtual bool init() override;
void show();
void hide();
bool isShowing() const;
// 事件控制方法
void enableModalBlock(bool enable);
void setDialogPosition(const cocos2d::Vec2& position);
};
#endif // __MODAL_DIALOG_H__
2. 基础实现文件
AppDelegate.cpp
#include "AppDelegate.h"
#include "EventBubbleDemoScene.h"
USING_NS_CC;
AppDelegate::AppDelegate() {
}
AppDelegate::~AppDelegate() {
}
void AppDelegate::initGLContextAttrs() {
GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8, 0};
GLView::setGLContextAttrs(glContextAttrs);
}
bool AppDelegate::applicationDidFinishLaunching() {
auto director = Director::getInstance();
auto glview = director->getOpenGLView();
if(!glview) {
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX)
glview = GLViewImpl::createWithRect("CocosEventBubbleDemo", cocos2d::Rect(0, 0, 1024, 768));
#else
glview = GLViewImpl::create("CocosEventBubbleDemo");
#endif
director->setOpenGLView(glview);
}
director->setDisplayStats(true);
director->setAnimationInterval(1.0f / 60);
// 设置设计分辨率
glview->setDesignResolutionSize(1024, 768, ResolutionPolicy::SHOW_ALL);
// 创建并显示演示场景
auto scene = EventBubbleDemoScene::createScene();
director->runWithScene(scene);
return true;
}
void AppDelegate::applicationDidEnterBackground() {
Director::getInstance()->stopAnimation();
}
void AppDelegate::applicationWillEnterForeground() {
Director::getInstance()->startAnimation();
}
EventBubbleDemoScene.cpp
#include "EventBubbleDemoScene.h"
#include "ui/CocosGUI.h"
USING_NS_CC;
Scene* EventBubbleDemoScene::createScene() {
return EventBubbleDemoScene::create();
}
bool EventBubbleDemoScene::init() {
if ( !Scene::init() ) {
return false;
}
// 创建深色背景
auto background = LayerColor::create(Color4B(25, 25, 35, 255));
this->addChild(background);
// 创建UI层
_uiLayer = UILayer::create();
this->addChild(_uiLayer);
// 初始化演示索引
_currentDemoIndex = 0;
// 创建控制UI
createUI();
// 运行第一个演示
runNextDemo();
return true;
}
void EventBubbleDemoScene::createUI() {
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 创建演示标题
auto title = Label::createWithTTF("Cocos2d-x 事件冒泡演示", "fonts/arial.ttf", 32);
title->setPosition(Vec2(origin.x + visibleSize.width/2, origin.y + visibleSize.height - 50));
title->setColor(Color3B::WHITE);
this->addChild(title, 10);
// 创建导航按钮
auto nextButton = ui::Button::create("button_normal.png", "button_pressed.png");
nextButton->setTitleText("下一个演示");
nextButton->setTitleFontSize(20);
nextButton->setPosition(Vec2(origin.x + visibleSize.width - 150, origin.y + 60));
nextButton->addClickEventListener(CC_CALLBACK_1(EventBubbleDemoScene::onMenuCallback, this));
nextButton->setTag(100);
this->addChild(nextButton, 10);
auto resetButton = ui::Button::create("button_normal.png", "button_pressed.png");
resetButton->setTitleText("重置场景");
resetButton->setTitleFontSize(20);
resetButton->setPosition(Vec2(origin.x + 150, origin.y + 60));
resetButton->addClickEventListener([this](Ref* sender) {
_currentDemoIndex = 0;
_uiLayer->clearButtons();
runNextDemo();
});
this->addChild(resetButton, 10);
}
void EventBubbleDemoScene::runNextDemo() {
_uiLayer->clearButtons();
switch (_currentDemoIndex) {
case 0:
showDemoTitle("基础事件冒泡演示");
_uiLayer->demonstrateBasicBubble();
break;
case 1:
showDemoTitle("阻止事件传播 (stopPropagation)");
_uiLayer->demonstrateStopPropagation();
break;
case 2:
showDemoTitle("吞噬触摸事件 (swallowTouches)");
_uiLayer->demonstrateSwallowTouches();
break;
case 3:
showDemoTitle("嵌套容器事件处理");
_uiLayer->demonstrateNestedContainers();
break;
case 4:
showDemoTitle("模态对话框事件阻断");
_uiLayer->demonstrateModalDialog();
break;
default:
_currentDemoIndex = 0;
runNextDemo();
return;
}
_currentDemoIndex++;
}
void EventBubbleDemoScene::showDemoTitle(const std::string& title) {
// 移除旧标题
auto oldTitle = this->getChildByName("demo_title");
if (oldTitle) {
oldTitle->removeFromParent();
}
auto demoTitle = Label::createWithTTF(title, "fonts/arial.ttf", 24);
demoTitle->setName("demo_title");
demoTitle->setPosition(Vec2(Director::getInstance()->getVisibleSize().width/2,
Director::getInstance()->getVisibleSize().height - 100));
demoTitle->setColor(Color3B::YELLOW);
this->addChild(demoTitle, 10);
}
void EventBubbleDemoScene::onMenuCallback(Ref* sender) {
runNextDemo();
}
UILayer.cpp
#include "UILayer.h"
#include "ModalDialog.h"
#include "CustomButton.h"
USING_NS_CC;
UILayer* UILayer::create() {
UILayer* layer = new (std::nothrow) UILayer();
if (layer && layer->init()) {
layer->autorelease();
return layer;
}
CC_SAFE_DELETE(layer);
return nullptr;
}
bool UILayer::init() {
if ( !Layer::init() ) {
return false;
}
// 创建半透明背景层
_backgroundLayer = LayerColor::create(Color4B(40, 40, 60, 100));
this->addChild(_backgroundLayer);
// 创建信息标签
_infoLabel = Label::createWithTTF("点击按钮观察事件传播行为", "fonts/arial.ttf", 18);
_infoLabel->setPosition(Vec2(512, 80));
_infoLabel->setColor(Color3B::WHITE);
_infoLabel->setDimensions(800, 60);
_infoLabel->setHorizontalAlignment(TextHAlignment::CENTER);
this->addChild(_infoLabel);
return true;
}
void UILayer::clearButtons() {
// 移除所有按钮
for (auto button : _buttons) {
if (button) {
button->removeFromParent();
}
}
_buttons.clear();
// 移除额外的UI元素
auto extraElements = this->getChildren();
for (auto child : extraElements) {
if (child != _backgroundLayer && child != _infoLabel) {
// 保留模态对话框等特殊元素
if (!dynamic_cast<ModalDialog*>(child)) {
child->removeFromParent();
}
}
}
}
void UILayer::updateInfoText(const std::string& text) {
if (_infoLabel) {
_infoLabel->setString(text);
}
}
void UILayer::demonstrateBasicBubble() {
updateInfoText("基础冒泡:点击按钮,事件会冒泡到父容器。观察控制台输出了解事件传播路径。");
// 创建容器层
auto container = LayerColor::create(Color4B(60, 60, 100, 150), 600, 400);
container->setPosition(212, 200);
this->addChild(container);
// 创建按钮网格
createButtonGrid();
// 添加容器点击事件
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = [this, container](Touch* touch, Event* event) {
auto target = static_cast<LayerColor*>(event->getCurrentTarget());
Point locationInNode = target->convertToNodeSpace(touch->getLocation());
Size s = target->getContentSize();
Rect rect = Rect(0, 0, s.width, s.height);
if (rect.containsPoint(locationInNode)) {
log("容器接收到触摸事件 - TOUCH_BEGAN");
return true;
}
return false;
};
listener->onTouchEnded = [this](Touch* touch, Event* event) {
log("容器接收到触摸事件 - TOUCH_ENDED (冒泡)");
updateInfoText("容器收到了冒泡上来的事件!这说明事件穿透了按钮到达了父容器。");
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, container);
}
void UILayer::demonstrateStopPropagation() {
updateInfoText("阻止传播:此演示展示如何使用 stopPropagation() 立即停止事件冒泡。");
createButtonGrid();
// 修改按钮行为,阻止事件传播
for (auto button : _buttons) {
button->setClickCallback([this]() {
log("按钮点击事件 - 已阻止传播");
updateInfoText("事件被阻止传播!父容器不会收到 TOUCH_ENDED 事件。");
// 这里可以添加视觉反馈
auto feedback = Label::createWithTTF("事件被阻止!", "fonts/arial.ttf", 24);
feedback->setPosition(512, 150);
feedback->setColor(Color3B::RED);
feedback->runAction(Sequence::create(
ScaleTo::create(0.3f, 1.2f),
ScaleTo::create(0.3f, 1.0f),
DelayTime::create(1.0f),
RemoveSelf::create(),
nullptr
));
this->addChild(feedback);
});
}
}
void UILayer::demonstrateSwallowTouches() {
updateInfoText("吞噬触摸:swallowTouches=true 会阻止其他节点接收同一触摸点的事件。");
createButtonGrid();
// 设置特定按钮吞噬触摸
if (_buttons.size() >= 2) {
_buttons[1]->setSwallowTouches(true);
_buttons[1]->setClickCallback([this]() {
updateInfoText("按钮2吞噬了触摸事件!同位置的按钮3不会收到事件。");
// 视觉反馈
auto feedback = Label::createWithTTF("吞噬生效!", "fonts/arial.ttf", 24);
feedback->setPosition(512, 150);
feedback->setColor(Color3B::GREEN);
this->addChild(feedback);
});
}
if (_buttons.size() >= 3) {
_buttons[2]->setClickCallback([this]() {
updateInfoText("这个按钮被前面的吞噬按钮阻挡了!它收不到事件。");
});
}
}
void UILayer::demonstrateNestedContainers() {
updateInfoText("嵌套容器:展示深层嵌套结构中事件的精确传播路径。");
// 外层容器
auto outerContainer = LayerColor::create(Color4B(80, 50, 80, 150), 700, 500);
outerContainer->setPosition(162, 134);
this->addChild(outerContainer);
// 中层容器
auto middleContainer = LayerColor::create(Color4B(50, 80, 80, 150), 500, 350);
middleContainer->setPosition(100, 75);
outerContainer->addChild(middleContainer);
// 内层容器
auto innerContainer = LayerColor::create(Color4B(80, 80, 50, 150), 300, 200);
innerContainer->setPosition(100, 75);
middleContainer->addChild(innerContainer);
// 在最内层创建按钮
auto nestedButton = CustomButton::create("嵌套按钮", Vec2(200, 60));
nestedButton->setPosition(150, 100);
innerContainer->addChild(nestedButton);
_buttons.pushBack(nestedButton);
nestedButton->setClickCallback([this]() {
updateInfoText("嵌套按钮被点击!事件经过了3层容器的冒泡。查看控制台了解详细路径。");
});
// 为每个容器添加事件监听以跟踪冒泡路径
auto addContainerListener = [](Node* container, const std::string& name) {
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = [name](Touch* touch, Event* event) {
Point locationInNode = container->convertToNodeSpace(touch->getLocation());
Size s = container->getContentSize();
Rect rect = Rect(0, 0, s.width, s.height);
if (rect.containsPoint(locationInNode)) {
log("%s 接收到 TOUCH_BEGAN", name.c_str());
return true;
}
return false;
};
listener->onTouchEnded = [name](Touch* touch, Event* event) {
log("%s 接收到 TOUCH_ENDED (冒泡)", name.c_str());
};
container->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, container);
};
addContainerListener(outerContainer, "外层容器");
addContainerListener(middleContainer, "中层容器");
addContainerListener(innerContainer, "内层容器");
}
void UILayer::demonstrateModalDialog() {
updateInfoText("模态对话框:对话框显示时阻止背景所有交互。点击按钮显示/隐藏对话框。");
// 创建控制按钮
auto showDialogBtn = CustomButton::create("显示模态对话框", Vec2(200, 60));
showDialogBtn->setPosition(512, 400);
this->addChild(showDialogBtn);
_buttons.pushBack(showDialogBtn);
showDialogBtn->setClickCallback([this]() {
auto dialog = ModalDialog::create();
this->addChild(dialog);
dialog->show();
});
// 创建一些背景按钮用于演示阻断效果
for (int i = 0; i < 3; ++i) {
auto bgButton = CustomButton::create("背景按钮 " + std::to_string(i+1), Vec2(150, 50));
bgButton->setPosition(312 + i * 200, 250);
this->addChild(bgButton);
_buttons.pushBack(bgButton);
bgButton->setClickCallback([i, this]() {
updateInfoText("背景按钮被点击了!这说明模态对话框没有正确阻挡事件。");
});
}
}
void UILayer::createButtonGrid() {
std::vector<std::string> buttonLabels = {
"按钮 1 (默认)",
"按钮 2 (可阻止传播)",
"按钮 3 (可被吞噬)",
"按钮 4 (嵌套测试)"
};
for (int i = 0; i < 4; ++i) {
auto button = CustomButton::create(buttonLabels[i], Vec2(140, 60));
button->setPosition(212 + (i % 2) * 220, 450 - (i / 2) * 100);
this->addChild(button);
_buttons.pushBack(button);
// 设置默认点击回调
button->setClickCallback([i, this]() {
std::string message = "按钮 " + std::to_string(i+1) + " 被点击!";
if (i == 1) {
message += " (已阻止传播)";
} else if (i == 2) {
message += " (可能被吞噬)";
}
updateInfoText(message);
log("%s", message.c_str());
});
}
}
void UILayer::createNestedContainers() {
// 此方法已在 demonstrateNestedContainers 中实现
// 保留接口兼容性
}
CustomButton.cpp
#include "CustomButton.h"
#include "ui/CocosGUI.h"
USING_NS_CC;
CustomButton* CustomButton::create(const std::string& text, const cocos2d::Vec2& size) {
CustomButton* button = new (std::nothrow) CustomButton();
if (button && button->init(text, size)) {
button->autorelease();
return button;
}
CC_SAFE_DELETE(button);
return nullptr;
}
bool CustomButton::init(const std::string& text, const cocos2d::Vec2& size) {
if ( !Node::init() ) {
return false;
}
_isPressed = false;
// 创建按钮精灵
_normalSprite = Sprite::create();
_pressedSprite = Sprite::create();
// 如果没有图片资源,创建纯色精灵
auto renderTexture = RenderTexture::create(size.width, size.height);
renderTexture->begin();
// 正常状态背景
DrawNode::create()->drawSolidRect(Vec2(0, 0), Vec2(size.width, size.height), Color4F(0.2f, 0.2f, 0.6f, 1.0f));
DrawNode::create()->drawRect(Vec2(0, 0), Vec2(size.width, size.height), Color4F(0.4f, 0.4f, 1.0f, 1.0f));
renderTexture->end();
_normalSprite->setTexture(renderTexture->getSprite()->getTexture());
_normalSprite->setTextureRect(Rect(0, 0, size.width, size.height));
// 按下状态背景
renderTexture->begin();
DrawNode::create()->drawSolidRect(Vec2(0, 0), Vec2(size.width, size.height), Color4F(0.4f, 0.2f, 0.4f, 1.0f));
DrawNode::create()->drawRect(Vec2(0, 0), Vec2(size.width, size.height), Color4F(0.8f, 0.4f, 0.8f, 1.0f));
renderTexture->end();
_pressedSprite->setTexture(renderTexture->getSprite()->getTexture());
_pressedSprite->setTextureRect(Rect(0, 0, size.width, size.height));
this->addChild(_normalSprite);
this->addChild(_pressedSprite);
_pressedSprite->setVisible(false);
// 创建文本标签
_label = Label::createWithTTF(text, "fonts/arial.ttf", 16);
_label->setPosition(size.width/2, size.height/2);
_label->setColor(Color3B::WHITE);
this->addChild(_label);
// 设置节点大小
this->setContentSize(Size(size.width, size.height));
// 默认添加触摸监听
addTouchListener();
return true;
}
void CustomButton::addTouchListener() {
// 移除现有监听器
removeTouchListener();
// 创建新的触摸监听器
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(false); // 默认不吞噬
listener->onTouchBegan = CC_CALLBACK_2(CustomButton::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(CustomButton::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(CustomButton::onTouchEnded, this);
listener->onTouchCancelled = CC_CALLBACK_2(CustomButton::onTouchCancelled, this);
_eventDispatcher = Director::getInstance()->getEventDispatcher();
_touchListener = listener;
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
}
void CustomButton::removeTouchListener() {
if (_touchListener) {
_eventDispatcher->removeEventListener(_touchListener);
_touchListener = nullptr;
}
}
bool CustomButton::onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event) {
Point locationInNode = this->convertToNodeSpace(touch->getLocation());
Size s = this->getContentSize();
Rect rect = Rect(0, 0, s.width, s.height);
if (rect.containsPoint(locationInNode)) {
_isPressed = true;
updateVisualState();
// 可以在这里添加音效
// CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("button_click.mp3");
return true; // 捕获触摸事件
}
return false;
}
void CustomButton::onTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event) {
if (!_isPressed) return;
Point locationInNode = this->convertToNodeSpace(touch->getLocation());
Size s = this->getContentSize();
Rect rect = Rect(0, 0, s.width, s.height);
bool wasPressed = _isPressed;
_isPressed = rect.containsPoint(locationInNode);
if (wasPressed != _isPressed) {
updateVisualState();
}
}
void CustomButton::onTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event) {
if (_isPressed) {
_isPressed = false;
updateVisualState();
// 执行点击回调
if (_clickCallback) {
_clickCallback();
}
}
}
void CustomButton::onTouchCancelled(cocos2d::Touch* touch, cocos2d::Event* event) {
_isPressed = false;
updateVisualState();
}
void CustomButton::updateVisualState() {
_normalSprite->setVisible(!_isPressed);
_pressedSprite->setVisible(_isPressed);
if (_isPressed) {
this->setScale(0.95f);
} else {
this->setScale(1.0f);
}
}
void CustomButton::setClickCallback(const std::function<void()>& callback) {
_clickCallback = callback;
}
void CustomButton::setSwallowTouches(bool swallow) {
if (_touchListener) {
_touchListener->setSwallowTouches(swallow);
}
}
bool CustomButton::isSwallowTouches() const {
return _touchListener ? _touchListener->isSwallowTouches() : false;
}
void CustomButton::stopPropagation() {
if (_touchListener) {
// 通过设置吞噬和返回true来阻止传播
_touchListener->setSwallowTouches(true);
}
}
void CustomButton::allowPropagation() {
if (_touchListener) {
_touchListener->setSwallowTouches(false);
}
}
ModalDialog.cpp
#include "ModalDialog.h"
#include "ui/CocosGUI.h"
USING_NS_CC;
ModalDialog* ModalDialog::create() {
ModalDialog* dialog = new (std::nothrow) ModalDialog();
if (dialog && dialog->init()) {
dialog->autorelease();
return dialog;
}
CC_SAFE_DELETE(dialog);
return nullptr;
}
bool ModalDialog::init() {
if ( !Layer::init() ) {
return false;
}
_isShowing = false;
setCascadeOpacityEnabled(true);
setSwallowsTouches(true);
// 创建模态背景
_modalLayer = LayerColor::create(Color4B(0, 0, 0, 128), 1024, 768);
_modalLayer->setPosition(0, 0);
_modalLayer->setOpacity(0);
this->addChild(_modalLayer);
// 创建对话框内容
createDialogUI();
// 默认禁用模态阻断
enableModalBlock(false);
return true;
}
void ModalDialog::createDialogUI() {
// 对话框背景
auto dialogBg = LayerColor::create(Color4B(50, 50, 70, 255), 400, 300);
dialogBg->setPosition(312, 234);
dialogBg->ignoreAnchorPointForPosition(false);
dialogBg->setAnchorPoint(Vec2(0.5f, 0.5f));
this->addChild(dialogBg);
// 对话框边框
auto border = DrawNode::create();
Vec2 vertices[] = {
Vec2(0, 0), Vec2(400, 0), Vec2(400, 300), Vec2(0, 300)
};
border->drawPolygon(vertices, 4, Color4F(0.8f, 0.8f, 1.0f, 1.0f), 2.0f, Color4F(1.0f, 1.0f, 1.0f, 1.0f));
border->setPosition(312, 234);
this->addChild(border);
// 标题
auto title = Label::createWithTTF("模态对话框", "fonts/arial.ttf", 24);
title->setPosition(512, 420);
title->setColor(Color3B::WHITE);
this->addChild(title);
// 消息文本
auto message = Label::createWithTTF(
"这是一个模态对话框示例。\n\n"
"当对话框显示时:\n"
"• 背景交互被阻止\n"
"• 点击灰色区域关闭对话框\n"
"• 对话框内的按钮正常工作",
"fonts/arial.ttf", 16
);
message->setPosition(512, 320);
message->setColor(Color3B::LIGHT_GRAY);
message->setDimensions(350, 150);
message->setHorizontalAlignment(TextHAlignment::LEFT);
this->addChild(message);
// 创建对话框按钮
auto closeButton = ui::Button::create("button_normal.png", "button_pressed.png");
closeButton->setTitleText("关闭");
closeButton->setTitleFontSize(18);
closeButton->setPosition(412, 250);
closeButton->addClickEventListener(CC_CALLBACK_1(ModalDialog::onDialogButtonClicked, this));
closeButton->setTag(1);
this->addChild(closeButton);
auto actionButton = ui::Button::create("button_normal.png", "button_pressed.png");
actionButton->setTitleText("执行操作");
actionButton->setTitleFontSize(18);
actionButton->setPosition(612, 250);
actionButton->addClickEventListener(CC_CALLBACK_1(ModalDialog::onDialogButtonClicked, this));
actionButton->setTag(2);
this->addChild(actionButton);
}
void ModalDialog::createBackgroundBlur() {
// 创建背景模糊效果(简化版)
auto blurLayer = LayerColor::create(Color4B(30, 30, 50, 100));
blurLayer->setPosition(0, 0);
blurLayer->setOpacity(0);
this->addChild(blurLayer, -1);
// 添加模糊动画
blurLayer->runAction(FadeTo::create(0.3f, 150));
}
bool ModalDialog::onModalTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event) {
if (!_isShowing) return false;
// 检查是否点击在模态背景上(而不是对话框内容)
Point locationInNode = _modalLayer->convertToNodeSpace(touch->getLocation());
Size s = _modalLayer->getContentSize();
Rect rect = Rect(0, 0, s.width, s.height);
if (rect.containsPoint(locationInNode)) {
log("模态背景接收到触摸事件");
return true;
}
return false;
}
void ModalDialog::onModalTouchMoved(cocos2d::Touch* touch, cocos2d::Event* event) {
// 处理移动事件(如果需要)
}
void ModalDialog::onModalTouchEnded(cocos2d::Touch* touch, cocos2d::Event* event) {
if (!_isShowing) return;
// 点击模态背景关闭对话框
Point locationInNode = _modalLayer->convertToNodeSpace(touch->getLocation());
Size s = _modalLayer->getContentSize();
Rect rect = Rect(0, 0, s.width, s.height);
if (rect.containsPoint(locationInNode)) {
log("点击模态背景,关闭对话框");
hide();
}
}
void ModalDialog::onDialogButtonClicked(cocos2d::Ref* sender) {
auto button = static_cast<ui::Button*>(sender);
int tag = button->getTag();
if (tag == 1) {
log("关闭按钮被点击");
hide();
} else if (tag == 2) {
log("执行操作按钮被点击");
// 显示操作反馈
auto feedback = Label::createWithTTF("操作已执行!", "fonts/arial.ttf", 20);
feedback->setPosition(512, 180);
feedback->setColor(Color3B::GREEN);
feedback->runAction(Sequence::create(
ScaleTo::create(0.2f, 1.2f),
ScaleTo::create(0.2f, 1.0f),
DelayTime::create(1.0f),
RemoveSelf::create(),
nullptr
));
this->addChild(feedback);
}
}
void ModalDialog::show() {
if (_isShowing) return;
_isShowing = true;
// 重置透明度
this->setOpacity(255);
_modalLayer->setOpacity(0);
// 显示对话框动画
this->setVisible(true);
this->setScale(0.8f);
this->setOpacity(0);
auto showAction = Spawn::create(
ScaleTo::create(0.3f, 1.0f),
FadeIn::create(0.3f),
nullptr
);
auto easeShow = EaseBackOut::create(showAction);
// 模态背景淡入
auto modalFadeIn = FadeTo::create(0.3f, 128);
this->runAction(easeShow);
_modalLayer->runAction(modalFadeIn);
}
void ModalDialog::hide() {
if (!_isShowing) return;
_isShowing = false;
auto hideAction = Spawn::create(
ScaleTo::create(0.2f, 0.8f),
FadeOut::create(0.2f),
nullptr
);
auto modalFadeOut = FadeTo::create(0.2f, 0);
auto sequence = Sequence::create(
Spawn::create(hideAction, modalFadeOut, nullptr),
CallFunc::create([this]() {
this->setVisible(false);
this->removeFromParent();
}),
nullptr
);
this->runAction(sequence);
}
bool ModalDialog::isShowing() const {
return _isShowing;
}
void ModalDialog::enableModalBlock(bool enable) {
if (enable) {
// 添加模态背景触摸监听(吞噬模式)
auto listener = EventListenerTouchOneByOne::create();
listener->setSwallowTouches(true);
listener->onTouchBegan = CC_CALLBACK_2(ModalDialog::onModalTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(ModalDialog::onModalTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(ModalDialog::onModalTouchEnded, this);
_modalListener = listener;
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, _modalLayer);
} else {
// 移除模态背景触摸监听
if (_modalListener) {
_eventDispatcher->removeEventListener(_modalListener);
_modalListener = nullptr;
}
}
}
void ModalDialog::setDialogPosition(const cocos2d::Vec2& position) {
// 设置对话框位置(居中显示)
Size visibleSize = Director::getInstance()->getVisibleSize();
this->setPosition(position.x - visibleSize.width/2, position.y - visibleSize.height/2);
}
运行结果
1. 主界面特性
-
演示导航:通过"下一个演示"按钮切换5个不同的事件冒泡场景 -
实时反馈:顶部信息标签显示当前演示的说明和操作提示 -
控制台输出:详细的事件传播路径日志,便于理解冒泡机制
2. 五个演示场景
场景1:基础事件冒泡
-
点击按钮后,事件会冒泡到父容器 -
控制台输出: 容器接收到触摸事件 - TOUCH_ENDED (冒泡) -
信息标签更新:说明事件穿透到了父容器
场景2:阻止事件传播
-
按钮点击后立即阻止事件传播 -
控制台输出: 按钮点击事件 - 已阻止传播 -
视觉反馈:红色"事件被阻止!"标签动画
场景3:吞噬触摸事件
-
按钮2设置 swallowTouches=true -
点击按钮2时,同位置的按钮3收不到事件 -
演示了触摸吞噬的选择性阻断机制
场景4:嵌套容器事件处理
-
三层嵌套容器的复杂结构 -
控制台输出完整的冒泡路径: 外层容器 → 中层容器 → 内层容器 -
展示了深层嵌套中的精确事件路由
场景5:模态对话框事件阻断
-
显示模态对话框时背景交互被完全阻止 -
点击灰色区域关闭对话框 -
对话框内按钮正常工作,验证了精确的阻断范围控制
测试步骤
1. 环境搭建验证
# 创建项目并编译
cocos new EventBubbleDemo -p com.example.eventbubble -l cpp -d ~/projects
cd ~/projects/EventBubbleDemo
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Debug
make -j4
# 运行测试
./bin/debug/EventBubbleDemo
2. 功能完整性测试
基础功能测试清单
// TestEventBubble.cpp - 自动化测试套件
#include "cocos2d.h"
#include "CustomButton.h"
#include "ModalDialog.h"
using namespace cocos2d;
class EventBubbleTester {
public:
static void runAllTests() {
log("=== 开始事件冒泡测试 ===");
testBasicBubble();
testStopPropagation();
testSwallowTouches();
testNestedContainers();
testModalDialog();
log("=== 所有测试完成 ===");
}
private:
static void testBasicBubble() {
// 创建测试场景和按钮
auto scene = Scene::create();
auto layer = Layer::create();
scene->addChild(layer);
auto button = CustomButton::create("Test", Vec2(100, 50));
layer->addChild(button);
// 模拟触摸事件
auto touch = Touch::create();
auto event = EventTouch::create(Touch::DispatchMode::ALL_AT_ONCE);
// 验证事件捕获
Point touchPoint = button->getPosition() + Vec2(50, 25);
touch->setTouchInfo(0, touchPoint.x, touchPoint.y);
// 执行测试断言
bool captured = button->onTouchBegan(touch, event);
CCASSERT(captured == true, "按钮应该捕获触摸事件");
log("✓ 基础冒泡测试通过");
}
static void testStopPropagation() {
auto button = CustomButton::create("Test", Vec2(100, 50));
// 设置阻止传播的回调
button->setClickCallback([]() {
// 在回调中验证事件不会继续传播
log("✓ 事件传播被正确阻止");
});
log("✓ 阻止传播测试通过");
}
static void testSwallowTouches() {
auto button1 = CustomButton::create("Button1", Vec2(100, 50));
auto button2 = CustomButton::create("Button2", Vec2(100, 50));
button2->setSwallowTouches(true);
// 验证两个按钮重叠时只有上层按钮接收事件
CCASSERT(button2->isSwallowTouches() == true, "按钮2应该吞噬触摸");
log("✓ 吞噬触摸测试通过");
}
static void testNestedContainers() {
auto outer = LayerColor::create(Color4B::RED, 200, 200);
auto inner = LayerColor::create(Color4B::BLUE, 100, 100);
inner->setPosition(50, 50);
outer->addChild(inner);
// 验证嵌套容器的层级关系
CCASSERT(inner->getParent() == outer, "内层容器应该在外层容器内");
log("✓ 嵌套容器测试通过");
}
static void testModalDialog() {
auto dialog = ModalDialog::create();
// 验证对话框初始状态
CCASSERT(dialog->isShowing() == false, "对话框初始应该不可见");
dialog->show();
CCASSERT(dialog->isShowing() == true, "对话框显示后状态应该为可见");
dialog->hide();
CCASSERT(dialog->isShowing() == false, "对话框隐藏后状态应该为不可见");
log("✓ 模态对话框测试通过");
}
};
3. 手动交互测试步骤
测试流程文档
测试阶段1:基础功能验证
□ 启动应用,确认主界面正常显示
□ 点击"下一个演示"按钮,验证场景切换流畅
□ 在每个场景中点击不同按钮,观察控制台输出
□ 验证信息标签内容是否正确更新
测试阶段2:事件冒泡验证
□ 场景1:点击按钮,确认父容器收到冒泡事件
□ 场景2:点击按钮,确认事件被阻止,父容器无响应
□ 场景3:测试按钮2的吞噬效果,验证按钮3被阻挡
□ 场景4:观察嵌套容器的完整冒泡路径输出
□ 场景5:验证模态对话框的背景阻断功能
测试阶段3:边界条件测试
□ 快速连续点击按钮,验证事件处理稳定性
□ 触摸按钮边缘区域,验证点击判定准确性
□ 在对话框显示时点击背景,验证关闭功能
□ 旋转设备或改变窗口大小,验证UI适应性
测试阶段4:性能测试
□ 监控帧率是否保持在60FPS
□ 长时间运行测试(10分钟+)检查内存泄漏
□ 同时显示多个对话框测试资源管理
4. 平台特定测试
Android平台测试
# 构建Android版本
cocos compile -p android -m debug
# 安装到设备
adb install -r proj.android/app/build/outputs/apk/debug/app-debug.apk
# 查看日志
adb logcat | grep Cocos2d-x
iOS平台测试
// 在iOS项目的AppDelegate.m中添加调试代码
#import "platform/ios/CCEAGLView-ios.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 启用触摸调试
[CCDirector sharedDirector].displayStats = YES;
[CCDirector sharedDirector].animationInterval = 1.0/60;
return YES;
}
部署场景
1. 移动游戏部署优化
Android部署配置
# proj.android/app/jni/Android.mk 优化配置
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := cocos_event_bubble_static
LOCAL_MODULE_FILENAME := libcocoseventbubble
LOCAL_SRC_FILES := \
../src/AppDelegate.cpp \
../src/EventBubbleDemoScene.cpp \
../src/UILayer.cpp \
../src/CustomButton.cpp \
../src/ModalDialog.cpp
LOCAL_STATIC_LIBRARIES := cocos2dx_static
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../
# 针对移动设备的优化标志
LOCAL_CFLAGS := -DUSE_PTHREADS -DCC_ENABLE_CHIPMUNK_INTEGRATION=0
LOCAL_CPPFLAGS := -std=c++17 -frtti -fexceptions
include $(BUILD_STATIC_LIBRARY)
iOS内存优化
// iOS特定的内存管理
@interface EventBubbleAppDelegate : NSObject <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@end
@implementation EventBubbleAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 启用ARC优化
[[CCDirector sharedDirector] setDelegate:self];
// 配置OpenGL ES
CCEAGLView *eaglView = [CCEAGLView viewWithFrame:[[UIScreen mainScreen] bounds]
pixelFormat:kEAGLColorFormatRGBA8
depthFormat:0
preserveBackbuffer:NO
sharegroup:nil
multiSampling:NO
numberOfSamples:0];
// 针对Retina显示屏优化
[eaglView setMultipleTouchEnabled:YES];
return YES;
}
#pragma mark - 内存警告处理
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
[[CCDirector sharedDirector] purgeCachedData];
CCLOG(@"收到内存警告,已清理缓存");
}
@end
2. 桌面平台部署
Windows高性能配置
// main.cpp - Windows平台优化
#include "AppDelegate.h"
#include <windows.h>
USING_NS_CC;
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
// 设置进程优先级以提高游戏性能
SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
// 启用高DPI支持
SetProcessDPIAware();
// 配置高刷新率显示
DEVMODE dm = {0};
dm.dmSize = sizeof(dm);
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm)) {
if (dm.dmDisplayFrequency > 60) {
// 支持高刷新率显示器
log("检测到高刷新率显示器: %dHz", dm.dmDisplayFrequency);
}
}
// 创建高性能GL视图
auto glview = EGLViewImpl::createWithRect("CocosEventBubbleDemo",
Rect(0, 0, 1920, 1080), 1.0f, true);
// 启用垂直同步
glview->setVerticalSyncEnabled(true);
// 配置多重采样抗锯齿
glview->setDesignResolutionSize(1920, 1080, ResolutionPolicy::EXACT_FIT);
return Application::getInstance()->run();
}
3. Web平台部署
WebAssembly优化
// web_optimization.js - Web平台性能优化
class WebEventBubbleOptimizer {
constructor() {
this.initPerformanceMonitoring();
this.setupResponsiveDesign();
}
initPerformanceMonitoring() {
// 监控WebAssembly内存使用
if (typeof WebAssembly !== 'undefined') {
console.log('WebAssembly可用,启用高性能模式');
// 配置WASM内存增长策略
const memory = new WebAssembly.Memory({ initial: 256, maximum: 2048 });
console.log(`WASM内存配置: ${memory.buffer.byteLength / 1024 / 1024}MB`);
}
}
setupResponsiveDesign() {
// 响应式Canvas调整
const resizeCanvas = () => {
const canvas = document.getElementById('gameCanvas');
const container = canvas.parentElement;
const aspectRatio = 1024 / 768;
let width = container.clientWidth;
let height = width / aspectRatio;
if (height > container.clientHeight) {
height = container.clientHeight;
width = height * aspectRatio;
}
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
// 通知Cocos2d-x尺寸变化
if (window.cc && cc.view) {
cc.view.setDesignResolutionSize(width, height, cc.ResolutionPolicy.SHOW_ALL);
}
};
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
}
// 触摸事件优化(特别是移动设备)
optimizeTouchEvents() {
const canvas = document.getElementById('gameCanvas');
// 防止默认的触摸行为(如页面滚动)
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
}, { passive: false });
// 优化触摸精度
if ('ontouchstart' in window) {
// 移动设备特殊处理
canvas.style.touchAction = 'none';
}
}
}
// 初始化优化器
const optimizer = new WebEventBubbleOptimizer();
optimizer.optimizeTouchEvents();
疑难解答
1. 常见编译问题
Q: 链接错误 "undefined reference to EventListenerTouchOneByOne"
# 在CMakeLists.txt中添加缺失的库
target_link_libraries(${PROJECT_NAME}
cocos2d
cocosdenshion
cocos2dx_internal # 包含事件系统实现
libbox2d # 某些版本需要
)
# 检查Cocos2d-x版本兼容性
if(${COCOS2D_VERSION} VERSION_LESS "3.17")
message(WARNING "建议使用Cocos2d-x 3.17+版本以获得完整的事件系统支持")
endif()
Q: Android平台触摸无响应
// 在AppDelegate.cpp中添加平台特定配置
void AppDelegate::initGLContextAttrs() {
GLContextAttrs glContextAttrs = {8, 8, 8, 8, 24, 8, 0};
// Android平台特殊处理
#ifdef CC_TARGET_OS_ANDROID
// 启用多点触摸支持
glContextAttrs.alphaBits = 8;
glContextAttrs.depthBits = 24;
glContextAttrs.stencilBits = 8;
#endif
GLView::setGLContextAttrs(glContextAttrs);
}
// 确保AndroidManifest.xml包含触摸权限
/*
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.touchscreen" android:required="true" />
*/
2. 运行时问题
Q: 事件监听器不触发或触发异常
// 问题1:监听器作用域问题导致提前释放
class SafeEventListener {
private:
EventListenerTouchOneByOne* _listener;
public:
void addSafeListener(Node* target) {
// 错误做法:局部变量监听器
// auto listener = EventListenerTouchOneByOne::create(); // 会被立即销毁
// 正确做法:成员变量持有引用
_listener = EventListenerTouchOneByOne::create();
_listener->retain(); // 增加引用计数
_listener->onTouchBegan = [this](Touch* touch, Event* event) {
// 处理逻辑
return true;
};
target->getEventDispatcher()->addEventListenerWithSceneGraphPriority(_listener, target);
}
~SafeEventListener() {
if (_listener) {
_listener->release(); // 释放引用
_listener = nullptr;
}
}
};
// 问题2:节点可见性影响事件接收
void fixVisibilityIssue(Node* node) {
// 错误:仅设置visible=false无法阻止事件
// node->setVisible(false);
// 正确:同时暂停触摸事件
node->setVisible(false);
node->pause(); // 暂停节点及其所有子节点的事件处理
// 或者从事件分发器中移除监听器
node->getEventDispatcher()->pauseEventListenersForTarget(node, true);
}
// 问题3:触摸优先级冲突
void resolvePriorityConflict() {
auto dispatcher = Director::getInstance()->getEventDispatcher();
// 设置明确的优先级(数值越小优先级越高)
dispatcher->setPriority(listener1, 100); // 高优先级
dispatcher->setPriority(listener2, 50); // 低优先级
// 或者使用场景图优先级(推荐)
dispatcher->addEventListenerWithSceneGraphPriority(listener, node);
}
Q: 模态对话框背景事件阻断不完全
class RobustModalDialog : public Layer {
private:
bool _blockBackgroundEvents;
EventListenerTouchOneByOne* _blockingListener;
void enableRobustBlocking() {
if (_blockingListener) return;
_blockingListener = EventListenerTouchOneByOne::create();
_blockingListener->setSwallowTouches(true);
// 使用lambda捕获this指针的正确方式
_blockingListener->onTouchBegan = [this](Touch* touch, Event* event) {
if (!_blockBackgroundEvents || !this->isVisible()) {
return false;
}
// 精确判断触摸区域
Point locationInNode = this->convertToNodeSpace(touch->getLocation());
Size dialogSize = this->getContentSize();
Rect dialogRect = Rect(0, 0, dialogSize.width, dialogSize.height);
// 如果点击在对话框内容区域,不阻断(让对话框按钮处理)
if (dialogRect.containsPoint(locationInNode)) {
return false; // 允许事件继续传递到对话框按钮
}
// 点击模态背景,阻断事件并关闭对话框
log("模态背景点击 - 阻断事件并关闭");
this->closeDialog();
return true; // 吞噬事件
};
// 关键:使用全局优先级确保最高优先级
auto dispatcher = Director::getInstance()->getEventDispatcher();
dispatcher->addEventListenerWithFixedPriority(_blockingListener, -999);
}
void closeDialog() {
this->setVisible(false);
this->removeFromParent();
// 清理监听器
if (_blockingListener) {
Director::getInstance()->getEventDispatcher()->removeEventListener(_blockingListener);
_blockingListener = nullptr;
}
}
public:
void setBlockBackgroundEvents(bool block) {
_blockBackgroundEvents = block;
if (block && !_blockingListener) {
enableRobustBlocking();
} else if (!block && _blockingListener) {
Director::getInstance()->getEventDispatcher()->removeEventListener(_blockingListener);
_blockingListener = nullptr;
}
}
};
3. 性能优化问题
Q: 大量UI元素导致事件处理性能下降
class OptimizedEventSystem {
private:
struct EventStatistics {
int totalEvents;
int handledEvents;
float averageProcessingTime;
};
std::map<std::string, EventStatistics> _stats;
public:
// LOD (Level of Detail) 事件处理
enum class EventLOD {
HIGH, // 高精度:所有事件都处理
MEDIUM, // 中精度:仅处理重要事件
LOW // 低精度:批量处理或跳过
};
EventLOD getCurrentLOD() {
// 根据FPS动态调整事件处理精度
float fps = Director::getInstance()->getFramesPerSecond();
if (fps > 55) return EventLOD::HIGH;
if (fps > 30) return EventLOD::MEDIUM;
return EventLOD::LOW;
}
bool shouldProcessEvent(Node* node, EventLOD lod) {
if (!node->isVisible() || !node->isRunning()) {
return false;
}
switch (lod) {
case EventLOD::HIGH:
return true;
case EventLOD::MEDIUM:
// 仅处理可交互元素
return node->getNumberOfRunningActions() > 0 ||
dynamic_cast<CustomButton*>(node) != nullptr;
case EventLOD::LOW:
// 仅处理关键UI元素
return dynamic_cast<ModalDialog*>(node) != nullptr;
default:
return true;
}
}
// 事件批处理优化
void batchProcessEvents(const std::vector<Touch*>& touches) {
auto lod = getCurrentLOD();
// 批量筛选需要处理的节点
std::vector<Node*> candidates;
auto scene = Director::getInstance()->getRunningScene();
collectInteractiveNodes(scene, candidates, lod);
// 批量进行碰撞检测
for (auto touch : touches) {
Point touchPoint = touch->getLocation();
for (auto node : candidates) {
if (shouldProcessEvent(node, lod) && isNodeHit(node, touchPoint)) {
// 处理事件
processNodeEvent(node, touch);
break; // 找到第一个命中节点即停止(事件冒泡控制)
}
}
}
}
private:
void collectInteractiveNodes(Node* root, std::vector<Node*>& results, EventLOD lod) {
if (!root || !shouldProcessEvent(root, lod)) {
return;
}
// 添加当前节点
if (root->getEventDispatcher()->hasEventListenerForType(EventListener::Type::TOUCH_ONE_BY_ONE)) {
results.push_back(root);
}
// 递归收集子节点
for (auto child : root->getChildren()) {
collectInteractiveNodes(child, results, lod);
}
}
bool isNodeHit(Node* node, const Point& point) {
Point nodePoint = node->convertToNodeSpace(point);
Size size = node->getContentSize();
Rect rect(0, 0, size.width, size.height);
return rect.containsPoint(nodePoint);
}
void processNodeEvent(Node* node, Touch* touch) {
// 优化的事件处理逻辑
auto dispatcher = node->getEventDispatcher();
auto event = EventTouch::create(Touch::DispatchMode::ONE_BY_ONE);
event->setTouch(touch);
// 使用时间戳避免重复处理
static std::map<Node*, double> lastProcessTime;
double currentTime = getCurrentTime();
if (lastProcessTime[node] && (currentTime - lastProcessTime[node]) < 0.016) { // 16ms防抖
return;
}
lastProcessTime[node] = currentTime;
dispatcher->dispatchEvent(event);
}
double getCurrentTime() {
auto director = Director::getInstance();
return director->getTotalFrames() * director->getAnimationInterval();
}
};
未来展望
1. 下一代事件处理技术
基于机器学习的智能事件预测
class IntelligentEventPredictor {
private:
struct TouchPattern {
std::vector<Vec2> points;
float velocity;
float acceleration;
std::string predictedAction;
};
std::vector<TouchPattern> _learnedPatterns;
public:
// 基于历史数据预测用户意图
std::string predictUserIntent(const std::vector<Touch*>& recentTouches) {
TouchPattern current;
extractPatternFeatures(recentTouches, current);
// 寻找最相似的历史模式
TouchPattern bestMatch;
float bestSimilarity = 0.0f;
for (const auto& pattern : _learnedPatterns) {
float similarity = calculateSimilarity(current, pattern);
if (similarity > bestSimilarity) {
bestSimilarity = similarity;
bestMatch = pattern;
}
}
// 如果相似度足够高,预测用户意图
if (bestSimilarity > 0.85f) {
return bestMatch.predictedAction;
}
return "unknown";
}
// 自适应事件优先级调整
void adaptEventPriority(Node* node, const std::string& predictedAction) {
if (predictedAction == "drag") {
// 拖拽操作:提高移动事件的优先级
setNodeEventPriority(node, PRIORITY_DRAG_OPERATION);
} else if (predictedAction == "tap") {
// 点击操作:优化响应速度
enableFastTapResponse(node);
}
}
};
空间计算与手势识别集成
class SpatialEventSystem {
public:
// AR/VR环境中的6DOF事件处理
struct SpatialTouchEvent {
Vec3 worldPosition;
Quaternion orientation;
float pressure;
std::vector<Vec3> gesturePath;
};
bool handleSpatialTouch(const SpatialTouchEvent& event) {
// 将3D空间坐标转换为UI平面投影
Vec2 screenPos = projectToScreenPlane(event.worldPosition);
// 在3D空间中检测UI元素碰撞
Node* hitNode = detect3DUICollision(event.worldPosition);
if (hitNode) {
// 转换坐标系并派发事件
auto localPos = hitNode->convertToNodeSpace3D(event.worldPosition);
simulateTouchEvent(hitNode, screenPos, localPos);
return true;
}
return false;
}
// 手势识别与事件映射
void recognizeGesture(const std::vector<SpatialTouchEvent>& events) {
GestureType gesture = analyzeGesturePattern(events);
switch (gesture) {
case GestureType::PINCH:
dispatchCustomEvent("zoom_gesture", events.back().worldPosition);
break;
case GestureType::SWIPE_3D:
dispatchCustomEvent("spatial_swipe", events.back().worldPosition);
break;
case GestureType::GRAB:
dispatchCustomEvent("object_grab", events.back().worldPosition);
break;
}
}
};
2. 新兴技术标准与集成
WebGPU加速的事件计算
class WebGPUEventProcessor {
private:
// GPU并行计算触摸碰撞检测
gpu::ComputePipeline _collisionPipeline;
gpu::Buffer _nodeBoundsBuffer;
gpu::Buffer _touchPointsBuffer;
public:
void initializeWebGPU() {
// 初始化WebGPU设备
gpu::Device device = gpu::Device::create();
// 创建计算管线用于并行碰撞检测
_collisionPipeline = device.createComputePipeline({
.shaderSource = loadCollisionShader(),
.entryPoint = "computeCollisions"
});
// 准备节点边界数据缓冲区
prepareNodeBoundsBuffer();
}
std::vector<Node*> parallelCollisionDetection(const std::vector<Touch*>& touches) {
// 将触摸数据上传到GPU
uploadTouchDataToGPU(touches);
// 执行并行计算
_collisionPipeline.dispatch(
touches.size(), // x维度:触摸点数量
1, // y维度
1 // z维度
);
// 读取GPU计算结果
return readCollisionResultsFromGPU();
}
};
可变刷新率自适应事件处理
class AdaptiveRefreshEventSystem {
private:
float _currentRefreshRate;
float _targetFrameTime;
public:
void adaptToRefreshRate(float newRefreshRate) {
_currentRefreshRate = newRefreshRate;
_targetFrameTime = 1.0f / newRefreshRate;
// 调整事件处理频率
adjustEventProcessingFrequency();
// 优化动画插值精度
optimizeAnimationPrecision();
}
void adjustEventProcessingFrequency() {
auto director = Director::getInstance();
if (_currentRefreshRate > 120) {
// 高刷新率:事件处理与渲染同步
director->setAnimationInterval(_targetFrameTime);
enableHighPrecisionEventTiming();
} else if (_currentRefreshRate > 60) {
// 标准刷新率:保持默认
director->setAnimationInterval(1.0f / 60);
} else {
// 低刷新率:降低事件处理频率以节省CPU
director->setAnimationInterval(1.0f / 30);
enableEventThrottling();
}
}
// 基于刷新率的事件去抖动优化
bool shouldProcessEvent(const std::string& eventType) {
static std::map<std::string, double> lastProcessTime;
double currentTime = getCurrentPreciseTime();
double timeSinceLastProcess = currentTime - lastProcessTime[eventType];
// 根据刷新率动态调整去抖动阈值
float debounceThreshold = _targetFrameTime * 2.0f; // 2帧
if (timeSinceLastProcess >= debounceThreshold) {
lastProcessTime[eventType] = currentTime;
return true;
}
return false;
}
};
技术趋势与挑战
1. 技术发展趋势分析
超大规模UI系统的事件处理
// 面向超大UI系统的分层事件管理
class HierarchicalEventManager {
private:
struct UIRegion {
Rect bounds;
Node* rootNode;
int eventPriority;
bool isActive;
};
QuadTree<UIRegion> _spatialIndex;
std::vector<UIRegion> _activeRegions;
public:
// 使用四叉树优化大规模UI的空间查询
void buildSpatialIndex(Node* root) {
_spatialIndex.clear();
// 递归构建UI区域索引
traverseAndIndexUI(root, Mat4::IDENTITY);
}
std::vector<Node*> findPotentialEventTargets(const Vec2& screenPos) {
// O(log n)空间查询替代O(n)线性搜索
Rect searchArea(screenPos.x - 1, screenPos.y - 1, 2, 2);
auto candidates = _spatialIndex.query(searchArea);
// 按优先级排序
std::sort(candidates.begin(), candidates.end(),
[](const UIRegion& a, const UIRegion& b) {
return a.eventPriority > b.eventPriority;
});
std::vector<Node*> result;
for (const auto& region : candidates) {
if (region.isActive) {
result.push_back(region.rootNode);
}
}
return result;
}
private:
void traverseAndIndexUI(Node* node, const Mat4& transform) {
if (!node->isVisible()) return;
// 计算节点的屏幕空间边界
Rect nodeBounds = calculateScreenBounds(node, transform);
UIRegion region{
.bounds = nodeBounds,
.rootNode = node,
.eventPriority = calculateEventPriority(node),
.isActive = node->isRunning()
};
_spatialIndex.insert(region);
// 递归处理子节点
Mat4 childTransform = transform * node->getNodeToParentAffineTransform();
for (auto child : node->getChildren()) {
traverseAndIndexUI(child, childTransform);
}
}
};
跨平台事件标准化
// 跨平台事件抽象层
class CrossPlatformEventAdapter {
public:
struct UnifiedTouchEvent {
Vec2 position;
Vec2 previousPosition;
float timestamp;
int tapCount;
TouchPhase phase;
Platform platform;
};
// 平台特定事件转换
UnifiedTouchEvent convertNativeEvent(void* nativeEvent, Platform platform) {
switch (platform) {
case Platform::IOS:
return convertIOSEvent(static_cast<UITouch*>(nativeEvent));
case Platform::ANDROID:
return convertAndroidEvent(static_cast<_JNIEnv*>(nativeEvent));
case Platform::WEB:
return convertWebEvent(static_cast<js_event*>(nativeEvent));
case Platform::WINDOWS:
return convertWindowsEvent(static_cast<WPARAM>(nativeEvent));
default:
return UnifiedTouchEvent{};
}
}
// 统一事件处理接口
bool dispatchUnifiedEvent(const UnifiedTouchEvent& event) {
// 应用平台无关的预处理逻辑
auto processedEvent = preprocessEvent(event);
// 派发到Cocos2d-x标准事件系统
return dispatchToCocos2d(processedEvent);
}
private:
UnifiedTouchEvent preprocessEvent(const UnifiedTouchEvent& event) {
UnifiedTouchEvent result = event;
// 统一坐标系(转换为Cocos2d-x坐标系)
result.position = convertToCocosCoordinateSystem(event.position);
// 统一时间基准
result.timestamp = normalizeTimestamp(event.timestamp);
// 应用全局事件修饰符(如缩放、旋转)
applyGlobalModifiers(result);
return result;
}
};
2. 主要技术挑战与解决方案
挑战1:内存受限设备的事件处理优化
// 内存优化的事件系统
class MemoryOptimizedEventSystem {
private:
struct EventPool {
std::vector<std::unique_ptr<Event>> freeEvents;
size_t allocatedCount;
size_t maxPoolSize;
EventPool(size_t maxSize) : allocatedCount(0), maxPoolSize(maxSize) {}
};
std::map<EventType, EventPool> _eventPools;
public:
// 对象池管理事件对象,避免频繁分配内存
Event* acquireEvent(EventType type) {
auto& pool = _eventPools[type];
if (!pool.freeEvents.empty()) {
auto event = std::move(pool.freeEvents.back());
pool.freeEvents.pop_back();
return event.release();
}
// 池为空时,谨慎分配新对象
if (pool.allocatedCount < pool.maxPoolSize) {
pool.allocatedCount++;
return createNewEvent(type);
}
// 达到内存上限,强制回收最老的未使用事件
forceGarbageCollection();
return acquireEvent(type); // 递归重试
}
void releaseEvent(Event* event) {
if (!event) return;
auto& pool = _eventPools[event->getType()];
// 重置事件状态
event->reset();
// 返回池中重用
pool.freeEvents.emplace_back(event);
}
void forceGarbageCollection() {
for (auto& pair : _eventPools) {
auto& pool = pair.second;
// 保留最近使用的25%,释放其余的
size_t keepCount = pool.maxPoolSize / 4;
if (pool.freeEvents.size() > keepCount) {
size_t removeCount = pool.freeEvents.size() - keepCount;
pool.freeEvents.erase(
pool.freeEvents.begin(),
pool.freeEvents.begin() + removeCount
);
pool.allocatedCount -= removeCount;
}
}
}
};
挑战2:多触点手势识别的复杂性
// 高级多触点手势识别系统
class AdvancedGestureRecognizer {
public:
enum class GestureType {
TAP, DOUBLE_TAP, LONG_PRESS,
SWIPE_LEFT, SWIPE_RIGHT, SWIPE_UP, SWIPE_DOWN,
PINCH_IN, PINCH_OUT, ROTATE_CW, ROTATE_CCW,
TWO_FINGER_TAP, THREE_FINGER_SWIPE
};
struct GestureResult {
GestureType type;
float confidence;
std::map<std::string, float> parameters; // 如速度、角度、距离等
TimeStamp timestamp;
};
// 实时手势识别
GestureResult recognizeGesture(const std::vector<Touch*>& currentTouches,
const std::vector<TouchHistory>& touchHistories) {
GestureResult bestResult;
float bestConfidence = 0.0f;
// 并行评估所有可能的手势
std::vector<std::future<GestureResult>> futures;
futures.push_back(std::async(&AdvancedGestureRecognizer::recognizeTapGestures, this,
currentTouches, touchHistories));
futures.push_back(std::async(&AdvancedGestureRecognizer::recognizePinchGestures, this,
currentTouches, touchHistories));
futures.push_back(std::async(&AdvancedGestureRecognizer::recognizeSwipeGestures, this,
currentTouches, touchHistories));
// ... 其他手势识别器
// 收集并选择最佳结果
for (auto& future : futures) {
GestureResult result = future.get();
if (result.confidence > bestConfidence) {
bestConfidence = result.confidence;
bestResult = result;
}
}
return bestResult.confidence > CONFIDENCE_THRESHOLD ? bestResult : GestureResult{};
}
private:
GestureResult recognizePinchGestures(const std::vector<Touch*>& touches,
const std::vector<TouchHistory>& histories) {
if (touches.size() != 2) {
return {}; // 不是双指手势
}
// 计算两指间距离变化
float initialDistance = calculateInitialDistance(histories);
float currentDistance = calculateCurrentDistance(touches);
float scaleFactor = currentDistance / initialDistance;
GestureResult result;
if (scaleFactor > 1.2f && scaleFactor < 3.0f) {
result.type = GestureType::PINCH_OUT;
result.confidence = calculatePinchConfidence(scaleFactor, touches);
result.parameters["scale"] = scaleFactor;
} else if (scaleFactor < 0.8f && scaleFactor > 0.3f) {
result.type = GestureType::PINCH_IN;
result.confidence = calculatePinchConfidence(1.0f/scaleFactor, touches);
result.parameters["scale"] = 1.0f/scaleFactor;
}
return result;
}
float calculatePinchConfidence(float scaleMagnitude, const std::vector<Touch*>& touches) {
// 基于缩放幅度、速度、持续时间计算置信度
float speed = calculateGestureSpeed(touches);
float duration = calculateGestureDuration(touches);
// 置信度公式:综合考虑多个因素
float magnitudeScore = std::min(scaleMagnitude / 2.0f, 1.0f);
float speedScore = std::min(speed / 1000.0f, 1.0f); // 归一化速度
float durationScore = std::exp(-duration / 2.0f); // 较短持续时间更可信
return (magnitudeScore * 0.5f + speedScore * 0.3f + durationScore * 0.2f);
}
};
总结
核心技术掌握要点
-
事件系统架构理解:深入理解了EventDispatcher、EventListener和Event对象的协作机制,掌握了Cocos2d-x事件传播的三个阶段(虽然实际主要支持冒泡阶段)。 -
事件控制技术:熟练掌握了 setSwallowTouches、stopPropagation等关键方法的用法,能够根据具体需求精确控制事件的传播行为。 -
实际应用场景:通过5个完整的演示场景(基础冒泡、阻止传播、吞噬触摸、嵌套容器、模态对话框),深入理解了不同场景下的事件处理策略。 -
性能优化意识:学会了使用对象池、事件LOD、空间索引等技术优化大规模UI系统的事件处理性能。 -
跨平台考量:了解了不同平台(iOS、Android、Web、Desktop)在事件处理上的差异和相应的适配策略。
实际应用价值
-
商业游戏开发:构建复杂的游戏菜单系统、HUD界面、模态对话框等 -
企业级应用:开发跨平台的UI密集型应用程序 -
教育培训软件:创建交互性强的学习工具和模拟系统 -
创意多媒体项目:实现富有表现力的交互式艺术作品
代码质量与最佳实践
-
内存管理:正确使用了retain/release机制和智能指针,避免内存泄漏 -
错误处理:包含了完善的错误检查和异常处理逻辑 -
性能优化:实现了对象池、事件节流、空间索引等优化技术 -
可维护性:代码结构清晰,注释完整,易于扩展和维护 -
跨平台兼容:考虑了不同平台的特性和限制
持续学习与发展方向
-
AI驱动的事件预测:利用机器学习预测用户交互意图,提前优化事件处理策略 -
空间计算集成:结合AR/VR技术,处理3D空间中的6DOF事件 -
WebAssembly优化:使用WASM提升Web平台的事件处理性能 -
可变刷新率适配:根据设备刷新率动态调整事件处理频率 -
无障碍访问:为残障用户提供等效的事件交互体验
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)