Cocos2d-x 3.x基础学习:裁剪节点总结

2015年03月23日 10:01 0 点赞 0 评论 更新于 2025-11-21 18:12

在游戏开发过程中,我们时常会遇到仅显示图片部分区域的需求,例如文字遮罩、图片遮罩等场景。本节将着重介绍 ClippingNode,其功能效果与上述遮罩效果类似。

一、ClippingNode 原理

ClippingNode(裁剪节点)是 Node 的子类,可像普通节点一样添加到 LayerScene 或其他 Node 中,主要用于对节点进行裁剪操作。它依据一个模板(Stencil)来切割图片节点,从而生成任意形状的节点显示效果。ClippingNode 利用模板遮罩技术实现对 Node 区域的裁剪。

为了更好地理解 ClippingNode 的遮罩原理,我们通过以下例子进行说明。

二、举例说明

2.1 模板与底板选择

模板(Stencil)和底板均可选用 LayerNodeSprite 等类型的节点。

2.2 不同情况示例

第一组(Layer 层无背景图片)

  • 模板(Stencil):使用 Node 节点,并添加 5 个 Sprite 小球。
  • 底板:同样使用 Node 节点,添加 1 个显示 ABCDSprite 图片。
  • Layer 层:该层无元素,背景颜色设置为黑色。

第二组(Layer 层有背景图片)

  • 模板(Stencil)Node 节点,包含 5 个 Sprite 小球。
  • 底板Node 节点,添加 1 个显示 ABCDSprite 图片。
  • Layer 层:添加一个显示 Cocos2d-xSprite 背景图片。

2.3 分析总结

通过 ClippingNode 进行裁剪遮罩的过程如下: 首先,将模板(Stencil)上所有元素的形状集合作为“形状模板”,但模板元素本身不会进行渲染。然后,使用该“形状模板”对底板进行裁剪操作,最后显示从底板上裁剪下来的图片区域。

形象地说,模板(Stencil)就像一个带有许多不同形状“洞洞”的样板,我们依据这个样板对底板进行裁剪“挖洞”,再将裁剪下来的碎片按照原来的位置摆放。需要注意的是,模板(Stencil)仅作为“形状模板”,其本身的图片不会被绘制。

三、主要函数

ClippingNode 继承自 Node 类,主要用于节点的裁剪与遮罩操作。以下是其主要函数的详细介绍:

3.1 创建 ClippingNode

可通过两种方式创建 ClippingNode,即是否使用模板(stencil):

// 创建,不含模板(stencil)
ClippingNode* clippingNode = ClippingNode::create();

// 创建,使用模板(stencil)
ClippingNode* clippingNode = ClippingNode::create(stencil);

3.2 设置模板(Stencil)

模板节点是 Node 的子类,通常使用 DrawNode,因为它能够绘制不同形状的图形,当然也可以直接使用 Node 节点作为模板。示例代码如下:

// 模板stencil节点Node
Node* stencil = Node::create();
// 添加小球1
stencil->addChild(spriteBall1);
// 添加小球2
stencil->addChild(spriteBall2);
// 添加小球3
stencil->addChild(spriteBall3);
// 添加小球4
stencil->addChild(spriteBall4);
// 添加小球5
stencil->addChild(spriteBall5);
// 设置模板Stencil
clippingNode->setStencil(stencil);

3.3 设置底板(Content)

创建 ClippingNode 后,使用 addChild() 方法添加的节点即为底板内容:

// 设置底板
clippingNode->addChild(content);

3.4 倒置显示(Inverted)

  • false:显示被模板裁剪下来的底板内容,此为默认值。
  • true:显示剩余部分。
    // 默认为false,表示显示被裁剪下来的底板内容
    clippingNode->setInverted(false);
    

3.5 alpha 阈值(alphaThreshold)

alpha 表示像素的透明度值,只有模板(stencil)中像素的 alpha 值大于 alpha 阈值时,内容才会被绘制。alpha 阈值(alphaThreshold)的取值范围是 [0, 1],默认值为 1,表示 alpha 测试默认关闭,即全部绘制;若不为 1,则只绘制模板中 alpha 像素大于 alphaThreshold 的内容。

// 设置alpha透明度闸值,即显示模板中,alpha像素大于0.05的内容
holesClipper->setAlphaThreshold(0.05f);

以下通过一张 40 * 40 的图片进行具体说明,该图片中小球以外的其他区域像素为透明(即 alpha 为 0):

  • 不设置 AlphaThreshold 阈值,或者设置为 setAlphaThreshold(1.0f):模板绘制的区域为一个 40 * 40 的矩形。
  • 设置 setAlphaThreshold(0.5f):透明度 alpha 为 0 的像素不被绘制,仅绘制一个小圆。

四、代码实战

4.1 官方的“打洞”

官方 cpp - test 项目中有一个使用 ClippingNode 实现“打洞”效果的示例,具有一定的趣味性,更多用法可参考官方 cpp - test 项目。

4.1.1 素材

此处省略素材相关的详细描述,可参考官方项目。

4.1.2 在 HelloWorld.h 中添加变量与函数

// 裁剪节点
ClippingNode* holesClipper;
// 模板节点
Node* holesStencil;
// 底板节点
Node* holes;

// 触摸回调
void onTouchesBegan(const std::vector<Touch*>& touches, Event *unused_event);
// 添加小洞
void pokeHoleAtPoint(Vec2 point);

4.1.3 在 HelloWorld.cppinit() 中创建裁剪节点 ClippingNode

// [1].背景图片(Layer层中)
Sprite* bg = Sprite::create("HelloWorld.png");
bg->setPosition(visibleSize / 2);
this->addChild(bg);

// [2].创建裁剪节点 : holesClipper
holesClipper = ClippingNode::create();
holesClipper->setPosition(visibleSize / 2);
this->addChild(holesClipper);

// 属性设置
holesClipper->setInverted(true); // 倒置显示,未被裁剪下来的剩余部分
holesClipper->setAlphaThreshold(0.5f); // 设置alpha透明度闸值
holesClipper->runAction(RepeatForever::create(RotateBy::create(1, 45))); // 旋转动作

// [3].创建模板 : holesStencil
holesStencil = Node::create();
holesClipper->setStencil(holesStencil); // 设置模板节点

// 添加一个模板遮罩 ball
holesStencil->addChild(Sprite::create("ball.png"), -1);

// [4].创建底板 : holes
holes = Node::create();
holesClipper->addChild(holes); // 设置底板

// 添加另一个底板内容 blocks
Sprite* content = Sprite::create("blocks.png");
holesClipper->addChild(content, -1, "content");

// [5].触摸事件
auto listener = EventListenerTouchAllAtOnce::create();
listener->onTouchesBegan = CC_CALLBACK_2(HelloWorld::onTouchesBegan, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

4.1.4 设置触摸事件回调

当触摸点在底板区域内部时,执行“打洞”操作:

void HelloWorld::onTouchesBegan(const std::vector<Touch*>& touches, Event *unused_event)
{
// [1].获取触点, 转换为相对holesClipper节点的 相对坐标
Vec2 point = touches[0]->getLocation();
point = holesClipper->convertToNodeSpace(point);

// [2].获取底板区域矩形Rect
Sprite* content = (Sprite*)holesClipper->getChildByName("content");
Size contentSize = content->getContentSize();
Rect rect = Rect(-contentSize.width / 2, -contentSize.height / 2, contentSize.width, contentSize.height);

// [3].触摸点在底板内部, 进行"打洞"
if (rect.containsPoint(point))
{
pokeHoleAtPoint(point);
}
}

4.1.5 实现“打洞”操作函数

void HelloWorld::pokeHoleAtPoint(Vec2 point)
{
CCLOG("Add a Hole!!!");
// [1].添加底板内容 : 一个洞的痕迹
auto hole = Sprite::create("hole_effect.png");
hole->setPosition(point);
holes->addChild(hole);

// [2].添加模板内容 : 一个小洞
auto holeStencil = Sprite::create("hole_stencil.png");
holeStencil->setPosition(point);
holesStencil->addChild(holeStencil);

// [3].动作效果 : 放大缩小
holesClipper->runAction(Sequence::create(ScaleTo::create(0.05f, 1.05f), ScaleTo::create(0.05f, 1.0f), nullptr));
}

4.1.6 分析与总结

这里设置了倒置显示(Inverted),即使用模板对底板进行裁剪后,显示未被剪下的剩余部分。

  • 模板 Stencil:由添加的小球和洞的模板组成。
  • 底板:包含背景图片和添加的底板内容。
  • 裁剪遮罩效果图:显示未被模板裁剪的剩余部分。

4.2 “文字遮罩特效”

4.2.1 效果展示

此处省略效果展示的详细描述,可运行代码查看。

4.2.2 素材

此处省略素材相关的详细描述。

4.2.3 代码实现

// [1].背景图片
Sprite* bg = Sprite::create("HelloWorld.png");
bg->setPosition(visibleSize / 2);
this->addChild(bg, -1);

// [2].创建主题文字 : gameTitle
Sprite* gameTitle = Sprite::create("game_title.png");
// 获取尺寸大小
Size clipSize = gameTitle->getContentSize();

// [3].创建底板的发光图片 : spark
Sprite* spark = Sprite::create("spark.png");
spark->setPosition(-clipSize.width, 0);

// [4].创建裁剪节点 : clippingNode
ClippingNode* clippingNode = ClippingNode::create();
clippingNode->setPosition(visibleSize / 2);
this->addChild(clippingNode);

clippingNode->setAlphaThreshold(0.05f); // 设置alpha闸值
clippingNode->setContentSize(clipSize); // 设置尺寸大小
clippingNode->setStencil(gameTitle); // 设置模板stencil
clippingNode->addChild(gameTitle, 1); // 先添加标题,会完全显示出来,因为跟模板一样大小
clippingNode->addChild(spark, 2); // 会被裁减

// [5].左右移动spark
MoveTo* moveAction = MoveTo::create(2.0f, Vec2(clipSize.width, 0));
MoveTo* moveBackAction = MoveTo::create(2.0f, Vec2(-clipSize.width, 0));
spark->runAction(RepeatForever::create(Sequence::create(moveAction, moveBackAction, nullptr)));

4.2.4 分析与总结

实际上,该特效是将文字作为模板 Stencil,构建文字的“形状模板”,然后对底板进行裁剪。底板由文字和发光棒组合而成,通过移动发光棒,即可呈现文字发光的效果。

  • 模板 Stencil:主题文字图片。
  • 底板:由主题文字和发光棒组成。
  • 剪裁遮罩效果图:呈现文字发光的动态效果。

综上所述,ClippingNode 类在游戏开发中具有广泛的应用场景,可帮助开发者实现各种复杂的裁剪和遮罩效果。

作者信息

boke

boke

共发布了 3994 篇文章