mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
Add enable collision property and logic. Add AddEmptyAnimation and ClearTrack actions.
This commit is contained in:
parent
24e8a86081
commit
7dd315e2dd
@ -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;
|
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;
|
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) {
|
protected circle (x: number, y: number, radius: number) {
|
||||||
let segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
|
let segments = Math.max(1, (6 * MathUtils.cbrt(radius)) | 0);
|
||||||
@ -432,11 +432,14 @@ export class C3RendererEditor extends C3SkeletonRenderer<SDK.Gfx.IWebGLRenderer,
|
|||||||
this.renderer.DrawMesh(vertices, uvs, indices, colors);
|
this.renderer.DrawMesh(vertices, uvs, indices, colors);
|
||||||
};
|
};
|
||||||
|
|
||||||
public renderGameObjectBounds (x: number, y: number, quad: SDK.Quad): void {
|
public renderGameObjectBounds (x: number, y: number, quad: SDK.Quad, spriteBody: boolean): void {
|
||||||
const { renderer, matrix } = this;
|
const { renderer, matrix } = this;
|
||||||
renderer.SetAlphaBlend();
|
renderer.SetAlphaBlend();
|
||||||
renderer.SetColorFillMode();
|
renderer.SetColorFillMode();
|
||||||
renderer.SetColorRgba(0.25, 0, 0, 0.25);
|
if (spriteBody)
|
||||||
|
renderer.SetColorRgba(0, 0, 0.25, 0.25);
|
||||||
|
else
|
||||||
|
renderer.SetColorRgba(0.25, 0, 0, 0.25);
|
||||||
renderer.LineQuad(quad);
|
renderer.LineQuad(quad);
|
||||||
renderer.Line(x, y, matrix.tx, matrix.ty);
|
renderer.Line(x, y, matrix.tx, matrix.ty);
|
||||||
}
|
}
|
||||||
|
|||||||
210
spine-ts/spine-construct3/spine-construct3-lib/src/CustomUI.ts
Normal file
210
spine-ts/spine-construct3/spine-construct3-lib/src/CustomUI.ts
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
/******************************************************************************
|
||||||
|
* Spine Runtimes License Agreement
|
||||||
|
* Last updated April 5, 2025. Replaces all prior versions.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-2025, Esoteric Software LLC
|
||||||
|
*
|
||||||
|
* Integration of the Spine Runtimes into software or otherwise creating
|
||||||
|
* derivative works of the Spine Runtimes is permitted under the terms and
|
||||||
|
* conditions of Section 2 of the Spine Editor License Agreement:
|
||||||
|
* http://esotericsoftware.com/spine-editor-license
|
||||||
|
*
|
||||||
|
* Otherwise, it is permitted to integrate the Spine Runtimes into software
|
||||||
|
* or otherwise create derivative works of the Spine Runtimes (collectively,
|
||||||
|
* "Products"), provided that each user of the Products must obtain their own
|
||||||
|
* Spine Editor license and redistribution of the Products in any form must
|
||||||
|
* include this license and copyright notice.
|
||||||
|
*
|
||||||
|
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
|
||||||
|
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*****************************************************************************/
|
||||||
|
|
||||||
|
interface ModalButton<T> {
|
||||||
|
text: string;
|
||||||
|
color?: string;
|
||||||
|
value?: T;
|
||||||
|
style?: 'primary' | 'secondary';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModalOptions<T> {
|
||||||
|
darkMode: boolean;
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
buttons: ModalButton<T>[];
|
||||||
|
maxWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showModal<T> (options: ModalOptions<T>): Promise<T | undefined> {
|
||||||
|
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 = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="${theme.closeColor}"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>`;
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@ -3,4 +3,5 @@ export * from './AssetLoader.js';
|
|||||||
export * from './C3Matrix.js';
|
export * from './C3Matrix.js';
|
||||||
export * from './C3SkeletonRenderer.js';
|
export * from './C3SkeletonRenderer.js';
|
||||||
export * from './C3Texture.js';
|
export * from './C3Texture.js';
|
||||||
|
export * from './CustomUI.js';
|
||||||
export * from './SpineBoundsProvider.js';
|
export * from './SpineBoundsProvider.js';
|
||||||
|
|||||||
@ -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",
|
"id": "set-attachment",
|
||||||
"scriptName": "SetAttachment",
|
"scriptName": "SetAttachment",
|
||||||
@ -328,6 +347,17 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "clear-track",
|
||||||
|
"scriptName": "ClearTrack",
|
||||||
|
"highlight": false,
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"id": "track-index",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "set-slot-color",
|
"id": "set-slot-color",
|
||||||
"scriptName": "SetSlotColor",
|
"scriptName": "SetSlotColor",
|
||||||
|
|||||||
@ -33,6 +33,10 @@ C3.Plugins.EsotericSoftware_SpineConstruct3.Acts =
|
|||||||
this.stop();
|
this.stop();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
AddEmptyAnimation (this: SDKInstanceClass, track: number, mixDuration: number, delay: number) {
|
||||||
|
this.addEmptyAnimation(track, mixDuration, delay);
|
||||||
|
},
|
||||||
|
|
||||||
SetEmptyAnimation (this: SDKInstanceClass, track: number, mixDuration: number) {
|
SetEmptyAnimation (this: SDKInstanceClass, track: number, mixDuration: number) {
|
||||||
this.setEmptyAnimation(track, mixDuration);
|
this.setEmptyAnimation(track, mixDuration);
|
||||||
},
|
},
|
||||||
@ -81,6 +85,10 @@ C3.Plugins.EsotericSoftware_SpineConstruct3.Acts =
|
|||||||
this.setTrackMixBlend(mixBlend, trackIndex);
|
this.setTrackMixBlend(mixBlend, trackIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ClearTrack (this: SDKInstanceClass, trackIndex: number) {
|
||||||
|
this.clearTrack(trackIndex);
|
||||||
|
},
|
||||||
|
|
||||||
SetSlotColor (this: SDKInstanceClass, slotName: string, color: string) {
|
SetSlotColor (this: SDKInstanceClass, slotName: string, color: string) {
|
||||||
this.setSlotColor(slotName, color);
|
this.setSlotColor(slotName, color);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -18,8 +18,11 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
|||||||
propScaleY = 1;
|
propScaleY = 1;
|
||||||
propDebugSkeleton = false;
|
propDebugSkeleton = false;
|
||||||
propBoundsProvider: SpineBoundsProviderType = "setup";
|
propBoundsProvider: SpineBoundsProviderType = "setup";
|
||||||
|
propEnableCollision = false;
|
||||||
|
|
||||||
isFlippedX = false;
|
isFlippedX = false;
|
||||||
|
collisionSpriteInstance?: IWorldInstance;
|
||||||
|
collisionSpriteClassName = "";
|
||||||
isPlaying = true;
|
isPlaying = true;
|
||||||
animationSpeed = 1.0;
|
animationSpeed = 1.0;
|
||||||
physicsMode = spine.Physics.update;
|
physicsMode = spine.Physics.update;
|
||||||
@ -79,16 +82,19 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
|||||||
this.propSkin = skinProp === "" ? [] : skinProp.split(",");
|
this.propSkin = skinProp === "" ? [] : skinProp.split(",");
|
||||||
this.propAnimation = properties[4] as string;
|
this.propAnimation = properties[4] as string;
|
||||||
this.propDebugSkeleton = properties[5] as boolean;
|
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";
|
this.propBoundsProvider = boundsProviderIndex === 0 ? "setup" : "animation-skin";
|
||||||
// properties[7] is PROP_BOUNDS_PROVIDER_MOVE
|
// properties[8] is PROP_BOUNDS_PROVIDER_MOVE
|
||||||
this.propOffsetX = properties[8] as number;
|
this.propOffsetX = properties[9] as number;
|
||||||
this.propOffsetY = properties[9] as number;
|
this.propOffsetY = properties[10] as number;
|
||||||
this.propOffsetAngle = properties[10] as number;
|
this.propOffsetAngle = properties[11] as number;
|
||||||
this.propScaleX = properties[11] as number;
|
this.propScaleX = properties[12] as number;
|
||||||
this.propScaleY = properties[12] as number;
|
this.propScaleY = properties[13] as number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.collisionSpriteClassName = `${this.objectType.name}_CollisionBody`;
|
||||||
|
|
||||||
this.assetLoader = new spine.AssetLoader();
|
this.assetLoader = new spine.AssetLoader();
|
||||||
this.matrix = new spine.C3Matrix();
|
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.width / this.spineBounds.width * this.propScaleX * (this.isFlippedX ? -1 : 1),
|
||||||
this.height / this.spineBounds.height * this.propScaleY);
|
this.height / this.spineBounds.height * this.propScaleY);
|
||||||
|
|
||||||
|
this.updateCollisionSprite();
|
||||||
|
|
||||||
if (this.isPlaying) this.update(this.dt);
|
if (this.isPlaying) this.update(this.dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,6 +402,11 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
|||||||
this.skeleton = undefined;
|
this.skeleton = undefined;
|
||||||
this.state = undefined;
|
this.state = undefined;
|
||||||
this.dragHandleDispose();
|
this.dragHandleDispose();
|
||||||
|
|
||||||
|
if (this.collisionSpriteInstance) {
|
||||||
|
this.collisionSpriteInstance.destroy();
|
||||||
|
this.collisionSpriteInstance = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**********/
|
/**********/
|
||||||
@ -443,11 +456,34 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
|||||||
|
|
||||||
this.update(0);
|
this.update(0);
|
||||||
|
|
||||||
|
this.createCollisionSprite();
|
||||||
|
|
||||||
this.skeletonLoaded = true;
|
this.skeletonLoaded = true;
|
||||||
this._trigger(C3.Plugins.EsotericSoftware_SpineConstruct3.Cnds.OnSkeletonLoaded);
|
this._trigger(C3.Plugins.EsotericSoftware_SpineConstruct3.Cnds.OnSkeletonLoaded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createCollisionSprite () {
|
||||||
|
if (!this.propEnableCollision) return;
|
||||||
|
|
||||||
|
const objectType = (this.runtime.objects as Record<string, IObjectType<IWorldInstance>>)[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 () {
|
private calculateBounds () {
|
||||||
const { skeleton } = this;
|
const { skeleton } = this;
|
||||||
if (!skeleton) return;
|
if (!skeleton) return;
|
||||||
@ -489,7 +525,11 @@ class SpineC3Instance extends globalThis.ISDKWorldInstanceBase {
|
|||||||
this.isPlaying = true;
|
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);
|
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) {
|
private triggerAnimationEvent (eventName: string, track: number, animation: string, event?: Event) {
|
||||||
this.triggeredEventTrack = track;
|
this.triggeredEventTrack = track;
|
||||||
this.triggeredEventAnimation = animation;
|
this.triggeredEventAnimation = animation;
|
||||||
|
|||||||
@ -141,7 +141,7 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
|||||||
this.positioningBounds = false;
|
this.positioningBounds = false;
|
||||||
this.resetBounds(true);
|
this.resetBounds(true);
|
||||||
this.layoutView?.Refresh();
|
this.layoutView?.Refresh();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id === PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE) {
|
if (id === PLUGIN_CLASS.PROP_BOUNDS_PROVIDER_MOVE) {
|
||||||
@ -154,7 +154,12 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
|||||||
this.positionModePrevHeight = this._inst.GetHeight();
|
this.positionModePrevHeight = this._inst.GetHeight();
|
||||||
}
|
}
|
||||||
this.positioningBounds = value;
|
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();
|
const quad = _inst.GetQuad();
|
||||||
if (_inst.GetPropertyValue(PLUGIN_CLASS.PROP_DEBUG_SKELETON) as boolean)
|
if (_inst.GetPropertyValue(PLUGIN_CLASS.PROP_DEBUG_SKELETON) as boolean)
|
||||||
this.skeletonRenderer.drawDebug(skeleton, rectX, rectY, quad);
|
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 {
|
} else {
|
||||||
iRenderer.SetAlphaBlend();
|
iRenderer.SetAlphaBlend();
|
||||||
@ -513,6 +518,58 @@ class SpineC3PluginInstance extends SDK.IWorldInstanceBase {
|
|||||||
this._inst.SetPropertyValue(PLUGIN_CLASS.PROP_BOUNDS_OFFSET_ANGLE, value);
|
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 () {
|
GetTexture () {
|
||||||
const image = this.GetObjectType().GetImage();
|
const image = this.GetObjectType().GetImage();
|
||||||
return super.GetTexture(image);
|
return super.GetTexture(image);
|
||||||
|
|||||||
@ -58,6 +58,10 @@
|
|||||||
"name": "Debug skeleton",
|
"name": "Debug skeleton",
|
||||||
"desc": "Draw debug visualization of the skeleton bones"
|
"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": {
|
"spine-bounds-offset-x": {
|
||||||
"name": "Offset X",
|
"name": "Offset X",
|
||||||
"desc": "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": {
|
"set-attachment": {
|
||||||
"list-name": "Set attachment",
|
"list-name": "Set attachment",
|
||||||
"display-text": "Set slot {0} attachment to {1}",
|
"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": {
|
"set-slot-color": {
|
||||||
"list-name": "Set slot color",
|
"list-name": "Set slot color",
|
||||||
"display-text": "Set slot {0} color to {1}",
|
"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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,6 +58,10 @@
|
|||||||
"name": "调试骨架",
|
"name": "调试骨架",
|
||||||
"desc": "绘制骨架骨骼的调试可视化"
|
"desc": "绘制骨架骨骼的调试可视化"
|
||||||
},
|
},
|
||||||
|
"spine-enable-collision": {
|
||||||
|
"name": "启用碰撞",
|
||||||
|
"desc": "通过创建精灵碰撞体启用碰撞检测"
|
||||||
|
},
|
||||||
"spine-bounds-offset-x": {
|
"spine-bounds-offset-x": {
|
||||||
"name": "X偏移",
|
"name": "X偏移",
|
||||||
"desc": "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": {
|
"set-attachment": {
|
||||||
"list-name": "设置附件",
|
"list-name": "设置附件",
|
||||||
"display-text": "将插槽{0}的附件设置为{1}",
|
"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": {
|
"set-slot-color": {
|
||||||
"list-name": "设置插槽颜色",
|
"list-name": "设置插槽颜色",
|
||||||
"display-text": "设置插槽{0}的颜色为{1}",
|
"display-text": "设置插槽{0}的颜色为{1}",
|
||||||
@ -643,6 +677,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"custom_ui": {
|
||||||
|
"showModal": {
|
||||||
|
"spine-enable-collision": {
|
||||||
|
"title": "删除精灵对象类型",
|
||||||
|
"text": "这是{0}的最后一个实例。您可以删除{0}或仅禁用精灵碰撞体。删除精灵碰撞体将移除事件表中所有相关的ACE(如果有)。您想如何继续?",
|
||||||
|
"buttons": {
|
||||||
|
"0": "仅禁用",
|
||||||
|
"1": "禁用并删除{0}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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_X = "spine-offset-scale-x";
|
||||||
static PROP_SKELETON_OFFSET_SCALE_Y = "spine-offset-scale-y";
|
static PROP_SKELETON_OFFSET_SCALE_Y = "spine-offset-scale-y";
|
||||||
static PROP_DEBUG_SKELETON = "spine-debug-skeleton";
|
static PROP_DEBUG_SKELETON = "spine-debug-skeleton";
|
||||||
|
static PROP_ENABLE_COLLISION = "spine-enable-collision";
|
||||||
|
|
||||||
static TYPE_BOUNDS_SETUP: SpineBoundsProviderType = "setup";
|
static TYPE_BOUNDS_SETUP: SpineBoundsProviderType = "setup";
|
||||||
static TYPE_BOUNDS_ANIMATION_SKIN: SpineBoundsProviderType = "animation-skin";
|
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_SKIN, ""),
|
||||||
new SDK.PluginProperty("text", SpineC3Plugin.PROP_ANIMATION, ""),
|
new SDK.PluginProperty("text", SpineC3Plugin.PROP_ANIMATION, ""),
|
||||||
new SDK.PluginProperty("check", SpineC3Plugin.PROP_DEBUG_SKELETON, false),
|
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("group", SpineC3Plugin.PROP_BOUNDS_PROVIDER_GROUP),
|
||||||
new SDK.PluginProperty("combo", SpineC3Plugin.PROP_BOUNDS_PROVIDER, {
|
new SDK.PluginProperty("combo", SpineC3Plugin.PROP_BOUNDS_PROVIDER, {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user