Skip to content

Commit bbcdd99

Browse files
committed
reasonable checkpoint i think
1 parent 0dbd185 commit bbcdd99

Some content is hidden

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

73 files changed

+4642
-284
lines changed

Parts/AreaTrigger.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Part } from "./Part";
2+
import { Collider } from "./Children/Collider";
3+
4+
export class AreaTrigger extends Part {
5+
onEnter?: (other: Collider) => void;
6+
onExit?: (other: Collider) => void;
7+
private activeCollisions: Set<string> = new Set();
8+
9+
constructor({ name, onEnter, onExit }: { name?: string, onEnter?: (other: Collider) => void, onExit?: (other: Collider) => void }) {
10+
super({ name: name || 'AreaTrigger' });
11+
this.onEnter = onEnter;
12+
this.onExit = onExit;
13+
this.debugEmoji = "🧲";
14+
}
15+
16+
act() {
17+
super.act();
18+
const collider = this.sibling<Collider>("Collider");
19+
if (!collider) {
20+
console.warn(`AreaTrigger <${this.name}> requires a Collider sibling.`);
21+
return;
22+
}
23+
24+
const currentCollisions = new Set<string>();
25+
if (collider.colliding) {
26+
for (const other of collider.collidingWith) {
27+
currentCollisions.add(other.id);
28+
if (!this.activeCollisions.has(other.id)) {
29+
// New collision - onEnter
30+
if (this.onEnter) {
31+
this.onEnter(other);
32+
}
33+
}
34+
}
35+
}
36+
37+
// 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
41+
// A more robust way would be to store references to the actual collider objects
42+
// For simplicity, we'll just call onExit without the specific collider for now
43+
if (this.onExit) {
44+
// We can't easily get the 'other' collider object here after it's no longer colliding
45+
// A more advanced solution would involve tracking the actual collider objects.
46+
this.onExit(exitedCollider || ({} as Collider)); // Placeholder
47+
}
48+
}
49+
}
50+
51+
this.activeCollisions = currentCollisions;
52+
this.hoverbug = `Active: ${this.activeCollisions.size}`;
53+
}
54+
}

Parts/CameraShake.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Part } from "./Part";
2+
import { Camera } from "./Camera";
3+
import { Transform } from "./Children/Transform";
4+
import { Vector } from "../Math/Vector";
5+
6+
export class CameraShake extends Part {
7+
intensity: number;
8+
duration: number;
9+
private shakeTimer: number = 0;
10+
private originalCameraPosition: Vector | null = null;
11+
12+
constructor({ name, intensity = 5, duration = 60 }: { name?: string, intensity?: number, duration?: number }) {
13+
super({ name: name || 'CameraShake' });
14+
this.intensity = intensity;
15+
this.duration = duration;
16+
this.debugEmoji = " tremors";
17+
}
18+
19+
onMount(parent: Part) {
20+
super.onMount(parent);
21+
// Find the camera in the scene and store its original position
22+
const camera = this.top?.currentScene?.activeCamera;
23+
if (camera) {
24+
const cameraTransform = camera.sibling<Transform>("Transform");
25+
if (cameraTransform) {
26+
this.originalCameraPosition = cameraTransform.position.clone();
27+
}
28+
}
29+
}
30+
31+
shake() {
32+
this.shakeTimer = this.duration;
33+
}
34+
35+
act() {
36+
super.act();
37+
if (this.shakeTimer > 0) {
38+
const camera = this.top?.currentScene?.activeCamera;
39+
if (camera) {
40+
const cameraTransform = camera.sibling<Transform>("Transform");
41+
if (cameraTransform) {
42+
const offsetX = (Math.random() - 0.5) * this.intensity;
43+
const offsetY = (Math.random() - 0.5) * this.intensity;
44+
cameraTransform.position = this.originalCameraPosition!.add(new Vector(offsetX, offsetY));
45+
}
46+
}
47+
this.shakeTimer--;
48+
} else if (this.originalCameraPosition) {
49+
// Reset camera position after shake
50+
const camera = this.top?.currentScene?.activeCamera;
51+
if (camera) {
52+
const cameraTransform = camera.sibling<Transform>("Transform");
53+
if (cameraTransform) {
54+
cameraTransform.position = this.originalCameraPosition;
55+
}
56+
}
57+
}
58+
this.hoverbug = `Shaking: ${this.shakeTimer > 0 ? "✅" : "❌"}`;
59+
}
60+
}

Parts/CharacterMovement.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Part } from "./Part";
2+
import { Transform } from "./Children/Transform";
3+
import { Input } from "./Input";
4+
5+
export class CharacterMovement extends Part {
6+
speed: number;
7+
movementType: 'WASD' | 'ArrowKeys';
8+
input: Input | undefined;
9+
10+
constructor({ name, speed = 5, movementType = 'WASD', input }: { name?: string, speed?: number, movementType?: 'WASD' | 'ArrowKeys', input?: Input }) {
11+
super({ name: name || 'CharacterMovement' });
12+
this.speed = speed;
13+
this.movementType = movementType;
14+
this.input = input
15+
}
16+
17+
18+
act(): void {
19+
if (!this.input) {
20+
return;
21+
}
22+
23+
const transform = this.sibling<Transform>("Transform");
24+
if (!transform) {
25+
return;
26+
}
27+
28+
const keys = this.input.downkeys;
29+
30+
if (this.movementType === 'WASD') {
31+
if (keys.has('w')) {
32+
transform.position.y -= this.speed;
33+
}
34+
if (keys.has('s')) {
35+
transform.position.y += this.speed;
36+
}
37+
if (keys.has('a')) {
38+
transform.position.x -= this.speed;
39+
}
40+
if (keys.has('d')) {
41+
transform.position.x += this.speed;
42+
}
43+
} else if (this.movementType === 'ArrowKeys') {
44+
if (keys.has('ArrowUp')) {
45+
transform.position.y -= this.speed;
46+
}
47+
if (keys.has('ArrowDown')) {
48+
transform.position.y += this.speed;
49+
}
50+
if (keys.has('ArrowLeft')) {
51+
transform.position.x -= this.speed;
52+
}
53+
if (keys.has('ArrowRight')) {
54+
transform.position.x += this.speed;
55+
}
56+
}
57+
}
58+
}

Parts/Children/AnimatedSprite.ts

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class AnimatedSprite extends Renderer {
1717
currentAnimation: string = "default"; // Current animation name
1818
disableAntiAliasing: boolean = false; // Option to disable anti-aliasing
1919
onAnimationComplete?: (animationName: string, sprite: AnimatedSprite) => void; // Callback for when an animation completes
20-
constructor({ spritesheet, width, height, startingAnimation, disableAntiAliasing = false, onAnimationComplete }: { spritesheet: 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 }: { spritesheet: string, spritesheetImage?: string, width: number, height: number, startingAnimation?: string, disableAntiAliasing?: boolean, onAnimationComplete?: (animationName: string, sprite: AnimatedSprite) => void }) {
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
@@ -36,42 +36,53 @@ export class AnimatedSprite extends Renderer {
3636
parent.setSuperficialDimensions(this.width, this.height); // Set dimensions for the parent part
3737
console.log(this.width, this.height);
3838
console.log(this.parent)
39-
const response = await fetch(this.spritesheet);
40-
if (!response.ok) {
41-
throw new Error(`Failed to load spritesheet: ${response.statusText}`);
39+
let spritesheetData: SpriteSheetData;
40+
if (this.spritesheet.startsWith("data:application/json")) {
41+
spritesheetData = JSON.parse(atob(this.spritesheet.split(',')[1]));
42+
} else {
43+
const response = await fetch(this.spritesheet);
44+
if (!response.ok) {
45+
throw new Error(`Failed to load spritesheet: ${response.statusText}`);
46+
}
47+
spritesheetData = await response.json() as SpriteSheetData; // Assuming the spritesheet is a JSON file
4248
}
43-
const data = await response.json() as SpriteSheetData; // Assuming the spritesheet is a JSON file
49+
4450
// Validate the data structure
45-
if (!data.frames || !Array.isArray(data.frames)) {
51+
if (!spritesheetData.frames || !Array.isArray(spritesheetData.frames)) {
4652
throw new Error("Invalid spritesheet format: 'frames' array is missing or not an array.");
4753
}
48-
if (!data.meta || !data.meta.image) {
54+
if (!spritesheetData.meta || !spritesheetData.meta.image) {
4955
throw new Error("Invalid spritesheet format: 'meta.image' is missing.");
5056
}
51-
if (!data.meta.size || typeof data.meta.size.w !== "number" || typeof data.meta.size.h !== "number") {
57+
if (!spritesheetData.meta.size || typeof spritesheetData.meta.size.w !== "number" || typeof spritesheetData.meta.size.h !== "number") {
5258
throw new Error("Invalid spritesheet format: 'meta.size' is missing or invalid.");
5359
}
54-
if (!data.meta.animations || typeof data.meta.animations !== "object") {
60+
if (!spritesheetData.meta.animations || typeof spritesheetData.meta.animations !== "object") {
5561
throw new Error("Invalid spritesheet format: 'meta.animations' is missing or not an object.");
5662
}
5763

5864
const image = new Image();
59-
const relativeToSpritesheet = this.spritesheet.split("/").slice(0, -1).join("/");
60-
const path = data.meta.image.startsWith("http") ? data.meta.image : new URL(relativeToSpritesheet + "/" + data.meta.image, window.location.href).href; // Handle relative paths
61-
image.src = path; // Set the image source to the spritesheet image path
65+
if (spritesheetImage) {
66+
image.src = spritesheetImage;
67+
} 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
71+
}
72+
6273
image.onerror = (err) => {
63-
console.error(`Failed to load spritesheet image <${data.meta.image}>:`, err);
74+
console.error(`Failed to load spritesheet image <${spritesheetData.meta.image}>:`, err);
6475
this.ready = false;
6576
};
66-
this.spritesheetData = data; // Store the parsed spritesheet data
67-
this.frames = Object.fromEntries(Object.keys(data.meta.animations).map(animationName => [animationName, Array(data.meta.animations[animationName].frames.length).fill(null)])); // Initialize frames for each animation
77+
this.spritesheetData = spritesheetData; // Store the parsed spritesheet data
78+
this.frames = Object.fromEntries(Object.keys(spritesheetData.meta.animations).map(animationName => [animationName, Array(spritesheetData.meta.animations[animationName].frames.length).fill(null)])); // Initialize frames for each animation
6879
await new Promise<void>((resolve, reject) => {
6980
image.onload = () => {
7081
this.loadedSheet = image;
7182
resolve();
7283
};
7384
image.onerror = (err) => {
74-
console.error(`Failed to load spritesheet image <${data.meta.image}>:`, err);
85+
console.error(`Failed to load spritesheet image <${spritesheetData.meta.image}>:`, err);
7586
this.ready = false;
7687
reject(err);
7788
};

Parts/Children/Button.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,42 @@ import { Part } from "../Part";
44
import { Input } from "../Input";
55
import { Scene } from "../Scene";
66
import { Renderer } from "./Renderer";
7+
import { Sound } from "../Sound";
78

89
export class Button extends Renderer {
910
styles?: ButtonStyles;
1011
private isHovered: boolean = false;
1112
private isActive: boolean = false;
1213
private onClickHandler: () => void;
14+
clickSound?: Sound;
15+
hoverSound?: Sound;
16+
activeSound?: Sound;
1317

14-
constructor({ label, onClick, styles }: { label: string; onClick: () => void; styles?: ButtonStyles }) {
18+
constructor({ label, onClick, styles, clickSound, hoverSound, activeSound }: { label: string; onClick: () => void; styles?: ButtonStyles, clickSound?: Sound, hoverSound?: Sound, activeSound?: Sound }) {
1519
super({ width: styles?.default?.width ?? 100, height: styles?.default?.height ?? 50, disableAntiAliasing: true });
1620
this.name = label;
1721
this.onClickHandler = onClick;
1822
this.styles = styles;
23+
this.clickSound = clickSound;
24+
this.hoverSound = hoverSound;
25+
this.activeSound = activeSound;
1926

2027
this.onclick = (event: MouseEvent, input: any) => {
2128
if (this.onClickHandler) {
2229
this.onClickHandler();
2330
}
31+
if (this.clickSound) {
32+
this.clickSound.play({ clone: true });
33+
}
2434
event.stopPropagation(); // Prevent further propagation of the click event
2535
event.preventDefault(); // Prevent default action of the click event
2636
};
2737

2838
this.onhover = () => {
2939
this.isHovered = true;
40+
if (this.hoverSound) {
41+
this.hoverSound.play({ clone: true });
42+
}
3043
console.log(`Button ${this.name} hovered`);
3144
};
3245

@@ -39,6 +52,9 @@ export class Button extends Renderer {
3952
this.onmousedown = (event: MouseEvent) => {
4053
if (event.button === 0) { // Left mouse button
4154
this.isActive = true;
55+
if (this.activeSound) {
56+
this.activeSound.play({ clone: true });
57+
}
4258
}
4359
};
4460

Parts/Clickable.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Part } from "./Part";
2+
import { Input } from "./Input";
3+
4+
export class Clickable extends Part {
5+
onClick?: (event: MouseEvent, clicked: Part) => void;
6+
onHover?: () => void;
7+
onUnhover?: () => void;
8+
9+
constructor({ name, onClick, onHover, onUnhover }: { name?: string, onClick?: (event: MouseEvent, clicked: Part) => void, onHover?: () => void, onUnhover?: () => void }) {
10+
super({ name: name || 'Clickable' });
11+
this.onClick = onClick;
12+
this.onHover = onHover;
13+
this.onUnhover = onUnhover;
14+
this.debugEmoji = "👆";
15+
}
16+
17+
onMount(parent: Part) {
18+
super.onMount(parent);
19+
// Ensure an Input instance is attached to the scene
20+
if (this.top && this.top.currentScene && !this.top.currentScene.sibling("Input")) {
21+
this.top.currentScene.addChild(new Input({
22+
key: () => {},
23+
keyup: () => {},
24+
mousemove: () => {},
25+
click: () => {},
26+
}));
27+
}
28+
}
29+
30+
onclick(event: MouseEvent, clicked: Part) {
31+
if (this.onClick) {
32+
this.onClick(event, clicked);
33+
}
34+
super.onclick(event, clicked);
35+
}
36+
37+
onhover() {
38+
if (this.onHover) {
39+
this.onHover();
40+
}
41+
super.onhover();
42+
}
43+
44+
onunhover() {
45+
if (this.onUnhover) {
46+
this.onUnhover();
47+
}
48+
super.onunhover();
49+
}
50+
}

0 commit comments

Comments
 (0)