import { _decorator, Component, Enum, Node, setPropertyEnumType, sp } from 'cc'; const { ccclass, property, type } = _decorator; export enum SocketPath {} Enum(SocketPath); export enum SpineAnimation {} Enum(SpineAnimation); export enum SpineSkin {} Enum(SpineSkin); @ccclass('SpineAnimationHandler') export default class SpineAnimationHandler extends Component { @property(sp.Skeleton) private skeleton: sp.Skeleton; @property({ visible: true }) private _flipX: boolean = false; @property({ visible: true }) private _flipY: boolean = false; public get flipX() { return this._flipX; } public set flipX(value: boolean) { if (value) { this.skeleton._skeleton.scaleX = -Math.abs(this.skeleton._skeleton.scaleX); } else { this.skeleton._skeleton.scaleX = Math.abs(this.skeleton._skeleton.scaleX); } this._flipX = value; } public get flipY() { return this._flipY; } public set flipY(value: boolean) { if (value) { this.skeleton._skeleton.scaleY = -Math.abs(this.skeleton._skeleton.scaleY); } else { this.skeleton._skeleton.scaleY = Math.abs(this.skeleton._skeleton.scaleY); } this._flipX = value; } private _enumSocketPath = Enum({}); private _enumAnimation = Enum({}); private _enumSkin = Enum({}); onFocusInEditor(): void { this.setPropertySocketPath(this, ''); } protected onLoad(): void { this.skeleton._skeleton.scaleX = this._flipX ? -this.skeleton._skeleton.scaleX : this.skeleton._skeleton.scaleX; this.skeleton._skeleton.scaleY = this._flipY ? -this.skeleton._skeleton.scaleY : this.skeleton._skeleton.scaleY; } public setPropertySocketPath(object: object, propertyName: string) { //update enum SocketPath let pathEnum = {}; this.skeleton.querySockets().forEach((path, i) => { pathEnum[path] = i; }); this._enumSocketPath = Enum({}); Object.assign(this._enumSocketPath, pathEnum); Enum.update(this._enumSocketPath); setPropertyEnumType(object, propertyName, this._enumSocketPath); } public setPropertySpineAnimation(object: object, propertyName: string) { //update enum Animations let animEnum = this.skeleton.skeletonData.getAnimsEnum(); this._enumAnimation = Enum({}); Object.assign(this._enumAnimation, animEnum); Enum.update(this._enumAnimation); setPropertyEnumType(object, propertyName, this._enumAnimation); } public setPropertySpineSkin(object: object, propertyName: string) { //update enum Skin let skinEnum = this.skeleton.skeletonData.getSkinsEnum(); this._enumSkin = Enum({}); Object.assign(this._enumSkin, skinEnum); Enum.update(this._enumSkin); setPropertyEnumType(object, propertyName, this._enumSkin); } public setAnimation( animName: string, options?: { loop?: boolean; trackIndex?: number; ignoreAnimationRunning?: boolean; onComplete?: (entry: sp.spine.TrackEntry) => void; }, ): sp.spine.TrackEntry { if (!this.skeleton) return null; const opts = { trackIndex: 0, loop: false, ...options, }; if (opts.ignoreAnimationRunning) { const trackEntry = this.skeleton.getCurrent(opts.trackIndex); const animRunning = trackEntry?.animation?.name; if (animRunning && animRunning == animName) return trackEntry; } this.skeleton.setCompleteListener(null); const trackEntry = this.skeleton.setAnimation(opts?.trackIndex || 0, animName, opts?.loop); if (opts.onComplete) { this.skeleton.setCompleteListener(opts.onComplete); } return trackEntry; } public setListener(listener: (entry: sp.spine.TrackEntry, e: sp.spine.Event) => void) { this.skeleton.setEventListener(listener); } public setAnimationAsync( animName: string, options?: { loop?: boolean; trackIndex?: number; ignoreAnimationRunning?: boolean; }, ): Promise { return new Promise((resolve) => { this.setAnimation(animName, { ...options, onComplete(entry) { resolve(entry); }, }); }); } public addAnimation( animName: string, options?: { loop?: boolean; trackIndex?: number; mixDuration?: number; delay?: number; onComplete?: (entry: sp.spine.TrackEntry) => void; }, ): sp.spine.TrackEntry { if (!this.skeleton) return null; const opts = { trackIndex: 0, loop: false, ...options, }; this.skeleton.setCompleteListener(null); const trackEntry = this.skeleton.addAnimation(opts?.trackIndex || 0, animName, opts?.loop, opts?.delay); if (opts?.mixDuration) { trackEntry.mixDuration = opts.mixDuration; } if (opts.onComplete) { this.skeleton.setCompleteListener(opts.onComplete); } return trackEntry; } public addAnimationAsync( animName: string, options?: { loop?: boolean; trackIndex?: number; mixDuration?: number; delay?: number; }, ): Promise { return new Promise((resolve) => { this.addAnimation(animName, { ...options, onComplete(entry) { resolve(entry); }, }); }); } public addEmptyAnimation(options?: { trackIndex?: number; mixDuration?: number; delay?: number; onComplete?: (entry: sp.spine.TrackEntry) => void; }): sp.spine.TrackEntry { if (!this.skeleton) return; const opts = { trackIndex: 0, mixDuration: 0.2, delay: 0.2, ...options, }; const trackEntry = this.skeleton.getState().addEmptyAnimation(opts.trackIndex, opts.mixDuration, opts.delay); if (opts.onComplete) { this.skeleton.setTrackCompleteListener(trackEntry, opts.onComplete); } return trackEntry; } public addEmptyAnimationAsync(options?: { trackIndex?: number; mixDuration?: number; delay?: number; }): Promise { return new Promise((resolve) => { this.addEmptyAnimation({ ...options, onComplete(entry) { resolve(entry); }, }); }); } public clearTrack(trackIndex: number = 0): void { if (!this.skeleton) return; this.skeleton.clearTrack(trackIndex); } public findBone(boneName: string): sp.spine.Bone { return this.skeleton.findBone(boneName); } public socketPathToString(socketPath: SocketPath): string { return this.skeleton.querySockets()[socketPath]; } public SpineAnimationToString(animationName: SpineAnimation): string { return this.skeleton._skeleton.data.animations[animationName].name; } public SpineAnimationToAnimation(animationName: SpineAnimation): sp.spine.Animation { return this.skeleton._skeleton.data.animations[animationName]; } public addSocket(socketPath: SocketPath | string, target: Node): sp.SpineSocket { let socket: sp.SpineSocket; if (typeof socketPath === 'string') { socket = new sp.SpineSocket(socketPath, target); } else { socket = new sp.SpineSocket(this.socketPathToString(socketPath), target); } this.skeleton.sockets.push(socket); this.skeleton!.sockets = this.skeleton!.sockets; return socket; } public updateSocketPath(socket: sp.SpineSocket, socketPath: SocketPath | string): sp.SpineSocket { let newSocket: sp.SpineSocket; if (typeof socketPath === 'string') { newSocket = new sp.SpineSocket(socketPath, socket.target); } else { newSocket = new sp.SpineSocket(this.socketPathToString(socketPath), socket.target); } const index = this.skeleton.sockets.indexOf(socket); this.skeleton.sockets[index] = newSocket; this.skeleton!.sockets = this.skeleton!.sockets; return newSocket; } public removeSocket(socket: sp.SpineSocket): void { const index = this.skeleton.sockets.indexOf(socket); this.skeleton.sockets.splice(index, 1); this.skeleton!.sockets = this.skeleton!.sockets; } }