100 lines
2.5 KiB
TypeScript
100 lines
2.5 KiB
TypeScript
import { EffectAsset, Material, Sprite, Vec4 } from 'cc';
|
||
|
||
/**
|
||
* 圆角 Sprite 材质工具。
|
||
*
|
||
* 基于 rounded-sprite.effect 的 SDF alpha 裁剪实现,直接作用于 Sprite 的自定义材质。
|
||
* 每个 Sprite 都会获得独立 Material,避免 roundedParams / uvRect 互相覆盖。
|
||
*/
|
||
|
||
let cachedTemplate: Material | null = null;
|
||
let cachedEffectRef: EffectAsset | null = null;
|
||
|
||
const getOrCreateTemplate = (effectAsset: EffectAsset): Material => {
|
||
if (cachedTemplate && cachedEffectRef === effectAsset) {
|
||
return cachedTemplate;
|
||
}
|
||
|
||
const template = new Material();
|
||
template.initialize({
|
||
effectAsset,
|
||
defines: { USE_TEXTURE: true },
|
||
});
|
||
|
||
cachedTemplate = template;
|
||
cachedEffectRef = effectAsset;
|
||
|
||
return template;
|
||
};
|
||
|
||
const extractUvRect = (sprite: Sprite): Vec4 => {
|
||
const spriteFrame = sprite.spriteFrame;
|
||
if (!spriteFrame) {
|
||
return new Vec4(0, 0, 1, 1);
|
||
}
|
||
|
||
const uv = spriteFrame.uv;
|
||
if (!uv || uv.length < 8) {
|
||
return new Vec4(0, 0, 1, 1);
|
||
}
|
||
|
||
const u0 = uv[0];
|
||
const v0 = uv[1];
|
||
const u1 = uv[2];
|
||
const v1 = uv[3];
|
||
const u2 = uv[4];
|
||
const v2 = uv[5];
|
||
const u3 = uv[6];
|
||
const v3 = uv[7];
|
||
|
||
const minU = Math.min(u0, u1, u2, u3);
|
||
const maxU = Math.max(u0, u1, u2, u3);
|
||
const minV = Math.min(v0, v1, v2, v3);
|
||
const maxV = Math.max(v0, v1, v2, v3);
|
||
const rangeU = maxU - minU;
|
||
const rangeV = maxV - minV;
|
||
|
||
if (rangeU <= 0 || rangeV <= 0) {
|
||
return new Vec4(0, 0, 1, 1);
|
||
}
|
||
|
||
return new Vec4(minU, minV, rangeU, rangeV);
|
||
};
|
||
|
||
export const applyRoundedCorner = (
|
||
sprite: Sprite,
|
||
effectAsset: EffectAsset,
|
||
width: number,
|
||
height: number,
|
||
cornerRadius = 0.1,
|
||
grayscale = false,
|
||
): void => {
|
||
if (!sprite || !sprite.isValid) {
|
||
console.warn('[roundedMaterial] Invalid sprite, skipping');
|
||
return;
|
||
}
|
||
|
||
if (!effectAsset) {
|
||
console.warn('[roundedMaterial] EffectAsset is null, skipping');
|
||
return;
|
||
}
|
||
|
||
const template = getOrCreateTemplate(effectAsset);
|
||
const materialInstance = new Material();
|
||
const defines: Record<string, boolean> = { USE_TEXTURE: true };
|
||
if (grayscale) {
|
||
defines.IS_GRAY = true;
|
||
}
|
||
|
||
materialInstance.initialize({
|
||
effectAsset,
|
||
defines,
|
||
});
|
||
|
||
const params = new Vec4(cornerRadius, 0, width, height);
|
||
materialInstance.setProperty('roundedParams', params);
|
||
materialInstance.setProperty('uvRect', extractUvRect(sprite));
|
||
|
||
sprite.customMaterial = materialInstance;
|
||
};
|