Cocos2dx UI组件:按钮(Button)、标签(Label)、进度条(LoadingBar)详解
【摘要】 引言在游戏开发中,用户界面(UI)是连接玩家与游戏世界的桥梁。优秀的UI设计能显著提升用户体验,而按钮、标签和进度条则是构建游戏UI的基础组件。Cocos2dx作为成熟的跨平台游戏引擎,提供了功能强大且易于使用的UI组件系统。本文将深入探讨Cocos2dx中按钮(Button)、标签(Label)和进度条(LoadingBar)的实现原理与应用技巧,帮助开发者构建直观、美观且高效的游戏界面。...
引言
技术背景
UI组件在游戏中的作用
-
信息传递:显示游戏状态、分数、生命值等关键信息 -
操作控制:提供玩家与游戏交互的入口 -
视觉引导:引导玩家注意力,提升游戏体验 -
反馈机制:响应玩家操作,提供视觉/听觉反馈 -
叙事辅助:推进游戏剧情,展示对话内容
Cocos2dx UI系统架构
UI Tree
├── Canvas (根节点)
│ ├── Layout (布局容器)
│ │ ├── Button (按钮)
│ │ ├── Label (标签)
│ │ ├── LoadingBar (进度条)
│ │ └── ...
│ └── ...
组件继承关系
classDiagram
Widget <|-- Button
Widget <|-- Label
Widget <|-- LoadingBar
Widget <|-- ImageView
Widget <|-- Layout
class Widget {
+setPosition(Vec2)
+setContentSize(Size)
+setAnchorPoint(Vec2)
+addTouchEventListener()
+setVisible(bool)
+setEnabled(bool)
}
class Button {
+loadTextureNormal(string)
+loadTexturePressed(string)
+loadTextureDisabled(string)
+setTitleText(string)
+setTitleFontSize(float)
+addClickEventListener()
}
class Label {
+setString(string)
+setSystemFontName(string)
+setSystemFontSize(float)
+setTTFConfig(TTFConfig)
+setTextColor(Color4B)
}
class LoadingBar {
+loadTexture(string)
+setDirection(Direction)
+setPercent(float)
+setScale9Enabled(bool)
}
应用使用场景
按钮(Button)应用场景
-
游戏菜单:开始、设置、退出等选项 -
功能开关:音效/音乐开关、暂停/继续 -
物品交互:拾取、使用、装备物品 -
技能释放:主动技能按钮 -
导航控制:翻页、切换标签页
标签(Label)应用场景
-
状态显示:分数、生命值、金币数量 -
对话系统:NPC对话内容 -
提示信息:任务目标、操作指引 -
数据统计:伤害输出、战斗统计 -
多语言支持:国际化文本显示
进度条(LoadingBar)应用场景
-
资源加载:场景切换加载进度 -
角色状态:生命值、魔法值、耐力值 -
技能冷却:技能恢复倒计时 -
任务进度:收集/杀敌进度 -
时间限制:限时任务倒计时
不同场景下详细代码实现
场景1:按钮(Button)实现
// ButtonExample.h
#ifndef __BUTTON_EXAMPLE_H__
#define __BUTTON_EXAMPLE_H__
#include "cocos2d.h"
#include "ui/CocosGUI.h"
class ButtonExample : public cocos2d::Layer {
public:
static cocos2d::Scene* createScene();
virtual bool init() override;
CREATE_FUNC(ButtonExample);
void onButtonClicked(cocos2d::Ref* sender, cocos2d::ui::Widget::TouchEventType type);
void onToggleClicked(cocos2d::Ref* sender, cocos2d::ui::Widget::TouchEventType type);
void onScaleButtonClicked(cocos2d::Ref* sender);
private:
cocos2d::ui::Button* _normalButton;
cocos2d::ui::Button* _toggleButton;
cocos2d::ui::Button* _scaleButton;
cocos2d::Label* _statusLabel;
};
#endif // __BUTTON_EXAMPLE_H__
// ButtonExample.cpp
#include "ButtonExample.h"
#include "ui/CocosGUI.h"
USING_NS_CC;
Scene* ButtonExample::createScene() {
auto scene = Scene::create();
auto layer = ButtonExample::create();
scene->addChild(layer);
return scene;
}
bool ButtonExample::init() {
if (!Layer::init()) {
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 创建状态标签
_statusLabel = Label::createWithTTF("按钮状态: 等待操作", "fonts/Marker Felt.ttf", 24);
_statusLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 100));
this->addChild(_statusLabel, 1);
// 普通按钮
_normalButton = ui::Button::create("button_normal.png", "button_pressed.png", "button_disabled.png");
_normalButton->setTitleText("普通按钮");
_normalButton->setTitleFontSize(24);
_normalButton->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 200));
_normalButton->addTouchEventListener(CC_CALLBACK_2(ButtonExample::onButtonClicked, this));
this->addChild(_normalButton, 1);
// 切换按钮
_toggleButton = ui::Button::create("toggle_off.png", "toggle_on.png");
_toggleButton->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 300));
_toggleButton->addTouchEventListener(CC_CALLBACK_2(ButtonExample::onToggleClicked, this));
this->addChild(_toggleButton, 1);
// 缩放按钮
_scaleButton = ui::Button::create("button_normal.png", "button_pressed.png");
_scaleButton->setTitleText("缩放按钮");
_scaleButton->setTitleFontSize(24);
_scaleButton->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 400));
_scaleButton->addClickEventListener(CC_CALLBACK_1(ButtonExample::onScaleButtonClicked, this));
this->addChild(_scaleButton, 1);
return true;
}
void ButtonExample::onButtonClicked(Ref* sender, ui::Widget::TouchEventType type) {
switch (type) {
case ui::Widget::TouchEventType::BEGAN:
_statusLabel->setString("按钮状态: 按下");
break;
case ui::Widget::TouchEventType::ENDED:
_statusLabel->setString("按钮状态: 抬起");
break;
case ui::Widget::TouchEventType::CANCELED:
_statusLabel->setString("按钮状态: 取消");
break;
default:
break;
}
}
void ButtonExample::onToggleClicked(Ref* sender, ui::Widget::TouchEventType type) {
if (type == ui::Widget::TouchEventType::ENDED) {
if (_toggleButton->isSelected()) {
_statusLabel->setString("切换按钮: 开启");
_toggleButton->loadTextureBackGround("toggle_on.png");
} else {
_statusLabel->setString("切换按钮: 关闭");
_toggleButton->loadTextureBackGround("toggle_off.png");
}
}
}
void ButtonExample::onScaleButtonClicked(Ref* sender) {
auto scaleTo = ScaleTo::create(0.2f, 1.2f);
auto scaleBack = ScaleTo::create(0.2f, 1.0f);
auto sequence = Sequence::create(scaleTo, scaleBack, nullptr);
_scaleButton->runAction(sequence);
_statusLabel->setString("缩放按钮: 点击效果");
}
场景2:标签(Label)实现
// LabelExample.h
#ifndef __LABEL_EXAMPLE_H__
#define __LABEL_EXAMPLE_H__
#include "cocos2d.h"
#include "ui/CocosGUI.h"
class LabelExample : public cocos2d::Layer {
public:
static cocos2d::Scene* createScene();
virtual bool init() override;
CREATE_FUNC(LabelExample);
void updateScore(float dt);
private:
cocos2d::ui::Label* _ttfLabel;
cocos2d::ui::Label* _bmfontLabel;
cocos2d::ui::Label* _systemLabel;
cocos2d::ui::Label* _outlineLabel;
cocos2d::ui::Label* _shadowLabel;
cocos2d::ui::Label* _gradientLabel;
int _score;
};
#endif // __LABEL_EXAMPLE_H__
// LabelExample.cpp
#include "LabelExample.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
Scene* LabelExample::createScene() {
auto scene = Scene::create();
auto layer = LabelExample::create();
scene->addChild(layer);
return scene;
}
bool LabelExample::init() {
if (!Layer::init()) {
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
_score = 0;
// TTF字体标签
_ttfLabel = ui::Label::createWithTTF("TTF字体标签", "fonts/Marker Felt.ttf", 32);
_ttfLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 100));
_ttfLabel->setTextColor(Color4B::WHITE);
this->addChild(_ttfLabel, 1);
// BMFont标签
_bmfontLabel = ui::Label::createWithBMFont("fonts/bitmapFont.fnt", "BMFont标签");
_bmfontLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 150));
this->addChild(_bmfontLabel, 1);
// 系统字体标签
_systemLabel = ui::Label::createWithSystemFont("系统字体标签", "Arial", 32);
_systemLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 200));
_systemLabel->setTextColor(Color4B(255, 200, 0, 255));
this->addChild(_systemLabel, 1);
// 带轮廓的标签
_outlineLabel = ui::Label::createWithTTF("轮廓标签", "fonts/Marker Felt.ttf", 32);
_outlineLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 250));
_outlineLabel->enableOutline(Color4B::BLUE, 2);
this->addChild(_outlineLabel, 1);
// 带阴影的标签
_shadowLabel = ui::Label::createWithTTF("阴影标签", "fonts/Marker Felt.ttf", 32);
_shadowLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 300));
_shadowLabel->enableShadow(Color4B(0, 0, 0, 128), Size(2, -2), 0);
this->addChild(_shadowLabel, 1);
// 渐变标签
_gradientLabel = ui::Label::createWithTTF("渐变标签", "fonts/Marker Felt.ttf", 32);
_gradientLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 350));
_gradientLabel->enableGradient(Color3B::RED, Color3B::YELLOW);
this->addChild(_gradientLabel, 1);
// 分数标签
auto scoreLabel = ui::Label::createWithTTF("分数: 0", "fonts/Marker Felt.ttf", 28);
scoreLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 400));
scoreLabel->setTag(100); // 设置标签用于后续查找
this->addChild(scoreLabel, 1);
// 定时更新分数
schedule(CC_SCHEDULE_SELECTOR(LabelExample::updateScore), 1.0f);
return true;
}
void LabelExample::updateScore(float dt) {
_score += 10;
auto scoreLabel = static_cast<ui::Label*>(getChildByTag(100));
if (scoreLabel) {
scoreLabel->setString(StringUtils::format("分数: %d", _score));
// 分数变化时添加动画效果
auto scaleUp = ScaleTo::create(0.1f, 1.2f);
auto scaleDown = ScaleTo::create(0.1f, 1.0f);
scoreLabel->runAction(Sequence::create(scaleUp, scaleDown, nullptr));
}
}
场景3:进度条(LoadingBar)实现
// LoadingBarExample.h
#ifndef __LOADING_BAR_EXAMPLE_H__
#define __LOADING_BAR_EXAMPLE_H__
#include "cocos2d.h"
#include "ui/CocosGUI.h"
class LoadingBarExample : public cocos2d::Layer {
public:
static cocos2d::Scene* createScene();
virtual bool init() override;
CREATE_FUNC(LoadingBarExample);
void updateProgressBar(float dt);
void onStartButtonClicked(cocos2d::Ref* sender);
private:
cocos2d::ui::LoadingBar* _horizontalBar;
cocos2d::ui::LoadingBar* _verticalBar;
cocos2d::ui::LoadingBar* _customBar;
cocos2d::ui::Button* _startButton;
float _progress;
bool _isAnimating;
};
#endif // __LOADING_BAR_EXAMPLE_H__
// LoadingBarExample.cpp
#include "LoadingBarExample.h"
USING_NS_CC;
Scene* LoadingBarExample::createScene() {
auto scene = Scene::create();
auto layer = LoadingBarExample::create();
scene->addChild(layer);
return scene;
}
bool LoadingBarExample::init() {
if (!Layer::init()) {
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
_progress = 0.0f;
_isAnimating = false;
// 水平进度条
_horizontalBar = ui::LoadingBar::create("loadingbar_bg.png", "loadingbar_fg.png");
_horizontalBar->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 150));
_horizontalBar->setPercent(0);
_horizontalBar->setScale9Enabled(true);
_horizontalBar->setCapInsets(Rect(2, 2, 12, 12));
this->addChild(_horizontalBar, 1);
// 垂直进度条
_verticalBar = ui::LoadingBar::create("loadingbar_bg.png", "loadingbar_fg.png");
_verticalBar->setPosition(Vec2(origin.x + visibleSize.width/2 - 200,
origin.y + visibleSize.height - 300));
_verticalBar->setDirection(ui::LoadingBar::Direction::VERTICAL);
_verticalBar->setPercent(0);
_verticalBar->setScale9Enabled(true);
this->addChild(_verticalBar, 1);
// 自定义样式进度条
_customBar = ui::LoadingBar::create();
_customBar->setPosition(Vec2(origin.x + visibleSize.width/2 + 200,
origin.y + visibleSize.height - 300));
_customBar->loadTexture("progress_striped.png", ui::Widget::TextureResType::LOCAL);
_customBar->setPercent(0);
_customBar->setScale9Enabled(true);
this->addChild(_customBar, 1);
// 开始按钮
_startButton = ui::Button::create("button_normal.png", "button_pressed.png");
_startButton->setTitleText("开始加载");
_startButton->setTitleFontSize(24);
_startButton->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 400));
_startButton->addClickEventListener(CC_CALLBACK_1(LoadingBarExample::onStartButtonClicked, this));
this->addChild(_startButton, 1);
// 进度标签
auto progressLabel = ui::Label::createWithTTF("0%", "fonts/Marker Felt.ttf", 24);
progressLabel->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 200));
progressLabel->setTag(101);
this->addChild(progressLabel, 1);
return true;
}
void LoadingBarExample::onStartButtonClicked(Ref* sender) {
if (!_isAnimating) {
_isAnimating = true;
_progress = 0.0f;
_startButton->setTitleText("加载中...");
_startButton->setEnabled(false);
schedule(CC_SCHEDULE_SELECTOR(LoadingBarExample::updateProgressBar), 0.05f);
}
}
void LoadingBarExample::updateProgressBar(float dt) {
_progress += 1.0f;
if (_progress > 100.0f) {
_progress = 100.0f;
unschedule(CC_SCHEDULE_SELECTOR(LoadingBarExample::updateProgressBar));
_isAnimating = false;
_startButton->setTitleText("完成!");
// 完成后启用按钮
this->scheduleOnce([this](float dt) {
_startButton->setTitleText("重新开始");
_startButton->setEnabled(true);
}, 1.0f, "enable_button");
}
_horizontalBar->setPercent(_progress);
_verticalBar->setPercent(_progress);
_customBar->setPercent(_progress);
// 更新进度标签
auto progressLabel = static_cast<ui::Label*>(getChildByTag(101));
if (progressLabel) {
progressLabel->setString(StringUtils::format("%.0f%%", _progress));
}
}
原理解释
按钮(Button)工作原理
-
状态管理: -
Normal(正常状态) -
Pressed(按下状态) -
Disabled(禁用状态) -
Selected(选中状态,用于切换按钮)
-
-
事件处理流程: graph TD A[触摸开始] --> B{触摸点在按钮内?} B -- 是 --> C[切换到Pressed状态] C --> D[触发BEGAN事件] B -- 否 --> E[保持原状态] D --> F[触摸移动] F --> G{仍在按钮内?} G -- 是 --> H[保持Pressed状态] G -- 否 --> I[切换到Normal状态] I --> J[触发CANCELED事件] F --> K[触摸结束] K --> L{结束点在按钮内?} L -- 是 --> M[触发ENDED事件] L -- 否 --> N[触发CANCELED事件] M --> O[执行点击回调] -
渲染机制: -
使用三张图片分别表示不同状态 -
通过精灵(Sprite)组件渲染 -
标题文本使用Label组件叠加显示
-
标签(Label)工作原理
-
字体渲染引擎: -
TTF字体:基于FreeType库渲染矢量字体 -
BMFont:位图字体,使用预渲染的字符图像 -
SystemFont:使用系统字体渲染
-
-
文本布局算法: -
自动换行:根据内容宽度分割文本 -
对齐方式:左对齐、居中、右对齐 -
行间距:控制多行文本的行距
-
-
特效实现: -
轮廓(Outline):在文本边缘绘制轮廓色 -
阴影(Shadow):在文本下方绘制偏移阴影 -
渐变(Gradient):在文本上应用垂直/水平渐变
-
进度条(LoadingBar)工作原理
-
渲染机制: -
背景:完整的进度条背景图 -
前景:表示进度的部分,通过改变宽度/高度实现 -
九宫格缩放:支持非均匀拉伸
-
-
方向控制: -
LEFT:从左到右(默认) -
RIGHT:从右到左 -
UP:从下到上 -
DOWN:从上到下
-
-
进度计算: 前景宽度 = 背景宽度 × (当前进度百分比 / 100) 前景高度 = 背景高度 × (当前进度百分比 / 100)
核心特性
按钮(Button)核心特性
-
多状态支持:正常、按下、禁用、选中 -
标题定制:文本内容、字体、大小、颜色 -
缩放效果:点击时的缩放动画 -
九宫格缩放:适应不同尺寸的按钮 -
事件回调:支持多种触摸事件类型 -
禁用状态:视觉变灰且不可交互
标签(Label)核心特性
-
多字体支持:TTF、BMFont、SystemFont -
文本样式:颜色、透明度、轮廓、阴影、渐变 -
自动换行:根据宽度自动分割文本 -
对齐控制:左/中/右/顶部/底部对齐 -
性能优化:文本缓存机制 -
多语言支持:UTF-8编码支持
进度条(LoadingBar)核心特性
-
方向控制:水平和垂直方向 -
进度范围:0-100%可调 -
九宫格缩放:适应不同尺寸 -
自定义纹理:支持任意图片作为进度条 -
平滑动画:进度变化平滑过渡 -
反向进度:支持从满到零的倒计时
原理流程图及解释
按钮事件处理流程图
graph TD
A[触摸开始] --> B{触点进入按钮区域?}
B -- 是 --> C[设置按下状态]
C --> D[触发BEGAN事件]
D --> E[触摸移动]
E --> F{触点仍在区域内?}
F -- 是 --> G[保持按下状态]
F -- 否 --> H[恢复普通状态]
H --> I[触发CANCELED事件]
E --> J[触摸结束]
J --> K{结束点在区域内?}
K -- 是 --> L[触发ENDED事件]
K -- 否 --> M[触发CANCELED事件]
L --> N[执行点击回调]
N --> O[切换选中状态]
O --> P[结束]
-
当用户触摸屏幕时,系统检测触点是否在按钮区域内 -
如果是,按钮切换到按下状态并触发BEGAN事件 -
触点移动时,持续检测是否在按钮区域内 -
如果移出区域,按钮恢复普通状态并触发CANCELED事件 -
触摸结束时,如果在区域内则触发ENDED事件并执行点击回调 -
对于切换按钮,会切换选中状态
标签渲染流程图
graph TD
A[设置文本内容] --> B{选择字体类型}
B -- TTF --> C[加载TTF字体文件]
B -- BMFont --> D[加载BMFont配置文件]
B -- SystemFont --> E[使用系统字体]
C --> F[解析字形轮廓]
D --> G[加载字符位图]
E --> H[调用系统渲染API]
F --> I[生成纹理图集]
G --> I
H --> I
I --> J[创建精灵显示文本]
J --> K[应用文本样式]
K --> L[添加到场景]
-
根据设置的字体类型选择渲染路径 -
TTF字体:加载字体文件,解析字形轮廓,生成纹理 -
BMFont:加载配置文件和位图,提取字符图像 -
系统字体:调用操作系统字体渲染API -
合并所有字符到纹理图集 -
创建精灵显示文本 -
应用轮廓、阴影等样式效果 -
最终添加到场景树中
进度条更新流程图
graph TD
A[设置进度百分比] --> B{方向类型}
B -- 水平 --> C[计算前景宽度]
B -- 垂直 --> D[计算前景高度]
C --> E[更新前景精灵尺寸]
D --> E
E --> F[更新纹理坐标]
F --> G[标记脏矩形]
G --> H[下一帧渲染]
-
设置新的进度百分比值 -
根据进度条方向计算前景尺寸 -
水平方向:宽度 = 背景宽度 × (百分比/100) -
垂直方向:高度 = 背景高度 × (百分比/100) -
更新前景精灵的尺寸 -
调整纹理坐标确保正确显示 -
标记脏矩形通知渲染系统更新 -
在下一帧渲染时更新显示
环境准备
开发环境要求
-
引擎版本:Cocos2dx v3.17+ 或 v4.x -
编程语言:C++11 或更高 -
开发工具: -
Windows: Visual Studio 2019+ -
macOS: Xcode 11+ -
Android: Android Studio + NDK -
iOS: Xcode + iOS SDK
-
-
依赖资源: -
按钮图片:normal/pressed/disabled状态 -
字体文件:TTF/BMFont格式 -
进度条图片:背景和前景图
-
安装与配置步骤
-
下载Cocos2dx引擎: git clone https://github.com/cocos2d/cocos2d-x.git cd cocos2d-x python download-deps.py -
创建新项目: cocos new UIComponentsDemo -p com.yourcompany.uicomp -l cpp -d ./projects -
添加UI组件示例类: -
创建 Classes/ButtonExample.h和Classes/ButtonExample.cpp -
创建 Classes/LabelExample.h和Classes/LabelExample.cpp -
创建 Classes/LoadingBarExample.h和Classes/LoadingBarExample.cpp
-
-
准备资源文件: -
按钮图片: button_normal.png,button_pressed.png,button_disabled.png -
字体文件: Marker Felt.ttf,bitmapFont.fnt及相关图片 -
进度条图片: loadingbar_bg.png,loadingbar_fg.png
-
-
配置项目属性: -
包含路径添加Classes目录 -
资源文件添加到Resources目录 -
链接必要的库文件
-
实际详细应用代码示例实现
主场景实现(整合所有组件)
// MainScene.h
#ifndef __MAIN_SCENE_H__
#define __MAIN_SCENE_H__
#include "cocos2d.h"
#include "ui/CocosGUI.h"
#include "ButtonExample.h"
#include "LabelExample.h"
#include "LoadingBarExample.h"
class MainScene : public cocos2d::Layer {
public:
static cocos2d::Scene* createScene();
virtual bool init() override;
CREATE_FUNC(MainScene);
void onButtonModeClicked(cocos2d::Ref* sender);
void onLabelModeClicked(cocos2d::Ref* sender);
void onLoadingBarModeClicked(cocos2d::Ref* sender);
private:
cocos2d::ui::Button* _buttonModeBtn;
cocos2d::ui::Button* _labelModeBtn;
cocos2d::ui::Button* _loadingBarModeBtn;
cocos2d::Layer* _currentDemo;
};
#endif // __MAIN_SCENE_H__
// MainScene.cpp
#include "MainScene.h"
USING_NS_CC;
Scene* MainScene::createScene() {
auto scene = Scene::create();
auto layer = MainScene::create();
scene->addChild(layer);
return scene;
}
bool MainScene::init() {
if (!Layer::init()) {
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 标题
auto title = Label::createWithTTF("Cocos2dx UI组件演示", "fonts/Marker Felt.ttf", 48);
title->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 80));
this->addChild(title, 1);
// 按钮模式按钮
_buttonModeBtn = ui::Button::create("button_normal.png", "button_pressed.png");
_buttonModeBtn->setTitleText("按钮演示");
_buttonModeBtn->setTitleFontSize(28);
_buttonModeBtn->setPosition(Vec2(origin.x + visibleSize.width/2 - 200,
origin.y + visibleSize.height - 180));
_buttonModeBtn->addClickEventListener(CC_CALLBACK_1(MainScene::onButtonModeClicked, this));
this->addChild(_buttonModeBtn, 1);
// 标签模式按钮
_labelModeBtn = ui::Button::create("button_normal.png", "button_pressed.png");
_labelModeBtn->setTitleText("标签演示");
_labelModeBtn->setTitleFontSize(28);
_labelModeBtn->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height - 180));
_labelModeBtn->addClickEventListener(CC_CALLBACK_1(MainScene::onLabelModeClicked, this));
this->addChild(_labelModeBtn, 1);
// 进度条模式按钮
_loadingBarModeBtn = ui::Button::create("button_normal.png", "button_pressed.png");
_loadingBarModeBtn->setTitleText("进度条演示");
_loadingBarModeBtn->setTitleFontSize(28);
_loadingBarModeBtn->setPosition(Vec2(origin.x + visibleSize.width/2 + 200,
origin.y + visibleSize.height - 180));
_loadingBarModeBtn->addClickEventListener(CC_CALLBACK_1(MainScene::onLoadingBarModeClicked, this));
this->addChild(_loadingBarModeBtn, 1);
// 默认显示按钮演示
onButtonModeClicked(nullptr);
return true;
}
void MainScene::onButtonModeClicked(Ref* sender) {
if (_currentDemo) {
_currentDemo->removeFromParent();
}
_currentDemo = ButtonExample::create();
this->addChild(_currentDemo, 0);
// 更新按钮状态
_buttonModeBtn->setBrightStyle(ui::Button::BrightStyle::HIGHLIGHT);
_labelModeBtn->setBrightStyle(ui::Button::BrightStyle::NORMAL);
_loadingBarModeBtn->setBrightStyle(ui::Button::BrightStyle::NORMAL);
}
void MainScene::onLabelModeClicked(Ref* sender) {
if (_currentDemo) {
_currentDemo->removeFromParent();
}
_currentDemo = LabelExample::create();
this->addChild(_currentDemo, 0);
// 更新按钮状态
_buttonModeBtn->setBrightStyle(ui::Button::BrightStyle::NORMAL);
_labelModeBtn->setBrightStyle(ui::Button::BrightStyle::HIGHLIGHT);
_loadingBarModeBtn->setBrightStyle(ui::Button::BrightStyle::NORMAL);
}
void MainScene::onLoadingBarModeClicked(Ref* sender) {
if (_currentDemo) {
_currentDemo->removeFromParent();
}
_currentDemo = LoadingBarExample::create();
this->addChild(_currentDemo, 0);
// 更新按钮状态
_buttonModeBtn->setBrightStyle(ui::Button::BrightStyle::NORMAL);
_labelModeBtn->setBrightStyle(ui::Button::BrightStyle::NORMAL);
_loadingBarModeBtn->setBrightStyle(ui::Button::BrightStyle::HIGHLIGHT);
}
AppDelegate集成
// AppDelegate.cpp
#include "AppDelegate.h"
#include "MainScene.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) {
glview = GLViewImpl::create("UI Components Demo");
director->setOpenGLView(glview);
}
director->setDisplayStats(true);
director->setAnimationInterval(1.0f / 60);
// 创建主场景
auto scene = MainScene::createScene();
director->runWithScene(scene);
return true;
}
void AppDelegate::applicationDidEnterBackground() {
Director::getInstance()->stopAnimation();
}
void AppDelegate::applicationWillEnterForeground() {
Director::getInstance()->startAnimation();
}
运行结果
主界面
Cocos2dx UI组件演示
[按钮演示] [标签演示] [进度条演示]
按钮演示界面
-
普通按钮:点击时显示不同状态 -
切换按钮:点击切换开/关状态 -
缩放按钮:点击时有缩放动画 -
状态标签:显示当前按钮操作状态
标签演示界面
-
TTF标签:使用TrueType字体渲染 -
BMFont标签:使用位图字体渲染 -
系统标签:使用系统字体渲染 -
轮廓标签:带蓝色轮廓的文本 -
阴影标签:带黑色阴影的文本 -
渐变标签:红黄渐变文本 -
分数标签:动态更新的分数显示
进度条演示界面
-
水平进度条:从左到右填充 -
垂直进度条:从下到上填充 -
自定义进度条:条纹样式进度条 -
开始按钮:触发进度条动画 -
进度标签:显示当前进度百分比
测试步骤以及详细代码
测试步骤
-
创建Cocos2dx项目并添加上述代码 -
准备所需资源文件(图片、字体) -
编译并运行项目 -
在主界面切换不同演示模式 -
测试各组件的视觉效果和交互功能 -
验证不同平台下的显示一致性
单元测试代码
// TestUIComponents.cpp
#include "gtest/gtest.h"
#include "ui/CocosGUI.h"
USING_NS_CC;
TEST(ButtonTest, CreationAndProperties) {
auto button = ui::Button::create("btn_normal.png", "btn_pressed.png", "btn_disabled.png");
EXPECT_NE(button, nullptr);
button->setTitleText("Test Button");
EXPECT_EQ(button->getTitleText(), "Test Button");
button->setTitleFontSize(24);
EXPECT_EQ(button->getTitleFontSize(), 24);
button->setEnabled(false);
EXPECT_FALSE(button->isEnabled());
button->setBright(false);
EXPECT_FALSE(button->isBright());
}
TEST(LabelTest, TextRendering) {
// TTF标签测试
auto ttfLabel = ui::Label::createWithTTF("TTF Text", "fonts/Marker Felt.ttf", 24);
EXPECT_NE(ttfLabel, nullptr);
EXPECT_EQ(ttfLabel->getString(), "TTF Text");
// 系统字体标签测试
auto sysLabel = ui::Label::createWithSystemFont("System Font", "Arial", 24);
EXPECT_NE(sysLabel, nullptr);
EXPECT_EQ(sysLabel->getString(), "System Font");
// 轮廓效果测试
ttfLabel->enableOutline(Color4B::RED, 2);
// 实际项目中可通过截图比较验证
// 阴影效果测试
sysLabel->enableShadow(Color4B::BLACK, Size(2, -2), 0);
// 实际项目中可通过截图比较验证
}
TEST(LoadingBarTest, ProgressControl) {
auto loadingBar = ui::LoadingBar::create();
loadingBar->loadTexture("progress_bg.png", "progress_fg.png");
loadingBar->setPercent(0);
EXPECT_EQ(loadingBar->getPercent(), 0);
loadingBar->setPercent(50);
EXPECT_EQ(loadingBar->getPercent(), 50);
loadingBar->setPercent(100);
EXPECT_EQ(loadingBar->getPercent(), 100);
loadingBar->setDirection(ui::LoadingBar::Direction::VERTICAL);
EXPECT_EQ(loadingBar->getDirection(), ui::LoadingBar::Direction::VERTICAL);
}
部署场景
-
移动平台: -
iOS/Android原生应用 -
跨平台发布(Cocos Play) -
小游戏平台(微信、抖音)
-
-
桌面平台: -
Windows/Mac/Linux客户端 -
WebGL网页游戏 -
Steam/Epic商店发行
-
-
TV平台: -
智能电视应用 -
机顶盒游戏 -
云游戏串流
-
-
嵌入式设备: -
教育平板应用 -
自助终端界面 -
数字标牌系统
-
疑难解答
问题1:按钮状态图片不显示
-
图片路径不正确 -
图片格式不支持 -
未正确设置按钮状态图片
// 正确设置按钮状态图片
auto button = ui::Button::create();
button->loadTextureNormal("button_normal.png", ui::Widget::TextureResType::LOCAL);
button->loadTexturePressed("button_pressed.png", ui::Widget::TextureResType::LOCAL);
button->loadTextureDisabled("button_disabled.png", ui::Widget::TextureResType::LOCAL);
// 检查图片资源是否存在
if (!FileUtils::getInstance()->isFileExist("button_normal.png")) {
CCLOG("错误:找不到按钮图片资源!");
}
问题2:标签字体渲染异常
-
字体文件缺失 -
字符编码问题 -
字体大小设置不当
// 使用系统字体作为后备
auto label = ui::Label::create();
if (FileUtils::getInstance()->isFileExist("fonts/custom_font.ttf")) {
label->setTTFConfig(TTFConfig("fonts/custom_font.ttf", 24));
} else {
label->setSystemFontName("Arial");
label->setSystemFontSize(24);
}
// 设置UTF-8编码文本
label->setString(u8"中文文本测试");
// 检查字体大小
label->setSystemFontSize(24); // 避免过小或过大的字号
问题3:进度条方向错误
-
方向设置错误 -
进度值范围误解
// 明确设置进度条方向
auto loadingBar = ui::LoadingBar::create();
loadingBar->setDirection(ui::LoadingBar::Direction::LEFT); // 从左到右
// 或者
loadingBar->setDirection(ui::LoadingBar::Direction::RIGHT); // 从右到左
// 设置进度值 (0-100)
loadingBar->setPercent(75.0f); // 75%填充
问题4:UI组件位置错乱
-
使用绝对坐标而非相对布局 -
未考虑设计分辨率和屏幕适配
// 使用相对坐标
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
// 水平居中
button->setPosition(Vec2(origin.x + visibleSize.width/2,
origin.y + visibleSize.height/2));
// 使用布局容器
auto layout = ui::LinearLayoutParameter::create();
layout->setGravity(ui::LinearLayoutParameter::LinearGravity::CENTER_VERTICAL);
button->setLayoutParameter(layout);
// 设置设计分辨率适配策略
glview->setDesignResolutionSize(960, 640, ResolutionPolicy::SHOW_ALL);
未来展望
-
声明式UI:基于XML/JSON的UI描述语言 -
数据绑定:UI与数据模型的自动同步 -
自适应布局:根据内容自动调整布局 -
3D UI元素:融合3D效果的UI组件 -
动画集成:内置丰富的UI动画效果 -
AI辅助设计:智能生成UI布局和配色 -
跨平台一致性:统一各平台的UI表现
技术趋势与挑战
趋势
-
组件化开发:可复用的UI组件库 -
热更新支持:运行时更新UI资源 -
无障碍访问:支持屏幕阅读器等辅助技术 -
多模态交互:结合触控、语音、手势输入 -
实时协作UI:多人同时编辑UI界面
挑战
-
性能优化:大量UI元素的渲染效率 -
多分辨率适配:应对碎片化设备生态 -
国际化支持:复杂语言文字排版 -
可访问性:满足不同用户群体的需求 -
设计系统整合:统一设计与开发工作流
总结
-
系统架构: -
深入解析UI组件继承体系 -
揭示组件间的关系与协作机制 -
提供完整的类图和使用示例
-
-
关键技术: -
按钮的多状态管理与事件处理 -
标签的多种字体渲染与特效实现 -
进度条的方向控制与动态更新
-
-
实践方案: -
提供可直接运行的完整代码示例 -
覆盖三种组件的不同应用场景 -
包含详细的测试方法和部署指南
-
-
创新点: -
组件状态切换的完整流程图 -
标签渲染过程的详细分解 -
进度条更新的数学原理说明 -
常见问题的系统性解决方案
-
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)