cb/assets/Script/module/Tool/RoundBox.ts
COMPUTER\EDY 0abe2eaf68 更新
2025-11-07 19:57:59 +08:00

309 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*******************************************************************************
* 作者: 水煮肉片饭(27185709@qq.com)
* 版本: v1.3
* 描述: 圆角矩形,支持合批
*******************************************************************************/
const CENTER_IDATA = [0, 9, 11, 0, 11, 1, 2, 8, 10, 2, 4, 8, 3, 5, 7, 3, 7, 6];
export enum SizeMode { 'CUSTOM: 自定义尺寸', 'TRIMMED: 原始尺寸裁剪透明像素', 'RAW: 图片原始尺寸' }
const { ccclass, property, menu } = cc._decorator;
@ccclass('Corner')
class Corner {
@property({ displayName: CC_DEV && '↙ 左下' })
leftBottom: boolean = true;
@property({ displayName: CC_DEV && '↘ 右下' })
rightBottom: boolean = true;
@property({ displayName: CC_DEV && '↗ 右上' })
rightTop: boolean = true;
@property({ displayName: CC_DEV && '↖ 左上' })
leftTop: boolean = true;
visible: boolean[] = null;
}
@ccclass
@menu('Comp/RoundBox')
export default class RoundBox extends cc.RenderComponent {
@property({ type: cc.SpriteAtlas, serializable: false, readonly: true, displayName: CC_DEV && 'Atlas' })
private atlas: cc.SpriteAtlas = null;
@property
private _spriteFrame: cc.SpriteFrame = null;
@property({ type: cc.SpriteFrame, displayName: CC_DEV && 'Sprite Frame' })
get spriteFrame() { return this._spriteFrame; }
set spriteFrame(value: cc.SpriteFrame) {
this._spriteFrame = value;
this.updateSpriteFrame();
this.updateSizeMode();
}
@property
private _sizeMode: SizeMode = SizeMode['TRIMMED: 原始尺寸裁剪透明像素'];
@property({ type: cc.Enum(SizeMode), displayName: CC_DEV && 'Size Mode' })
get sizeMode() { return this._sizeMode; }
set sizeMode(value: SizeMode) {
this._sizeMode = value;
this.updateSizeMode();
}
@property
private _radius: number = 100;
@property({ displayName: CC_DEV && '圆角半径' })
get radius() { return this._radius; }
set radius(value: number) {
this._radius = Math.max(value, 0);
this['setVertsDirty']();
}
@property
private _segment: number = 5;
@property({ type: cc.Integer, displayName: CC_DEV && '线段数量' })
get segment() { return this._segment; }
set segment(value: number) {
this._segment = Math.max(value, 1);
this.createBuffer();
this.updateIndice();
this['setVertsDirty']();
this.node['_renderFlag'] |= cc['RenderFlow'].FLAG_OPACITY_COLOR;
}
@property
private _corner: Corner = new Corner();
@property({ displayName: CC_DEV && '圆角可见性' })
get corner() { return this._corner; }
set corner(value: Corner) {
this._corner = value;
this.updateCorner();
this.createBuffer();
this.updateIndice();
this['setVertsDirty']();
this.node['_renderFlag'] |= cc['RenderFlow'].FLAG_OPACITY_COLOR;
}
private renderData = null; //提交给GPU的渲染数据包括vDatas、uintVDatas、iDatas
private xyOffset: number = 1e8; //顶点坐标数据,在顶点数组中的偏移
private uvOffset: number = 1e8; //顶点uv数据在顶点数组中的偏移
private colorOffset: number = 1e8; //顶点颜色数据,在顶点数组中的偏移
private step: number = 0; //单个顶点数据的长度例如顶点格式“x,y,u,v,color” step = 5
private local: number[] = []; //顶点本地坐标
protected _resetAssembler() {
//定制Assembler
let assembler = this['_assembler'] = new cc['Assembler']();
assembler.updateRenderData = this.updateVData.bind(this);
assembler.updateColor = this.updateColor.bind(this);
assembler.init(this);
//定制RenderData
this.renderData = new cc['RenderData']();
this.renderData.init(assembler);
//初始化顶点格式
let vfmt = assembler.getVfmt();
let fmtElement = vfmt._elements;
for (let i = fmtElement.length - 1; i > -1; this.step += fmtElement[i--].bytes >> 2);
let fmtAttr = vfmt._attr2el;
this.xyOffset = fmtAttr.a_position.offset >> 2;
this.uvOffset = fmtAttr.a_uv0.offset >> 2;
this.colorOffset = fmtAttr.a_color.offset >> 2;
}
protected onLoad() {
this.updateSpriteFrame();
this.updateSizeMode();
this.updateCorner();
this.createBuffer();
this.updateIndice();
this.node.on(cc.Node.EventType.ANCHOR_CHANGED, this.onAnchorChanged, this);
this.node.on(cc.Node.EventType.SIZE_CHANGED, this.onSizeChanged, this);
}
protected onDestroy() {
super.onDestroy();
this.node.off(cc.Node.EventType.ANCHOR_CHANGED, this.onAnchorChanged, this);
this.node.off(cc.Node.EventType.SIZE_CHANGED, this.onSizeChanged, this);
}
//更新圆角数据
private updateCorner() {
let corner = this._corner;
corner.visible = [corner.leftBottom, corner.rightBottom, corner.rightTop, corner.leftTop];
}
//设置顶点个数和三角形个数
private createBuffer() {
let cornerCnt = 0;
for (let i = 0, visible = this._corner.visible; i < 4; visible[i++] && ++cornerCnt);
let vertices = new Float32Array(5 * (12 + cornerCnt * (this._segment - 1)));
let indices = new Uint16Array(3 * (6 + cornerCnt * this._segment));
this.renderData.updateMesh(0, vertices, indices);
}
//Web平台将renderData的数据提交给GPU渲染vDatas使用世界坐标
//原生平台并不会执行该函数引擎另外实现了渲染函数vDatas使用本地坐标
private fillBuffers(comp: cc.RenderComponent, renderer: any) {
let vData = this.renderData.vDatas[0];
let iData = this.renderData.iDatas[0];
renderer.worldMatDirty && this.fitXY(vData);
let buffer = renderer._meshBuffer;
let offsetInfo = buffer.request(vData.length, iData.length);
let vertexOffset = offsetInfo.byteOffset >> 2;
let vbuf = buffer._vData;
if (vData.length + vertexOffset > vbuf.length) {
vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);
} else {
vbuf.set(vData, vertexOffset);
}
let ibuf = buffer._iData;
let indiceOffset = offsetInfo.indiceOffset;
let vertexId = offsetInfo.vertexOffset;
for (let i = 0, len = iData.length; i < len; ibuf[indiceOffset++] = vertexId + iData[i++]);
}
//可以传入cc.SpriteFrame图集帧支持合批推荐或单张图片cc.Texture2D
private updateSpriteFrame() {
let frame = this._spriteFrame;
this['_assembler'].fillBuffers = frame ? this.fillBuffers.bind(this) : () => { };
let material = this.getMaterial(0) || cc.Material.getBuiltinMaterial('2d-sprite');
material.define("USE_TEXTURE", true);
material.setProperty("texture", frame ? frame.getTexture() : null);
if (CC_EDITOR) {
if (frame && frame.isValid && frame['_atlasUuid']) {
cc.assetManager.loadAny(frame['_atlasUuid'], (err, asset: cc.SpriteAtlas) => {
this.atlas = asset;
});
} else {
this.atlas = null;
}
}
}
//根据尺寸模式,修改节点尺寸
private updateSizeMode() {
if (!this._spriteFrame) return;
switch (this._sizeMode) {
case SizeMode['TRIMMED: 原始尺寸裁剪透明像素']: this.node.setContentSize(this._spriteFrame['_rect'].size); break;
case SizeMode['RAW: 图片原始尺寸']: this.node.setContentSize(this._spriteFrame['_originalSize']); break;
}
}
//计算VData数据包括xy,uv,color
private updateVData() {
let vData = this.renderData.vDatas[0];
let local = cc.sys.isNative ? vData : this.local;
let node = this.node;
let cw = node.width, ch = node.height;
let l = -cw * node.anchorX;
let b = -ch * node.anchorY;
let r = cw * (1 - node.anchorX);
let t = ch * (1 - node.anchorY);
let radius = Math.min(this._radius, Math.min(cw, ch) / 2);
let lo = l + radius;
let bo = b + radius;
let ro = r - radius;
let to = t - radius;
let corner = this._corner;
local[0] = lo; local[1] = corner.leftBottom ? bo : b;
local[5] = l; local[6] = local[1];
local[10] = lo; local[11] = b;
local[15] = ro; local[16] = corner.rightBottom ? bo : b;
local[20] = ro; local[21] = b;
local[25] = r; local[26] = local[16];
local[30] = ro; local[31] = corner.rightTop ? to : t;
local[35] = r; local[36] = local[31];
local[40] = ro; local[41] = t;
local[45] = lo; local[46] = corner.leftTop ? to : t;
local[50] = lo; local[51] = t;
local[55] = l; local[56] = local[46];
let radian = Math.PI / (this._segment << 1);
let cos = Math.cos(radian);
let sin = Math.sin(radian);
let visible = corner.visible;
for (let i = 0, offset = 60, step = this.step; i < 4; ++i) {
if (!visible[i]) continue;
let id = 3 * i * step;
let ox = local[id];
let oy = local[id + 1];
id += step;
let deltX = local[id] - ox;
let deltY = local[id + 1] - oy;
for (let j = 0, len = this._segment - 1; j < len; ++j) {
local[offset] = ox + deltX * cos - deltY * sin;
local[offset + 1] = oy + deltY * cos + deltX * sin;
deltX = local[offset] - ox;
deltY = local[offset + 1] - oy;
offset += step;
}
}
!cc.sys.isNative && this.fitXY(vData);
for (let i = 0, len = vData.length, step = this.step; i < len; i += step) {
vData[i + 2] = (local[i] - l) / cw;
vData[i + 3] = 1 - (local[i + 1] - b) / ch;
}
this.fitUV(vData);
}
//自动适配XY修改顶点xy数据后需主动调用该函数
private fitXY(vData: Float32Array) {
let m = this.node['_worldMatrix'].m;
let m0 = m[0], m1 = m[1], m4 = m[4], m5 = m[5], m12 = m[12], m13 = m[13];
for (let i = this.xyOffset, len = vData.length, step = this.step, local = this.local; i < len; i += step) {
let x = local[i], y = local[i + 1];
vData[i] = x * m0 + y * m4 + m12;
vData[i + 1] = x * m1 + y * m5 + m13;
}
}
//自动适配UV修改顶点uv数据后需主动调用该函数
private fitUV(vData: Float32Array) {
let frame = this._spriteFrame;
if (frame === null) return;
let atlasW = frame['_texture'].width, atlasH = frame['_texture'].height;
let frameRect = frame['_rect'];
//计算图集帧在大图中的UV坐标
if (frame['_rotated']) {//如果图集帧发生旋转计算UV时需回正
for (let i = this.uvOffset, id = 0, len = vData.length, step = this.step; i < len; i += step, ++id) {
let tmp = vData[i];
vData[i] = ((1 - vData[i + 1]) * frameRect.height + frameRect.x) / atlasW;
vData[i + 1] = (tmp * frameRect.width + frameRect.y) / atlasH;
}
} else {//如果图集帧未发生旋转,正常计算即可
for (let i = this.uvOffset, id = 0, len = vData.length, step = this.step; i < len; i += step, ++id) {
vData[i] = (vData[i] * frameRect.width + frameRect.x) / atlasW;
vData[i + 1] = (vData[i + 1] * frameRect.height + frameRect.y) / atlasH;
}
}
}
//计算顶点颜色
private updateColor() {
let uintVData = this.renderData.uintVDatas[0];
let color = this.node.color['_val'];
for (let i = this.colorOffset, len = uintVData.length, step = this.step; i < len; uintVData[i] = color, i += step);
}
//计算顶点索引
private updateIndice() {
let iData = this.renderData.iDatas[0];
for (let i = CENTER_IDATA.length - 1; i > -1; iData[i] = CENTER_IDATA[i--]);
let offset = CENTER_IDATA.length;
let visible = this._corner.visible;
let id = 36;
for (let i = 0; i < 4; ++i) {
if (!visible[i]) continue;
let o = 3 * i;
let a = o + 1;
let b = id / 3;
for (let j = 0, len = this._segment - 1; j < len; ++j) {
iData[offset++] = o;
iData[offset++] = a;
iData[offset++] = b;
a = b++;
id += 3;
}
iData[offset++] = o;
iData[offset++] = a;
iData[offset++] = o + 2;
}
}
//修改节点锚点后,更新顶点数据
private onAnchorChanged() {
this['setVertsDirty']();
}
//修改节点尺寸后更新顶点数据并根据sizeMode设置图片宽高
private onSizeChanged() {
this['setVertsDirty']();
if (this._spriteFrame) {
switch (this._sizeMode) {
case SizeMode['TRIMMED: 原始尺寸裁剪透明像素']:
let rect = this._spriteFrame['_rect'].size;
if (this.node.width === rect.width && this.node.height === rect.height) return;
break;
case SizeMode['RAW: 图片原始尺寸']:
let size = this._spriteFrame['_originalSize'];
if (this.node.width === size.width && this.node.height === size.height) return;
break;
}
}
this._sizeMode = SizeMode['CUSTOM: 自定义尺寸'];
}
}