Skip to content

Commit c675e70

Browse files
committed
lots of updates, added sprite builder, better animations
1 parent d164989 commit c675e70

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+115159
-907
lines changed

Parts/AreaTrigger.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@ export class AreaTrigger extends Part {
1414
this.type = "AreaTrigger";
1515
}
1616

17+
clone(memo = new Map()): this {
18+
if (memo.has(this)) {
19+
return memo.get(this);
20+
}
21+
22+
const clonedTrigger = new AreaTrigger({
23+
onEnter: this.onEnter,
24+
onExit: this.onExit
25+
});
26+
27+
memo.set(this, clonedTrigger);
28+
29+
this._cloneProperties(clonedTrigger, memo);
30+
31+
// Reset properties that need re-initialization after construction
32+
clonedTrigger.activeCollisions = new Set<Collider>(); // Reset active collisions
33+
34+
return clonedTrigger as this;
35+
}
36+
1737
act(delta: number) {
1838
super.act(delta);
1939
const collider = this.sibling<Collider>("Collider");

Parts/CameraShake.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ export class CameraShake extends Part {
1818
this.type = "CameraShake";
1919
}
2020

21+
clone(memo = new Map()): this {
22+
if (memo.has(this)) {
23+
return memo.get(this);
24+
}
25+
26+
const clonedShake = new CameraShake({
27+
intensity: this.intensity,
28+
duration: this.duration
29+
});
30+
31+
memo.set(this, clonedShake);
32+
33+
this._cloneProperties(clonedShake, memo);
34+
35+
// Reset properties that need re-initialization after construction
36+
clonedShake.initialized = false;
37+
clonedShake.shakeTimer = 0;
38+
clonedShake.originalCameraPosition = null;
39+
40+
return clonedShake as this;
41+
}
42+
2143
initialize() {
2244
this.initialized = true;
2345
// Find the camera in the scene and store its original position

Parts/Children/AnimatedSprite.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,38 @@ export class AnimatedSprite extends Renderer {
3737
this.webEngine = webEngine; // Set the web engine flag
3838
this.type = "AnimatedSprite";
3939
}
40+
clone(memo = new Map()): this {
41+
if (memo.has(this)) {
42+
return memo.get(this);
43+
}
44+
45+
const clonedSprite = new AnimatedSprite({
46+
spritesheet: this.spritesheet,
47+
spritesheetImage: this.spritesheetImage,
48+
width: this.width,
49+
height: this.height,
50+
startingAnimation: this.currentAnimation,
51+
disableAntiAliasing: this.disableAntiAliasing,
52+
onAnimationComplete: this.onAnimationComplete,
53+
webEngine: this.webEngine,
54+
bounce: this.startBouncing,
55+
loop: this.startLoop
56+
});
57+
58+
memo.set(this, clonedSprite);
59+
60+
this._cloneProperties(clonedSprite, memo);
61+
62+
// Reset properties that need re-initialization after construction
63+
clonedSprite.ready = false;
64+
clonedSprite.currentFrameIndex = 0;
65+
clonedSprite.loadedSheet = undefined;
66+
clonedSprite.frames = {};
67+
clonedSprite.spritesheetData = undefined;
68+
clonedSprite.lastFrameTime = performance.now(); // Reset last frame time
69+
70+
return clonedSprite as this;
71+
}
4072
destroy() {
4173
super.destroy();
4274
// Clean up the loaded spritesheet image
@@ -135,6 +167,7 @@ export class AnimatedSprite extends Renderer {
135167
}
136168

137169
}
170+
await Promise.all(frameLoadPromises);
138171
if (this.currentAnimation === "default" && this.spritesheetData.meta.startingAnimation) {
139172
this.currentAnimation = this.spritesheetData.meta.startingAnimation;
140173
this.setAnimation(this.currentAnimation, { loop: this.startLoop, bounce: this.startBouncing });

Parts/Children/Button.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,71 @@ export class Button extends Renderer {
8989
}
9090
};
9191
}
92+
clone(memo = new Map()): this {
93+
if (memo.has(this)) {
94+
return memo.get(this);
95+
}
96+
97+
const clonedButton = new Button({
98+
label: this.name,
99+
onClick: this.onClickHandler,
100+
styles: this.styles,
101+
clickSound: this.clickSound,
102+
hoverSound: this.hoverSound,
103+
activeSound: this.activeSound,
104+
width: this.width,
105+
height: this.height,
106+
});
107+
108+
memo.set(this, clonedButton);
109+
110+
this._cloneProperties(clonedButton, memo);
111+
112+
// Reset properties that need re-initialization after construction
113+
clonedButton.isHovered = false;
114+
clonedButton.isActive = false;
115+
116+
// Re-assign event handlers to ensure they are bound to the cloned instance
117+
clonedButton.onclick = (event: MouseEvent, input: any) => {
118+
if (clonedButton.onClickHandler) {
119+
clonedButton.onClickHandler();
120+
}
121+
if (clonedButton.clickSound) {
122+
clonedButton.clickSound.play({ clone: true });
123+
}
124+
event.stopPropagation();
125+
event.preventDefault();
126+
};
127+
128+
clonedButton.onhover = () => {
129+
clonedButton.isHovered = true;
130+
if (clonedButton.hoverSound) {
131+
clonedButton.hoverSound.play({ clone: true });
132+
}
133+
};
134+
135+
clonedButton.onunhover = () => {
136+
clonedButton.isHovered = false;
137+
clonedButton.isActive = false;
138+
};
139+
140+
clonedButton.onmousedown = (event: MouseEvent) => {
141+
if (event.button === 0) {
142+
clonedButton.isActive = true;
143+
if (clonedButton.activeSound) {
144+
clonedButton.activeSound.play({ clone: true });
145+
}
146+
}
147+
};
148+
149+
clonedButton.onmouseup = (event: MouseEvent) => {
150+
if (event.button === 0) {
151+
clonedButton.isActive = false;
152+
}
153+
};
154+
155+
return clonedButton as this;
156+
}
92157
onMount(parent: Part) {
93158
super.onMount(parent);
94159
if (!this.sibling("Transform")) {

Parts/Children/Collider.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@ export class Collider extends Part {
1010
this.type = "Collider";
1111
}
1212

13+
clone(memo = new Map()): this {
14+
if (memo.has(this)) {
15+
return memo.get(this);
16+
}
17+
18+
const clonedCollider = new Collider();
19+
20+
memo.set(this, clonedCollider);
21+
22+
this._cloneProperties(clonedCollider, memo);
23+
24+
// Reset properties that need re-initialization after construction
25+
clonedCollider.colliding = false;
26+
clonedCollider.collidingWith = new Set<Collider>();
27+
28+
return clonedCollider as this;
29+
}
30+
1331
onMount(parent: Part) {
1432
super.onMount(parent);
1533
if (!this.sibling("Transform")) {

Parts/Children/ColorRender.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,24 @@ export class ColorRender extends Renderer {
4242

4343
parent.setSuperficialDimensions(this.width, this.height);
4444
}
45+
clone(memo = new Map()): this {
46+
if (memo.has(this)) {
47+
return memo.get(this);
48+
}
49+
50+
const clonedColor = new ColorRender({
51+
width: this.width,
52+
height: this.height,
53+
color: this.color,
54+
vertices: this.vertices.map(v => v.clone())
55+
});
56+
57+
memo.set(this, clonedColor);
4558

59+
this._cloneProperties(clonedColor, memo);
60+
61+
return clonedColor as this;
62+
}
4663
act(delta: number) {
4764
super.act(delta);
4865

Parts/Children/SpriteRender.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,33 @@ export class SpriteRender extends Renderer {
1919
this.ready = true;
2020
};
2121
this.image.onerror = (err: any) => {
22-
this.top?.error(`Failed to load image <${this.imageSource}>:`, err);
22+
this.top?.error(`[C] Failed to load image<${this.imageSource?.slice(0, 30)}>:`, err.message);
2323
};
2424
this.image.src = imageSource;
2525

2626
this.width = width;
2727
this.height = height;
2828
}
29+
clone(memo = new Map()): this {
30+
if (memo.has(this)) {
31+
return memo.get(this);
32+
}
33+
34+
const clonedSprite = new SpriteRender({
35+
imageSource: this.imageSource!,
36+
width: this.width,
37+
height: this.height
38+
});
39+
40+
memo.set(this, clonedSprite);
41+
42+
this._cloneProperties(clonedSprite, memo);
43+
44+
// Reset properties that need re-initialization after construction
45+
clonedSprite.ready = false;
46+
47+
return clonedSprite as this;
48+
}
2949
onMount(parent: Part) {
3050
super.onMount(parent);
3151
if (!this.sibling("Transform")) {

Parts/Follow.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,34 @@ export class Follow extends Part {
2828
this.debugEmoji = "🎯";
2929
}
3030

31+
clone(memo = new Map()): this {
32+
if (memo.has(this)) {
33+
return memo.get(this);
34+
}
35+
36+
const clonedFollow = new Follow({
37+
name: this.name,
38+
target: this.target as Transform,
39+
offset: this.offset.clone(),
40+
interpolationSpeed: this.interpolationSpeed
41+
});
42+
43+
memo.set(this, clonedFollow);
44+
45+
this._cloneProperties(clonedFollow, memo);
46+
47+
// Reset properties that need re-initialization after construction
48+
// Handle target separately, as it might be a cloned object
49+
if (this.target && memo.has(this.target)) {
50+
clonedFollow.target = memo.get(this.target);
51+
} else {
52+
clonedFollow.target = this.target; // Keep reference to original if not cloned
53+
}
54+
clonedFollow.externalOffset = new Vector(0, 0); // Reset external offset
55+
56+
return clonedFollow as this;
57+
}
58+
3159
act(delta: number) {
3260
super.act(delta);
3361
if (this.target) {

Parts/Game.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Sound } from "../engine/bundle";
21
import { getDebugInfo } from "../helpers";
32
import { Part } from "./Part";
43
import { Scene } from "./Scene";
@@ -62,6 +61,47 @@ export class Game extends Part {
6261
}
6362
}
6463

64+
clone(memo = new Map()): this {
65+
if (memo.has(this)) {
66+
return memo.get(this);
67+
}
68+
69+
// Game constructor requires canvas, width, height, name
70+
// We cannot clone the canvas directly, so we'll create a placeholder
71+
// The user will need to re-assign a real canvas after cloning
72+
const clonedGame = new Game({
73+
name: this.name,
74+
canvas: document.createElement('canvas'), // Placeholder canvas
75+
devmode: this.devmode,
76+
width: this.width,
77+
height: this.height,
78+
disableAntiAliasing: !this.context.imageSmoothingEnabled, // Infer from original context
79+
showtoolTips: this.showtoolTips
80+
});
81+
82+
memo.set(this, clonedGame);
83+
84+
this._cloneProperties(clonedGame, memo);
85+
86+
// Reset properties that are tied to the DOM or internal state
87+
clonedGame.canvas = undefined as any; // User must provide a real canvas
88+
clonedGame.context = undefined as any; // Context will be derived from new canvas
89+
clonedGame.currentScene = undefined; // Will be set by start() or setScene()
90+
// clonedGame.childrenArray is handled by _cloneProperties
91+
clonedGame.hovering = undefined; // Reset hovering part
92+
clonedGame.tooltipLocked = undefined; // Reset tooltip lock
93+
clonedGame.lastMousePosition = { x: 0, y: 0 }; // Reset mouse position
94+
clonedGame.scaleFactor = 1; // Reset scale factor
95+
clonedGame.canvasOffset = { x: 0, y: 0 }; // Reset canvas offset
96+
clonedGame.messageHook = undefined; // Clear message hook
97+
clonedGame._isRunning = false; // Reset running state
98+
clonedGame._isPaused = false; // Reset paused state
99+
clonedGame._animationFrameId = undefined; // Clear animation frame ID
100+
clonedGame._lastUpdateTime = 0; // Reset last update time
101+
102+
return clonedGame as this;
103+
}
104+
65105
changeCanvasSize(width: number, height: number) {
66106
this.canvas.width = width;
67107
this.canvas.height = height;

Parts/Health.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,27 @@ export class Health extends Part {
1515
this.type = "Health";
1616
}
1717

18+
clone(memo = new Map()): this {
19+
if (memo.has(this)) {
20+
return memo.get(this);
21+
}
22+
23+
const clonedHealth = new Health({
24+
maxHealth: this.maxHealth,
25+
onDeath: this.onDeath
26+
});
27+
28+
memo.set(this, clonedHealth);
29+
30+
this._cloneProperties(clonedHealth, memo);
31+
32+
// Reset properties that need re-initialization after construction
33+
clonedHealth.isDead = false;
34+
clonedHealth.currentHealth = clonedHealth.maxHealth;
35+
36+
return clonedHealth as this;
37+
}
38+
1839
takeDamage(amount: number) {
1940
this.currentHealth -= amount;
2041
if (this.currentHealth <= 0) {

0 commit comments

Comments
 (0)