Skip to content

Commit ddecae2

Browse files
committed
physics works at least...
1 parent bbcdd99 commit ddecae2

Some content is hidden

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

57 files changed

+3367
-977
lines changed

Math/Vector.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ export class Vector {
1212
}
1313
return new Vector(this.x + other, this.y + other);
1414
}
15+
toObject(): { x: number, y: number } {
16+
return { x: this.x, y: this.y };
17+
}
18+
toArray(): [number, number] {
19+
return [this.x, this.y];
20+
}
1521
subtract(other: number | Vector): Vector {
1622
if (other instanceof Vector) {
1723
return new Vector(this.x - other.x, this.y - other.y);
@@ -46,6 +52,21 @@ export class Vector {
4652
dot(other: Vector): number {
4753
return this.x * other.x + this.y * other.y;
4854
}
55+
clone(): Vector {
56+
return new Vector(this.x, this.y);
57+
}
58+
set(...args: [number, number] | [Vector]): Vector {
59+
if (args.length === 1 && args[0] instanceof Vector) {
60+
this.x = args[0].x;
61+
this.y = args[0].y;
62+
} else if (args.length === 2) {
63+
this.x = args[0];
64+
this.y = args[1];
65+
} else {
66+
throw new Error("Invalid arguments for set method");
67+
}
68+
return this;
69+
}
4970
static From(scalar: number): Vector {
5071
return new Vector(scalar, scalar);
5172
}

Parts/AreaTrigger.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Collider } from "./Children/Collider";
44
export class AreaTrigger extends Part {
55
onEnter?: (other: Collider) => void;
66
onExit?: (other: Collider) => void;
7-
private activeCollisions: Set<string> = new Set();
7+
activeCollisions: Set<Collider> = new Set();
88

99
constructor({ name, onEnter, onExit }: { name?: string, onEnter?: (other: Collider) => void, onExit?: (other: Collider) => void }) {
1010
super({ name: name || 'AreaTrigger' });
@@ -13,19 +13,19 @@ export class AreaTrigger extends Part {
1313
this.debugEmoji = "🧲";
1414
}
1515

16-
act() {
17-
super.act();
16+
act(delta: number) {
17+
super.act(delta);
1818
const collider = this.sibling<Collider>("Collider");
1919
if (!collider) {
2020
console.warn(`AreaTrigger <${this.name}> requires a Collider sibling.`);
2121
return;
2222
}
2323

24-
const currentCollisions = new Set<string>();
24+
const currentCollisions = new Set<Collider>();
2525
if (collider.colliding) {
2626
for (const other of collider.collidingWith) {
27-
currentCollisions.add(other.id);
28-
if (!this.activeCollisions.has(other.id)) {
27+
currentCollisions.add(other);
28+
if (!this.activeCollisions.has(other)) {
2929
// New collision - onEnter
3030
if (this.onEnter) {
3131
this.onEnter(other);
@@ -35,9 +35,9 @@ export class AreaTrigger extends Part {
3535
}
3636

3737
// Check for exited collisions - onExit
38-
for (const activeId of this.activeCollisions) {
39-
if (!currentCollisions.has(activeId)) {
40-
const exitedCollider = collider.collidingWith.find(c => c.id === activeId); // This might be tricky as collidingWith is reset
38+
for (const other of this.activeCollisions) {
39+
if (!currentCollisions.has(other)) {
40+
const exitedCollider = Array.from(collider.collidingWith).find(c => c.id === other.id); // This might be tricky as collidingWith is reset
4141
// A more robust way would be to store references to the actual collider objects
4242
// For simplicity, we'll just call onExit without the specific collider for now
4343
if (this.onExit) {

Parts/Camera.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,11 @@ import { Scene } from "./Scene";
77
export class Camera extends Part {
88
zoom: Vector;
99

10-
constructor({ name, position = new Vector(0, 0), zoom = Vector.From(1) }: { name: string, position: Vector, zoom?: Vector }) {
10+
constructor({ name }: { name: string, zoom?: Vector }) {
1111
super();
1212
this.name = name;
13-
this.zoom = zoom;
13+
this.zoom = Vector.From(1);
1414
this.debugEmoji = "📷"; // Camera specific emoji for debugging
15-
16-
this.addChild(new Transform({ position })); // Add a Transform part for position
17-
1815
}
1916

2017
onMount(parent: Part) {
@@ -47,11 +44,13 @@ export class Camera extends Part {
4744
};
4845
}
4946

50-
act() {
51-
super.act();
47+
act(delta: number) {
48+
super.act(delta);
5249
const transform = this.children["Transform"] as Transform;
5350
if (transform) {
5451
this.zoom = transform.scale;
52+
} else {
53+
console.warn(`Camera <${this.name}> (${this.id}) does not have a Transform component. Camera zoom will not be updated.`);
5554
}
5655
}
5756

Parts/CameraShake.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class CameraShake extends Part {
2121
// Find the camera in the scene and store its original position
2222
const camera = this.top?.currentScene?.activeCamera;
2323
if (camera) {
24-
const cameraTransform = camera.sibling<Transform>("Transform");
24+
const cameraTransform = camera.children["Transform"] as Transform | undefined;
2525
if (cameraTransform) {
2626
this.originalCameraPosition = cameraTransform.position.clone();
2727
}
@@ -32,26 +32,26 @@ export class CameraShake extends Part {
3232
this.shakeTimer = this.duration;
3333
}
3434

35-
act() {
36-
super.act();
35+
act(delta: number) {
36+
super.act(delta);
3737
if (this.shakeTimer > 0) {
3838
const camera = this.top?.currentScene?.activeCamera;
3939
if (camera) {
40-
const cameraTransform = camera.sibling<Transform>("Transform");
40+
const cameraTransform = camera.children["Transform"] as Transform | undefined;
4141
if (cameraTransform) {
4242
const offsetX = (Math.random() - 0.5) * this.intensity;
4343
const offsetY = (Math.random() - 0.5) * this.intensity;
44-
cameraTransform.position = this.originalCameraPosition!.add(new Vector(offsetX, offsetY));
44+
cameraTransform.moveTo(this.originalCameraPosition!.add(new Vector(offsetX, offsetY)));
4545
}
4646
}
4747
this.shakeTimer--;
4848
} else if (this.originalCameraPosition) {
4949
// Reset camera position after shake
5050
const camera = this.top?.currentScene?.activeCamera;
5151
if (camera) {
52-
const cameraTransform = camera.sibling<Transform>("Transform");
52+
const cameraTransform = camera.children["Transform"] as Transform | undefined;
5353
if (cameraTransform) {
54-
cameraTransform.position = this.originalCameraPosition;
54+
cameraTransform.moveTo(this.originalCameraPosition);
5555
}
5656
}
5757
}

Parts/CharacterMovement.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import { Part } from "./Part";
22
import { Transform } from "./Children/Transform";
33
import { Input } from "./Input";
4+
import { Vector } from "../Math/Vector";
45

56
export class CharacterMovement extends Part {
67
speed: number;
7-
movementType: 'WASD' | 'ArrowKeys';
8+
movementType: 'WASD' | 'ArrowKeys' | 'BOTH';
89
input: Input | undefined;
910

10-
constructor({ name, speed = 5, movementType = 'WASD', input }: { name?: string, speed?: number, movementType?: 'WASD' | 'ArrowKeys', input?: Input }) {
11+
constructor({ name, speed = 5, movementType = 'WASD', input }: { name?: string, speed?: number, movementType?: 'WASD' | 'ArrowKeys' | 'BOTH', input?: Input }) {
1112
super({ name: name || 'CharacterMovement' });
1213
this.speed = speed;
1314
this.movementType = movementType;
1415
this.input = input
1516
}
1617

17-
18-
act(): void {
18+
act(_delta: number): void {
1919
if (!this.input) {
2020
return;
2121
}
@@ -27,32 +27,44 @@ export class CharacterMovement extends Part {
2727

2828
const keys = this.input.downkeys;
2929

30-
if (this.movementType === 'WASD') {
30+
let dx = 0;
31+
let dy = 0;
32+
33+
if (this.movementType === 'WASD' || this.movementType === 'BOTH') {
3134
if (keys.has('w')) {
32-
transform.position.y -= this.speed;
35+
dy -= 1;
3336
}
3437
if (keys.has('s')) {
35-
transform.position.y += this.speed;
38+
dy += 1;
3639
}
3740
if (keys.has('a')) {
38-
transform.position.x -= this.speed;
41+
dx -= 1;
3942
}
4043
if (keys.has('d')) {
41-
transform.position.x += this.speed;
44+
dx += 1;
4245
}
43-
} else if (this.movementType === 'ArrowKeys') {
46+
}
47+
if (this.movementType === 'ArrowKeys' || this.movementType === 'BOTH') {
4448
if (keys.has('ArrowUp')) {
45-
transform.position.y -= this.speed;
49+
dy -= 1;
4650
}
4751
if (keys.has('ArrowDown')) {
48-
transform.position.y += this.speed;
52+
dy += 1;
4953
}
5054
if (keys.has('ArrowLeft')) {
51-
transform.position.x -= this.speed;
55+
dx -= 1;
5256
}
5357
if (keys.has('ArrowRight')) {
54-
transform.position.x += this.speed;
58+
dx += 1;
5559
}
5660
}
61+
62+
// Normalize diagonal movement
63+
if (dx !== 0 && dy !== 0) {
64+
dx *= Math.SQRT1_2; // 1 / sqrt(2)
65+
dy *= Math.SQRT1_2;
66+
}
67+
68+
transform.move(new Vector(dx * this.speed, dy * this.speed));
5769
}
5870
}

Parts/Children/AnimatedSprite.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,37 @@ export class AnimatedSprite extends Renderer {
88
spritesheetData?: SpriteSheetData; // Parsed spritesheet data
99
loadedSheet?: HTMLImageElement; // Loaded image for the spritesheet
1010
frames: Record<string, HTMLImageElement[]> = {}; // Object to hold individual frame images for each animation
11-
lastFrameTime: number = 0; // Timestamp of the last frame update
1211
currentFrameIndex: number = 0; // Index of the current frame being displayed
1312
hasWarnedAboutTransform: boolean = false; // Flag to prevent multiple warnings about missing Transform part
1413
width: number; // Width of the animated sprite
1514
height: number; // Height of the animated sprite
1615
bouncing: boolean = false; // Flag to indicate if the sprite animation is in reverse (bouncing)
1716
currentAnimation: string = "default"; // Current animation name
1817
disableAntiAliasing: boolean = false; // Option to disable anti-aliasing
18+
webEngine: boolean = false; // Flag to indicate if this is running in a web engine context
1919
onAnimationComplete?: (animationName: string, sprite: AnimatedSprite) => void; // Callback for when an animation completes
20-
constructor({ spritesheet, spritesheetImage, width, height, startingAnimation, disableAntiAliasing = false, onAnimationComplete }: { spritesheet: string, spritesheetImage?: string, width: number, height: number, startingAnimation?: string, disableAntiAliasing?: boolean, onAnimationComplete?: (animationName: string, sprite: AnimatedSprite) => void }) {
20+
constructor({ spritesheet, spritesheetImage, width, height, startingAnimation, disableAntiAliasing = false, onAnimationComplete, webEngine = false }: { spritesheet: string, spritesheetImage?: string, width: number, height: number, startingAnimation?: string, disableAntiAliasing?: boolean, onAnimationComplete?: (animationName: string, sprite: AnimatedSprite) => void, webEngine?: boolean }) {
2121
super({ width, height }); // Call the parent constructor with empty imageSource
2222
this.name = "AnimatedSprite";
2323
this.debugEmoji = "🎞️"; // Default emoji for debugging the animated sprite
2424
this.spritesheet = spritesheet;
2525
this.width = width;
2626
this.height = height;
2727
this.ready = false;
28+
this.spritesheetImage = spritesheetImage; // Optional image for the spritesheet
2829
this.currentAnimation = startingAnimation || "default";
2930
this.disableAntiAliasing = disableAntiAliasing;
3031
this.onAnimationComplete = onAnimationComplete; // Set the callback for animation completion
31-
32+
this.webEngine = webEngine; // Set the web engine flag
3233
}
3334

3435
async onMount(parent: Part) {
3536
super.onMount(parent);
3637
parent.setSuperficialDimensions(this.width, this.height); // Set dimensions for the parent part
37-
console.log(this.width, this.height);
38-
console.log(this.parent)
3938
let spritesheetData: SpriteSheetData;
39+
if (!this.spritesheet) {
40+
return;
41+
}
4042
if (this.spritesheet.startsWith("data:application/json")) {
4143
spritesheetData = JSON.parse(atob(this.spritesheet.split(',')[1]));
4244
} else {
@@ -46,7 +48,6 @@ export class AnimatedSprite extends Renderer {
4648
}
4749
spritesheetData = await response.json() as SpriteSheetData; // Assuming the spritesheet is a JSON file
4850
}
49-
5051
// Validate the data structure
5152
if (!spritesheetData.frames || !Array.isArray(spritesheetData.frames)) {
5253
throw new Error("Invalid spritesheet format: 'frames' array is missing or not an array.");
@@ -62,12 +63,15 @@ export class AnimatedSprite extends Renderer {
6263
}
6364

6465
const image = new Image();
65-
if (spritesheetImage) {
66-
image.src = spritesheetImage;
66+
// If spritesheetImage is provided, use it directly. Otherwise, try to resolve from spritesheet data.
67+
if (this.spritesheetImage) {
68+
image.src = this.spritesheetImage;
6769
} else {
68-
const relativeToSpritesheet = this.spritesheet.startsWith("data:") ? "" : this.spritesheet.split("/").slice(0, -1).join("/");
69-
const path = spritesheetData.meta.image.startsWith("http") ? spritesheetData.meta.image : new URL(relativeToSpritesheet + "/" + spritesheetData.meta.image, window.location.href).href; // Handle relative paths
70-
image.src = path; // Set the image source to the spritesheet image path
70+
if (!this.webEngine) {
71+
const relativeToSpritesheet = this.spritesheet.startsWith("data:") ? "" : this.spritesheet.split("/").slice(0, -1).join("/");
72+
const path = spritesheetData.meta.image.startsWith("http") ? spritesheetData.meta.image : new URL(relativeToSpritesheet + "/" + spritesheetData.meta.image, window.location.href).href; // Handle relative paths
73+
image.src = path; // Set the image source to the spritesheet image path
74+
}
7175
}
7276

7377
image.onerror = (err) => {
@@ -91,9 +95,9 @@ export class AnimatedSprite extends Renderer {
9195
for (const animation of Object.keys(this.frames)) {
9296
this.frames[animation] = new Array(this.frames[animation].length);
9397
for (let i = 0; i < this.frames[animation].length; i++) {
94-
const frameIndex = this.spritesheetData?.frames.findIndex(frame => frame.filename === data.meta.animations[animation].frames[i]);
98+
const frameIndex = this.spritesheetData?.frames.findIndex(frame => frame.filename === this.spritesheetData!.meta.animations[animation].frames[i]);
9599
if (frameIndex === -1) {
96-
throw new Error(`Frame '${data.meta.animations[animation].frames[i]}' does not exist in spritesheet for animated sprite <${this.name}> attached to ${this.parent?.name}.`);
100+
throw new Error(`Frame '${this.spritesheetData!.meta.animations[animation].frames[i]}' does not exist in spritesheet for animated sprite <${this.name}> attached to ${this.parent?.name}.`);
97101
}
98102
const frame: HTMLImageElement | null = this.frame(frameIndex!);
99103
if (frame) {
@@ -171,7 +175,6 @@ export class AnimatedSprite extends Renderer {
171175
this.currentAnimation = animationName;
172176
this.currentFrameIndex = 0; // Reset to the first frame of the new animation
173177
this.bouncing = bounce ?? this.spritesheetData.meta.animations[animationName].bounce ?? false; // Reset bouncing state
174-
this.lastFrameTime = performance.now(); // Reset frame timing
175178

176179
if (loop !== undefined) {
177180
this.spritesheetData.meta.animations[this.currentAnimation].loop = loop;
@@ -180,10 +183,11 @@ export class AnimatedSprite extends Renderer {
180183
console.warn(`Animation '${animationName}' does not exist in spritesheet for animated sprite <${this.name}> attached to ${this.parent?.name}.`);
181184
}
182185
}
183-
act() {
184-
super.act();
185-
const now = performance.now();
186-
const delta = now - this.lastFrameTime;
186+
act(delta: number) {
187+
super.act(delta);
188+
if (!this.ready) {
189+
return;
190+
}
187191
const duration = (this.spritesheetData?.frames[this.currentFrameIndex].duration || 100)
188192
if (this.ready && this.spritesheetData) {
189193
if (delta > duration) {
@@ -220,7 +224,6 @@ export class AnimatedSprite extends Renderer {
220224
// Stay at the last frame if we've reached it
221225
}
222226
}
223-
this.lastFrameTime = now;
224227
}
225228
const transform = this.sibling<Transform>("Transform");
226229
if (!transform) {

0 commit comments

Comments
 (0)