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:
loop: identifies this as a loop instruction
trackNumber: the number of the track to loop
+ holdDurationLastAnimation: the number of seconds to wait after the last animation is completed before repeating the loop
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,
});