Cocos2d-x 遮罩(Mask)效果实现(ClippingNode)

举报
William 发表于 2025/11/25 09:48:05 2025/11/25
【摘要】 引言在游戏开发中,遮罩(Mask)效果是实现界面美化和交互反馈的重要手段,广泛应用于UI设计、角色特效、场景过渡等场景。Cocos2d-x作为成熟的跨平台游戏引擎,提供了强大的 ClippingNode组件来实现各种复杂的遮罩效果。通过 ClippingNode,开发者可以轻松创建圆形头像、进度条、聚光灯、文字镂空等视觉效果,极大地丰富了游戏的视觉表现力。本文将深入探讨Cocos2d-x中 ...


引言

在游戏开发中,遮罩(Mask)效果是实现界面美化和交互反馈的重要手段,广泛应用于UI设计、角色特效、场景过渡等场景。Cocos2d-x作为成熟的跨平台游戏引擎,提供了强大的 ClippingNode组件来实现各种复杂的遮罩效果。通过 ClippingNode,开发者可以轻松创建圆形头像、进度条、聚光灯、文字镂空等视觉效果,极大地丰富了游戏的视觉表现力。本文将深入探讨Cocos2d-x中 ClippingNode遮罩技术的实现原理与应用方法,通过多维度代码示例展示其核心逻辑,并探讨背后的技术原理与优化技巧。

一、技术背景

1.1 ClippingNode 核心概念

ClippingNode是Cocos2d-x中实现遮罩效果的专用节点,其工作原理基于 OpenGL模板测试(Stencil Test)​ 技术:
  • 模板缓冲区:GPU维护的一个位平面,用于存储每个像素的模板值
  • 模板测试:在片段着色阶段,根据模板值与参考值的比较决定是否绘制像素
  • 遮罩原理:通过设置模板缓冲区的值,只渲染满足特定条件的像素区域

1.2 遮罩效果的分类

遮罩类型
实现方式
典型应用场景
形状遮罩
使用精灵作为模板
圆形头像、心形按钮
纹理遮罩
使用Alpha通道纹理
渐变遮罩、复杂图案遮罩
进度遮罩
动态调整遮罩范围
血条、能量条、加载进度
动画遮罩
结合时间函数动态变化
呼吸灯效果、闪烁遮罩
反向遮罩
反转遮罩逻辑
聚光灯效果、挖空文字

二、应用使用场景

2.1 UI界面美化

  • 圆形头像:社交游戏中玩家头像显示为圆形
  • 特殊按钮:心形、星形等非矩形按钮样式
  • 进度指示器:技能冷却、资源恢复的进度显示

2.2 游戏特效

  • 聚光灯效果:突出显示特定区域或角色
  • 传送门效果:魔法阵、时空门的视觉表现
  • 能量护盾:角色周围的防护罩可视化

2.3 场景过渡

  • 擦除转场:类似橡皮擦的页面切换效果
  • 百叶窗过渡:水平或垂直的窗帘式场景切换
  • 渐变溶解:基于噪声图的溶解过渡效果

三、不同场景下的代码实现

3.1 场景1:基础形状遮罩(圆形头像)

代码实现

// CircularAvatar.h
#ifndef __CIRCULAR_AVATAR_H__
#define __CIRCULAR_AVATAR_H__

#include "cocos2d.h"

class CircularAvatar : public cocos2d::Node {
public:
    static CircularAvatar* create(const std::string& avatarPath);
    bool init(const std::string& avatarPath);
    
private:
    cocos2d::Sprite* _avatarSprite;
};

#endif

// CircularAvatar.cpp
#include "CircularAvatar.h"

USING_NS_CC;

CircularAvatar* CircularAvatar::create(const std::string& avatarPath) {
    auto* node = new CircularAvatar();
    if (node && node->init(avatarPath)) {
        node->autorelease();
        return node;
    }
    CC_SAFE_DELETE(node);
    return nullptr;
}

bool CircularAvatar::init(const std::string& avatarPath) {
    if (!Node::init()) {
        return false;
    }
    
    // 1. 创建被遮罩的内容(头像图片)
    _avatarSprite = Sprite::create(avatarPath);
    _avatarSprite->setScale(0.8f); // 适当缩放
    
    // 2. 创建遮罩模板(圆形)
    auto stencil = DrawNode::create();
    Vec2 circleCenter = Vec2(0, 0); // 圆心在节点中心
    float radius = 50.0f; // 圆半径
    Color4F whiteColor = Color4F::WHITE;
    
    // 绘制圆形路径
    stencil->drawSolidCircle(circleCenter, radius, 0, 50, whiteColor);
    
    // 3. 创建ClippingNode并设置参数
    auto clippingNode = ClippingNode::create();
    clippingNode->setStencil(stencil); // 设置模板
    clippingNode->setInverted(false);  // 正常模式(非反转)
    clippingNode->addChild(_avatarSprite); // 添加被遮罩内容
    
    // 4. 将ClippingNode添加到自身
    this->addChild(clippingNode);
    this->setContentSize(_avatarSprite->getContentSize());
    
    return true;
}

使用示例

// 在场景中使用圆形头像
auto circularAvatar = CircularAvatar::create("avatar.png");
circularAvatar->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));
this->addChild(circularAvatar);

3.2 场景2:纹理遮罩(渐变遮罩效果)

代码实现

// GradientMask.h
#ifndef __GRADIENT_MASK_H__
#define __GRADIENT_MASK_H__

#include "cocos2d.h"

class GradientMask : public cocos2d::Node {
public:
    static GradientMask* create(const std::string& contentPath, 
                               const std::string& maskPath);
    bool init(const std::string& contentPath, const std::string& maskPath);
    
private:
    cocos2d::Sprite* _contentSprite;
    cocos2d::Sprite* _maskSprite;
};

#endif

// GradientMask.cpp
#include "GradientMask.h"

USING_NS_CC;

GradientMask* GradientMask::create(const std::string& contentPath, 
                                   const std::string& maskPath) {
    auto* node = new GradientMask();
    if (node && node->init(contentPath, maskPath)) {
        node->autorelease();
        return node;
    }
    CC_SAFE_DELETE(node);
    return nullptr;
}

bool GradientMask::init(const std::string& contentPath, const std::string& maskPath) {
    if (!Node::init()) {
        return false;
    }
    
    // 1. 创建被遮罩的内容
    _contentSprite = Sprite::create(contentPath);
    _contentSprite->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
    
    // 2. 创建纹理遮罩(使用带Alpha通道的图片)
    _maskSprite = Sprite::create(maskPath);
    _maskSprite->setAnchorPoint(Vec2::ANCHOR_MIDDLE);
    
    // 3. 创建ClippingNode
    auto clippingNode = ClippingNode::create();
    clippingNode->setStencil(_maskSprite); // 使用精灵作为模板
    clippingNode->setAlphaThreshold(0.1f); // Alpha阈值,小于此值的像素不显示
    clippingNode->addChild(_contentSprite);
    
    // 4. 调整遮罩和内容的位置匹配
    clippingNode->setPosition(Vec2(_contentSprite->getContentSize().width/2,
                                  _contentSprite->getContentSize().height/2));
    
    this->addChild(clippingNode);
    this->setContentSize(_contentSprite->getContentSize());
    
    return true;
}

渐变遮罩图片制作说明

需要准备一张PNG格式的渐变遮罩图,其中:
  • 白色区域(Alpha=255):完全显示内容
  • 灰色区域(Alpha=128):半透明显示
  • 黑色区域(Alpha=0):完全隐藏内容

3.3 场景3:动态进度遮罩(血条效果)

代码实现

// ProgressMask.h
#ifndef __PROGRESS_MASK_H__
#define __PROGRESS_MASK_H__

#include "cocos2d.h"

class ProgressMask : public cocos2d::Node {
public:
    static ProgressMask* create(const std::string& bgPath, 
                                const std::string& fillPath,
                                float maxProgress = 100.0f);
    bool init(const std::string& bgPath, const std::string& fillPath, 
              float maxProgress);
    
    void setProgress(float progress); // 设置进度值
    float getProgress() const { return _currentProgress; }
    
private:
    void updateMask(); // 更新遮罩
    
    cocos2d::Sprite* _bgSprite;
    cocos2d::Sprite* _fillSprite;
    cocos2d::DrawNode* _stencil;
    float _maxProgress;
    float _currentProgress;
    float _barWidth;
    float _barHeight;
};

#endif

// ProgressMask.cpp
#include "ProgressMask.h"

USING_NS_CC;

ProgressMask* ProgressMask::create(const std::string& bgPath, 
                                   const std::string& fillPath,
                                   float maxProgress) {
    auto* node = new ProgressMask();
    if (node && node->init(bgPath, fillPath, maxProgress)) {
        node->autorelease();
        return node;
    }
    CC_SAFE_DELETE(node);
    return nullptr;
}

bool ProgressMask::init(const std::string& bgPath, const std::string& fillPath, 
                       float maxProgress) {
    if (!Node::init()) {
        return false;
    }
    
    _maxProgress = maxProgress;
    _currentProgress = maxProgress;
    
    // 获取进度条尺寸
    auto tempBg = Sprite::create(bgPath);
    _barWidth = tempBg->getContentSize().width;
    _barHeight = tempBg->getContentSize().height;
    
    // 1. 背景
    _bgSprite = Sprite::create(bgPath);
    _bgSprite->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
    this->addChild(_bgSprite);
    
    // 2. 填充内容
    _fillSprite = Sprite::create(fillPath);
    _fillSprite->setAnchorPoint(Vec2::ANCHOR_BOTTOM_LEFT);
    _fillSprite->setPosition(Vec2(0, 0));
    
    // 3. 创建遮罩模板(矩形,宽度随进度变化)
    _stencil = DrawNode::create();
    
    // 4. ClippingNode
    auto clippingNode = ClippingNode::create();
    clippingNode->setStencil(_stencil);
    clippingNode->setInverted(false);
    clippingNode->addChild(_fillSprite);
    clippingNode->setPosition(Vec2(0, 0));
    
    this->addChild(clippingNode);
    this->setContentSize(tempBg->getContentSize());
    
    // 初始化遮罩
    updateMask();
    
    return true;
}

void ProgressMask::updateMask() {
    _stencil->clear();
    
    // 计算当前进度的宽度
    float progressWidth = (_currentProgress / _maxProgress) * _barWidth;
    
    // 创建矩形遮罩
    Vec2 rectOrigin = Vec2(0, 0);
    Vec2 rectDestination = Vec2(progressWidth, _barHeight);
    Color4F whiteColor = Color4F::WHITE;
    
    _stencil->drawSolidRect(rectOrigin, rectDestination, whiteColor);
}

void ProgressMask::setProgress(float progress) {
    _currentProgress = MAX(0, MIN(progress, _maxProgress));
    updateMask();
}

使用示例

// 创建血条
auto healthBar = ProgressMask::create("health_bg.png", "health_fill.png", 100.0f);
healthBar->setPosition(Vec2(100, 50));
this->addChild(healthBar);

// 更新血量(例如受到伤害)
healthBar->setProgress(75.0f); // 设置为75%

3.4 场景4:动画遮罩(呼吸灯效果)

代码实现

// BreathingMask.h
#ifndef __BREATHING_MASK_H__
#define __BREATHING_MASK_H__

#include "cocos2d.h"

class BreathingMask : public cocos2d::Node {
public:
    static BreathingMask* create(const std::string& spritePath);
    bool init(const std::string& spritePath);
    
    void startBreathing();
    void stopBreathing();
    
private:
    void breathingAnimation(float dt);
    
    cocos2d::Sprite* _sprite;
    cocos2d::DrawNode* _stencil;
    cocos2d::ClippingNode* _clippingNode;
    bool _isBreathing;
    float _breathTime;
    float _minRadius;
    float _maxRadius;
};

#endif

// BreathingMask.cpp
#include "BreathingMask.h"

USING_NS_CC;

BreathingMask* BreathingMask::create(const std::string& spritePath) {
    auto* node = new BreathingMask();
    if (node && node->init(spritePath)) {
        node->autorelease();
        return node;
    }
    CC_SAFE_DELETE(node);
    return nullptr;
}

bool BreathingMask::init(const std::string& spritePath) {
    if (!Node::init()) {
        return false;
    }
    
    _isBreathing = false;
    _breathTime = 0.0f;
    _minRadius = 30.0f;
    _maxRadius = 60.0f;
    
    // 1. 创建被遮罩的精灵
    _sprite = Sprite::create(spritePath);
    _sprite->setScale(0.7f);
    
    // 2. 创建遮罩模板
    _stencil = DrawNode::create();
    
    // 3. 创建ClippingNode
    _clippingNode = ClippingNode::create();
    _clippingNode->setStencil(_stencil);
    _clippingNode->addChild(_sprite);
    
    this->addChild(_clippingNode);
    this->setContentSize(_sprite->getContentSize());
    
    // 初始遮罩
    breathingAnimation(0.0f);
    
    return true;
}

void BreathingMask::startBreathing() {
    _isBreathing = true;
    _breathTime = 0.0f;
    this->schedule(CC_SCHEDULE_SELECTOR(BreathingMask::breathingAnimation), 0.02f);
}

void BreathingMask::stopBreathing() {
    _isBreathing = false;
    this->unschedule(CC_SCHEDULE_SELECTOR(BreathingMask::breathingAnimation));
}

void BreathingMask::breathingAnimation(float dt) {
    if (!_isBreathing) return;
    
    _breathTime += dt;
    
    // 使用正弦函数创建平滑的呼吸效果
    float breathFactor = (sin(_breathTime * 2.0f) + 1.0f) * 0.5f; // 0~1
    float currentRadius = _minRadius + (_maxRadius - _minRadius) * breathFactor;
    
    // 更新遮罩
    _stencil->clear();
    Vec2 center = Vec2(_sprite->getContentSize().width/2, 
                      _sprite->getContentSize().height/2);
    Color4F color = Color4F::WHITE;
    
    _stencil->drawSolidCircle(center, currentRadius, 0, 50, color);
}

使用示例

// 创建呼吸灯效果
auto breathingEffect = BreathingMask::create("magic_circle.png");
breathingEffect->setPosition(Vec2(visibleSize.width/2, visibleSize.height/2));
this->addChild(breathingEffect);

// 开始呼吸动画
breathingEffect->startBreathing();

// 停止呼吸动画(可选)
// breathingEffect->stopBreathing();

四、原理解释与核心特性

4.1 ClippingNode 工作原理

graph TD
    A[应用程序] --> B[ClippingNode]
    B --> C[设置模板 Stencil]
    B --> D[添加被遮罩内容 Content]
    C --> E[模板渲染到模板缓冲区]
    D --> F[内容渲染到颜色缓冲区]
    E --> G[模板测试]
    F --> G
    G --> H[只渲染通过模板测试的像素]
    H --> I[最终显示结果]
核心流程
  1. 模板设置:将模板节点的形状渲染到模板缓冲区,设置对应区域的模板值为1
  2. 内容渲染:正常渲染被遮罩的内容到颜色缓冲区
  3. 模板测试:GPU比较每个像素的模板值,只渲染模板值为1的区域
  4. 结果显示:最终只显示模板区域内的内容部分

4.2 核心特性对比

特性
实现方式
性能影响
适用场景
静态遮罩
固定模板
UI元素、固定形状
动态遮罩
每帧更新模板
中-高
进度条、动画效果
纹理遮罩
Alpha通道测试
复杂图案、渐变效果
反向遮罩
setInverted(true)
聚光灯、挖空效果
Alpha阈值
setAlphaThreshold()
软边缘遮罩

五、环境准备

5.1 开发环境配置

  • 引擎版本:Cocos2d-x 3.x 或 4.x
  • 开发工具:Visual Studio / Xcode / Android Studio
  • 依赖库:OpenGL ES 2.0+(移动端)/ OpenGL 2.1+(桌面端)

5.2 项目配置要点

# CMakeLists.txt 中添加必要配置
find_package(OpenGL REQUIRED)

# 确保启用模板测试
target_compile_definitions(${APP_NAME} PRIVATE
    GL_ES_VERSION_2_0=1
)

六、实际详细应用代码示例

综合案例:游戏角色选择界面

// CharacterSelectionScene.h
#ifndef __CHARACTER_SELECTION_SCENE_H__
#define __CHARACTER_SELECTION_SCENE_H__

#include "cocos2d.h"
#include "CircularAvatar.h"
#include "ProgressMask.h"

class CharacterSelectionScene : public cocos2d::Scene {
public:
    static cocos2d::Scene* createScene();
    virtual bool init() override;
    CREATE_FUNC(CharacterSelectionScene);
    
private:
    void createCharacterCards();
    void onCharacterSelected(cocos2d::Ref* sender);
    
    std::vector<cocos2d::Sprite*> _characterSprites;
    std::vector<ProgressMask*> _healthBars;
};

#endif

// CharacterSelectionScene.cpp
#include "CharacterSelectionScene.h"

USING_NS_CC;

Scene* CharacterSelectionScene::createScene() {
    return CharacterSelectionScene::create();
}

bool CharacterSelectionScene::init() {
    if (!Scene::init()) {
        return false;
    }
    
    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();
    
    // 背景
    auto background = LayerColor::create(Color4B(50, 50, 80, 255));
    this->addChild(background);
    
    // 标题
    auto title = Label::createWithTTF("选择角色", "fonts/Marker Felt.ttf", 36);
    title->setPosition(Vec2(visibleSize.width/2, visibleSize.height - 50));
    this->addChild(title);
    
    // 创建角色卡片
    createCharacterCards();
    
    return true;
}

void CharacterSelectionScene::createCharacterCards() {
    auto visibleSize = Director::getInstance()->getVisibleSize();
    
    // 角色数据
    std::vector<std::string> characterNames = {"战士", "法师", "射手"};
    std::vector<std::string> avatarPaths = {"warrior.png", "mage.png", "archer.png"};
    std::vector<int> healthValues = {100, 80, 90};
    
    float cardWidth = 200;
    float cardHeight = 250;
    float spacing = 50;
    float totalWidth = characterNames.size() * cardWidth + (characterNames.size() - 1) * spacing;
    float startX = (visibleSize.width - totalWidth) / 2;
    
    for (int i = 0; i < characterNames.size(); ++i) {
        float x = startX + i * (cardWidth + spacing);
        float y = visibleSize.height / 2;
        
        // 卡片背景
        auto cardBg = LayerColor::create(Color4B(100, 100, 120, 255), cardWidth, cardHeight);
        cardBg->setPosition(Vec2(x, y));
        this->addChild(cardBg);
        
        // 圆形头像
        auto avatar = CircularAvatar::create(avatarPaths[i]);
        avatar->setPosition(Vec2(cardWidth/2, cardHeight - 60));
        cardBg->addChild(avatar);
        
        // 角色名称
        auto nameLabel = Label::createWithTTF(characterNames[i], "fonts/Marker Felt.ttf", 24);
        nameLabel->setPosition(Vec2(cardWidth/2, cardHeight - 120));
        cardBg->addChild(nameLabel);
        
        // 血条
        auto healthBar = ProgressMask::create("health_bg.png", "health_fill_red.png", 100);
        healthBar->setPosition(Vec2(cardWidth/2 - 50, cardHeight - 160));
        healthBar->setProgress(healthValues[i]);
        cardBg->addChild(healthBar);
        
        // 选择按钮
        auto selectBtn = ui::Button::create("button_normal.png", "button_pressed.png");
        selectBtn->setTitleText("选择");
        selectBtn->setPosition(Vec2(cardWidth/2, 30));
        selectBtn->addClickEventListener(CC_CALLBACK_1(CharacterSelectionScene::onCharacterSelected, this));
        cardBg->addChild(selectBtn);
        
        // 存储引用
        _characterSprites.push_back(cardBg);
        _healthBars.push_back(healthBar);
    }
}

void CharacterSelectionScene::onCharacterSelected(cocos2d::Ref* sender) {
    auto button = dynamic_cast<ui::Button*>(sender);
    // 这里可以添加角色选择逻辑
    CCLOG("角色被选择!");
}

七、测试步骤与详细代码

测试1:基础遮罩功能验证

  1. 步骤:运行程序,观察圆形头像是否正确显示
  2. 预期:头像只显示在圆形区域内,外部区域透明

测试2:纹理遮罩效果

  1. 步骤:使用渐变遮罩图片,观察内容是否按Alpha通道显示
  2. 预期:内容按遮罩的透明度渐变显示

测试3:动态进度更新

  1. 步骤:调用 setProgress()方法改变进度值
  2. 预期:血条宽度平滑变化,符合进度比例

测试4:动画性能测试

  1. 步骤:同时运行多个呼吸灯效果,观察帧率
  2. 预期:在移动设备上保持30FPS以上

八、部署场景

8.1 平台适配

  • 移动端:iOS/Android,注意内存管理和渲染性能
  • 桌面端:Windows/Mac/Linux,可利用更强的GPU性能
  • Web端:通过WebGL,注意浏览器兼容性

8.2 性能优化策略

// 性能优化配置示例
void optimizeClippingNode(ClippingNode* clipNode) {
    // 1. 减少模板更新频率
    clipNode->setAlphaThreshold(0.05f); // 提高Alpha测试精度
    
    // 2. 对于静态遮罩,考虑缓存模板
    if (clipNode->getStencil()->getNumberOfRunningActions() == 0) {
        clipNode->setStencil(nullptr); // 移除不必要的模板引用
    }
    
    // 3. 合并多个小遮罩为一个大遮罩
    // 4. 使用简单的几何形状代替复杂纹理
}

九、疑难解答

9.1 常见问题及解决方案

问题现象
可能原因
解决方案
遮罩不生效
模板未正确设置或Alpha阈值过高
检查 setStencil()setAlphaThreshold()
遮罩边缘锯齿严重
模板分辨率过低或抗锯齿未开启
提高模板分辨率,启用MSAA抗锯齿
性能下降明显
过多动态遮罩或高频率更新
合并遮罩、降低更新频率、使用对象池
移动端显示异常
OpenGL ES版本兼容性问题
检查设备支持的OpenGL ES版本
遮罩内容错位
坐标系统不匹配
统一使用相同的锚点和坐标系统

9.2 调试技巧

// 调试遮罩边界
void debugClippingNode(ClippingNode* clipNode) {
    // 临时显示模板轮廓
    auto stencil = clipNode->getStencil();
    if (stencil) {
        stencil->setBlendFunc(BlendFunc::ALPHA_PREMULTIPLIED);
        stencil->setOpacity(128); // 半透明显示模板
    }
    
    // 输出遮罩信息
    CCLOG("ClippingNode size: %f x %f", 
          clipNode->getContentSize().width,
          clipNode->getContentSize().height);
}

十、未来展望与技术趋势

10.1 技术发展趋势

  1. 可编程遮罩:支持自定义Shader实现复杂遮罩效果
  2. GPU加速优化:利用Compute Shader进一步优化模板计算
  3. 物理遮罩:结合物理引擎实现碰撞检测的实时遮罩
  4. VR/AR集成:针对虚拟现实的特殊遮罩需求
  5. 机器学习辅助:AI生成复杂遮罩形状

10.2 新兴应用场景

  • 元宇宙界面:虚拟世界中的交互式遮罩
  • 实时视频处理:直播中的动态贴纸和特效
  • 教育应用:交互式学习中的高亮和聚焦效果
  • 数据可视化:图表中的动态筛选和突出显示

十一、总结

Cocos2d-x中的 ClippingNode遮罩技术为游戏开发者提供了强大而灵活的视觉表现工具:

核心价值

  • 视觉丰富性:轻松实现各种复杂的遮罩效果,提升游戏美术品质
  • 交互反馈:通过动态遮罩提供直观的用户反馈和游戏状态指示
  • 性能可控:基于GPU硬件加速,在保证效果的同时维持良好性能
  • 跨平台一致:统一的API在不同平台上产生一致的视觉效果

关键技术要点

  1. 模板测试原理:理解OpenGL模板缓冲区的工作原理是基础
  2. 性能平衡:在效果和性能之间找到合适的平衡点
  3. 平台适配:针对不同硬件能力进行优化和降级处理
  4. 创意应用:突破传统遮罩思维,创造独特的视觉体验

发展建议

随着移动设备性能的不断提升和用户对视觉体验要求的日益提高,遮罩技术将在游戏开发中扮演越来越重要的角色。开发者应当:
  • 深入理解底层图形学原理
  • 关注新技术标准和发展趋势
  • 在实际项目中积累性能优化经验
  • 探索遮罩技术与游戏玩法的创新结合
掌握Cocos2d-x的遮罩技术,不仅能够实现精美的视觉效果,更能为游戏增添独特的魅力和专业的品质感,是每位Cocos2d-x开发者必备的重要技能。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。