From 7dd315e2dda608f947ca633a623ddd5af803e190 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Fri, 23 Jan 2026 17:17:29 +0100 Subject: [PATCH] Add enable collision property and logic. Add AddEmptyAnimation and ClearTrack actions. --- .../src/C3SkeletonRenderer.ts | 11 +- .../spine-construct3-lib/src/CustomUI.ts | 210 ++++++++++++++++++ .../spine-construct3-lib/src/index.ts | 1 + spine-ts/spine-construct3/src/aces.json | 30 +++ .../spine-construct3/src/c3runtime/actions.ts | 8 + .../src/c3runtime/instance.ts | 65 +++++- spine-ts/spine-construct3/src/instance.ts | 63 +++++- spine-ts/spine-construct3/src/lang/en-US.json | 46 ++++ spine-ts/spine-construct3/src/lang/zh-CN.json | 46 ++++ spine-ts/spine-construct3/src/plugin.ts | 2 + 10 files changed, 467 insertions(+), 15 deletions(-) create mode 100644 spine-ts/spine-construct3/spine-construct3-lib/src/CustomUI.ts diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts index 75e7e0273..0ce899bd2 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/C3SkeletonRenderer.ts @@ -261,7 +261,7 @@ abstract class C3SkeletonRenderer< } } - this.renderGameObjectBounds(x, y, quad); + this.renderGameObjectBounds(x, y, quad, false); } protected abstract setColor (r: number, g: number, b: number, a: number): void; @@ -274,7 +274,7 @@ abstract class C3SkeletonRenderer< }; protected abstract renderSkeleton (vertices: Float32Array, uvs: Float32Array, indices: Uint16Array, colors: Float32Array, texture: Texture, blendMode: BlendMode): void; - public abstract renderGameObjectBounds (x: number, y: number, quad: DOMQuad | SDK.Quad): void; + public abstract renderGameObjectBounds (x: number, y: number, quad: DOMQuad | SDK.Quad, spriteBody: boolean): void; protected circle (x: number, y: number, radius: number) { let segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0); @@ -432,11 +432,14 @@ export class C3RendererEditor extends C3SkeletonRenderer { + text: string; + color?: string; + value?: T; + style?: 'primary' | 'secondary'; +} + +interface ModalOptions { + darkMode: boolean; + title: string; + text: string; + buttons: ModalButton[]; + maxWidth?: number; +} + +export function showModal (options: ModalOptions): Promise { + return new Promise((resolve) => { + const { title, text, buttons, darkMode = false } = options; + + const theme = darkMode ? { + overlayBg: 'rgba(0, 0, 0, 0.5)', + captionBg: 'rgb(71, 71, 71)', // gray9 + captionText: 'rgb(214, 214, 214)', // gray27 + contentBg: 'rgb(87, 87, 87)', // gray11 + contentText: 'rgb(214, 214, 214)', // gray27 + buttonBg: 'rgb(71, 71, 71)', // gray9 + buttonText: 'rgb(214, 214, 214)', // gray27 + buttonBorder: 'rgb(56, 56, 56)', // gray7 + buttonHoverBg: 'rgb(79, 79, 79)', // gray10 + closeColor: 'rgb(168, 168, 168)', // gray21 + } : { + overlayBg: 'rgba(0, 0, 0, 0.3)', + captionBg: 'rgb(247, 247, 247)', // gray31 + captionText: 'rgb(94, 94, 94)', // gray12 + contentBg: 'rgb(232, 232, 232)', // gray29 + contentText: 'rgb(94, 94, 94)', // gray12 + buttonBg: 'rgb(222, 222, 222)', // gray28 + buttonText: 'rgb(94, 94, 94)', // gray12 + buttonBorder: 'rgb(199, 199, 199)', // gray25 + buttonHoverBg: 'rgb(214, 214, 214)', // gray27 + closeColor: 'rgb(94, 94, 94)', // gray12 + }; + + const overlay = document.createElement('div'); + overlay.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: ${theme.overlayBg}; + display: flex; + align-items: center; + justify-content: center; + z-index: 999999; + font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + `; + + const dialog = document.createElement('div'); + dialog.style.cssText = ` + background: ${theme.contentBg}; + border-radius: 6px; + min-width: 200px; + max-width: 550px; + display: flex; + flex-direction: column; + filter: drop-shadow(0 4px 5px rgba(10,10,10,0.35)) drop-shadow(0 2px 1px rgba(10,10,10,0.5)); + `; + + const caption = document.createElement('div'); + caption.style.cssText = ` + background: ${theme.captionBg}; + color: ${theme.captionText}; + padding: 6px 10px; + display: flex; + align-items: center; + justify-content: space-between; + user-select: none; + border-radius: 6px 6px 0 0; + `; + + const titleSpan = document.createElement('span'); + titleSpan.textContent = title; + + const closeBtn = document.createElement('button'); + closeBtn.innerHTML = ``; + closeBtn.style.cssText = ` + background: transparent; + border: none; + cursor: pointer; + padding: 2px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + opacity: 0.7; + `; + closeBtn.onmouseover = () => { closeBtn.style.opacity = '1'; }; + closeBtn.onmouseout = () => { closeBtn.style.opacity = '0.7'; }; + + caption.appendChild(titleSpan); + caption.appendChild(closeBtn); + + const contents = document.createElement('div'); + contents.style.cssText = ` + padding: 12px 14px; + color: ${theme.contentText}; + line-height: 1.4; + `; + contents.textContent = text; + + const footer = document.createElement('div'); + footer.style.cssText = ` + padding: 8px 14px 12px; + display: flex; + justify-content: flex-end; + gap: 6px; + `; + + const cleanup = () => { + document.removeEventListener('keydown', handleKeyDown); + overlay.remove(); + }; + + closeBtn.addEventListener('click', () => { + cleanup(); + resolve(undefined); + }); + + buttons.forEach((buttonConfig, index) => { + const btn = document.createElement('button'); + btn.textContent = buttonConfig.text; + btn.style.cssText = ` + padding: 4px 14px; + border: 1px solid ${theme.buttonBorder}; + border-radius: 3px; + background: ${theme.buttonBg}; + color: ${theme.buttonText}; + font-size: 14px; + font-family: inherit; + cursor: pointer; + `; + btn.onmouseover = () => { btn.style.background = theme.buttonHoverBg; }; + btn.onmouseout = () => { btn.style.background = theme.buttonBg; }; + + btn.addEventListener('click', () => { + cleanup(); + resolve(buttonConfig.value); + }); + + footer.appendChild(btn); + + if (index === buttons.length - 1) { + setTimeout(() => btn.focus(), 0); + } + }); + + overlay.addEventListener('click', (e) => { + if (e.target === overlay) { + cleanup(); + resolve(undefined); + } + }); + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + cleanup(); + resolve(undefined); + } + }; + document.addEventListener('keydown', handleKeyDown); + + dialog.appendChild(caption); + dialog.appendChild(contents); + dialog.appendChild(footer); + overlay.appendChild(dialog); + document.body.appendChild(overlay); + }); +} + diff --git a/spine-ts/spine-construct3/spine-construct3-lib/src/index.ts b/spine-ts/spine-construct3/spine-construct3-lib/src/index.ts index 6abb47381..555edeb45 100644 --- a/spine-ts/spine-construct3/spine-construct3-lib/src/index.ts +++ b/spine-ts/spine-construct3/spine-construct3-lib/src/index.ts @@ -3,4 +3,5 @@ export * from './AssetLoader.js'; export * from './C3Matrix.js'; export * from './C3SkeletonRenderer.js'; export * from './C3Texture.js'; +export * from './CustomUI.js'; export * from './SpineBoundsProvider.js'; diff --git a/spine-ts/spine-construct3/src/aces.json b/spine-ts/spine-construct3/src/aces.json index 9ef864398..c12853c70 100644 --- a/spine-ts/spine-construct3/src/aces.json +++ b/spine-ts/spine-construct3/src/aces.json @@ -172,6 +172,25 @@ } ] }, + { + "id": "add-empty-animation", + "scriptName": "AddEmptyAnimation", + "highlight": false, + "params": [ + { + "id": "track", + "type": "number" + }, + { + "id": "mix-duration", + "type": "number" + }, + { + "id": "delay", + "type": "number" + } + ] + }, { "id": "set-attachment", "scriptName": "SetAttachment", @@ -328,6 +347,17 @@ } ] }, + { + "id": "clear-track", + "scriptName": "ClearTrack", + "highlight": false, + "params": [ + { + "id": "track-index", + "type": "number" + } + ] + }, { "id": "set-slot-color", "scriptName": "SetSlotColor", diff --git a/spine-ts/spine-construct3/src/c3runtime/actions.ts b/spine-ts/spine-construct3/src/c3runtime/actions.ts index b80e596d1..420990dfb 100644 --- a/spine-ts/spine-construct3/src/c3runtime/actions.ts +++ b/spine-ts/spine-construct3/src/c3runtime/actions.ts @@ -33,6 +33,10 @@ C3.Plugins.EsotericSoftware_SpineConstruct3.Acts = this.stop(); }, + AddEmptyAnimation (this: SDKInstanceClass, track: number, mixDuration: number, delay: number) { + this.addEmptyAnimation(track, mixDuration, delay); + }, + SetEmptyAnimation (this: SDKInstanceClass, track: number, mixDuration: number) { this.setEmptyAnimation(track, mixDuration); }, @@ -81,6 +85,10 @@ C3.Plugins.EsotericSoftware_SpineConstruct3.Acts = this.setTrackMixBlend(mixBlend, trackIndex); }, + ClearTrack (this: SDKInstanceClass, trackIndex: number) { + this.clearTrack(trackIndex); + }, + SetSlotColor (this: SDKInstanceClass, slotName: string, color: string) { this.setSlotColor(slotName, color); }, diff --git a/spine-ts/spine-construct3/src/c3runtime/instance.ts b/spine-ts/spine-construct3/src/c3runtime/instance.ts index 6193b50cf..210c11e3e 100644 --- a/spine-ts/spine-construct3/src/c3runtime/instance.ts +++ b/spine-ts/spine-construct3/src/c3runtime/instance.ts @@ -18,8 +18,11 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { propScaleY = 1; propDebugSkeleton = false; propBoundsProvider: SpineBoundsProviderType = "setup"; + propEnableCollision = false; isFlippedX = false; + collisionSpriteInstance?: IWorldInstance; + collisionSpriteClassName = ""; isPlaying = true; animationSpeed = 1.0; physicsMode = spine.Physics.update; @@ -79,16 +82,19 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { this.propSkin = skinProp === "" ? [] : skinProp.split(","); this.propAnimation = properties[4] as string; this.propDebugSkeleton = properties[5] as boolean; - const boundsProviderIndex = properties[6] as number; + this.propEnableCollision = properties[6] as boolean; + const boundsProviderIndex = properties[7] as number; this.propBoundsProvider = boundsProviderIndex === 0 ? "setup" : "animation-skin"; - // properties[7] is PROP_BOUNDS_PROVIDER_MOVE - this.propOffsetX = properties[8] as number; - this.propOffsetY = properties[9] as number; - this.propOffsetAngle = properties[10] as number; - this.propScaleX = properties[11] as number; - this.propScaleY = properties[12] as number; + // properties[8] is PROP_BOUNDS_PROVIDER_MOVE + this.propOffsetX = properties[9] as number; + this.propOffsetY = properties[10] as number; + this.propOffsetAngle = properties[11] as number; + this.propScaleX = properties[12] as number; + this.propScaleY = properties[13] as number; } + this.collisionSpriteClassName = `${this.objectType.name}_CollisionBody`; + this.assetLoader = new spine.AssetLoader(); this.matrix = new spine.C3Matrix(); @@ -129,6 +135,8 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { this.width / this.spineBounds.width * this.propScaleX * (this.isFlippedX ? -1 : 1), this.height / this.spineBounds.height * this.propScaleY); + this.updateCollisionSprite(); + if (this.isPlaying) this.update(this.dt); } @@ -394,6 +402,11 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { this.skeleton = undefined; this.state = undefined; this.dragHandleDispose(); + + if (this.collisionSpriteInstance) { + this.collisionSpriteInstance.destroy(); + this.collisionSpriteInstance = undefined; + } } /**********/ @@ -443,11 +456,34 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { this.update(0); + this.createCollisionSprite(); + this.skeletonLoaded = true; this._trigger(C3.Plugins.EsotericSoftware_SpineConstruct3.Cnds.OnSkeletonLoaded); } } + private createCollisionSprite () { + if (!this.propEnableCollision) return; + + const objectType = (this.runtime.objects as Record>)[this.collisionSpriteClassName]; + if (!objectType) + throw new Error(`[Spine] Collision sprite object type "${this.collisionSpriteClassName}" not found`); + + this.collisionSpriteInstance = objectType.createInstance(this.layer.name, this.x, this.y); + this.collisionSpriteInstance.setOrigin(this.originX, this.originY); + } + + private updateCollisionSprite () { + if (!this.collisionSpriteInstance) return; + + this.collisionSpriteInstance.x = this.x; + this.collisionSpriteInstance.y = this.y; + this.collisionSpriteInstance.width = this.width; + this.collisionSpriteInstance.height = this.height; + this.collisionSpriteInstance.angleDegrees = this.angleDegrees; + } + private calculateBounds () { const { skeleton } = this; if (!skeleton) return; @@ -489,7 +525,11 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { this.isPlaying = true; } - public setEmptyAnimation (track: number, mixDuration = 0) { + public addEmptyAnimation (track: number, mixDuration: number, delay: number) { + this.state?.addEmptyAnimation(track, mixDuration, delay); + } + + public setEmptyAnimation (track: number, mixDuration: number) { this.state?.setEmptyAnimation(track, mixDuration); } @@ -579,6 +619,15 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase { } } + public clearTrack (track: number) { + const { state } = this; + if (!state) return; + if (track === -1) + state.clearTracks(); + else + state.clearTrack(track); + } + private triggerAnimationEvent (eventName: string, track: number, animation: string, event?: Event) { this.triggeredEventTrack = track; this.triggeredEventAnimation = animation; diff --git a/spine-ts/spine-construct3/src/instance.ts b/spine-ts/spine-construct3/src/instance.ts index 0e474bb14..bfebb4582 100644 --- a/spine-ts/spine-construct3/src/instance.ts +++ b/spine-ts/spine-construct3/src/instance.ts @@ -141,7 +141,7 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { this.positioningBounds = false; this.resetBounds(true); this.layoutView?.Refresh(); - return + return; } if (id === PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE) { @@ -154,7 +154,12 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { this.positionModePrevHeight = this._inst.GetHeight(); } this.positioningBounds = value; - return + return; + } + + if (id === PLUGIN_CLASS.PROP_ENABLE_COLLISION) { + this.managePropCollision(value as boolean); + return; } } @@ -203,7 +208,7 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { const quad = _inst.GetQuad(); if (_inst.GetPropertyValue(PLUGIN_CLASS.PROP_DEBUG_SKELETON) as boolean) this.skeletonRenderer.drawDebug(skeleton, rectX, rectY, quad); - this.skeletonRenderer.renderGameObjectBounds(rectX, rectY, quad); + this.skeletonRenderer.renderGameObjectBounds(rectX, rectY, quad, _inst.GetPropertyValue(PLUGIN_CLASS.PROP_ENABLE_COLLISION) as boolean); } else { iRenderer.SetAlphaBlend(); @@ -513,6 +518,58 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase { this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE, value); } + private async managePropCollision (value: boolean) { + const project = this._inst.GetProject(); + const objectType = this._inst.GetObjectType(); + const objectTypeName = objectType.GetName(); + const collisionSpriteName = `${objectTypeName}_CollisionBody`; + const spriteType = project.GetObjectTypeByName(collisionSpriteName); + + const type = PLUGIN_CLASS.PROP_ENABLE_COLLISION; + if (!value) { + for (const inst of objectType.GetAllInstances()) { + if (inst !== this._inst && inst.GetPropertyValue(PLUGIN_CLASS.PROP_ENABLE_COLLISION) as boolean) return; + } + if (spriteType) { + const action = await spine.showModal({ + darkMode: false, + maxWidth: 500, + title: this.lang(`showModal.${type}.title`), + text: this.lang(`showModal.${type}.text`, [collisionSpriteName]), + buttons: [ + { + text: this.lang(`showModal.${type}.buttons.${0}`), + value: 'disable' + }, + { + text: this.lang(`showModal.${type}.buttons.${1}`, [collisionSpriteName]), + color: '#d9534f', + value: 'delete' + } + ] + }); + + switch (action) { + case 'disable': break; + case 'delete': spriteType.Delete(); break; + default: this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_ENABLE_COLLISION, true); break; + } + } + return; + } + + if (!spriteType) await project.CreateObjectType("Sprite", collisionSpriteName); + } + + private lang (stringKey: string, interpolate: (string | number)[] = []): string { + const pluginContext = "plugins.esotericsoftware_spineconstruct3.custom_ui."; + let intlString = globalThis.lang(`${pluginContext}${stringKey}`); + interpolate.forEach((toInterpolate, index) => { + intlString = intlString.replace(`{${index}}`, `${toInterpolate}`); + }) + return intlString; + } + GetTexture () { const image = this.GetObjectType().GetImage(); return super.GetTexture(image); diff --git a/spine-ts/spine-construct3/src/lang/en-US.json b/spine-ts/spine-construct3/src/lang/en-US.json index e2761553b..e0695e59c 100644 --- a/spine-ts/spine-construct3/src/lang/en-US.json +++ b/spine-ts/spine-construct3/src/lang/en-US.json @@ -58,6 +58,10 @@ "name": "Debug skeleton", "desc": "Draw debug visualization of the skeleton bones" }, + "spine-enable-collision": { + "name": "Enable collision", + "desc": "Enable collision detection by creating a Sprite collision body" + }, "spine-bounds-offset-x": { "name": "Offset X", "desc": "Offset X" @@ -249,6 +253,25 @@ } } }, + "add-empty-animation": { + "list-name": "Add empty animation", + "display-text": "Add empty animation on track {0} with mix duration {1} and delay {2}", + "description": "Add an empty animation to the animation queue on a specific track", + "params": { + "track": { + "name": "Track", + "desc": "Track index" + }, + "mix-duration": { + "name": "Mix duration", + "desc": "Duration in seconds to mix from the current animation to empty" + }, + "delay": { + "name": "Delay", + "desc": "Delay in seconds before the animation starts" + } + } + }, "set-attachment": { "list-name": "Set attachment", "display-text": "Set slot {0} attachment to {1}", @@ -418,6 +441,17 @@ } } }, + "clear-track": { + "list-name": "Clear track(s)", + "display-text": "Clear track {0}", + "description": "Clear te given track, or all tracks if -1", + "params": { + "track-index": { + "name": "Track index", + "desc": "Track index to clear. -1 to clear all tracks." + } + } + }, "set-slot-color": { "list-name": "Set slot color", "display-text": "Set slot {0} color to {1}", @@ -643,6 +677,18 @@ } } } + }, + "custom_ui": { + "showModal": { + "spine-enable-collision": { + "title": "Delete Sprite Object Type", + "text": "This is the last instance of {0}. You can delete the {0} or just disable the Sprite collision body. Deleting the Sprite collision body will remove all related ACEs in the event sheet, if there's any. How do you want to proceed?", + "buttons": { + "0": "Just disable", + "1": "Disable and delete {0}" + } + } + } } } } diff --git a/spine-ts/spine-construct3/src/lang/zh-CN.json b/spine-ts/spine-construct3/src/lang/zh-CN.json index 62545aba5..c9477daee 100644 --- a/spine-ts/spine-construct3/src/lang/zh-CN.json +++ b/spine-ts/spine-construct3/src/lang/zh-CN.json @@ -58,6 +58,10 @@ "name": "调试骨架", "desc": "绘制骨架骨骼的调试可视化" }, + "spine-enable-collision": { + "name": "启用碰撞", + "desc": "通过创建精灵碰撞体启用碰撞检测" + }, "spine-bounds-offset-x": { "name": "X偏移", "desc": "X偏移" @@ -249,6 +253,25 @@ } } }, + "add-empty-animation": { + "list-name": "添加空动画", + "display-text": "在轨道{0}上添加空动画,混合时长{1},延迟{2}", + "description": "将空动画添加到特定轨道的动画队列中", + "params": { + "track": { + "name": "轨道", + "desc": "轨道索引" + }, + "mix-duration": { + "name": "混合时长", + "desc": "从当前动画混合到空动画的持续时间(秒)" + }, + "delay": { + "name": "延迟", + "desc": "动画开始前的延迟时间(秒)" + } + } + }, "set-attachment": { "list-name": "设置附件", "display-text": "将插槽{0}的附件设置为{1}", @@ -418,6 +441,17 @@ } } }, + "clear-track": { + "list-name": "清除轨道", + "display-text": "清除轨道{0}", + "description": "清除给定的轨道,如果为-1则清除所有轨道", + "params": { + "track-index": { + "name": "轨道索引", + "desc": "要清除的轨道索引。-1表示清除所有轨道。" + } + } + }, "set-slot-color": { "list-name": "设置插槽颜色", "display-text": "设置插槽{0}的颜色为{1}", @@ -643,6 +677,18 @@ } } } + }, + "custom_ui": { + "showModal": { + "spine-enable-collision": { + "title": "删除精灵对象类型", + "text": "这是{0}的最后一个实例。您可以删除{0}或仅禁用精灵碰撞体。删除精灵碰撞体将移除事件表中所有相关的ACE(如果有)。您想如何继续?", + "buttons": { + "0": "仅禁用", + "1": "禁用并删除{0}" + } + } + } } } } diff --git a/spine-ts/spine-construct3/src/plugin.ts b/spine-ts/spine-construct3/src/plugin.ts index 354938333..ba4fdb1b1 100644 --- a/spine-ts/spine-construct3/src/plugin.ts +++ b/spine-ts/spine-construct3/src/plugin.ts @@ -34,6 +34,7 @@ const PLUGIN_CLASS = class SpineC3Plugin extends SDK.IPluginBase { static PROP_SKELETON_OFFSET_SCALE_X = "spine-offset-scale-x"; static PROP_SKELETON_OFFSET_SCALE_Y = "spine-offset-scale-y"; static PROP_DEBUG_SKELETON = "spine-debug-skeleton"; + static PROP_ENABLE_COLLISION = "spine-enable-collision"; static TYPE_BOUNDS_SETUP: SpineBoundsProviderType = "setup"; static TYPE_BOUNDS_ANIMATION_SKIN: SpineBoundsProviderType = "animation-skin"; @@ -80,6 +81,7 @@ const PLUGIN_CLASS = class SpineC3Plugin extends SDK.IPluginBase { new SDK.PluginProperty("text", SpineC3Plugin.PROP_SKIN, ""), new SDK.PluginProperty("text", SpineC3Plugin.PROP_ANIMATION, ""), new SDK.PluginProperty("check", SpineC3Plugin.PROP_DEBUG_SKELETON, false), + new SDK.PluginProperty("check", SpineC3Plugin.PROP_ENABLE_COLLISION, false), new SDK.PluginProperty("group", SpineC3Plugin.PROP_BOUNDS_PROVIDER_GROUP), new SDK.PluginProperty("combo", SpineC3Plugin.PROP_BOUNDS_PROVIDER, {