Cocos2d-x 3.x基础学习:裁剪节点总结
在游戏开发过程中,我们时常会遇到仅显示图片部分区域的需求,例如文字遮罩、图片遮罩等场景。本节将着重介绍 ClippingNode,其功能效果与上述遮罩效果类似。
一、ClippingNode 原理
ClippingNode(裁剪节点)是 Node 的子类,可像普通节点一样添加到 Layer、Scene 或其他 Node 中,主要用于对节点进行裁剪操作。它依据一个模板(Stencil)来切割图片节点,从而生成任意形状的节点显示效果。ClippingNode 利用模板遮罩技术实现对 Node 区域的裁剪。
为了更好地理解 ClippingNode 的遮罩原理,我们通过以下例子进行说明。
二、举例说明
2.1 模板与底板选择
模板(Stencil)和底板均可选用 Layer、Node、Sprite 等类型的节点。
2.2 不同情况示例
第一组(Layer 层无背景图片)
- 模板(Stencil):使用
Node节点,并添加 5 个Sprite小球。 - 底板:同样使用
Node节点,添加 1 个显示ABCD的Sprite图片。 - Layer 层:该层无元素,背景颜色设置为黑色。
第二组(Layer 层有背景图片)
- 模板(Stencil):
Node节点,包含 5 个Sprite小球。 - 底板:
Node节点,添加 1 个显示ABCD的Sprite图片。 - Layer 层:添加一个显示
Cocos2d-x的Sprite背景图片。
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.cpp 的 init() 中创建裁剪节点 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 类在游戏开发中具有广泛的应用场景,可帮助开发者实现各种复杂的裁剪和遮罩效果。