diff --git a/assets/resources/scenes/roundbox.scene b/assets/resources/scenes/roundbox.scene new file mode 100644 index 0000000..66a204a --- /dev/null +++ b/assets/resources/scenes/roundbox.scene @@ -0,0 +1,971 @@ +[ + { + "__type__": "cc.SceneAsset", + "_name": "roundbox", + "_objFlags": 0, + "__editorExtras__": {}, + "_native": "", + "scene": { + "__id__": 1 + } + }, + { + "__type__": "cc.Scene", + "_name": "roundbox", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": null, + "_children": [ + { + "__id__": 2 + }, + { + "__id__": 5 + }, + { + "__id__": 7 + } + ], + "_active": true, + "_components": [], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "autoReleaseAssets": false, + "_globals": { + "__id__": 22 + }, + "_id": "64bd0402-59ce-4670-ae7d-24b450f09bb9" + }, + { + "__type__": "cc.Node", + "_name": "Main Light", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 1 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 3 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": -0.06397656665577071, + "y": -0.44608233363525845, + "z": -0.8239028751062036, + "w": -0.3436591377065261 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": -117.894, + "y": -194.909, + "z": 38.562 + }, + "_id": "c0y6F5f+pAvI805TdmxIjx" + }, + { + "__type__": "cc.DirectionalLight", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 2 + }, + "_enabled": true, + "__prefab": null, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 250, + "b": 240, + "a": 255 + }, + "_useColorTemperature": false, + "_colorTemperature": 6550, + "_staticSettings": { + "__id__": 4 + }, + "_visibility": -325058561, + "_illuminanceHDR": 65000, + "_illuminance": 65000, + "_illuminanceLDR": 1.6927083333333335, + "_shadowEnabled": false, + "_shadowPcf": 0, + "_shadowBias": 0.00001, + "_shadowNormalBias": 0, + "_shadowSaturation": 1, + "_shadowDistance": 50, + "_shadowInvisibleOcclusionRange": 200, + "_csmLevel": 4, + "_csmLayerLambda": 0.75, + "_csmOptimizationMode": 2, + "_csmAdvancedOptions": false, + "_csmLayersTransition": false, + "_csmTransitionRange": 0.05, + "_shadowFixedArea": false, + "_shadowNear": 0.1, + "_shadowFar": 10, + "_shadowOrthoSize": 5, + "_id": "597uMYCbhEtJQc0ffJlcgA" + }, + { + "__type__": "cc.StaticLightSettings", + "_baked": false, + "_editorOnly": false, + "_castShadow": false + }, + { + "__type__": "cc.Node", + "_name": "Main Camera", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 1 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 6 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": -10, + "y": 10, + "z": 10 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": -0.27781593346944056, + "y": -0.36497167621709875, + "z": -0.11507512748638377, + "w": 0.8811195706053617 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": -35, + "y": -45, + "z": 0 + }, + "_id": "c9DMICJLFO5IeO07EPon7U" + }, + { + "__type__": "cc.Camera", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 5 + }, + "_enabled": true, + "__prefab": null, + "_projection": 1, + "_priority": 0, + "_fov": 45, + "_fovAxis": 0, + "_orthoHeight": 10, + "_near": 1, + "_far": 1000, + "_color": { + "__type__": "cc.Color", + "r": 51, + "g": 51, + "b": 51, + "a": 255 + }, + "_depth": 1, + "_stencil": 0, + "_clearFlags": 14, + "_rect": { + "__type__": "cc.Rect", + "x": 0, + "y": 0, + "width": 1, + "height": 1 + }, + "_aperture": 19, + "_shutter": 7, + "_iso": 0, + "_screenScale": 1, + "_visibility": 1822425087, + "_targetTexture": null, + "_postProcess": null, + "_usePostProcess": false, + "_cameraType": -1, + "_trackingType": 0, + "_id": "7dWQTpwS5LrIHnc1zAPUtf" + }, + { + "__type__": "cc.Node", + "_name": "Canvas", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 1 + }, + "_children": [ + { + "__id__": 8 + }, + { + "__id__": 10 + }, + { + "__id__": 13 + }, + { + "__id__": 16 + } + ], + "_active": true, + "_components": [ + { + "__id__": 19 + }, + { + "__id__": 20 + }, + { + "__id__": 21 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 640, + "y": 360, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 33554432, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "5fT/Uf85lJJZpgroGvWOUP" + }, + { + "__type__": "cc.Node", + "_name": "Camera", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 7 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 9 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 1000 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 1073741824, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "53wshulJZJKanZ/cnTuKZX" + }, + { + "__type__": "cc.Camera", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 8 + }, + "_enabled": true, + "__prefab": null, + "_projection": 0, + "_priority": 1073741824, + "_fov": 45, + "_fovAxis": 0, + "_orthoHeight": 459.1304347826087, + "_near": 1, + "_far": 2000, + "_color": { + "__type__": "cc.Color", + "r": 0, + "g": 0, + "b": 0, + "a": 255 + }, + "_depth": 1, + "_stencil": 0, + "_clearFlags": 6, + "_rect": { + "__type__": "cc.Rect", + "x": 0, + "y": 0, + "width": 1, + "height": 1 + }, + "_aperture": 19, + "_shutter": 7, + "_iso": 0, + "_screenScale": 1, + "_visibility": 41943040, + "_targetTexture": null, + "_postProcess": null, + "_usePostProcess": false, + "_cameraType": -1, + "_trackingType": 0, + "_id": "dd4qeuIZxNC6uH61NhldHf" + }, + { + "__type__": "cc.Node", + "_name": "Sprite", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 7 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 11 + }, + { + "__id__": 12 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": -354.815, + "y": -45.319, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 33554432, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "f9ysfwe4ZLTYYFY8wC/Jts" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 10 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 140, + "height": 140 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "92G9/yRZNHiKLCK8lou2cg" + }, + { + "__type__": "99a69kLVh1Ep7wj+z4OSfo/", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 10 + }, + "_enabled": true, + "__prefab": null, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_sizeMode": 1, + "_atlas": null, + "_segments": 10, + "_radius": 20, + "_spriteFrame": { + "__uuid__": "890f043a-8cad-46e4-b5b1-584554c0cad1@f9941", + "__expectedType__": "cc.SpriteFrame" + }, + "_leftTop": true, + "_rightTop": true, + "_leftBottom": true, + "_rightBottom": true, + "_id": "9fP6W2c0JG16NWr95ESiGu" + }, + { + "__type__": "cc.Node", + "_name": "Sprite-001", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 7 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 14 + }, + { + "__id__": 15 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": -151.432, + "y": -45.319, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 33554432, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "bd/vKa14NAi4pmNiXxAXVK" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 13 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 140, + "height": 140 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "1a8Pb9CC9N7oGMkNREzGEl" + }, + { + "__type__": "99a69kLVh1Ep7wj+z4OSfo/", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 13 + }, + "_enabled": true, + "__prefab": null, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_sizeMode": 1, + "_atlas": null, + "_segments": 10, + "_radius": 41.8, + "_spriteFrame": { + "__uuid__": "890f043a-8cad-46e4-b5b1-584554c0cad1@f9941", + "__expectedType__": "cc.SpriteFrame" + }, + "_leftTop": false, + "_rightTop": true, + "_leftBottom": true, + "_rightBottom": true, + "_id": "abjASJYeZLVLx0NfyXgj19" + }, + { + "__type__": "cc.Node", + "_name": "Sprite-002", + "_objFlags": 0, + "__editorExtras__": {}, + "_parent": { + "__id__": 7 + }, + "_children": [], + "_active": true, + "_components": [ + { + "__id__": 17 + }, + { + "__id__": 18 + } + ], + "_prefab": null, + "_lpos": { + "__type__": "cc.Vec3", + "x": 59.689, + "y": -45.319, + "z": 0 + }, + "_lrot": { + "__type__": "cc.Quat", + "x": 0, + "y": 0, + "z": 0, + "w": 1 + }, + "_lscale": { + "__type__": "cc.Vec3", + "x": 1, + "y": 1, + "z": 1 + }, + "_mobility": 0, + "_layer": 33554432, + "_euler": { + "__type__": "cc.Vec3", + "x": 0, + "y": 0, + "z": 0 + }, + "_id": "a0WXB1+odOopn63S//VwzS" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 16 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 140, + "height": 140 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "401TOO479LmbUeYXthL5C2" + }, + { + "__type__": "99a69kLVh1Ep7wj+z4OSfo/", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 16 + }, + "_enabled": true, + "__prefab": null, + "_customMaterial": null, + "_srcBlendFactor": 2, + "_dstBlendFactor": 4, + "_color": { + "__type__": "cc.Color", + "r": 255, + "g": 255, + "b": 255, + "a": 255 + }, + "_sizeMode": 1, + "_atlas": null, + "_segments": 10, + "_radius": 70, + "_spriteFrame": { + "__uuid__": "890f043a-8cad-46e4-b5b1-584554c0cad1@f9941", + "__expectedType__": "cc.SpriteFrame" + }, + "_leftTop": true, + "_rightTop": true, + "_leftBottom": true, + "_rightBottom": true, + "_id": "f2CeSLb09FPaKE976Tc3TG" + }, + { + "__type__": "cc.UITransform", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 7 + }, + "_enabled": true, + "__prefab": null, + "_contentSize": { + "__type__": "cc.Size", + "width": 1280, + "height": 720 + }, + "_anchorPoint": { + "__type__": "cc.Vec2", + "x": 0.5, + "y": 0.5 + }, + "_id": "86CSOZGUpGVKnT8V8Ci8rW" + }, + { + "__type__": "cc.Canvas", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 7 + }, + "_enabled": true, + "__prefab": null, + "_cameraComponent": { + "__id__": 9 + }, + "_alignCanvasWithScreen": true, + "_id": "0a62p7bzdJsYDJdwyJzFyP" + }, + { + "__type__": "cc.Widget", + "_name": "", + "_objFlags": 0, + "__editorExtras__": {}, + "node": { + "__id__": 7 + }, + "_enabled": true, + "__prefab": null, + "_alignFlags": 45, + "_target": null, + "_left": 0, + "_right": 0, + "_top": 0, + "_bottom": 0, + "_horizontalCenter": 0, + "_verticalCenter": 0, + "_isAbsLeft": true, + "_isAbsRight": true, + "_isAbsTop": true, + "_isAbsBottom": true, + "_isAbsHorizontalCenter": true, + "_isAbsVerticalCenter": true, + "_originalWidth": 0, + "_originalHeight": 0, + "_alignMode": 2, + "_lockFlags": 0, + "_id": "091TrFLkFO1aPnT2TxMiSY" + }, + { + "__type__": "cc.SceneGlobals", + "ambient": { + "__id__": 23 + }, + "shadows": { + "__id__": 24 + }, + "_skybox": { + "__id__": 25 + }, + "fog": { + "__id__": 26 + }, + "octree": { + "__id__": 27 + }, + "skin": { + "__id__": 28 + }, + "lightProbeInfo": { + "__id__": 29 + }, + "postSettings": { + "__id__": 30 + }, + "bakedWithStationaryMainLight": false, + "bakedWithHighpLightmap": false + }, + { + "__type__": "cc.AmbientInfo", + "_skyColorHDR": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.5, + "z": 0.8, + "w": 0.520833125 + }, + "_skyColor": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.5, + "z": 0.8, + "w": 0.520833125 + }, + "_skyIllumHDR": 20000, + "_skyIllum": 20000, + "_groundAlbedoHDR": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.2, + "z": 0.2, + "w": 1 + }, + "_groundAlbedo": { + "__type__": "cc.Vec4", + "x": 0.2, + "y": 0.2, + "z": 0.2, + "w": 1 + }, + "_skyColorLDR": { + "__type__": "cc.Vec4", + "x": 0.452588, + "y": 0.607642, + "z": 0.755699, + "w": 0 + }, + "_skyIllumLDR": 0.8, + "_groundAlbedoLDR": { + "__type__": "cc.Vec4", + "x": 0.618555, + "y": 0.577848, + "z": 0.544564, + "w": 0 + } + }, + { + "__type__": "cc.ShadowsInfo", + "_enabled": false, + "_type": 0, + "_normal": { + "__type__": "cc.Vec3", + "x": 0, + "y": 1, + "z": 0 + }, + "_distance": 0, + "_shadowColor": { + "__type__": "cc.Color", + "r": 76, + "g": 76, + "b": 76, + "a": 255 + }, + "_maxReceived": 4, + "_size": { + "__type__": "cc.Vec2", + "x": 1024, + "y": 1024 + } + }, + { + "__type__": "cc.SkyboxInfo", + "_envLightingType": 0, + "_envmapHDR": { + "__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0", + "__expectedType__": "cc.TextureCube" + }, + "_envmap": { + "__uuid__": "d032ac98-05e1-4090-88bb-eb640dcb5fc1@b47c0", + "__expectedType__": "cc.TextureCube" + }, + "_envmapLDR": { + "__uuid__": "6f01cf7f-81bf-4a7e-bd5d-0afc19696480@b47c0", + "__expectedType__": "cc.TextureCube" + }, + "_diffuseMapHDR": null, + "_diffuseMapLDR": null, + "_enabled": true, + "_useHDR": true, + "_editableMaterial": null, + "_reflectionHDR": null, + "_reflectionLDR": null, + "_rotationAngle": 0 + }, + { + "__type__": "cc.FogInfo", + "_type": 0, + "_fogColor": { + "__type__": "cc.Color", + "r": 200, + "g": 200, + "b": 200, + "a": 255 + }, + "_enabled": false, + "_fogDensity": 0.3, + "_fogStart": 0.5, + "_fogEnd": 300, + "_fogAtten": 5, + "_fogTop": 1.5, + "_fogRange": 1.2, + "_accurate": false + }, + { + "__type__": "cc.OctreeInfo", + "_enabled": false, + "_minPos": { + "__type__": "cc.Vec3", + "x": -1024, + "y": -1024, + "z": -1024 + }, + "_maxPos": { + "__type__": "cc.Vec3", + "x": 1024, + "y": 1024, + "z": 1024 + }, + "_depth": 8 + }, + { + "__type__": "cc.SkinInfo", + "_enabled": true, + "_blurRadius": 0.01, + "_sssIntensity": 3 + }, + { + "__type__": "cc.LightProbeInfo", + "_giScale": 1, + "_giSamples": 1024, + "_bounces": 2, + "_reduceRinging": 0, + "_showProbe": true, + "_showWireframe": true, + "_showConvex": false, + "_data": null, + "_lightProbeSphereVolume": 1 + }, + { + "__type__": "cc.PostSettingsInfo", + "_toneMappingType": 0 + } +] \ No newline at end of file diff --git a/assets/resources/scenes/roundbox.scene.meta b/assets/resources/scenes/roundbox.scene.meta new file mode 100644 index 0000000..7e3db06 --- /dev/null +++ b/assets/resources/scenes/roundbox.scene.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.1.49", + "importer": "scene", + "imported": true, + "uuid": "64bd0402-59ce-4670-ae7d-24b450f09bb9", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/assets/scripts/components/GPRoundBoxAssembler.ts b/assets/scripts/components/GPRoundBoxAssembler.ts new file mode 100644 index 0000000..6f4319d --- /dev/null +++ b/assets/scripts/components/GPRoundBoxAssembler.ts @@ -0,0 +1,248 @@ +import { IAssembler, IRenderData, RenderData, dynamicAtlasManager } from "cc"; +import { GPRoundBoxSprite } from "./GPRoundBoxSprite"; + +export const GPRoundBoxAssembler: IAssembler = { + + // 根据圆角segments参数,构造网格的顶点索引列表 + GetIndexBuffer(sprite:GPRoundBoxSprite) { + let indexBuffer = [ + 0, 1, 2, 2, 3, 0, + 4, 5, 6, 6, 7, 4, + 8, 9, 10, 10, 11, 8 + ] + + // 为四个角的扇形push进索引值 + let index = 12 + let fanIndexBuild = function(center, start, end) { + let last = start; + for (let i = 0; i < sprite.segments - 1; i++) { + // 左上角 p2为扇形圆心,p1/p5为两个边界 + let cur = index; + index++; + indexBuffer.push(center, last, cur); + last = cur; + } + indexBuffer.push(center, last, end) + } + if (sprite.leftBottom) + fanIndexBuild(3, 4, 0); + if (sprite.leftTop) + fanIndexBuild(2, 1, 5); + if (sprite.rightTop) + fanIndexBuild(9, 6, 10); + if (sprite.rightBottom) + fanIndexBuild(8, 11, 7); + return indexBuffer + }, + createData (sprite: GPRoundBoxSprite) { + const renderData = sprite.requestRenderData(); + let corner = 0; + corner += sprite.leftBottom ? 1: 0; + corner += sprite.leftTop ? 1: 0; + corner += sprite.rightTop ? 1: 0; + corner += sprite.rightBottom ? 1: 0; + + let vNum = 12 + (sprite.segments - 1) * corner; + renderData.dataLength = vNum; + renderData.resize(vNum, 18 + sprite.segments * 3 * corner); + + let indexBuffer = GPRoundBoxAssembler.GetIndexBuffer(sprite); + renderData.chunk.setIndexBuffer(indexBuffer); + return renderData; + }, + + // 照抄simple的 + updateRenderData (sprite: GPRoundBoxSprite) { + const frame = sprite.spriteFrame; + + dynamicAtlasManager.packToDynamicAtlas(sprite, frame); + this.updateUVs(sprite);// dirty need + //this.updateColor(sprite);// dirty need + + const renderData = sprite.renderData; + if (renderData && frame) { + if (renderData.vertDirty) { + this.updateVertexData(sprite); + } + renderData.updateRenderData(sprite, frame); + } + }, + + // 局部坐标转世界坐标 照抄的,不用改 + updateWorldVerts (sprite: GPRoundBoxSprite, chunk: { vb: any; }) { + const renderData = sprite.renderData!; + const vData = chunk.vb; + + const dataList: IRenderData[] = renderData.data; + const node = sprite.node; + const m = node.worldMatrix; + + const stride = renderData.floatStride; + let offset = 0; + const length = dataList.length; + for (let i = 0; i < length; i++) { + const curData = dataList[i]; + const x = curData.x; + const y = curData.y; + let rhw = m.m03 * x + m.m07 * y + m.m15; + rhw = rhw ? 1 / rhw : 1; + + offset = i * stride; + vData[offset + 0] = (m.m00 * x + m.m04 * y + m.m12) * rhw; + vData[offset + 1] = (m.m01 * x + m.m05 * y + m.m13) * rhw; + vData[offset + 2] = (m.m02 * x + m.m06 * y + m.m14) * rhw; + } + }, + + // 每帧调用的,把数据和到一整个meshbuffer里 + fillBuffers (sprite: GPRoundBoxSprite) { + if (sprite === null) { + return; + } + + const renderData = sprite.renderData!; + const chunk = renderData.chunk; + if (sprite.node.hasChangedFlags || renderData.vertDirty) { + // const vb = chunk.vertexAccessor.getVertexBuffer(chunk.bufferId); + this.updateWorldVerts(sprite, chunk); + renderData.vertDirty = false; + } + + // quick version + const bid = chunk.bufferId; + const vidOrigin = chunk.vertexOffset; + const meshBuffer = chunk.meshBuffer; + const ib = chunk.meshBuffer.iData; + let indexOffset = meshBuffer.indexOffset; + + const vid = vidOrigin; + // 沿着当前这个位置往后将我们这个对象的index放进去 + let indexBuffer = GPRoundBoxAssembler.GetIndexBuffer(sprite); + for (let i = 0; i < renderData.indexCount; i++) { + ib[indexOffset++] = vid + indexBuffer[i]; + } + meshBuffer.indexOffset += renderData.indexCount; + }, + + // 计算每个顶点相对于sprite坐标的位置 + updateVertexData (sprite: GPRoundBoxSprite) { + const renderData: RenderData | null = sprite.renderData; + if (!renderData) { + return; + } + + const uiTrans = sprite.node._uiProps.uiTransformComp!; + const dataList: IRenderData[] = renderData.data; + const cw = uiTrans.width; + const ch = uiTrans.height; + const appX = uiTrans.anchorX * cw; + const appY = uiTrans.anchorY * ch; + + const left = 0 - appX; + const right = cw - appX; + const top = ch - appY; + const bottom = 0 - appY; + + const left_r = left + sprite.radius; + const bottom_r = bottom + sprite.radius; + const top_r = top - sprite.radius; + const right_r = right - sprite.radius; + + // 三个矩形的顶点 + dataList[0].x = left; + dataList[0].y = sprite.leftBottom ? bottom_r: bottom; + dataList[1].x = left; + dataList[1].y = sprite.leftTop ? top_r: top; + dataList[2].x = left_r; + dataList[2].y = sprite.leftTop ? top_r: top; + dataList[3].x = left_r; + dataList[3].y = sprite.leftBottom ? bottom_r: bottom; + dataList[4].x = left_r; + dataList[4].y = bottom; + dataList[5].x = left_r; + dataList[5].y = top; + dataList[6].x = right_r; + dataList[6].y = top; + dataList[7].x = right_r; + dataList[7].y = bottom; + dataList[8].x = right_r; + dataList[8].y = sprite.rightBottom ? bottom_r: bottom; + dataList[9].x = right_r; + dataList[9].y = sprite.rightTop ? top_r: top; + dataList[10].x = right; + dataList[10].y = sprite.rightTop ? top_r: top; + dataList[11].x = right; + dataList[11].y = sprite.rightBottom ? bottom_r: bottom; + + // 扇形圆角的顶点 + let index = 12; + let fanPosBuild = function(center, startAngle) { + for (let i = 1; i < sprite.segments; i++) { + // 我这里顶点都是按顺时针分配的,所以角度要从开始角度减 + // 每个扇形都是90度 + let angle = startAngle * Math.PI / 180 - i / sprite.segments * 0.5 * Math.PI; + dataList[index].x = center.x + Math.cos(angle) * sprite.radius; + dataList[index].y = center.y + Math.sin(angle) * sprite.radius; + index++; + } + } + if (sprite.leftBottom) + fanPosBuild(dataList[3], 270); + if (sprite.leftTop) + fanPosBuild(dataList[2], 180); + if (sprite.rightTop) + fanPosBuild(dataList[9], 90); + if (sprite.rightBottom) + fanPosBuild(dataList[8], 0); + + renderData.vertDirty = true; + }, + + // 更新计算uv + updateUVs (sprite: GPRoundBoxSprite) { + if (!sprite.spriteFrame) return; + const renderData = sprite.renderData!; + const vData = renderData.chunk.vb; + const uv = sprite.spriteFrame.uv; + + // 这里我打印了一下uv的值,第一个看上去是左上角,但其实,opengl端的纹理存在上下颠倒问题,所以这里其实还是左下角 + // 左下,右下,左上,右上 + const uv_l = uv[0]; + const uv_b = uv[1]; + const uv_r = uv[2]; + const uv_t = uv[5]; + const uv_w = Math.abs(uv_r - uv_l); + const uv_h = uv_t - uv_b; + + const uiTrans = sprite.node._uiProps.uiTransformComp!; + const dataList: IRenderData[] = renderData.data; + const cw = uiTrans.width; + const ch = uiTrans.height; + const appX = uiTrans.anchorX * cw; + const appY = uiTrans.anchorY * ch; + + // 用相对坐标,计算uv + for (let i = 0; i < renderData.dataLength; i++) { + vData[i * renderData.floatStride + 3] = uv_l + (dataList[i].x + appX) / cw * uv_w; + vData[i * renderData.floatStride + 4] = uv_b + (dataList[i].y + appY) / ch * uv_h; + } + }, + + // 照抄,不用改 + updateColor (sprite: GPRoundBoxSprite) { + const renderData = sprite.renderData!; + const vData = renderData.chunk.vb; + let colorOffset = 5; + const color = sprite.color; + const colorR = color.r / 255; + const colorG = color.g / 255; + const colorB = color.b / 255; + const colorA = color.a / 255; + for (let i = 0; i < renderData.dataLength; i++, colorOffset += renderData.floatStride) { + vData[colorOffset] = colorR; + vData[colorOffset + 1] = colorG; + vData[colorOffset + 2] = colorB; + vData[colorOffset + 3] = colorA; + } + }, +}; \ No newline at end of file diff --git a/assets/scripts/components/GPRoundBoxAssembler.ts.meta b/assets/scripts/components/GPRoundBoxAssembler.ts.meta new file mode 100644 index 0000000..303fcf1 --- /dev/null +++ b/assets/scripts/components/GPRoundBoxAssembler.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "34dcec5f-d278-44e1-b921-ad33d4776917", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/scripts/components/GPRoundBoxSprite.ts b/assets/scripts/components/GPRoundBoxSprite.ts new file mode 100644 index 0000000..7f53bd6 --- /dev/null +++ b/assets/scripts/components/GPRoundBoxSprite.ts @@ -0,0 +1,336 @@ +import { _decorator, ccenum, CCFloat, CCInteger, cclegacy, Component, InstanceMaterialType, Material, Node, NodeEventType, RenderTexture, serializeTag, Sprite, SpriteAtlas, SpriteFrame, UIRenderer, Vec2} from 'cc'; +import { BUILD, EDITOR } from 'cc/env'; +import { GPRoundBoxAssembler } from './GPRoundBoxAssembler'; + +const { ccclass, property,type} = _decorator; +enum EventType { + SPRITE_FRAME_CHANGED = 'spriteframe-changed', +} + +@ccclass('GPRoundBoxSprite') +export class GPRoundBoxSprite extends UIRenderer { + // 尺寸模式,可以看枚举原本定义的地方有注释说明 + @property({serializable:true}) + protected _sizeMode = Sprite.SizeMode.TRIMMED; + @type(Sprite.SizeMode) + get sizeMode () { + return this._sizeMode; + } + set sizeMode (value) { + if (this._sizeMode === value) { + return; + } + + this._sizeMode = value; + if (value !== Sprite.SizeMode.CUSTOM) { + this._applySpriteSize(); + } + } + // 图集 + @property({serializable:true}) + protected _atlas: SpriteAtlas | null = null; + @type(SpriteAtlas) + get spriteAtlas () { + return this._atlas; + } + set spriteAtlas (value) { + if (this._atlas === value) { + return; + } + this._atlas = value; + } + // 圆角用三角形模拟扇形的线段数量,越大,则越圆滑 + @property({type:CCInteger, serializable:true}) + _segments:number = 10 + @property({type:CCInteger, serializable:true, min:1}) + public get segments() { + return this._segments; + } + public set segments(segments) { + this._segments = segments; + this._renderData = null; + this._flushAssembler(); + } + // 圆角半径 + @property({type:CCFloat, serializable:true}) + _radius:number = 20 + @property({type:CCFloat, serializable:true, min:0}) + public get radius() { + return this._radius + } + public set radius(radius) { + this._radius = radius; + this._updateUVs(); + this.markForUpdateRenderData(true) + } + + @property({serializable:true}) + protected _spriteFrame: SpriteFrame | null = null; + @type(SpriteFrame) + get spriteFrame () { + return this._spriteFrame; + } + set spriteFrame (value) { + if (this._spriteFrame === value) { + return; + } + + const lastSprite = this._spriteFrame; + this._spriteFrame = value; + this.markForUpdateRenderData(); + this._applySpriteFrame(lastSprite); + if (EDITOR) { + this.node.emit(EventType.SPRITE_FRAME_CHANGED, this); + } + } + @property({serializable:true}) + protected _leftTop:boolean = true; + @property({serializable:true}) + get leftTop () { + return this._leftTop; + } + set leftTop (value:boolean) { + this._leftTop = value; + this.resetAssembler(); + } + @property({serializable:true}) + protected _rightTop:boolean = true; + @property({serializable:true}) + get rightTop () { + return this._rightTop; + } + set rightTop (value:boolean) { + this._rightTop = value; + this.resetAssembler(); + } + @property({serializable:true}) + protected _leftBottom:boolean = true; + @property({serializable:true}) + get leftBottom () { + return this._leftBottom; + } + set leftBottom (value:boolean) { + this._leftBottom = value; + this.resetAssembler(); + } + @property({serializable:true}) + protected _rightBottom:boolean = true; + @property({serializable:true}) + get rightBottom () { + return this._rightBottom; + } + set rightBottom (value:boolean) { + this._rightBottom = value; + this.resetAssembler(); + } + onLoad(): void { + this._flushAssembler(); + } + + public __preload () { + this.changeMaterialForDefine(); + super.__preload(); + + if (EDITOR) { + this._resized(); + this.node.on(NodeEventType.SIZE_CHANGED, this._resized, this); + } + } + + public onEnable () { + super.onEnable(); + + // Force update uv, material define, active material, etc + this._activateMaterial(); + const spriteFrame = this._spriteFrame; + if (spriteFrame) { + this._updateUVs(); + } + } + + public onDestroy () { + if (EDITOR) { + this.node.off(NodeEventType.SIZE_CHANGED, this._resized, this); + } + super.onDestroy(); + } + + /** + * @en + * Quickly switch to other sprite frame in the sprite atlas. + * If there is no atlas, the switch fails. + * + * @zh + * 选取使用精灵图集中的其他精灵。 + * @param name @en Name of the spriteFrame to switch. @zh 要切换的 spriteFrame 名字。 + */ + public changeSpriteFrameFromAtlas (name: string) { + if (!this._atlas) { + console.warn('SpriteAtlas is null.'); + return; + } + const sprite = this._atlas.getSpriteFrame(name); + this.spriteFrame = sprite; + } + + /** + * @deprecated Since v3.7.0, this is an engine private interface that will be removed in the future. + */ + public changeMaterialForDefine () { + let texture; + const lastInstanceMaterialType = this._instanceMaterialType; + if (this._spriteFrame) { + texture = this._spriteFrame.texture; + } + let value = false; + if (texture instanceof cclegacy.TextureBase) { + const format = texture.getPixelFormat(); + value = (format === cclegacy.TextureBase.PixelFormat.RGBA_ETC1 || format === cclegacy.TextureBase.PixelFormat.RGB_A_PVRTC_4BPPV1 || format === cclegacy.TextureBase.PixelFormat.RGB_A_PVRTC_2BPPV1); + } + + if (value) { + this._instanceMaterialType = InstanceMaterialType.USE_ALPHA_SEPARATED; + } else { + this._instanceMaterialType = InstanceMaterialType.ADD_COLOR_AND_TEXTURE; + } + if (lastInstanceMaterialType !== this._instanceMaterialType) { + // this.updateMaterial(); + // d.ts里没有注上这个函数,直接调用会表红。 + this["updateMaterial"](); + } + } + + protected _updateBuiltinMaterial () { + let mat = super._updateBuiltinMaterial(); + if (this.spriteFrame && this.spriteFrame.texture instanceof RenderTexture) { + const defines = { SAMPLE_FROM_RT: true, ...mat.passes[0].defines }; + const renderMat = new Material(); + renderMat.initialize({ + effectAsset: mat.effectAsset, + defines, + }); + mat = renderMat; + } + return mat; + } + + protected _render (render) { + render.commitComp(this, this.renderData, this._spriteFrame, this._assembler, null); + } + + protected _canRender () { + if (!super._canRender()) { + return false; + } + + const spriteFrame = this._spriteFrame; + if (!spriteFrame || !spriteFrame.texture) { + return false; + } + + return true; + } + + protected resetAssembler() { + this._assembler = null; + this._flushAssembler(); + } + protected _flushAssembler () { + const assembler = GPRoundBoxAssembler; + + if (this._assembler !== assembler) { + this.destroyRenderData(); + this._assembler = assembler; + } + + + if (!this._renderData) { + if (this._assembler && this._assembler.createData) { + this._renderData = this._assembler.createData(this); + this._renderData!.material = this.getRenderMaterial(0); + this.markForUpdateRenderData(); + if (this.spriteFrame) { + this._assembler.updateRenderData(this); + } + this._updateColor(); + } + } + } + + private _applySpriteSize () { + if (this._spriteFrame) { + if (BUILD || !this._spriteFrame.isDefault) { + if (Sprite.SizeMode.RAW === this._sizeMode) { + const size = this._spriteFrame.originalSize; + this.node._uiProps.uiTransformComp!.setContentSize(size); + } else if (Sprite.SizeMode.TRIMMED === this._sizeMode) { + const rect = this._spriteFrame.rect; + this.node._uiProps.uiTransformComp!.setContentSize(rect.width, rect.height); + } + } + this.markForUpdateRenderData(true) + this._assembler.updateRenderData(this); + } + } + + private _resized () { + if (!EDITOR) { + return; + } + + if (this._spriteFrame) { + const actualSize = this.node._uiProps.uiTransformComp!.contentSize; + let expectedW = actualSize.width; + let expectedH = actualSize.height; + if (this._sizeMode === Sprite.SizeMode.RAW) { + const size = this._spriteFrame.originalSize; + expectedW = size.width; + expectedH = size.height; + } else if (this._sizeMode === Sprite.SizeMode.TRIMMED) { + const rect = this._spriteFrame.rect; + expectedW = rect.width; + expectedH = rect.height; + } + + if (expectedW !== actualSize.width || expectedH !== actualSize.height) { + this._sizeMode = Sprite.SizeMode.CUSTOM; + } + } + } + + private _activateMaterial () { + const spriteFrame = this._spriteFrame; + const material = this.getRenderMaterial(0); + if (spriteFrame) { + if (material) { + this.markForUpdateRenderData(); + } + } + + if (this.renderData) { + this.renderData.material = material; + } + } + + private _updateUVs () { + if (this._assembler) { + this._assembler.updateUVs(this); + } + } + + private _applySpriteFrame (oldFrame: SpriteFrame | null) { + const spriteFrame = this._spriteFrame; + + let textureChanged = false; + if (spriteFrame) { + if (!oldFrame || oldFrame.texture !== spriteFrame.texture) { + textureChanged = true; + } + if (textureChanged) { + if (this.renderData) this.renderData.textureDirty = true; + this.changeMaterialForDefine(); + } + this._applySpriteSize(); + } + } +} diff --git a/assets/scripts/components/GPRoundBoxSprite.ts.meta b/assets/scripts/components/GPRoundBoxSprite.ts.meta new file mode 100644 index 0000000..c86b6b4 --- /dev/null +++ b/assets/scripts/components/GPRoundBoxSprite.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.23", + "importer": "typescript", + "imported": true, + "uuid": "99a6990b-561d-44a7-bc23-fb3e0e49fa3f", + "files": [], + "subMetas": {}, + "userData": {} +}