@arcane-engine/runtime 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/agent/protocol.ts +35 -1
- package/src/agent/types.ts +98 -13
- package/src/particles/emitter.test.ts +323 -0
- package/src/particles/emitter.ts +409 -0
- package/src/particles/index.ts +25 -0
- package/src/particles/types.ts +236 -0
- package/src/pathfinding/astar.ts +27 -0
- package/src/pathfinding/types.ts +39 -0
- package/src/physics/aabb.ts +55 -8
- package/src/rendering/animation.ts +73 -0
- package/src/rendering/audio.ts +29 -9
- package/src/rendering/camera.ts +28 -4
- package/src/rendering/input.ts +45 -9
- package/src/rendering/lighting.ts +29 -3
- package/src/rendering/loop.ts +16 -3
- package/src/rendering/sprites.ts +24 -1
- package/src/rendering/text.ts +52 -6
- package/src/rendering/texture.ts +22 -4
- package/src/rendering/tilemap.ts +36 -4
- package/src/rendering/types.ts +37 -19
- package/src/rendering/validate.ts +48 -3
- package/src/state/error.ts +21 -2
- package/src/state/observe.ts +40 -9
- package/src/state/prng.ts +88 -10
- package/src/state/query.ts +115 -15
- package/src/state/store.ts +42 -11
- package/src/state/transaction.ts +116 -12
- package/src/state/types.ts +31 -5
- package/src/systems/system.ts +77 -5
- package/src/systems/types.ts +52 -6
- package/src/testing/harness.ts +103 -5
- package/src/testing/mock-renderer.test.ts +16 -20
- package/src/tweening/chain.test.ts +191 -0
- package/src/tweening/chain.ts +103 -0
- package/src/tweening/easing.test.ts +134 -0
- package/src/tweening/easing.ts +288 -0
- package/src/tweening/helpers.test.ts +185 -0
- package/src/tweening/helpers.ts +166 -0
- package/src/tweening/index.ts +76 -0
- package/src/tweening/tween.test.ts +322 -0
- package/src/tweening/tween.ts +296 -0
- package/src/tweening/types.ts +134 -0
- package/src/ui/colors.ts +129 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/primitives.ts +44 -5
- package/src/ui/types.ts +41 -2
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tweening helper functions for common game "juice" effects.
|
|
3
|
+
*
|
|
4
|
+
* Camera shake and screen flash are implemented as global singletons.
|
|
5
|
+
* Only one shake and one flash can be active at a time; starting a new one
|
|
6
|
+
* replaces the previous.
|
|
7
|
+
*
|
|
8
|
+
* Usage: call the effect function, then read the offset/flash state each frame
|
|
9
|
+
* when rendering.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { tween } from "./tween.ts";
|
|
13
|
+
import { easeOutQuad } from "./easing.ts";
|
|
14
|
+
|
|
15
|
+
/** Internal camera shake state (global singleton). */
|
|
16
|
+
let shakeState = {
|
|
17
|
+
offsetX: 0,
|
|
18
|
+
offsetY: 0,
|
|
19
|
+
active: false,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Start a camera shake effect that decays over time using easeOutQuad.
|
|
24
|
+
*
|
|
25
|
+
* Each frame, read the offset via {@link getCameraShakeOffset} and add it
|
|
26
|
+
* to your camera position. The offset oscillates randomly and decays to zero.
|
|
27
|
+
*
|
|
28
|
+
* @param intensity - Maximum shake offset in pixels. Higher = more violent. Must be > 0.
|
|
29
|
+
* @param duration - Duration of the shake in seconds. Must be > 0.
|
|
30
|
+
* @param frequency - Unused currently; reserved for future use. Default: 20.
|
|
31
|
+
*/
|
|
32
|
+
export function shakeCamera(
|
|
33
|
+
intensity: number,
|
|
34
|
+
duration: number,
|
|
35
|
+
frequency: number = 20,
|
|
36
|
+
): void {
|
|
37
|
+
shakeState.active = true;
|
|
38
|
+
|
|
39
|
+
// Store original intensity for interpolation
|
|
40
|
+
const startIntensity = intensity;
|
|
41
|
+
|
|
42
|
+
// Create a decay tween
|
|
43
|
+
const decay = { value: 1.0 };
|
|
44
|
+
tween(decay, { value: 0 }, duration, {
|
|
45
|
+
easing: easeOutQuad,
|
|
46
|
+
onUpdate: (progress) => {
|
|
47
|
+
// Decay intensity over time
|
|
48
|
+
const currentIntensity = startIntensity * (1 - progress);
|
|
49
|
+
|
|
50
|
+
// Generate random offsets with current intensity
|
|
51
|
+
// Use a simple pseudo-random pattern based on time
|
|
52
|
+
const angle = Math.random() * Math.PI * 2;
|
|
53
|
+
shakeState.offsetX = Math.cos(angle) * currentIntensity;
|
|
54
|
+
shakeState.offsetY = Math.sin(angle) * currentIntensity;
|
|
55
|
+
},
|
|
56
|
+
onComplete: () => {
|
|
57
|
+
shakeState.active = false;
|
|
58
|
+
shakeState.offsetX = 0;
|
|
59
|
+
shakeState.offsetY = 0;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the current camera shake offset for this frame.
|
|
66
|
+
* Returns {0, 0} when no shake is active.
|
|
67
|
+
*
|
|
68
|
+
* @returns Object with `x` and `y` pixel offsets to add to camera position.
|
|
69
|
+
*/
|
|
70
|
+
export function getCameraShakeOffset(): { x: number; y: number } {
|
|
71
|
+
return {
|
|
72
|
+
x: shakeState.offsetX,
|
|
73
|
+
y: shakeState.offsetY,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Check whether a camera shake effect is currently active.
|
|
79
|
+
* @returns True if shake is in progress, false otherwise.
|
|
80
|
+
*/
|
|
81
|
+
export function isCameraShaking(): boolean {
|
|
82
|
+
return shakeState.active;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Stop the camera shake immediately, resetting the offset to zero.
|
|
87
|
+
*/
|
|
88
|
+
export function stopCameraShake(): void {
|
|
89
|
+
shakeState.active = false;
|
|
90
|
+
shakeState.offsetX = 0;
|
|
91
|
+
shakeState.offsetY = 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Internal screen flash state (global singleton). */
|
|
95
|
+
let flashState = {
|
|
96
|
+
opacity: 0,
|
|
97
|
+
r: 1,
|
|
98
|
+
g: 1,
|
|
99
|
+
b: 1,
|
|
100
|
+
active: false,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Flash the screen with a colored overlay that fades out using easeOutQuad.
|
|
105
|
+
*
|
|
106
|
+
* Each frame, read the flash state via {@link getScreenFlash} and render
|
|
107
|
+
* a full-screen rectangle with the returned color and opacity.
|
|
108
|
+
*
|
|
109
|
+
* @param r - Red component, 0.0 (none) to 1.0 (full).
|
|
110
|
+
* @param g - Green component, 0.0 (none) to 1.0 (full).
|
|
111
|
+
* @param b - Blue component, 0.0 (none) to 1.0 (full).
|
|
112
|
+
* @param duration - Fade-out duration in seconds. Must be > 0.
|
|
113
|
+
* @param startOpacity - Initial opacity of the flash overlay. Default: 0.8. Range: 0.0..1.0.
|
|
114
|
+
*/
|
|
115
|
+
export function flashScreen(
|
|
116
|
+
r: number,
|
|
117
|
+
g: number,
|
|
118
|
+
b: number,
|
|
119
|
+
duration: number,
|
|
120
|
+
startOpacity: number = 0.8,
|
|
121
|
+
): void {
|
|
122
|
+
flashState.active = true;
|
|
123
|
+
flashState.r = r;
|
|
124
|
+
flashState.g = g;
|
|
125
|
+
flashState.b = b;
|
|
126
|
+
flashState.opacity = startOpacity;
|
|
127
|
+
|
|
128
|
+
// Fade out the flash
|
|
129
|
+
tween(flashState, { opacity: 0 }, duration, {
|
|
130
|
+
easing: easeOutQuad,
|
|
131
|
+
onComplete: () => {
|
|
132
|
+
flashState.active = false;
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the current screen flash color and opacity for this frame.
|
|
139
|
+
*
|
|
140
|
+
* @returns Flash state with `r`, `g`, `b` (0..1) and `opacity` (0..1), or `null` if no flash is active.
|
|
141
|
+
*/
|
|
142
|
+
export function getScreenFlash(): { r: number; g: number; b: number; opacity: number } | null {
|
|
143
|
+
if (!flashState.active) return null;
|
|
144
|
+
return {
|
|
145
|
+
r: flashState.r,
|
|
146
|
+
g: flashState.g,
|
|
147
|
+
b: flashState.b,
|
|
148
|
+
opacity: flashState.opacity,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Check whether a screen flash effect is currently active.
|
|
154
|
+
* @returns True if flash is in progress, false otherwise.
|
|
155
|
+
*/
|
|
156
|
+
export function isScreenFlashing(): boolean {
|
|
157
|
+
return flashState.active;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Stop the screen flash immediately, resetting opacity to zero.
|
|
162
|
+
*/
|
|
163
|
+
export function stopScreenFlash(): void {
|
|
164
|
+
flashState.active = false;
|
|
165
|
+
flashState.opacity = 0;
|
|
166
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tweening system
|
|
3
|
+
*
|
|
4
|
+
* Provides smooth animation of numeric properties over time.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
EasingFunction,
|
|
9
|
+
TweenCallback,
|
|
10
|
+
TweenUpdateCallback,
|
|
11
|
+
TweenOptions,
|
|
12
|
+
TweenProps,
|
|
13
|
+
Tween,
|
|
14
|
+
} from "./types.ts";
|
|
15
|
+
export { TweenState } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
tween,
|
|
19
|
+
updateTweens,
|
|
20
|
+
stopTween,
|
|
21
|
+
pauseTween,
|
|
22
|
+
resumeTween,
|
|
23
|
+
reverseTween,
|
|
24
|
+
stopAllTweens,
|
|
25
|
+
getActiveTweenCount,
|
|
26
|
+
linear,
|
|
27
|
+
} from "./tween.ts";
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
linear as easingLinear,
|
|
31
|
+
easeInQuad,
|
|
32
|
+
easeOutQuad,
|
|
33
|
+
easeInOutQuad,
|
|
34
|
+
easeInCubic,
|
|
35
|
+
easeOutCubic,
|
|
36
|
+
easeInOutCubic,
|
|
37
|
+
easeInQuart,
|
|
38
|
+
easeOutQuart,
|
|
39
|
+
easeInOutQuart,
|
|
40
|
+
easeInQuint,
|
|
41
|
+
easeOutQuint,
|
|
42
|
+
easeInOutQuint,
|
|
43
|
+
easeInSine,
|
|
44
|
+
easeOutSine,
|
|
45
|
+
easeInOutSine,
|
|
46
|
+
easeInExpo,
|
|
47
|
+
easeOutExpo,
|
|
48
|
+
easeInOutExpo,
|
|
49
|
+
easeInCirc,
|
|
50
|
+
easeOutCirc,
|
|
51
|
+
easeInOutCirc,
|
|
52
|
+
easeInBack,
|
|
53
|
+
easeOutBack,
|
|
54
|
+
easeInOutBack,
|
|
55
|
+
easeInElastic,
|
|
56
|
+
easeOutElastic,
|
|
57
|
+
easeInOutElastic,
|
|
58
|
+
easeInBounce,
|
|
59
|
+
easeOutBounce,
|
|
60
|
+
easeInOutBounce,
|
|
61
|
+
Easing,
|
|
62
|
+
} from "./easing.ts";
|
|
63
|
+
|
|
64
|
+
export type { TweenConfig } from "./chain.ts";
|
|
65
|
+
export { sequence, parallel, stagger } from "./chain.ts";
|
|
66
|
+
|
|
67
|
+
export {
|
|
68
|
+
shakeCamera,
|
|
69
|
+
getCameraShakeOffset,
|
|
70
|
+
isCameraShaking,
|
|
71
|
+
stopCameraShake,
|
|
72
|
+
flashScreen,
|
|
73
|
+
getScreenFlash,
|
|
74
|
+
isScreenFlashing,
|
|
75
|
+
stopScreenFlash,
|
|
76
|
+
} from "./helpers.ts";
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for core tweening system
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, assert } from "../testing/harness.ts";
|
|
6
|
+
import {
|
|
7
|
+
tween,
|
|
8
|
+
updateTweens,
|
|
9
|
+
stopTween,
|
|
10
|
+
pauseTween,
|
|
11
|
+
resumeTween,
|
|
12
|
+
stopAllTweens,
|
|
13
|
+
getActiveTweenCount,
|
|
14
|
+
} from "./tween.ts";
|
|
15
|
+
import { TweenState } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
describe("Core Tweening", () => {
|
|
18
|
+
it("should tween a single property", () => {
|
|
19
|
+
stopAllTweens();
|
|
20
|
+
const target = { x: 0 };
|
|
21
|
+
tween(target, { x: 100 }, 1.0);
|
|
22
|
+
|
|
23
|
+
// At 0s
|
|
24
|
+
assert.equal(target.x, 0);
|
|
25
|
+
|
|
26
|
+
// At 0.5s (halfway)
|
|
27
|
+
updateTweens(0.5);
|
|
28
|
+
assert.equal(target.x, 50);
|
|
29
|
+
|
|
30
|
+
// At 1.0s (complete)
|
|
31
|
+
updateTweens(0.5);
|
|
32
|
+
assert.equal(target.x, 100);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should tween multiple properties", () => {
|
|
36
|
+
stopAllTweens();
|
|
37
|
+
const target = { x: 0, y: 0, scale: 1 };
|
|
38
|
+
tween(target, { x: 100, y: 50, scale: 2 }, 1.0);
|
|
39
|
+
|
|
40
|
+
updateTweens(0.5);
|
|
41
|
+
assert.equal(target.x, 50);
|
|
42
|
+
assert.equal(target.y, 25);
|
|
43
|
+
assert.equal(target.scale, 1.5);
|
|
44
|
+
|
|
45
|
+
updateTweens(0.5);
|
|
46
|
+
assert.equal(target.x, 100);
|
|
47
|
+
assert.equal(target.y, 50);
|
|
48
|
+
assert.equal(target.scale, 2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should handle delay", () => {
|
|
52
|
+
const target = { x: 0 };
|
|
53
|
+
const t = tween(target, { x: 100 }, 1.0, { delay: 0.5 });
|
|
54
|
+
|
|
55
|
+
// Initially pending
|
|
56
|
+
assert.equal(t.state, TweenState.PENDING);
|
|
57
|
+
|
|
58
|
+
// During delay, target unchanged
|
|
59
|
+
updateTweens(0.3);
|
|
60
|
+
assert.equal(target.x, 0);
|
|
61
|
+
assert.equal(t.state, TweenState.PENDING);
|
|
62
|
+
|
|
63
|
+
// After delay, tween starts
|
|
64
|
+
updateTweens(0.2);
|
|
65
|
+
assert.equal(t.state, TweenState.ACTIVE);
|
|
66
|
+
|
|
67
|
+
// Now tweening
|
|
68
|
+
updateTweens(0.5);
|
|
69
|
+
assert.equal(target.x, 50);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should call onStart callback after delay", () => {
|
|
73
|
+
let started = false;
|
|
74
|
+
const target = { x: 0 };
|
|
75
|
+
|
|
76
|
+
tween(target, { x: 100 }, 1.0, {
|
|
77
|
+
delay: 0.5,
|
|
78
|
+
onStart: () => {
|
|
79
|
+
started = true;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
assert.equal(started, false);
|
|
84
|
+
|
|
85
|
+
// Before delay ends
|
|
86
|
+
updateTweens(0.3);
|
|
87
|
+
assert.equal(started, false);
|
|
88
|
+
|
|
89
|
+
// After delay ends
|
|
90
|
+
updateTweens(0.2);
|
|
91
|
+
assert.equal(started, true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should call onUpdate callback", () => {
|
|
95
|
+
let updateCount = 0;
|
|
96
|
+
let lastProgress = 0;
|
|
97
|
+
const target = { x: 0 };
|
|
98
|
+
|
|
99
|
+
tween(target, { x: 100 }, 1.0, {
|
|
100
|
+
onUpdate: (progress) => {
|
|
101
|
+
updateCount++;
|
|
102
|
+
lastProgress = progress;
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
updateTweens(0.5);
|
|
107
|
+
assert.equal(updateCount, 1);
|
|
108
|
+
assert.ok(Math.abs(lastProgress - 0.5) < 0.01, `Expected progress ~0.5, got ${lastProgress}`);
|
|
109
|
+
|
|
110
|
+
updateTweens(0.5);
|
|
111
|
+
assert.equal(updateCount, 2);
|
|
112
|
+
assert.ok(Math.abs(lastProgress - 1.0) < 0.01, `Expected progress ~1.0, got ${lastProgress}`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should call onComplete callback", () => {
|
|
116
|
+
let completed = false;
|
|
117
|
+
const target = { x: 0 };
|
|
118
|
+
|
|
119
|
+
tween(target, { x: 100 }, 1.0, {
|
|
120
|
+
onComplete: () => {
|
|
121
|
+
completed = true;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
updateTweens(0.5);
|
|
126
|
+
assert.equal(completed, false);
|
|
127
|
+
|
|
128
|
+
updateTweens(0.5);
|
|
129
|
+
assert.equal(completed, true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should remove completed tweens from active list", () => {
|
|
133
|
+
const target = { x: 0 };
|
|
134
|
+
tween(target, { x: 100 }, 1.0);
|
|
135
|
+
|
|
136
|
+
assert.equal(getActiveTweenCount(), 1);
|
|
137
|
+
|
|
138
|
+
updateTweens(1.0);
|
|
139
|
+
assert.equal(getActiveTweenCount(), 0);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should handle repeat", () => {
|
|
143
|
+
let repeatCount = 0;
|
|
144
|
+
const target = { x: 0 };
|
|
145
|
+
|
|
146
|
+
tween(target, { x: 100 }, 1.0, {
|
|
147
|
+
repeat: 2,
|
|
148
|
+
onRepeat: () => {
|
|
149
|
+
repeatCount++;
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// First iteration
|
|
154
|
+
updateTweens(1.0);
|
|
155
|
+
assert.equal(target.x, 100);
|
|
156
|
+
assert.equal(repeatCount, 1);
|
|
157
|
+
|
|
158
|
+
// Second iteration
|
|
159
|
+
updateTweens(1.0);
|
|
160
|
+
assert.equal(target.x, 100);
|
|
161
|
+
assert.equal(repeatCount, 2);
|
|
162
|
+
|
|
163
|
+
// Third iteration (completes)
|
|
164
|
+
updateTweens(1.0);
|
|
165
|
+
assert.equal(target.x, 100);
|
|
166
|
+
assert.equal(repeatCount, 2);
|
|
167
|
+
assert.equal(getActiveTweenCount(), 0);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should handle infinite repeat", () => {
|
|
171
|
+
const target = { x: 0 };
|
|
172
|
+
tween(target, { x: 100 }, 1.0, { repeat: -1 });
|
|
173
|
+
|
|
174
|
+
for (let i = 0; i < 10; i++) {
|
|
175
|
+
updateTweens(1.0);
|
|
176
|
+
assert.equal(target.x, 100);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Should still be active
|
|
180
|
+
assert.equal(getActiveTweenCount(), 1);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should handle yoyo mode", () => {
|
|
184
|
+
stopAllTweens();
|
|
185
|
+
const target = { x: 0 };
|
|
186
|
+
tween(target, { x: 100 }, 1.0, { repeat: 1, yoyo: true });
|
|
187
|
+
|
|
188
|
+
// Forward
|
|
189
|
+
updateTweens(1.0);
|
|
190
|
+
assert.equal(target.x, 100);
|
|
191
|
+
|
|
192
|
+
// Backward
|
|
193
|
+
updateTweens(1.0);
|
|
194
|
+
assert.equal(target.x, 0);
|
|
195
|
+
|
|
196
|
+
assert.equal(getActiveTweenCount(), 0);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should stop a tween", () => {
|
|
200
|
+
stopAllTweens();
|
|
201
|
+
const target = { x: 0 };
|
|
202
|
+
const t = tween(target, { x: 100 }, 1.0);
|
|
203
|
+
|
|
204
|
+
updateTweens(0.5);
|
|
205
|
+
assert.equal(target.x, 50);
|
|
206
|
+
|
|
207
|
+
stopTween(t);
|
|
208
|
+
assert.equal(t.state, TweenState.STOPPED);
|
|
209
|
+
assert.equal(getActiveTweenCount(), 0);
|
|
210
|
+
|
|
211
|
+
// Further updates do nothing
|
|
212
|
+
updateTweens(0.5);
|
|
213
|
+
assert.equal(target.x, 50);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should pause and resume a tween", () => {
|
|
217
|
+
const target = { x: 0 };
|
|
218
|
+
const t = tween(target, { x: 100 }, 1.0);
|
|
219
|
+
|
|
220
|
+
updateTweens(0.3);
|
|
221
|
+
assert.equal(target.x, 30);
|
|
222
|
+
|
|
223
|
+
pauseTween(t);
|
|
224
|
+
assert.equal(t.state, TweenState.PAUSED);
|
|
225
|
+
|
|
226
|
+
// No update while paused
|
|
227
|
+
updateTweens(0.5);
|
|
228
|
+
assert.equal(target.x, 30);
|
|
229
|
+
|
|
230
|
+
resumeTween(t);
|
|
231
|
+
assert.equal(t.state, TweenState.ACTIVE);
|
|
232
|
+
|
|
233
|
+
// Continues from where it left off
|
|
234
|
+
updateTweens(0.5);
|
|
235
|
+
assert.equal(target.x, 80);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("should handle multiple simultaneous tweens", () => {
|
|
239
|
+
stopAllTweens();
|
|
240
|
+
const target1 = { x: 0 };
|
|
241
|
+
const target2 = { y: 0 };
|
|
242
|
+
const target3 = { z: 0 };
|
|
243
|
+
|
|
244
|
+
tween(target1, { x: 100 }, 1.0);
|
|
245
|
+
tween(target2, { y: 200 }, 2.0);
|
|
246
|
+
tween(target3, { z: 50 }, 0.5);
|
|
247
|
+
|
|
248
|
+
assert.equal(getActiveTweenCount(), 3);
|
|
249
|
+
|
|
250
|
+
updateTweens(0.5);
|
|
251
|
+
assert.equal(target1.x, 50);
|
|
252
|
+
assert.equal(target2.y, 50);
|
|
253
|
+
assert.equal(target3.z, 50);
|
|
254
|
+
assert.equal(getActiveTweenCount(), 2); // target3 completed
|
|
255
|
+
|
|
256
|
+
updateTweens(0.5);
|
|
257
|
+
assert.equal(target1.x, 100);
|
|
258
|
+
assert.equal(target2.y, 100);
|
|
259
|
+
assert.equal(getActiveTweenCount(), 1); // target1 completed
|
|
260
|
+
|
|
261
|
+
updateTweens(1.0);
|
|
262
|
+
assert.equal(target2.y, 200);
|
|
263
|
+
assert.equal(getActiveTweenCount(), 0); // all completed
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should stop all tweens", () => {
|
|
267
|
+
stopAllTweens();
|
|
268
|
+
tween({ x: 0 }, { x: 100 }, 1.0);
|
|
269
|
+
tween({ y: 0 }, { y: 100 }, 1.0);
|
|
270
|
+
tween({ z: 0 }, { z: 100 }, 1.0);
|
|
271
|
+
|
|
272
|
+
assert.equal(getActiveTweenCount(), 3);
|
|
273
|
+
|
|
274
|
+
stopAllTweens();
|
|
275
|
+
assert.equal(getActiveTweenCount(), 0);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it("should handle custom easing function", () => {
|
|
279
|
+
const target = { x: 0 };
|
|
280
|
+
// Square easing: t^2
|
|
281
|
+
tween(target, { x: 100 }, 1.0, {
|
|
282
|
+
easing: (t) => t * t,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
updateTweens(0.5);
|
|
286
|
+
// With square easing: 0.5^2 = 0.25
|
|
287
|
+
assert.equal(target.x, 25);
|
|
288
|
+
|
|
289
|
+
updateTweens(0.5);
|
|
290
|
+
assert.equal(target.x, 100);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should handle negative target values", () => {
|
|
294
|
+
const target = { x: 100 };
|
|
295
|
+
tween(target, { x: -50 }, 1.0);
|
|
296
|
+
|
|
297
|
+
updateTweens(0.5);
|
|
298
|
+
assert.equal(target.x, 25); // Halfway between 100 and -50
|
|
299
|
+
|
|
300
|
+
updateTweens(0.5);
|
|
301
|
+
assert.equal(target.x, -50);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should initialize missing properties to zero", () => {
|
|
305
|
+
const target: any = {};
|
|
306
|
+
tween(target, { x: 100 }, 1.0);
|
|
307
|
+
|
|
308
|
+
updateTweens(0.5);
|
|
309
|
+
assert.equal(target.x, 50);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should handle zero duration", () => {
|
|
313
|
+
stopAllTweens();
|
|
314
|
+
const target = { x: 0 };
|
|
315
|
+
tween(target, { x: 100 }, 0);
|
|
316
|
+
|
|
317
|
+
// Should complete immediately
|
|
318
|
+
updateTweens(0);
|
|
319
|
+
assert.equal(target.x, 100);
|
|
320
|
+
assert.equal(getActiveTweenCount(), 0);
|
|
321
|
+
});
|
|
322
|
+
});
|