【CocosCreator入门】CocosCreator组件 | Mask(遮罩)组件
【CocosCreator入门】CocosCreator组件 | Mask(遮罩)组件
一、背景与问题
在Cocos Creator的2D/3D开发中,遮罩(Mask)组件是一种核心的视觉控制工具。它通过限制渲染区域的范围,实现对子节点的视觉裁剪效果。常见应用场景包括:
- 健康值/血量条的圆形遮罩
- 图片的圆角处理
- 动态遮罩效果(如光效扩散)
- UI元素的区域限制(如对话框内的输入框)
在实际开发中,开发者常常遇到以下问题:
- 遮罩效果不生效或出现锯齿
- 多层遮罩叠加时的渲染顺序问题
- 动态生成遮罩时的性能损耗
- 遮罩边界与父容器的坐标系错位
这些痛点需要通过深入理解Mask组件的底层实现原理来解决。
二、基本原理
Cocos Creator的Mask组件基于GPU的Stencil Buffer技术实现遮罩效果。其核心原理如下:
渲染流程:
- 遮罩层(Mask)节点首先渲染到Stencil Buffer
- 被遮罩的子节点在渲染时根据Stencil Buffer的掩码信息进行裁剪
- 可通过
maskType属性选择不同遮罩方式(圆形/矩形/自定义路径)
坐标系处理:
- 遮罩区域始终基于自身坐标系
- 遮罩的边界由
mask属性控制(Sprite/Path2D等) - 通过
ignoreParent属性控制是否继承父节点的坐标系
渲染顺序:
- 遮罩层在渲染顺序上优先于被遮罩的子节点
- 遮罩层的渲染模式需要设置为
DepthWrite,否则可能导致遮罩失效
三、环境准备
在开始开发前,需要准备以下环境:
# 安装Cocos Creator
npm install -g cocos-creator项目结构建议:
project/
├── assets/ # 资源目录
│ ├── mask.png # 遮罩素材
│ └── target.png # 被遮罩素材
├── scenes/ # 场景目录
│ └── mask-scene.json # 遮罩场景
├── scripts/ # 脚本目录
│ └── mask.ts # 遮罩控制脚本
└── config.json # 项目配置四、核心实现
1. 基础遮罩实现
// scripts/mask.ts
const { _decorator, Component, Node, Sprite, SpriteAtlas, Mask, Color, Vec2, Vec3 } = cc;
@_decorator.ccclass('MaskExample')
export class MaskExample extends Component {
onLoad() {
// 创建遮罩层
const maskNode = new Node('Mask');
const maskSprite = maskNode.addComponent(Sprite);
maskSprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
maskSprite.fillCenter = true;
maskSprite.fillMethod = Sprite.FillMethod.Radial;
maskSprite.fillTint = new Color(255, 255, 255, 128);
maskSprite.fillSize = 0.5;
// 设置遮罩属性
maskNode.addComponent(Mask);
maskNode.setMaskType(Mask.MaskType.Circle);
maskNode.setPosition(0, 0);
maskNode.setScale(1);
// 添加到场景
this.node.addChild(maskNode);
}
}关键代码解释:
fillCenter和fillMethod控制遮罩形状fillTint设置遮罩的透明度fillSize控制遮罩范围setMaskType指定遮罩类型(Circle/Rectangle/Custom)
2. 动态遮罩生成
// scripts/dynamic-mask.ts
const { _decorator, Component, Node, Sprite, Texture2D, Mask, Vec2, Color } = cc;
@_decorator.ccclass('DynamicMaskExample')
export class DynamicMaskExample extends Component {
private maskNode: Node;
private targetNode: Node;
onLoad() {
// 创建遮罩层
this.maskNode = new Node('DynamicMask');
const maskSprite = this.maskNode.addComponent(Sprite);
maskSprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
maskSprite.fillCenter = true;
maskSprite.fillMethod = Sprite.FillMethod.Radial;
maskSprite.fillTint = new Color(255, 255, 255, 128);
maskSprite.fillSize = 0.5;
// 设置遮罩属性
this.maskNode.addComponent(Mask);
this.maskNode.setMaskType(Mask.MaskType.Circle);
this.maskNode.setPosition(0, 0);
this.maskNode.setScale(1);
// 创建被遮罩对象
this.targetNode = new Node('Target');
const targetSprite = this.targetNode.addComponent(Sprite);
targetSprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
this.targetNode.setPosition(0, 0);
this.targetNode.setScale(1);
// 添加到场景
this.node.addChild(this.maskNode);
this.node.addChild(this.targetNode);
// 动态调整遮罩
this.schedule(() => {
this.maskNode.setPosition(100, 100);
this.targetNode.setPosition(100, 100);
}, 1);
}
}关键代码解释:
- 动态调整遮罩位置和大小
- 使用定时器实现动态效果
- 遮罩与被遮罩对象的位置同步
3. 自定义路径遮罩
// scripts/custom-mask.ts
const { _decorator, Component, Node, Sprite, Mask, Vec2, Color, ShapePath } = cc;
@_decorator.ccclass('CustomMaskExample')
export class CustomMaskExample extends Component {
onLoad() {
// 创建遮罩层
const maskNode = new Node('CustomMask');
const maskSprite = maskNode.addComponent(Sprite);
maskSprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
maskSprite.fillCenter = true;
maskSprite.fillMethod = Sprite.FillMethod.Radial;
maskSprite.fillTint = new Color(255, 255, 255, 128);
maskSprite.fillSize = 0.5;
// 设置自定义路径
const path = new ShapePath();
path.moveTo(0, 0);
path.lineTo(100, 0);
path.lineTo(100, 100);
path.lineTo(0, 100);
path.closePath();
maskSprite.setPath(path);
// 设置遮罩属性
maskNode.addComponent(Mask);
maskNode.setMaskType(Mask.MaskType.Custom);
maskNode.setPosition(0, 0);
maskNode.setScale(1);
// 添加到场景
this.node.addChild(maskNode);
}
}关键代码解释:
- 使用
ShapePath创建自定义路径 setPath方法设置遮罩形状setMaskType(Mask.MaskType.Custom)启用自定义遮罩
五、完整案例
圆形遮罩对话框
// scripts/circle-mask.ts
const { _decorator, Component, Node, Sprite, Mask, Color, Vec2, Size } = cc;
@_decorator.ccclass('CircleMaskExample')
export class CircleMaskExample extends Component {
private maskNode: Node;
private targetNode: Node;
onLoad() {
// 创建遮罩层
this.maskNode = new Node('CircleMask');
const maskSprite = this.maskNode.addComponent(Sprite);
maskSprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
maskSprite.fillCenter = true;
maskSprite.fillMethod = Sprite.FillMethod.Radial;
maskSprite.fillTint = new Color(255, 255, 255, 128);
maskSprite.fillSize = 0.5;
// 设置遮罩属性
this.maskNode.addComponent(Mask);
this.maskNode.setMaskType(Mask.MaskType.Circle);
this.maskNode.setPosition(0, 0);
this.maskNode.setScale(1);
// 创建被遮罩对象
this.targetNode = new Node('Target');
const targetSprite = this.targetNode.addComponent(Sprite);
targetSprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
this.targetNode.setPosition(0, 0);
this.targetNode.setScale(1);
// 添加到场景
this.node.addChild(this.maskNode);
this.node.addChild(this.targetNode);
// 设置遮罩边界
this.maskNode.getComponent(Mask).setMaskRect(new cc.Rect(0, 0, 200, 200));
}
}运行效果:
- 遮罩层创建圆形遮罩区域
- 被遮罩对象的渲染范围被限制在圆形区域内
- 可通过调整
fillSize控制遮罩范围
六、源码解析
Cocos Creator的Mask组件核心实现位于Mask.js中,关键代码如下:
// mask.js (简化版)
cc.Mask = cc.Component.extend({
_maskType: Mask.MaskType.Circle,
_maskRect: cc.Rect.ZERO,
_ignoreParent: false,
setMaskType(type) {
this._maskType = type;
this._updateMask();
},
setMaskRect(rect) {
this._maskRect = rect;
this._updateMask();
},
_updateMask() {
// 调用底层渲染接口更新遮罩
this._updateStencilBuffer();
this._updateRenderOrder();
},
_updateStencilBuffer() {
// 实现Stencil Buffer的更新逻辑
// 包括路径绘制、区域计算等
},
_updateRenderOrder() {
// 调整渲染顺序以确保遮罩层优先渲染
}
});关键实现逻辑:
- 通过
setMaskType和setMaskRect控制遮罩形状和边界 - 在
_updateStencilBuffer中处理GPU的Stencil Buffer更新 setMaskRect中的cc.Rect结构体控制遮罩区域
七、进阶使用
1. 动态遮罩动画
// scripts/animated-mask.ts
const { _decorator, Component, Node, Sprite, Mask, Vec2, Color, Timer } = cc;
@_decorator.ccclass('AnimatedMaskExample')
export class AnimatedMaskExample extends Component {
private maskNode: Node;
private timer: Timer;
onLoad() {
this.maskNode = new Node('AnimatedMask');
const maskSprite = this.maskNode.addComponent(Sprite);
maskSprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
maskSprite.fillCenter = true;
maskSprite.fillMethod = Sprite.FillMethod.Radial;
maskSprite.fillTint = new Color(255, 255, 255, 128);
maskSprite.fillSize = 0.5;
this.maskNode.addComponent(Mask);
this.maskNode.setMaskType(Mask.MaskType.Circle);
this.maskNode.setPosition(0, 0);
this.maskNode.setScale(1);
this.timer = new Timer(1, 0.1, () => {
const size = Math.sin(Date.now() * 0.001) * 0.5 + 0.5;
this.maskNode.getComponent(Sprite).fillSize = size;
});
}
}2. 多层遮罩叠加
// scripts/multi-mask.ts
const { _decorator, Component, Node, Sprite, Mask, Vec2, Color } = cc;
@_decorator.ccclass('MultiMaskExample')
export class MultiMaskExample extends Component {
onLoad() {
// 创建第一层遮罩
const mask1Node = new Node('Mask1');
const mask1Sprite = mask1Node.addComponent(Sprite);
mask1Sprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
mask1Sprite.fillCenter = true;
mask1Sprite.fillMethod = Sprite.FillMethod.Radial;
mask1Sprite.fillTint = new Color(255, 255, 255, 128);
mask1Sprite.fillSize = 0.5;
mask1Node.addComponent(Mask);
mask1Node.setMaskType(Mask.MaskType.Circle);
mask1Node.setPosition(0, 0);
mask1Node.setScale(1);
// 创建第二层遮罩
const mask2Node = new Node('Mask2');
const mask2Sprite = mask2Node.addComponent(Sprite);
mask2Sprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
mask2Sprite.fillCenter = true;
mask2Sprite.fillMethod = Sprite.FillMethod.Radial;
mask2Sprite.fillTint = new Color(255, 255, 255, 128);
mask2Sprite.fillSize = 0.5;
mask2Node.addComponent(Mask);
mask2Node.setMaskType(Mask.MaskType.Circle);
mask2Node.setPosition(0, 0);
mask2Node.setScale(1);
// 添加到场景
this.node.addChild(mask1Node);
this.node.addChild(mask2Node);
// 设置遮罩顺序
mask1Node.setSiblingIndex(1);
mask2Node.setSiblingIndex(2);
}
}八、性能与工程实践
1. 性能优化策略
- 减少遮罩层级:每个遮罩层都会产生额外的GPU计算,建议合并遮罩层
- 使用精灵图:通过预处理生成遮罩图,避免动态计算
- 限制遮罩范围:避免不必要的大范围遮罩
- 使用异步加载:对于动态生成的遮罩,使用异步加载资源
2. 异常处理
// scripts/mask-error.ts
const { _decorator, Component, Node, Sprite, Mask, Error } = cc;
@_decorator.ccclass('MaskErrorExample')
export class MaskErrorExample extends Component {
onLoad() {
try {
const maskNode = new Node('Mask');
const maskSprite = maskNode.addComponent(Sprite);
maskSprite.spriteFrame = this.getComponent(Sprite).spriteFrame;
maskSprite.fillCenter = true;
maskSprite.fillMethod = Sprite.FillMethod.Radial;
maskSprite.fillTint = new Color(255, 255, 255, 128);
maskSprite.fillSize = 0.5;
maskNode.addComponent(Mask);
maskNode.setMaskType(Mask.MaskType.Circle);
maskNode.setPosition(0, 0);
maskNode.setScale(1);
this.node.addChild(maskNode);
} catch (err) {
cc.error('Mask creation failed:', err);
}
}
}3. 安全考虑
- 资源验证:确保遮罩素材的格式和尺寸符合要求
- 参数校验:在设置遮罩属性时进行有效性检查
- 防止XSS:如果涉及动态生成遮罩内容,需进行内容过滤
九、常见问题与踩坑
1. 遮罩不生效的常见原因
- 未设置Mask组件:忘记在节点上添加Mask组件
- 渲染顺序错误:遮罩层未在被遮罩对象之前渲染
- 材质不兼容:使用了不支持遮罩的材质
- 坐标系错位:遮罩层的坐标系与被遮罩对象不一致
2. 遮罩锯齿问题
- 分辨率不足:在低分辨率设备上使用高精度遮罩
- 抗锯齿设置:未开启抗锯齿功能
- 缩放问题:遮罩层被非整数倍缩放
3. 性能损耗问题
- 过多遮罩层:每个遮罩层都会增加GPU计算负担
- 频繁更新:频繁修改遮罩属性会导致性能下降
- 复杂路径:复杂的自定义路径会增加计算量
十、最佳实践
- 优先使用内置遮罩类型:圆形、矩形等常用类型性能最优
- 合并遮罩层:多个遮罩层可合并为一个复合遮罩
- 使用SpriteAtlas:对需要频繁更新的遮罩使用精灵图
- 定期清理遮罩:在场景切换时移除不必要的遮罩
- 预处理遮罩素材:对复杂遮罩进行预处理生成图集
十一、总结
Cocos Creator的Mask组件是实现复杂视觉效果的重要工具,其基于Stencil Buffer的遮罩机制提供了灵活的视觉控制能力。通过深入理解其工作原理,开发者可以更有效地利用这一功能:
- 在需要精确控制显示区域的场景中(如健康值显示、动态光效)
- 在需要视觉裁剪的UI设计中(如对话框、图片圆角)
- 在需要动态遮罩效果的交互中(如光效扩散、动态遮挡)
同时,需要注意避免滥用遮罩带来的性能损耗,合理使用遮罩组件,结合具体场景选择最适合的实现方式。通过合理的设计和优化,可以充分发挥Mask组件的潜力,创建出更丰富的视觉效果。
评论已关闭