| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114 |
- import { _decorator, Component, Mask, Graphics, UITransform } from 'cc';
- const { ccclass, property, executeInEditMode, requireComponent } = _decorator;
- @ccclass('CustomMask')
- @requireComponent(Mask)
- @executeInEditMode // 允许在编辑器内预览绘制效果
- export class CustomMask extends Component {
- @property({ tooltip: '左上角圆角半径(像素)' })
- leftTopRadius = 0;
- @property({ tooltip: '右上角圆角半径(像素)' })
- rightTopRadius = 20;
- @property({ tooltip: '右下角圆角半径(像素)' })
- rightBottomRadius = 20;
- @property({ tooltip: '左下角圆角半径(像素)' })
- leftBottomRadius = 0;
- private _mask: Mask | null = null;
- onEnable() {
- this.updateMask();
- }
- // 当在编辑器中修改属性时自动刷新
- onValidate() {
- this.updateMask();
- }
- /** 绘制四个圆角可独立设置的遮罩路径。 */
- public updateMask() {
- this._mask = this.getComponent(Mask);
- if (!this._mask) return;
- // 自定义模板遮罩,避免矩形模式覆盖自定义路径。
- this._mask.type = Mask.Type.GRAPHICS_STENCIL;
- // 获取或添加 Graphics 组件
- let g = this.getComponent(Graphics);
- if (!g) {
- g = this.addComponent(Graphics);
- }
- const uiTrans = this.getComponent(UITransform);
- if (!uiTrans) return;
- const width = uiTrans.width;
- const height = uiTrans.height;
- const anchorX = uiTrans.anchorX;
- const anchorY = uiTrans.anchorY;
- // 根据锚点计算左下角起始坐标 (x, y)
- const x = -width * anchorX;
- const y = -height * anchorY;
- const leftTop = this.getRadius(this.leftTopRadius, width, height);
- const rightTop = this.getRadius(this.rightTopRadius, width, height);
- const rightBottom = this.getRadius(this.rightBottomRadius, width, height);
- const leftBottom = this.getRadius(this.leftBottomRadius, width, height);
- // 清除旧路径
- g.clear();
- // 从底边开始逆时针绘制,所有圆角保持在同一条闭合路径中。
- g.moveTo(x + leftBottom, y);
- g.lineTo(x + width - rightBottom, y);
- this.drawArc(g, x + width - rightBottom, y + rightBottom, rightBottom, -Math.PI / 2, 0);
- g.lineTo(x + width, y + height - rightTop);
- this.drawArc(g, x + width - rightTop, y + height - rightTop, rightTop, 0, Math.PI / 2);
- g.lineTo(x + leftTop, y + height);
- this.drawArc(g, x + leftTop, y + height - leftTop, leftTop, Math.PI / 2, Math.PI);
- g.lineTo(x, y + leftBottom);
- this.drawArc(g, x + leftBottom, y + leftBottom, leftBottom, Math.PI, Math.PI * 1.5);
- g.close();
- g.fill();
- }
- private getRadius(radius: number, width: number, height: number): number {
- if (!Number.isFinite(radius)) return 0;
- return Math.max(0, Math.min(radius, width / 2, height / 2));
- }
- private drawArc(
- graphics: Graphics,
- centerX: number,
- centerY: number,
- radius: number,
- startAngle: number,
- endAngle: number,
- ): void {
- if (radius > 0) {
- // Graphics.arc 内部会 moveTo,不能用于连续的圆角轮廓。
- // 使用三次贝塞尔近似四分之一圆,控制点沿端点切线排列。
- const tangent = radius * 4 / 3 * Math.tan((endAngle - startAngle) / 4);
- const startX = centerX + radius * Math.cos(startAngle);
- const startY = centerY + radius * Math.sin(startAngle);
- const endX = centerX + radius * Math.cos(endAngle);
- const endY = centerY + radius * Math.sin(endAngle);
- graphics.bezierCurveTo(
- startX - tangent * Math.sin(startAngle),
- startY + tangent * Math.cos(startAngle),
- endX + tangent * Math.sin(endAngle),
- endY - tangent * Math.cos(endAngle),
- endX,
- endY,
- );
- }
- }
- }
|