diff --git a/spine-ts/spine-webcomponents/example/tutorial.html b/spine-ts/spine-webcomponents/example/tutorial.html index 0bdd5fa23..899524222 100644 --- a/spine-ts/spine-webcomponents/example/tutorial.html +++ b/spine-ts/spine-webcomponents/example/tutorial.html @@ -765,10 +765,11 @@
  • mixDuration: the mix duration between this animation and the previous one (not used for the first animation on a track)
  • -

    To loop a track once it reaches the end, add the special group [loop, trackNumber], where:

    +

    To loop a track once it reaches the end, add the special group [loop, trackNumber, holdDurationLastAnimation], where:

    The parameters of the first group on each track are passed to the setAnimation method, while the remaining groups use addAnimation.

    diff --git a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts index 6d71da23a..45febf120 100644 --- a/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts +++ b/spine-ts/spine-webcomponents/src/SpineWebComponentSkeleton.ts @@ -58,7 +58,11 @@ type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: Anim export type OffScreenUpdateBehaviourType = "pause" | "update" | "pose"; export type ModeType = "inside" | "origin"; export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown"; -export type AnimationsInfo = Record }>; +export type AnimationsInfo = Record +}>; export type AnimationsType = { animationName: string | "#EMPTY#", loop?: boolean, delay?: number, mixDuration?: number }; export type CursorEventType = "down" | "up" | "enter" | "leave" | "move" | "drag"; export type CursorEventTypesInput = Exclude; @@ -1028,7 +1032,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable state.data.defaultMix = defaultMix; if (animationsInfo) { - for (const [trackIndexString, { cycle, animations }] of Object.entries(animationsInfo)) { + for (const [trackIndexString, { cycle, animations, holdDurationLastAnimation }] of Object.entries(animationsInfo)) { const cycleFn = () => { const trackIndex = Number(trackIndexString); for (const [index, { animationName, delay, loop, mixDuration }] of animations.entries()) { @@ -1050,7 +1054,15 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable if (mixDuration) track.mixDuration = mixDuration; if (cycle && index === animations.length - 1) { - track.listener = { complete: () => cycleFn() }; + track.listener = { + complete: () => { + if (holdDurationLastAnimation) + setTimeout(() => cycleFn(), 1000 * holdDurationLastAnimation); + else + cycleFn(); + delete track.listener?.complete; + } + }; }; } } diff --git a/spine-ts/spine-webcomponents/src/wcUtils.ts b/spine-ts/spine-webcomponents/src/wcUtils.ts index b566168a0..fbf7bbd2d 100644 --- a/spine-ts/spine-webcomponents/src/wcUtils.ts +++ b/spine-ts/spine-webcomponents/src/wcUtils.ts @@ -104,7 +104,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined if (!matches) return undefined; return matches.reduce((obj, group) => { - const [trackIndexStringOrLoopDefinition, animationNameOrTrackIndexStringCycle, loop, delayString, mixDurationString] = group.slice(1, -1).split(',').map(v => v.trim()); + const [trackIndexStringOrLoopDefinition, animationNameOrTrackIndexStringCycle, loopOrHoldDurationLastAnimation, delayString, mixDurationString] = group.slice(1, -1).split(',').map(v => v.trim()); if (trackIndexStringOrLoopDefinition === "loop") { if (!Number.isInteger(Number(animationNameOrTrackIndexStringCycle))) { @@ -112,6 +112,15 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined } const animationInfoObject = obj[animationNameOrTrackIndexStringCycle] ||= { animations: [] }; animationInfoObject.cycle = true; + + if (loopOrHoldDurationLastAnimation !== undefined) { + const holdDurationLastAnimation = Number(loopOrHoldDurationLastAnimation); + if (Number.isNaN(holdDurationLastAnimation)) { + throw new Error(`If present, duration of last animation of cycle in ${group} must be a positive integer number, instead it is ${loopOrHoldDurationLastAnimation}. Original value: ${value}`); + } + animationInfoObject.holdDurationLastAnimation = holdDurationLastAnimation; + } + return obj; } @@ -139,7 +148,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined const animationInfoObject = obj[trackIndexStringOrLoopDefinition] ||= { animations: [] }; animationInfoObject.animations.push({ animationName: animationNameOrTrackIndexStringCycle, - loop: loop.trim().toLowerCase() === "true", + loop: loopOrHoldDurationLastAnimation.trim().toLowerCase() === "true", delay, mixDuration, });