From 46920ff54ff2eee575353e704e297730f1752dac Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Thu, 14 Aug 2025 17:37:34 +0200 Subject: [PATCH] Improved reset bounds management. --- .../spine-construct3-lib/src/AssetLoader.ts | 32 +- .../src/c3runtime/instance.ts | 17 +- spine-ts/spine-construct3/src/instance.ts | 321 +++++++++++------- spine-ts/spine-construct3/src/lang/en-US.json | 13 +- spine-ts/spine-construct3/src/plugin.ts | 31 +- spine-ts/spine-construct3/src/spine_badge.png | Bin 0 -> 4567 bytes spine-ts/spine-construct3/src/type.ts | 24 ++ 7 files changed, 262 insertions(+), 176 deletions(-) create mode 100644 spine-ts/spine-construct3/src/spine_badge.png diff --git a/spine-ts/spine-construct3-lib/src/AssetLoader.ts b/spine-ts/spine-construct3-lib/src/AssetLoader.ts index dc81cdf31..6b5e36da4 100644 --- a/spine-ts/spine-construct3-lib/src/AssetLoader.ts +++ b/spine-ts/spine-construct3-lib/src/AssetLoader.ts @@ -32,32 +32,30 @@ import { C3Texture, C3TextureEditor } from "./C3Texture"; export class AssetLoader { - constructor (private type: "editor" | "runtime") { - } - public async loadSkeletonEditor (path: string, textureAtlas: TextureAtlas, instance: SDK.IWorldInstance) { - const projectFile = instance.GetProject().GetProjectFileByName(path); + public async loadSkeletonEditor (sid: number, textureAtlas: TextureAtlas, scale = 1, instance: SDK.IWorldInstance) { + const projectFile = instance.GetProject().GetProjectFileBySID(sid); if (!projectFile) return null; const blob = projectFile.GetBlob(); const atlasLoader = new AtlasAttachmentLoader(textureAtlas); - const isBinary = path.endsWith(".skel"); + const isBinary = projectFile.GetName().endsWith(".skel"); if (isBinary) { const skeletonFile = await blob.arrayBuffer(); const skeletonLoader = new SkeletonBinary(atlasLoader); - skeletonLoader.scale = 1; + skeletonLoader.scale = scale; return skeletonLoader.readSkeletonData(skeletonFile); } const skeletonFile = await blob.text(); const skeletonLoader = new SkeletonJson(atlasLoader); - skeletonLoader.scale = 1; + skeletonLoader.scale = scale; return skeletonLoader.readSkeletonData(skeletonFile); } - public async loadAtlasEditor (path: string, instance: SDK.IWorldInstance, renderer: SDK.Gfx.IWebGLRenderer) { - const projectFile = instance.GetProject().GetProjectFileByName(path); + public async loadAtlasEditor (sid: number, instance: SDK.IWorldInstance, renderer: SDK.Gfx.IWebGLRenderer) { + const projectFile = instance.GetProject().GetProjectFileBySID(sid); if (!projectFile) return null; const blob = projectFile.GetBlob(); @@ -77,31 +75,31 @@ export class AssetLoader { } public async loadSpineTextureEditor (pageName: string, pma = false, instance: SDK.IWorldInstance) { - const projectFile = instance.GetProject().GetProjectFileByName(pageName); + const projectFile = instance.GetProject().GetProjectFileByExportPath(pageName); if (!projectFile) { throw new Error(`An error occured while loading the texture: ${pageName}`); } const content = projectFile.GetBlob(); - return this.createImageBitmapFromBlob(content, pma); + return AssetLoader.createImageBitmapFromBlob(content, pma); } public async loadSkeletonRuntime (path: string, textureAtlas: TextureAtlas, scale = 1, instance: IRuntime) { const fullPath = await instance.assets.getProjectFileUrl(path); if (!fullPath) return null; - const content = await instance.assets.fetchArrayBuffer(fullPath); - if (!content) return null; - const atlasLoader = new AtlasAttachmentLoader(textureAtlas); const isBinary = path.endsWith(".skel"); if (isBinary) { + const content = await instance.assets.fetchArrayBuffer(fullPath); + if (!content) return null; const skeletonLoader = new SkeletonBinary(atlasLoader); skeletonLoader.scale = scale; return skeletonLoader.readSkeletonData(content); } - + const content = await instance.assets.fetchJson(fullPath); + if (!content) return null; const skeletonLoader = new SkeletonJson(atlasLoader); skeletonLoader.scale = scale; return skeletonLoader.readSkeletonData(content); @@ -133,10 +131,10 @@ export class AssetLoader { const content = await instance.assets.fetchBlob(fullPath); if (!content) return null; - return this.createImageBitmapFromBlob(content, pma); + return AssetLoader.createImageBitmapFromBlob(content, pma); } - private async createImageBitmapFromBlob (blob: Blob, pma: boolean): Promise { + static async createImageBitmapFromBlob (blob: Blob, pma: boolean): Promise { try { return createImageBitmap(blob, { premultiplyAlpha: pma ? "none" : "premultiply" }); } catch (e) { diff --git a/spine-ts/spine-construct3/src/c3runtime/instance.ts b/spine-ts/spine-construct3/src/c3runtime/instance.ts index a2eb93971..7fc95511a 100644 --- a/spine-ts/spine-construct3/src/c3runtime/instance.ts +++ b/spine-ts/spine-construct3/src/c3runtime/instance.ts @@ -8,6 +8,7 @@ spine.Skeleton.yDown = true; class DrawingInstance extends globalThis.ISDKWorldInstanceBase { propAtlas = ""; propSkel = ""; + propLoaderScale = 1; propSkin: string[] = []; propAnimation?: string; propOffsetX = 0; @@ -33,21 +34,23 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase { const properties = this._getInitProperties(); if (properties) { + console.log(properties); this.propAtlas = properties[0] as string; this.propSkel = properties[1] as string; - const skinProp = properties[2] as string; + this.propLoaderScale = properties[2] as number; + const skinProp = properties[3] as string; this.propSkin = skinProp === "" ? [] : skinProp.split(","); - this.propAnimation = properties[3] as string; + this.propAnimation = properties[4] as string; this.propOffsetX = properties[7] as number; this.propOffsetY = properties[8] as number; this.propOffsetAngle = properties[9] as number; this.propScaleX = properties[10] as number; this.propScaleY = properties[11] as number; - console.log(properties); + } - this.assetLoader = new spine.AssetLoader("runtime"); + this.assetLoader = new spine.AssetLoader(); this.skeletonRenderer = new spine.SkeletonRendererCore(); this._setTicking(true); @@ -81,7 +84,7 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase { const propValue = this.propSkel; if (this.atlasLoaded && this.textureAtlas) { - const skeletonData = await this.assetLoader.loadSkeletonRuntime(propValue, this.textureAtlas, 1, this.plugin.runtime); + const skeletonData = await this.assetLoader.loadSkeletonRuntime(propValue, this.textureAtlas, this.propLoaderScale, this.plugin.runtime); if (!skeletonData) return; this.skeleton = new spine.Skeleton(skeletonData); @@ -96,13 +99,9 @@ class DrawingInstance extends globalThis.ISDKWorldInstanceBase { this.update(0); - // Initially, width and height are values set on C3 Editor side that allows to determine the right scale this.skeleton.scaleX = this.propScaleX; this.skeleton.scaleY = this.propScaleY; - // this.setSize(this._spineBounds.width * this.skeleton.scaleX, this._spineBounds.height * -this.skeleton.scaleY); - // this.setOrigin(-this._spineBounds.x * this.skeleton.scaleX / this.width, this._spineBounds.y * this.skeleton.scaleY / this.height); - this.skeletonLoaded = true; this._trigger(C3.Plugins.EsotericSoftware_SpineConstruct3.Cnds.OnSkeletonLoaded); } diff --git a/spine-ts/spine-construct3/src/instance.ts b/spine-ts/spine-construct3/src/instance.ts index 41f94cdde..ce348b055 100644 --- a/spine-ts/spine-construct3/src/instance.ts +++ b/spine-ts/spine-construct3/src/instance.ts @@ -14,52 +14,53 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { private layoutView?: SDK.UI.ILayoutView; private renderer?: SDK.Gfx.IWebGLRenderer; - private currentSkelName = ""; - private currentAtlasName = ""; + private currentAtlasFileSID = -1; private textureAtlas?: TextureAtlas; skeleton?: Skeleton; state?: AnimationState; skins: string[] = []; - currentSkinString?: string; animation?: string; private assetLoader: AssetLoader; private skeletonRenderer: SkeletonRendererCore; - private positioningBounds = false; - private spineBoundsProviderType: SpineBoundsProviderType = "setup"; - private spineBoundsProvider?: SpineBoundsProvider; - private initialBounds = { - x: 0, - y: 0, - width: 0, - height: 0, - }; - // position mode + private positioningBounds = false; private positionModePrevX = 0; private positionModePrevY = 0; private positionModePrevAngle = 0; + private spineBounds = { + x: 0, + y: 0, + width: 100, + height: 100, + }; + + // utils for drawing private tempVertices = new Float32Array(4096); + // errors + private errors: Record = {}; + constructor (sdkType: SDK.ITypeBase, inst: SDK.IWorldInstance) { super(sdkType, inst); if (!spine) spine = globalThis.spine; spine.Skeleton.yDown = true; - this.assetLoader = new spine.AssetLoader("editor"); + this.assetLoader = new spine.AssetLoader(); this.skeletonRenderer = new spine.SkeletonRendererCore(); + (this._inst as any).errors = this.errors; } Release () { } OnCreate () { - console.log("OnCreate"); this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE, false); + this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON) as number; } OnPlacedInLayout () { @@ -70,7 +71,6 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { this.layoutView ||= iDrawParams.GetLayoutView(); this.renderer ||= iRenderer; - this.loadAtlas(); this.loadSkeleton(); @@ -89,8 +89,8 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { offsetY += rectY; offsetAngle += rectAngle; - const baseScaleX = this._inst.GetWidth() / this.initialBounds.width; - const baseScaleY = this._inst.GetHeight() / this.initialBounds.height; + const baseScaleX = this._inst.GetWidth() / this.spineBounds.width; + const baseScaleY = this._inst.GetHeight() / this.spineBounds.height; this.skeleton.scaleX = baseScaleX; this.skeleton.scaleY = baseScaleY; this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X, baseScaleX); @@ -129,7 +129,9 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { vertices[dstIndex + 2] = 0; } + iRenderer.ResetColor(); iRenderer.SetAlphaBlend(); + iRenderer.SetTextureFillMode(); iRenderer.SetTexture(command.texture.texture); iRenderer.DrawMesh( vertices.subarray(0, numVertices * 3), @@ -145,59 +147,96 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { iRenderer.SetColorRgba(0.25, 0, 0, 0.25); iRenderer.LineQuad(this._inst.GetQuad()); iRenderer.Line(rectX, rectY, offsetX, offsetY); - console.log(offsetX, offsetY); + + if (this.hasErrors()) { + iRenderer.SetColorFillMode(); + iRenderer.SetColorRgba(1, 0, 0, .5); + iRenderer.Quad(this._inst.GetQuad()); + } } else { - // render placeholder - iRenderer.SetAlphaBlend(); - iRenderer.SetColorFillMode(); - if (this.HadTextureError()) - iRenderer.SetColorRgba(0.25, 0, 0, 0.25); - else - iRenderer.SetColorRgba(0, 0, 0.1, 0.1); + const sdkType = (this._sdkType as any); + + + const logo = sdkType.getSpineLogo(iRenderer); + if (logo) { + iRenderer.ResetColor(); + iRenderer.SetAlphaBlend(); + iRenderer.SetTexture(logo); + iRenderer.Quad(this._inst.GetQuad()); + } else { + iRenderer.SetAlphaBlend(); + iRenderer.SetColorFillMode(); + + if (this.HadTextureError()) + iRenderer.SetColorRgba(0.25, 0, 0, 0.25); + else + iRenderer.SetColorRgba(0, 0, 0.1, 0.1); + + iRenderer.Quad(this._inst.GetQuad()); + } + - iRenderer.Quad(this._inst.GetQuad()); } } async OnPropertyChanged (id: string, value: EditorPropertyValueType) { console.log(`Prop change - Name: ${id} - Value: ${value}`); - switch (id) { - case PLUGIN_CLASS.PROP_ATLAS: - this.layoutView?.Refresh(); - break; - case PLUGIN_CLASS.PROP_SKELETON: - this.layoutView?.Refresh(); - break; - case PLUGIN_CLASS.PROP_SKIN: - this.layoutView?.Refresh(); - break; - case PLUGIN_CLASS.PROP_ANIMATION: - this.layoutView?.Refresh(); - break; - case PLUGIN_CLASS.PROP_BOUNDS_PROVIDER: - this.setC3Bounds(true); - this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X, 0); - this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y, 0); - this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE, 0); - this.layoutView?.Refresh(); - break; - case PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE: { - value = value as boolean - if (value) { - this.positionModePrevX = this._inst.GetX(); - this.positionModePrevY = this._inst.GetY(); - this.positionModePrevAngle = this._inst.GetAngle(); - } else { - const scaleX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X) as number; - const scaleY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number; - this.initialBounds.width = this._inst.GetWidth() / scaleX; - this.initialBounds.height = this._inst.GetHeight() / scaleY; - } - this.positioningBounds = value; - break; + if (id === PLUGIN_CLASS.PROP_ATLAS) { + this.textureAtlas?.dispose(); + this.textureAtlas = undefined; + this.skins = []; + this.layoutView?.Refresh(); + return; + } + + if (id === PLUGIN_CLASS.PROP_SKELETON) { + this.skeleton = undefined; + this.skins = []; + this.layoutView?.Refresh(); + return; + } + + if (id === PLUGIN_CLASS.PROP_LOADER_SCALE) { + this.skeleton = undefined; + this.skins = []; + this.layoutView?.Refresh(); + return; + } + + if (id === PLUGIN_CLASS.PROP_SKIN) { + this.skins = []; + this.setSkin(); + this.layoutView?.Refresh(); + return; + } + + if (id === PLUGIN_CLASS.PROP_ANIMATION) { + this.layoutView?.Refresh(); + return; + } + + if (id === PLUGIN_CLASS.PROP_BOUNDS_PROVIDER) { + this.resetBounds(); + this.layoutView?.Refresh(); + return + } + + if (id === PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE) { + value = value as boolean + if (value) { + this.positionModePrevX = this._inst.GetX(); + this.positionModePrevY = this._inst.GetY(); + this.positionModePrevAngle = this._inst.GetAngle(); + } else { + const scaleX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X) as number; + const scaleY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number; + this.spineBounds.width = this._inst.GetWidth() / scaleX; + this.spineBounds.height = this._inst.GetHeight() / scaleY; } + this.positioningBounds = value; + return } console.log("Prop change end"); @@ -209,13 +248,12 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKIN) as string; - if (this.currentSkinString === propValue) return; - this.currentSkinString = propValue; - const skins = propValue === "" ? [] : propValue.split(","); this.skins = skins; - if (skins.length === 1) { + if (skins.length === 0) { + skeleton.setSkin(null); + } else if (skins.length === 1) { const skinName = skins[0]; const skin = skeleton.data.findSkin(skinName); if (!skin) { @@ -223,7 +261,7 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { return; } skeleton.setSkin(skins[0]); - } else if (skins.length > 1) { + } else { const customSkin = new spine.Skin(propValue); for (const s of skins) { const skin = skeleton.data.findSkin(s) @@ -238,71 +276,103 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { skeleton.setupPose(); this.update(0); - - this.setC3Bounds(); } private async loadSkeleton () { - const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON) as string; - const projectFile = this._inst.GetProject().GetProjectFileByName(propValue); + if (!this.renderer || !this.textureAtlas) return; + if (this.skeleton) return; - if (projectFile && this.textureAtlas) { - if (this.currentSkelName === propValue) return; - this.currentSkelName = propValue; + console.log("Loading skeleton"); - const skeletonData = await this.assetLoader.loadSkeletonEditor(propValue, this.textureAtlas, this._inst); - if (!skeletonData) return; + const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON) as number; + const loaderScale = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_LOADER_SCALE) as number; + const skeletonData = await this.assetLoader.loadSkeletonEditor(propValue, this.textureAtlas, loaderScale, this._inst); + console.log(skeletonData); + if (!skeletonData) return; - this.skeleton = new spine.Skeleton(skeletonData); - const animationStateData = new spine.AnimationStateData(skeletonData); - this.state = new spine.AnimationState(animationStateData); + this.skeleton = new spine.Skeleton(skeletonData); + const animationStateData = new spine.AnimationStateData(skeletonData); + this.state = new spine.AnimationState(animationStateData); - this.update(0); + this.setSkin(); + this.update(0); + this.setBoundsFromBoundsProvider(); + this.initBounds(); - this.setSkin(); - - const offsetX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X) as number; - const offsetY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y) as number; - const offsetAngle = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE) as number; - const scaleX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X) as number; - const scaleY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number; - const init = offsetX !== 0 && offsetY !== 0 && offsetAngle !== 0 && scaleX !== 1 && scaleY !== 1; - this.setC3Bounds(init); - - this.layoutView?.Refresh(); - console.log("SKELETON LOADED"); - } + this.layoutView?.Refresh(); + console.log("SKELETON LOADED"); } - private setC3Bounds (init = false) { + private async loadAtlas () { + if (!this.renderer) return; + + const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_ATLAS) as number; + console.log("Loading atlas"); + + if (this.currentAtlasFileSID === propValue) return; + this.currentAtlasFileSID = propValue; + + const textureAtlas = await this.assetLoader.loadAtlasEditor(propValue, this._inst, this.renderer); + if (!textureAtlas) return; + + this.textureAtlas = textureAtlas; + this.layoutView?.Refresh(); + } + + private setBoundsFromBoundsProvider () { const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER) as SpineBoundsProviderType; + let spineBoundsProvider: SpineBoundsProvider; if (propValue === "animation-skin") { const { skins, animation } = this; if ((skins && skins.length > 0) || animation) { - this.spineBoundsProvider = new spine.SkinsAndAnimationBoundsProvider(animation, skins); + spineBoundsProvider = new spine.SkinsAndAnimationBoundsProvider(animation, skins); } else { - throw new Error("One among skin and animation needs to have a value to set this bounds provider."); + return false; } } else if (propValue === "setup") { - this.spineBoundsProvider = new spine.SetupPoseBoundsProvider(); + spineBoundsProvider = new spine.SetupPoseBoundsProvider(); } else { - this.spineBoundsProvider = new spine.AABBRectangleBoundsProvider(0, 0, 100, 100); + spineBoundsProvider = new spine.AABBRectangleBoundsProvider(0, 0, 100, 100); } - this.initialBounds = this.spineBoundsProvider.calculateBounds(this); + this.spineBounds = spineBoundsProvider.calculateBounds(this); - const { x, y, width, height } = this.initialBounds; + return true; + } - if (width <= 0 || height <= 0 || !init) return; + private resetBounds () { + this.setBoundsFromBoundsProvider(); + if (this.hasErrors()) return; + const { x, y, width, height } = this.spineBounds; + + this._inst.SetOrigin(-x / width, -y / height); this._inst.SetSize(width, height); - if (propValue === "AABB") { - this._inst.SetOrigin(.5, .5); - } else { - this._inst.SetOrigin(-x / width, -y / height); + this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X, 0); + this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y, 0); + this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE, 0); + return; + } + + private initBounds () { + const offsetX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_X) as number; + const offsetY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_Y) as number; + const offsetAngle = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE) as number; + const shiftedBounds = offsetX !== 0 || offsetY !== 0 || offsetAngle !== 0; + + const scaleX = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_X) as number; + const scaleY = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_SKELETON_SCALE_Y) as number; + const scaledBounds = scaleX !== 1 || scaleY !== 1; + + if (shiftedBounds || scaledBounds) { + this.spineBounds.width = this._inst.GetWidth() / scaleX; + this.spineBounds.height = this._inst.GetHeight() / scaleY; + return; } + + this.resetBounds(); } private update (delta: number) { @@ -316,19 +386,31 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { skeleton.updateWorldTransform(spine.Physics.update); } - private async loadAtlas () { - if (!this.renderer) return; + private setError (key: string, condition: boolean, message: string) { + if (condition) { + this.errors[key] = message; + return; + } + delete this.errors[key]; + } + private hasErrors () { + const { errors, skins, animation, spineBounds } = this; - const propValue = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_ATLAS) as string; + const boundsType = this._inst.GetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_PROVIDER) as SpineBoundsProviderType; + this.setError( + "boundsAnimationSkinType", + boundsType === "animation-skin" && ((!skins || skins.length === 0) && !animation), + "Animation/Skin bounds provider requires one between skin and animation to be set." + ); - if (this.currentAtlasName === propValue) return; - this.currentAtlasName = propValue; + const { width, height } = spineBounds; + this.setError( + "boundsNoDimension", + width <= 0 || height <= 0, + "A bounds cannot have negative dimension" + ); - const textureAtlas = await this.assetLoader.loadAtlasEditor(propValue, this._inst, this.renderer); - if (!textureAtlas) return; - - this.textureAtlas = textureAtlas; - this.layoutView?.Refresh(); + return Object.keys(errors).length > 0; } GetTexture () { @@ -341,20 +423,15 @@ class MyDrawingInstance extends SDK.IWorldInstanceBase { } GetOriginalWidth () { - if (!this.initialBounds) return 100; - return this.initialBounds.width; + return this.spineBounds.width; } GetOriginalHeight () { - if (!this.initialBounds) return 100; - return this.initialBounds.height; + return this.spineBounds.height; } OnMakeOriginalSize () { - if (!this.initialBounds) - this._inst.SetSize(100, 100); - else - this._inst.SetSize(this.initialBounds.width, this.initialBounds.height); + this._inst.SetSize(this.spineBounds.width, this.spineBounds.height); } HasDoubleTapHandler () { diff --git a/spine-ts/spine-construct3/src/lang/en-US.json b/spine-ts/spine-construct3/src/lang/en-US.json index e88eee863..9ad218b2f 100644 --- a/spine-ts/spine-construct3/src/lang/en-US.json +++ b/spine-ts/spine-construct3/src/lang/en-US.json @@ -18,9 +18,9 @@ "name": "Atlas", "desc": "Atlas file" }, - "spine-skeleton-file-blob": { - "name": "Skel Blob", - "desc": "Skel file blob" + "spine-loader-scale": { + "name": "Loader scale", + "desc": "Loader scale" }, "spine-animation": { "name": "animation", @@ -30,6 +30,10 @@ "name": "skin", "desc": "skin" }, + "spine-errors": { + "name": "Errors", + "desc": "errors" + }, "spine-bounds-provider-group": { "name": "Bounds provider", "desc": "Select the desired buound provider and fill the respective properties." @@ -38,9 +42,8 @@ "name": "Bounds provider", "desc": "The bounds provider to use.", "items": { - "AABB": "AABB Rectangle", "setup": "Setup pose bounds", - "animation-skin": "Animation + Skin bounds" + "animation-skin": "Animation/Skin bounds" } }, "spine-bounds-provider-move": { diff --git a/spine-ts/spine-construct3/src/plugin.ts b/spine-ts/spine-construct3/src/plugin.ts index e8bf2a29a..c6f7c21ab 100644 --- a/spine-ts/spine-construct3/src/plugin.ts +++ b/spine-ts/spine-construct3/src/plugin.ts @@ -15,17 +15,15 @@ const PLUGIN_ID = "EsotericSoftware_SpineConstruct3"; const PLUGIN_CATEGORY = "general"; -let app = null; - const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase { static PROP_ATLAS = "spine-atlas-file"; static PROP_SKELETON = "spine-skeleton-file"; + static PROP_LOADER_SCALE = "spine-loader-scale"; static PROP_SKIN = "spine-skin"; static PROP_ANIMATION = "spine-animation"; static PROP_ERRORS = "spine-errors"; static PROP_RATIO_WIDTH = "spine-restore-ratio-width"; static PROP_RATIO_HEIGHT = "spine-restore-ratio-height"; - static PROP_SKELETON_BLOB = "spine-skeleton-file-blob"; static PROP_BOUNDS_PROVIDER_GROUP = "spine-bounds-provider-group"; static PROP_BOUNDS_PROVIDER = "spine-bounds-provider"; static PROP_BOUNDS_PROVIDER_MOVE = "spine-bounds-provider-move"; @@ -37,12 +35,10 @@ const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase { static TYPE_BOUNDS_SETUP = "setup"; static TYPE_BOUNDS_ANIMATION_SKIN = "animation-skin"; - static TYPE_BOUNDS_AABB = "AABB"; constructor () { super(PLUGIN_ID); - SDK.Lang.PushContext("plugins." + PLUGIN_ID.toLowerCase()); // @ts-ignore @@ -69,37 +65,26 @@ const PLUGIN_CLASS = class MyDrawingPlugin extends SDK.IPluginBase { SDK.Lang.PushContext(".properties"); this._info.SetProperties([ - new SDK.PluginProperty("text", MyDrawingPlugin.PROP_ATLAS, ""), - new SDK.PluginProperty("text", MyDrawingPlugin.PROP_SKELETON, ""), + new SDK.PluginProperty("projectfile", MyDrawingPlugin.PROP_ATLAS, ""), + new SDK.PluginProperty("projectfile", MyDrawingPlugin.PROP_SKELETON, ""), + new SDK.PluginProperty("float", MyDrawingPlugin.PROP_LOADER_SCALE, 1), new SDK.PluginProperty("text", MyDrawingPlugin.PROP_SKIN, ""), new SDK.PluginProperty("text", MyDrawingPlugin.PROP_ANIMATION, ""), - new SDK.PluginProperty("projectfile", MyDrawingPlugin.PROP_SKELETON_BLOB, ""), new SDK.PluginProperty("info", MyDrawingPlugin.PROP_ERRORS, { infoCallback (inst) { - const atlas = inst.GetInstance().GetPropertyValue(MyDrawingPlugin.PROP_ATLAS); - const skeleton = inst.GetInstance().GetPropertyValue(MyDrawingPlugin.PROP_SKELETON); - - let error = ""; - if (atlas && skeleton) { - error = "You can't set both .skel and .json skeleton file."; - } - - if (!atlas && !skeleton) { - error = "Missing skeleton file."; - } - - return error; + const errors = (inst.GetInstance() as unknown as { errors: Record }).errors; + return Object.values(errors).reduce((acc, next) => { + return acc === "" ? next : `${acc}\n${next}`; + }, ""); }, }), - new SDK.PluginProperty("group", MyDrawingPlugin.PROP_BOUNDS_PROVIDER_GROUP), new SDK.PluginProperty("combo", MyDrawingPlugin.PROP_BOUNDS_PROVIDER, { initialValue: "setup", items: [ MyDrawingPlugin.TYPE_BOUNDS_SETUP, MyDrawingPlugin.TYPE_BOUNDS_ANIMATION_SKIN, - MyDrawingPlugin.TYPE_BOUNDS_AABB ], }), new SDK.PluginProperty("check", MyDrawingPlugin.PROP_BOUNDS_PROVIDER_MOVE, false), diff --git a/spine-ts/spine-construct3/src/spine_badge.png b/spine-ts/spine-construct3/src/spine_badge.png new file mode 100644 index 0000000000000000000000000000000000000000..422f11a8a54bad9410806524611f221501baacb8 GIT binary patch literal 4567 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 char.charCodeAt(0))], { type: "image/png" }); + globalThis.spine.AssetLoader.createImageBitmapFromBlob(blob, false).then((image) => { + if (image === null) return; + + this.spineLogo = iRenderer.CreateDynamicTexture(image.width, image.height); + iRenderer.UpdateTexture(image, this.spineLogo); + }) + } + }; +const spineLogoBase64 = "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"; + + export { } \ No newline at end of file