malc-game-engine 1.0.1 → 1.0.3

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.
Files changed (3) hide show
  1. package/malc.js +1065 -1040
  2. package/malc.min.js +299 -0
  3. package/package.json +1 -1
package/malc.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MALC Game Engine Library
3
- * Version: 1.0.0
3
+ * Version: 1.0.3
4
4
  * Description: A comprehensive 2D game engine for p5.js
5
5
  */
6
6
 
@@ -17,6 +17,9 @@
17
17
  }
18
18
  }(typeof self !== 'undefined' ? self : this, function(p5) {
19
19
 
20
+ // Store reference to p5 instance
21
+ const _p5 = p5;
22
+
20
23
  // ========== GLOBAL ARRAYS ==========
21
24
  const MALCgameObjects = [];
22
25
  const MALCbuttons = [];
@@ -37,1121 +40,1213 @@ function generateId(prefix) {
37
40
  return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
38
41
  }
39
42
 
40
- // ========== GAME OBJECT CLASS WITH GRAVITY ==========
41
- class gameObject {
42
- static objects = [];
43
- static started = false;
44
- static gravity = GRAVITY;
45
- static terminalVelocity = TERMINAL_VELOCITY;
43
+ // ========== COLORED TEXT FUNCTIONS (SAFE VERSION) ==========
44
+ function parseColoredLine(str) {
45
+ const regex = /\\([^|\\\n]+)\|([^|]+)\|/g;
46
+ const parts = [];
47
+ let lastIndex = 0;
48
+ let match;
46
49
 
47
- static render() {
48
- MALCgameObjects.forEach(o => {
49
- if (o.active) o.render();
50
+ while ((match = regex.exec(str)) !== null) {
51
+ if (match.index > lastIndex) {
52
+ parts.push({
53
+ text: str.substring(lastIndex, match.index),
54
+ color: null
55
+ });
56
+ }
57
+
58
+ parts.push({
59
+ text: match[2],
60
+ color: match[1]
50
61
  });
62
+
63
+ lastIndex = match.index + match[0].length;
51
64
  }
52
65
 
53
- static update() {
54
- this.started = true;
55
- this.objects = MALCgameObjects;
56
-
57
- // Update all active objects
58
- MALCgameObjects.forEach(o => {
59
- if (o.active) o.update();
66
+ if (lastIndex < str.length) {
67
+ parts.push({
68
+ text: str.substring(lastIndex),
69
+ color: null
60
70
  });
61
71
  }
72
+
73
+ return parts.length ? parts : [{ text: str, color: null }];
74
+ }
62
75
 
63
- static initialize() {
64
- console.log("MALC gameObjects initialized");
76
+ function parseColoredText(str) {
77
+ const lines = str.split('\n');
78
+ const result = [];
79
+
80
+ for (let i = 0; i < lines.length; i++) {
81
+ const line = lines[i];
82
+ const parts = parseColoredLine(line);
65
83
 
66
- // Add objects to their scenes
67
- MALCgameObjects.forEach(o => {
68
- o.scenes.forEach(sceneId => {
69
- let scene = Scene.getSceneById(sceneId);
70
- if (scene && !scene.objects.includes(o)) {
71
- scene.objects.push(o);
72
- }
84
+ result.push(...parts);
85
+
86
+ if (i < lines.length - 1) {
87
+ result.push({
88
+ text: '\n',
89
+ color: null,
90
+ isNewline: true
73
91
  });
92
+ }
93
+ }
94
+
95
+ return result;
96
+ }
97
+
98
+ function renderColoredText(p, str, x, y, horizontal, vertical, maxWidth) {
99
+ const parts = parseColoredText(str);
100
+ let currentX = x;
101
+ let currentY = y;
102
+
103
+ p.push();
104
+ p.textAlign(horizontal, vertical);
105
+
106
+ for (const part of parts) {
107
+ if (part.isNewline) {
108
+ currentX = x;
109
+ currentY += p.textLeading() || p.textSize() * 1.2;
110
+ continue;
111
+ }
112
+
113
+ if (part.color) {
114
+ try {
115
+ p.fill(part.color);
116
+ } catch (e) {
117
+ p.fill(255);
118
+ }
119
+ }
120
+
121
+ p.text(part.text, currentX, currentY, maxWidth);
122
+ currentX += p.textWidth(part.text);
123
+ }
124
+
125
+ p.pop();
126
+ }
127
+
128
+ // ========== SCENE CLASS (DEFINED FIRST) ==========
129
+ class Scene {
130
+ static scenes = [];
131
+ static activeScene = "blank";
132
+ static started = false;
133
+ static sceneHistory = [];
134
+ static historyLimit = 10;
135
+
136
+ static update() {
137
+ this.started = true;
138
+ this.scenes = MALCScene;
139
+
140
+ let activeSceneFound = false;
141
+
142
+ this.scenes.forEach(S => {
143
+ if (S.id == this.activeScene) {
144
+ activeSceneFound = true;
145
+
146
+ this.scenes.forEach(s => {
147
+ if (s != S) {
148
+ s._active = false;
149
+ s.active = false;
150
+
151
+ s.objects.forEach(o => {
152
+ if (o && typeof o.active !== 'undefined') o.active = false;
153
+ });
154
+ }
155
+ });
156
+
157
+ S.active = true;
158
+
159
+ if (!S._active) {
160
+ S.activated = MALC.time.getTime();
161
+ if (typeof S.onActivate == 'function') S.onActivate();
162
+ }
163
+
164
+ S._active = true;
165
+ S.timeActive = MALC.time.getTime() - S.activated;
166
+
167
+ S.objects.forEach(o => {
168
+ if (o && typeof o.active !== 'undefined') o.active = true;
169
+ });
170
+
171
+ _p5.prototype.push();
172
+ if (window.camera && typeof window.camera.render == 'function') {
173
+ window.camera.render();
174
+ }
175
+
176
+ S.render();
177
+
178
+ _p5.prototype.pop();
179
+ }
74
180
  });
181
+
182
+ if (!activeSceneFound && this.activeScene != "blank") {
183
+ console.warn(`Scene "${this.activeScene}" not found, switching to blank`);
184
+ this.activeScene = "blank";
185
+ }
75
186
  }
76
187
 
77
- static getObjectByIndex(index) {
78
- return MALCgameObjects[index] || null;
188
+ static getSceneById(id) {
189
+ return MALCScene.find(scene => scene.id == id) || null;
79
190
  }
80
191
 
81
- static getActiveObjects() {
82
- return MALCgameObjects.filter(o => o.active);
192
+ static getActiveScene() {
193
+ return this.getSceneById(this.activeScene);
83
194
  }
84
195
 
85
- static getObjectsInScene(sceneId) {
86
- let scene = Scene.getSceneById(sceneId);
87
- return scene ? scene.objects : [];
196
+ static switchToScene(id, addToHistory = true) {
197
+ let scene = this.getSceneById(id);
198
+ if (scene) {
199
+ if (addToHistory && this.activeScene) {
200
+ this.sceneHistory.push(this.activeScene);
201
+ if (this.sceneHistory.length > this.historyLimit) {
202
+ this.sceneHistory.shift();
203
+ }
204
+ }
205
+ this.activeScene = id;
206
+ } else {
207
+ console.error(`Cannot switch to scene "${id}" - not found`);
208
+ }
88
209
  }
89
210
 
90
- static setGlobalGravity(value) {
91
- this.gravity = value;
211
+ static goBack() {
212
+ if (this.sceneHistory.length > 0) {
213
+ let previousScene = this.sceneHistory.pop();
214
+ this.switchToScene(previousScene, false);
215
+ return true;
216
+ }
217
+ return false;
92
218
  }
93
219
 
94
- static getGlobalGravity() {
95
- return this.gravity;
220
+ static getAllScenes() {
221
+ return [...MALCScene];
222
+ }
223
+
224
+ static getScenesWithObject(object) {
225
+ return MALCScene.filter(scene => scene.objects.includes(object));
226
+ }
227
+
228
+ static getScenesByTag(tag) {
229
+ return MALCScene.filter(scene => scene.hasTag(tag));
96
230
  }
97
231
 
98
- constructor(x = 0, y = 0, w = 20, h = 20, ...scenes) {
99
- this.id = generateId('gameObject');
100
- this.x = x;
101
- this.y = y;
102
- this.width = w;
103
- this.height = h;
104
- this.rotation = 0;
105
- this.rotationMode = "degrees";
106
- this.velocity = [0, 0];
107
- this.velocityMatrix = [0, 0];
108
- this.velocityMode = "polar";
109
- this.rvm = "unlinked";
110
-
111
- // Gravity properties
112
- this.gravity = {
113
- enabled: false,
114
- velocity: 0,
115
- grounded: false,
116
- groundTolerance: 1, // pixels
117
- mass: 1,
118
- bounce: 0, // 0 = no bounce, 1 = full bounce
119
- friction: 0.1 // ground friction
120
- };
232
+ constructor(id, backgroundColor, ...scripts) {
233
+ MALCScene.forEach(s => {
234
+ if (s.id == id || typeof id != "string") {
235
+ throw new Error(`Scenes must have unique IDs and be strings. Duplicate/Invalid ID: "${id}"`);
236
+ }
237
+ });
121
238
 
122
- this.formatting = {
123
- outline: [false, 0, "black"],
124
- color: "white",
125
- };
239
+ this.id = id;
240
+ this.backColor = backgroundColor;
241
+ this.scripts = scripts;
126
242
 
127
- this.collition = true;
243
+ this.objects = [];
244
+ this.uiPlanes = [];
128
245
 
129
- this.scripts = [];
130
- this.scenes = scenes.length < 1 ? ["blank"] : [...new Set(scenes)];
131
246
  this.active = false;
132
- this.visible = true;
133
- this.parentScene = null;
134
-
135
- this.debug = false;
136
- this.hitbox = {
137
- x: 0,
138
- y: 0,
139
- width: 0,
140
- height: 0,
141
- rotation: 0,
142
- parts: null,
143
- outline: 1,
144
- };
247
+ this._active = false;
248
+ this.activated = 0;
249
+ this.timeActive = -1;
145
250
 
146
- this.objectInstance = MALCgameObjects.length;
147
- this.lastGroundY = y;
251
+ this.tags = [];
252
+ this.paused = false;
253
+ this.transition = null;
254
+ this.onActivateCallbacks = [];
255
+ this.onDeactivateCallbacks = [];
256
+ this.onUpdateCallbacks = [];
148
257
 
149
- MALCgameObjects.push(this);
258
+ this.sceneInstance = MALCScene.length;
259
+ MALCScene.push(this);
150
260
  }
151
261
 
152
- // Enable gravity for this object
153
- enableGravity() {
154
- this.gravity.enabled = true;
155
- return this;
156
- }
157
-
158
- // Disable gravity for this object
159
- disableGravity() {
160
- this.gravity.enabled = false;
161
- this.gravity.velocity = 0;
162
- return this;
163
- }
164
-
165
- // Set gravity parameters
166
- setGravity(options = {}) {
167
- if (options.enabled !== undefined) this.gravity.enabled = options.enabled;
168
- if (options.mass !== undefined) this.gravity.mass = Math.max(0.1, options.mass);
169
- if (options.bounce !== undefined) this.gravity.bounce = Math.min(1, Math.max(0, options.bounce));
170
- if (options.friction !== undefined) this.gravity.friction = Math.min(1, Math.max(0, options.friction));
171
- if (options.groundTolerance !== undefined) this.gravity.groundTolerance = options.groundTolerance;
172
- return this;
173
- }
174
-
175
- // Apply gravity to this object
176
- applyGravity() {
177
- if (!this.gravity.enabled) return;
178
-
179
- // Apply gravity acceleration (scaled by mass)
180
- this.gravity.velocity += gameObject.gravity * this.gravity.mass;
262
+ render() {
263
+ if (this.paused) return;
181
264
 
182
- // Limit to terminal velocity
183
- this.gravity.velocity = Math.min(this.gravity.velocity, gameObject.terminalVelocity);
265
+ if (this.transition) {
266
+ this.applyTransition();
267
+ }
184
268
 
185
- // Store last position before moving
186
- let lastY = this.y;
269
+ _p5.prototype.background(this.backColor);
187
270
 
188
- // Apply vertical movement
189
- this.y += this.gravity.velocity;
271
+ this.scripts.forEach(exe => {
272
+ if (typeof exe == "function") exe(this);
273
+ });
190
274
 
191
- // Check for ground collision with other objects
192
- this.checkGroundCollision();
275
+ this.onUpdateCallbacks.forEach(cb => {
276
+ if (typeof cb == "function") cb(this);
277
+ });
193
278
 
194
- // If we just landed, stop downward velocity
195
- if (this.gravity.grounded) {
196
- this.gravity.velocity = 0;
197
-
198
- // Apply ground friction to horizontal movement
199
- if (this.gravity.friction > 0 && this.velocityMode === "polar") {
200
- this.velocity[0] *= (1 - this.gravity.friction);
201
- if (Math.abs(this.velocity[0]) < 0.01) this.velocity[0] = 0;
279
+ this.objects.forEach(o => {
280
+ if (o && typeof o.update == "function") {
281
+ o.update(true);
202
282
  }
203
- }
204
- }
205
-
206
- // Check if this object is standing on another object
207
- checkGroundCollision() {
208
- if (!this.collition || !this.gravity.enabled) return;
209
-
210
- let wasGrounded = this.gravity.grounded;
211
- this.gravity.grounded = false;
283
+ if (o && typeof o.render == "function") {
284
+ o.render();
285
+ }
286
+ });
212
287
 
213
- // Check collision with other objects in the same scene
214
- if (this.parentScene && this.parentScene.objects) {
215
- this.parentScene.objects.forEach(other => {
216
- // Skip self and inactive objects
217
- if (other.id === this.id || !other.active) return;
218
-
219
- // Only check if gravity is enabled on this object and we're moving downward
220
- if (this.gravity.velocity <= 0) return;
221
-
222
- // Check if other object is below this one
223
- let verticalDistance = (other.y - other.height/2) - (this.y + this.height/2);
224
-
225
- // If within ground tolerance and horizontally overlapping
226
- if (Math.abs(verticalDistance) <= this.gravity.groundTolerance &&
227
- this.x + this.width/2 > other.x - other.width/2 &&
228
- this.x - this.width/2 < other.x + other.width/2) {
229
-
230
- this.gravity.grounded = true;
231
- this.lastGroundY = other.y - other.height/2 - this.height/2;
232
-
233
- // Apply bounce if enabled
234
- if (this.gravity.bounce > 0 && wasGrounded === false) {
235
- this.gravity.velocity = -this.gravity.velocity * this.gravity.bounce;
236
-
237
- // If bounce velocity is very small, just set to zero
238
- if (Math.abs(this.gravity.velocity) < 0.1) {
239
- this.gravity.velocity = 0;
240
- }
241
- } else {
242
- // Position exactly on ground
243
- this.y = this.lastGroundY;
244
- }
288
+ if (typeof UIPlanes !== 'undefined' && UIPlanes.length > 0) {
289
+ UIPlanes.forEach(ui => {
290
+ if (ui && typeof ui.belongsToScene == "function" && ui.belongsToScene(this.id)) {
291
+ ui.render();
245
292
  }
246
293
  });
247
294
  }
248
- }
249
-
250
- update() {
251
- if (!this.active) return;
252
-
253
- // Apply gravity if enabled
254
- this.applyGravity();
255
295
 
256
- let vel = this.velocity[0];
257
- let angle = this.velocity[1];
258
-
259
- if (this.velocityMode == "polar") {
260
- let linked = false;
261
- if (!/unlinked/i.test(this.rvm) && /linked/i.test(this.rvm)) {
262
- this.velocity[1] = this.rotation;
263
- linked = true;
264
- }
265
-
266
- let rot = linked ?
267
- (this.rotationMode == "degrees" ? (this.rotation) : radians(this.rotation)) :
268
- (this.rotationMode == "degrees" ? (this.velocity[1]) : (this.velocity[1]));
269
-
270
- if(isNaN(rot)){
271
- vel = 0;
272
- rot = 0;
273
- }
274
-
275
- let vx = vel * cos(rot);
276
- let vy = vel * sin(rot);
277
-
278
- this.velocityMatrix = [vx, vy];
279
-
280
- // Don't apply horizontal movement if gravity is enabled and we're grounded with friction
281
- if (!(this.gravity.enabled && this.gravity.grounded && this.gravity.friction > 0)) {
282
- this.x += vx;
283
- }
284
-
285
- // Vertical movement is handled by gravity when enabled
286
- if (!this.gravity.enabled) {
287
- this.y += vy;
288
- }
289
- } else {
290
- // Cartesian velocity mode
291
- if (!(this.gravity.enabled && this.gravity.grounded && this.gravity.friction > 0)) {
292
- this.x += vel;
293
- }
294
- if (!this.gravity.enabled) {
295
- this.y += angle;
296
+ this.uiPlanes.forEach(ui => {
297
+ if (ui && typeof ui.render == "function") {
298
+ ui.render();
296
299
  }
297
- }
298
-
299
- // Update parent scene reference
300
- this.updateParentScene();
300
+ });
301
301
 
302
- MALCgameObjects[this.objectInstance] = this;
302
+ MALCScene[this.sceneInstance] = this;
303
303
  }
304
304
 
305
- render() {
306
- if (!this.active) return;
307
-
308
- this.scripts.forEach(s => {
309
- if(typeof s == "function")s(this);
310
- });
305
+ applyTransition() {
306
+ if (!this.transition || !this.transition.active) return;
311
307
 
312
- let outline = this.formatting.outline;
313
- let hb = this.hitbox;
308
+ this.transition.progress += 1/60;
314
309
 
315
- // Draw debug hitbox if enabled
316
- if (this.debug) {
317
- push();
318
- translate(this.x, this.y);
319
- rectMode(CENTER);
320
- if (this.rotationMode == "degrees") angleMode(DEGREES);
321
- rotate(this.rotation + hb.rotation);
322
-
323
- stroke("#00FF27");
324
- strokeWeight(hb.outline);
325
- noFill();
326
- rect(hb.x, hb.y, this.width + hb.width, this.height + hb.height);
327
-
328
- // Draw gravity indicator if enabled
329
- if (this.gravity.enabled) {
330
- stroke(0, 255, 0, 100);
331
- line(0, 0, 0, this.gravity.velocity * 5);
332
- }
333
-
334
- pop();
310
+ if (this.transition.progress >= this.transition.duration) {
311
+ this.transition.active = false;
312
+ this.transition = null;
313
+ return;
335
314
  }
336
315
 
337
- if (!this.visible) return;
338
-
339
- push();
340
- translate(this.x, this.y);
341
- rectMode(CENTER);
342
- if (this.rotationMode == "degrees") angleMode(DEGREES);
343
- rotate(this.rotation);
316
+ let t = this.transition.progress / this.transition.duration;
344
317
 
345
- if (outline[0]) {
346
- strokeWeight(outline[1]);
347
- stroke(outline[2]);
348
- } else {
349
- noStroke();
318
+ _p5.prototype.push();
319
+ switch(this.transition.type) {
320
+ case "fade":
321
+ _p5.prototype.fill(0, 255 * (1 - t));
322
+ _p5.prototype.rect(0, 0, _p5.prototype.width, _p5.prototype.height);
323
+ break;
324
+ case "slide":
325
+ _p5.prototype.translate(_p5.prototype.width * (1 - t), 0);
326
+ break;
350
327
  }
351
-
352
- fill(this.formatting.color);
353
- rect(0, 0, this.width, this.height);
354
- pop();
355
- }
356
-
357
- // ========== HELPFUL METHODS ==========
358
-
359
- belongsToScene(sceneId) {
360
- return this.scenes.includes(sceneId);
328
+ _p5.prototype.pop();
361
329
  }
362
330
 
363
- addToScene(sceneId) {
364
- if (!this.scenes.includes(sceneId)) {
365
- this.scenes.push(sceneId);
366
- let scene = Scene.getSceneById(sceneId);
367
- if (scene && !scene.objects.includes(this)) {
368
- scene.objects.push(this);
331
+ addObject(object) {
332
+ if (object && !this.objects.includes(object)) {
333
+ this.objects.push(object);
334
+ if (typeof object.addToScene == "function") {
335
+ object.addToScene(this.id);
369
336
  }
370
337
  }
371
338
  return this;
372
339
  }
373
340
 
374
- removeFromScene(sceneId) {
375
- this.scenes = this.scenes.filter(id => id != sceneId);
376
- let scene = Scene.getSceneById(sceneId);
377
- if (scene) {
378
- scene.objects = scene.objects.filter(obj => obj != this);
341
+ addObjects(objects) {
342
+ objects.forEach(obj => this.addObject(obj));
343
+ return this;
344
+ }
345
+
346
+ removeObject(object) {
347
+ this.objects = this.objects.filter(obj => obj != object);
348
+ if (object && typeof object.removeFromScene == "function") {
349
+ object.removeFromScene(this.id);
379
350
  }
380
351
  return this;
381
352
  }
382
353
 
383
- removeFromAllScenes() {
384
- this.scenes.forEach(sceneId => {
385
- let scene = Scene.getSceneById(sceneId);
386
- if (scene) {
387
- scene.objects = scene.objects.filter(obj => obj != this);
354
+ clearObjects() {
355
+ this.objects.forEach(obj => {
356
+ if (obj && typeof obj.removeFromScene == "function") {
357
+ obj.removeFromScene(this.id);
388
358
  }
389
359
  });
390
- this.scenes = [];
360
+ this.objects = [];
391
361
  return this;
392
362
  }
393
363
 
394
- updateParentScene() {
395
- if (Scene.activeScene) {
396
- let activeScene = Scene.getSceneById(Scene.activeScene);
397
- if (activeScene && this.belongsToScene(activeScene.id)) {
398
- this.parentScene = activeScene;
399
- }
364
+ getObjects(filter) {
365
+ if (typeof filter == "function") {
366
+ return this.objects.filter(filter);
367
+ } else if (filter == "button") {
368
+ return this.objects.filter(obj => obj instanceof Button);
369
+ } else if (filter == "gameObject") {
370
+ return this.objects.filter(obj => obj instanceof gameObject);
400
371
  }
372
+ return this.objects;
401
373
  }
402
374
 
403
- distanceTo(target) {
404
- let dx = target.x !== undefined ? target.x - this.x : target[0] - this.x;
405
- let dy = target.y !== undefined ? target.y - this.y : target[1] - this.y;
406
- return Math.sqrt(dx * dx + dy * dy);
375
+ getObjectById(id) {
376
+ return this.objects.find(obj => obj && obj.id == id);
407
377
  }
408
378
 
409
- collidesWith(other) {
410
- return (this.x < other.x + other.width &&
411
- this.x + this.width > other.x &&
412
- this.y < other.y + other.height &&
413
- this.y + this.height > other.y);
379
+ addUIPlane(uiPlane) {
380
+ if (uiPlane && !this.uiPlanes.includes(uiPlane)) {
381
+ this.uiPlanes.push(uiPlane);
382
+ if (typeof uiPlane.addToScene == "function") {
383
+ uiPlane.addToScene(this.id);
384
+ }
385
+ }
386
+ return this;
414
387
  }
415
388
 
416
- directionTo(x, y, err = 0.5) {
417
- let pa = [x - this.x, y - this.y];
418
-
419
- let angle = (x && y) ? atan(pa[1]/pa[0]) : this.rotation;
420
- var quads = [
421
- pa[0] < -err && pa[1] > err,
422
- pa[0] < -err && pa[1] < -err,
423
- pa[0] > err && pa[1] > err,
424
- pa[0] > err && pa[1] < -err,
425
- (pa[0] < err && pa[0] > -err),
426
- (pa[1] < err && pa[1] > -err),
427
- ];
428
-
429
- let da = (Math.atan(pa[1]/pa[0])*180)/Math.PI;
430
-
431
- if((pa[1] > -err && pa[1] < err) && (pa[0] > -err && pa[0] < err)){
432
- angle = NaN;
433
- } else if(quads[0]){
434
- angle = da+180;
435
- } else if(quads[1]){
436
- angle = da+180;
437
- } else if(quads[2]){
438
- angle = da;
439
- } else if(quads[3]){
440
- angle = da;
441
- } else if(quads[4]){
442
- if(pa[1] < -err) {
443
- angle = -90;
444
- } else if(pa[1] > err) {
445
- angle = 90;
446
- }
447
- } else if(quads[5]){
448
- if(pa[0] < -err) {
449
- angle = 180;
450
- } else if(pa[0] > err) {
451
- angle = 0;
389
+ removeUIPlane(uiPlane) {
390
+ this.uiPlanes = this.uiPlanes.filter(ui => ui != uiPlane);
391
+ if (uiPlane && typeof uiPlane.removeFromScene == "function") {
392
+ uiPlane.removeFromScene(this.id);
393
+ }
394
+ return this;
395
+ }
396
+
397
+ clearUIPlanes() {
398
+ this.uiPlanes.forEach(ui => {
399
+ if (ui && typeof ui.removeFromScene == "function") {
400
+ ui.removeFromScene(this.id);
452
401
  }
402
+ });
403
+ this.uiPlanes = [];
404
+ return this;
405
+ }
406
+
407
+ addScript(script) {
408
+ if (typeof script == "function" && !this.scripts.includes(script)) {
409
+ this.scripts.push(script);
453
410
  }
454
-
455
- return angle;
411
+ return this;
456
412
  }
457
413
 
458
- pointTo(target) {
459
- this.rotation = this.directionTo(target);
414
+ removeScript(script) {
415
+ this.scripts = this.scripts.filter(s => s != script);
460
416
  return this;
461
417
  }
462
418
 
463
- setPosition(x, y) {
464
- this.x = x;
465
- this.y = y;
419
+ clearScripts() {
420
+ this.scripts = [];
466
421
  return this;
467
422
  }
468
423
 
469
- move(dx, dy) {
470
- this.x += dx;
471
- this.y += dy;
424
+ onActivate(callback) {
425
+ if (typeof callback == "function") {
426
+ this.onActivateCallbacks.push(callback);
427
+ }
472
428
  return this;
473
429
  }
474
430
 
475
- setVelocity(speed, x, y, err = 0.5){
476
- let angle = this.directionTo(x,y,err);
477
-
478
- if(isNaN(angle)){
479
- angle = 0;
480
- speed = 0;
431
+ onDeactivate(callback) {
432
+ if (typeof callback == "function") {
433
+ this.onDeactivateCallbacks.push(callback);
481
434
  }
482
-
483
- this.velocity = [speed, angle];
484
- return this.velocity;
435
+ return this;
485
436
  }
486
437
 
487
- destroy() {
488
- this.removeFromAllScenes();
489
- let index = MALCgameObjects.indexOf(this);
490
- if (index > -1) {
491
- MALCgameObjects.splice(index, 1);
438
+ onUpdate(callback) {
439
+ if (typeof callback == "function") {
440
+ this.onUpdateCallbacks.push(callback);
492
441
  }
442
+ return this;
493
443
  }
494
444
 
495
- clone() {
496
- let clone = new gameObject(this.x, this.y, this.width, this.height, ...this.scenes);
497
- clone.rotation = this.rotation;
498
- clone.rotationMode = this.rotationMode;
499
- clone.velocity = [...this.velocity];
500
- clone.velocityMode = this.velocityMode;
501
- clone.rvm = this.rvm;
502
- clone.formatting = JSON.parse(JSON.stringify(this.formatting));
503
- clone.gravity = JSON.parse(JSON.stringify(this.gravity));
504
- clone.debug = this.debug;
505
- clone.hitbox = JSON.parse(JSON.stringify(this.hitbox));
506
- return clone;
445
+ pause() {
446
+ this.paused = true;
447
+ return this;
507
448
  }
508
-
509
- screenToWorld(screenX, screenY) {
510
- if (window.camera && typeof camera.screenToWorld == "function") {
511
- return camera.screenToWorld(screenX, screenY);
449
+
450
+ resume() {
451
+ this.paused = false;
452
+ return this;
453
+ }
454
+
455
+ setTransition(type, duration = 1.0) {
456
+ this.transition = {
457
+ type: type,
458
+ duration: duration,
459
+ progress: 0,
460
+ active: true
461
+ };
462
+ return this;
463
+ }
464
+
465
+ addTag(tag) {
466
+ if (!this.tags.includes(tag)) {
467
+ this.tags.push(tag);
512
468
  }
513
- return { x: screenX, y: screenY };
469
+ return this;
514
470
  }
515
471
 
516
- isOnScreen() {
517
- if (!window.camera) return true;
472
+ removeTag(tag) {
473
+ this.tags = this.tags.filter(t => t != tag);
474
+ return this;
475
+ }
476
+
477
+ hasTag(tag) {
478
+ return this.tags.includes(tag);
479
+ }
480
+
481
+ reset() {
482
+ this.clearObjects();
483
+ this.clearUIPlanes();
484
+ this.clearScripts();
485
+ this.onActivateCallbacks = [];
486
+ this.onDeactivateCallbacks = [];
487
+ this.onUpdateCallbacks = [];
488
+ this.tags = [];
489
+ this.paused = false;
490
+ this.transition = null;
491
+ this.timeActive = 0;
492
+ return this;
493
+ }
494
+
495
+ destroy() {
496
+ let index = MALCScene.indexOf(this);
497
+ if (index > -1) {
498
+ MALCScene.splice(index, 1);
499
+ }
518
500
 
519
- let cameraPos = camera.getOrientation();
520
- let screenRight = cameraPos[0] + camera.width;
521
- let screenBottom = cameraPos[1] + camera.height;
501
+ this.clearObjects();
502
+ this.clearUIPlanes();
522
503
 
523
- return (this.x + this.width/2 > cameraPos[0] &&
524
- this.x - this.width/2 < screenRight &&
525
- this.y + this.height/2 > cameraPos[1] &&
526
- this.y - this.height/2 < screenBottom);
504
+ if (Scene.activeScene == this.id) {
505
+ Scene.activeScene = "blank";
506
+ }
507
+ }
508
+
509
+ clone(newId) {
510
+ let clone = new Scene(newId || this.id + "_copy", this.backColor, ...this.scripts);
511
+ clone.objects = [...this.objects];
512
+ clone.uiPlanes = [...this.uiPlanes];
513
+ clone.tags = [...this.tags];
514
+ return clone;
515
+ }
516
+
517
+ getInfo() {
518
+ return {
519
+ id: this.id,
520
+ active: this.active,
521
+ timeActive: this.timeActive,
522
+ objectCount: this.objects.length,
523
+ uiPlaneCount: this.uiPlanes.length,
524
+ scriptCount: this.scripts.length,
525
+ tags: this.tags,
526
+ paused: this.paused
527
+ };
527
528
  }
528
529
  }
529
530
 
530
- // ========== BUTTON CLASS ==========
531
- class Button extends gameObject {
532
- static buttons = [];
531
+ // ========== GAME OBJECT CLASS WITH GRAVITY ==========
532
+ class gameObject {
533
+ static objects = [];
534
+ static started = false;
535
+ static gravity = GRAVITY;
536
+ static terminalVelocity = TERMINAL_VELOCITY;
533
537
 
534
- static updateButton() {
535
- this.startedButtons = true;
536
- this.buttons = MALCbuttons;
537
-
538
- this.buttons.forEach(b => {
539
- if (!b.active) return;
540
-
541
- b.isHovered = b.events.hover();
542
-
543
- if (MALCbuttons.every(b => !b.events.hover())) {
544
- cursor();
545
- } else if (b.isHovered) {
546
- cursor(b.cursor);
547
- }
538
+ static render() {
539
+ MALCgameObjects.forEach(o => {
540
+ if (o.active) o.render();
548
541
  });
549
542
  }
550
543
 
551
- static getButtonByIndex(index) {
552
- return MALCbuttons[index] || null;
544
+ static update() {
545
+ this.started = true;
546
+ this.objects = MALCgameObjects;
547
+
548
+ // Update all active objects
549
+ MALCgameObjects.forEach(o => {
550
+ if (o.active) o.update();
551
+ });
552
+ }
553
+
554
+ static initialize() {
555
+ console.log("MALC gameObjects initialized");
556
+
557
+ // Add objects to their scenes
558
+ MALCgameObjects.forEach(o => {
559
+ o.scenes.forEach(sceneId => {
560
+ let scene = Scene.getSceneById(sceneId);
561
+ if (scene && !scene.objects.includes(o)) {
562
+ scene.objects.push(o);
563
+ }
564
+ });
565
+ });
553
566
  }
554
567
 
555
- static getHoveredButton() {
556
- return MALCbuttons.find(b => b.active && b.events.hover());
568
+ static getObjectByIndex(index) {
569
+ return MALCgameObjects[index] || null;
557
570
  }
558
571
 
559
- static getPressedButton() {
560
- return MALCbuttons.find(b => b.active && b.events.pressed());
572
+ static getActiveObjects() {
573
+ return MALCgameObjects.filter(o => o.active);
561
574
  }
562
575
 
563
- constructor(x = 0, y = 0, w = 20, h = 20, displayText = "Button", ...scenes) {
564
- super(x, y, w, h, ...scenes);
565
-
566
- this.formatting.button = {
567
- hover: 220,
568
- clicked: 190,
569
- text: {
570
- color: 0,
571
- size: 14,
572
- style:"normal",
573
- display: displayText,
574
- },
575
- colors: {
576
- normal: 255,
577
- hover: 220,
578
- pressed: 190,
579
- disabled: 150
580
- }
581
- };
582
-
583
- this.cursor = "pointer";
584
- this.isHovered = false;
585
- this.isPressed = false;
586
- this.isDisabled = false;
587
- this.clickCooldown = 100;
588
- this.lastClickTime = 0;
589
- this.cooldownActive = false;
590
-
591
- this.events = {
592
- hover: (err = 0) => {
593
- return !this.isDisabled && (
594
- mouse.x < this.x + this.width / 2 + err &&
595
- mouse.x > this.x - (this.width / 2 + err) &&
596
- mouse.y < this.y + this.height / 2 + err &&
597
- mouse.y > this.y - (this.height / 2 + err)
598
- );
599
- },
600
- pressed: () => {
601
- return this.events.hover() && mouse.down;
602
- },
603
- clicked: () => {
604
- let wasPressed = this.wasPressed;
605
- let isHovering = this.events.hover();
606
- let mouseReleased = !mouse.down && wasPressed;
607
-
608
- this.wasPressed = mouse.down && isHovering;
609
-
610
- return mouseReleased && isHovering;
611
- }
612
- };
613
-
614
- this.wasPressed = false;
615
- this.onClick = null;
616
-
617
- this.buttonIndex = MALCbuttons.length;
618
- MALCbuttons.push(this);
576
+ static getObjectsInScene(sceneId) {
577
+ let scene = Scene.getSceneById(sceneId);
578
+ return scene ? scene.objects : [];
619
579
  }
620
-
621
- update(boolean) {
622
- super.update(boolean);
623
-
624
- if (this.cooldownActive) {
625
- let currentTime = Date.now();
626
- if (currentTime - this.lastClickTime >= this.clickCooldown) {
627
- this.cooldownActive = false;
628
- }
629
- }
630
-
631
- this.isHovered = this.events.hover();
632
- this.isPressed = this.events.pressed();
633
-
634
- if (this.events.clicked() && this.onClick && !this.isDisabled && !this.cooldownActive) {
635
- this.onClick(this);
636
- this.lastClickTime = Date.now();
637
- this.cooldownActive = true;
638
- }
639
-
640
- MALCbuttons[this.buttonIndex] = this;
580
+
581
+ static setGlobalGravity(value) {
582
+ this.gravity = value;
583
+ }
584
+
585
+ static getGlobalGravity() {
586
+ return this.gravity;
641
587
  }
642
588
 
643
- render() {
644
- if (!this.active) return;
645
-
646
- let btnFormat = this.formatting.button;
647
- let buttonColor;
589
+ constructor(x = 0, y = 0, w = 20, h = 20, ...scenes) {
590
+ this.id = generateId('gameObject');
591
+ this.x = x;
592
+ this.y = y;
593
+ this.width = w;
594
+ this.height = h;
595
+ this.rotation = 0;
596
+ this.rotationMode = "degrees";
597
+ this.velocity = [0, 0];
598
+ this.velocityMatrix = [0, 0];
599
+ this.velocityMode = "polar";
600
+ this.rvm = "unlinked";
648
601
 
649
- if (this.isDisabled) {
650
- buttonColor = btnFormat.colors.disabled;
651
- } else if (this.isPressed) {
652
- buttonColor = btnFormat.colors.pressed;
653
- } else if (this.isHovered) {
654
- buttonColor = btnFormat.colors.hover;
655
- } else {
656
- buttonColor = btnFormat.colors.normal;
657
- }
602
+ // Gravity properties
603
+ this.gravity = {
604
+ enabled: false,
605
+ velocity: 0,
606
+ grounded: false,
607
+ groundTolerance: 1, // pixels
608
+ mass: 1,
609
+ bounce: 0, // 0 = no bounce, 1 = full bounce
610
+ friction: 0.1 // ground friction
611
+ };
658
612
 
659
- let originalColor = this.formatting.color;
660
- this.formatting.color = buttonColor;
613
+ this.formatting = {
614
+ outline: [false, 0, "black"],
615
+ color: "white",
616
+ };
661
617
 
662
- super.render();
618
+ this.collition = true;
663
619
 
664
- if(!this.visible) return;
620
+ this.scripts = [];
621
+ this.scenes = scenes.length < 1 ? ["blank"] : [...new Set(scenes)];
622
+ this.active = false;
623
+ this.visible = true;
624
+ this.parentScene = null;
665
625
 
666
- push();
667
- translate(this.x, this.y);
668
- if (this.rotationMode == "degrees") angleMode(DEGREES);
669
- rotate(this.rotation);
626
+ this.debug = false;
627
+ this.hitbox = {
628
+ x: 0,
629
+ y: 0,
630
+ width: 0,
631
+ height: 0,
632
+ rotation: 0,
633
+ parts: null,
634
+ outline: 1,
635
+ };
670
636
 
671
- textStyle(btnFormat.text.style);
672
- textSize(btnFormat.text.size);
673
- fill(btnFormat.text.color);
674
- coloredText(btnFormat.text.display, 0, 0, CENTER, CENTER);
675
- pop();
637
+ this.objectInstance = MALCgameObjects.length;
638
+ this.lastGroundY = y;
676
639
 
677
- this.formatting.color = originalColor;
640
+ MALCgameObjects.push(this);
678
641
  }
679
642
 
680
- // ========== BUTTON-SPECIFIC HELPER METHODS ==========
643
+ // Enable gravity for this object
644
+ enableGravity() {
645
+ this.gravity.enabled = true;
646
+ return this;
647
+ }
681
648
 
682
- setText(text) {
683
- this.formatting.button.text.display = text;
649
+ // Disable gravity for this object
650
+ disableGravity() {
651
+ this.gravity.enabled = false;
652
+ this.gravity.velocity = 0;
684
653
  return this;
685
654
  }
686
655
 
687
- getRGBFromColor(colorInput) {
688
- let c = color(colorInput);
689
- return [red(c), green(c), blue(c)];
656
+ // Set gravity parameters
657
+ setGravity(options = {}) {
658
+ if (options.enabled !== undefined) this.gravity.enabled = options.enabled;
659
+ if (options.mass !== undefined) this.gravity.mass = Math.max(0.1, options.mass);
660
+ if (options.bounce !== undefined) this.gravity.bounce = Math.min(1, Math.max(0, options.bounce));
661
+ if (options.friction !== undefined) this.gravity.friction = Math.min(1, Math.max(0, options.friction));
662
+ if (options.groundTolerance !== undefined) this.gravity.groundTolerance = options.groundTolerance;
663
+ return this;
690
664
  }
691
665
 
692
- getBrightness(colorInput) {
693
- let rgb = this.getRGBFromColor(colorInput);
694
- return 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
666
+ // Apply gravity to this object
667
+ applyGravity() {
668
+ if (!this.gravity.enabled) return;
669
+
670
+ // Apply gravity acceleration (scaled by mass)
671
+ this.gravity.velocity += gameObject.gravity * this.gravity.mass;
672
+
673
+ // Limit to terminal velocity
674
+ this.gravity.velocity = Math.min(this.gravity.velocity, gameObject.terminalVelocity);
675
+
676
+ // Store last position before moving
677
+ let lastY = this.y;
678
+
679
+ // Apply vertical movement
680
+ this.y += this.gravity.velocity;
681
+
682
+ // Check for ground collision with other objects
683
+ this.checkGroundCollision();
684
+
685
+ // If we just landed, stop downward velocity
686
+ if (this.gravity.grounded) {
687
+ this.gravity.velocity = 0;
688
+
689
+ // Apply ground friction to horizontal movement
690
+ if (this.gravity.friction > 0 && this.velocityMode === "polar") {
691
+ this.velocity[0] *= (1 - this.gravity.friction);
692
+ if (Math.abs(this.velocity[0]) < 0.01) this.velocity[0] = 0;
693
+ }
694
+ }
695
695
  }
696
696
 
697
- scaleColor(baseColor, scaleFactor) {
698
- let rgb = this.getRGBFromColor(baseColor);
699
- let scaledRGB = rgb.map(val => constrain(val * scaleFactor, 0, 255));
700
- return color(scaledRGB);
697
+ // Check if this object is standing on another object
698
+ checkGroundCollision() {
699
+ if (!this.collition || !this.gravity.enabled) return;
700
+
701
+ let wasGrounded = this.gravity.grounded;
702
+ this.gravity.grounded = false;
703
+
704
+ // Check collision with other objects in the same scene
705
+ if (this.parentScene && this.parentScene.objects) {
706
+ this.parentScene.objects.forEach(other => {
707
+ // Skip self and inactive objects
708
+ if (other.id === this.id || !other.active) return;
709
+
710
+ // Only check if gravity is enabled on this object and we're moving downward
711
+ if (this.gravity.velocity <= 0) return;
712
+
713
+ // Check if other object is below this one
714
+ let verticalDistance = (other.y - other.height/2) - (this.y + this.height/2);
715
+
716
+ // If within ground tolerance and horizontally overlapping
717
+ if (Math.abs(verticalDistance) <= this.gravity.groundTolerance &&
718
+ this.x + this.width/2 > other.x - other.width/2 &&
719
+ this.x - this.width/2 < other.x + other.width/2) {
720
+
721
+ this.gravity.grounded = true;
722
+ this.lastGroundY = other.y - other.height/2 - this.height/2;
723
+
724
+ // Apply bounce if enabled
725
+ if (this.gravity.bounce > 0 && wasGrounded === false) {
726
+ this.gravity.velocity = -this.gravity.velocity * this.gravity.bounce;
727
+
728
+ // If bounce velocity is very small, just set to zero
729
+ if (Math.abs(this.gravity.velocity) < 0.1) {
730
+ this.gravity.velocity = 0;
731
+ }
732
+ } else {
733
+ // Position exactly on ground
734
+ this.y = this.lastGroundY;
735
+ }
736
+ }
737
+ });
738
+ }
701
739
  }
702
740
 
703
- setColors(normal, hover = null, pressed = null, disabled = null) {
704
- if (hover === null && pressed === null) {
705
- let originalNormal = this.formatting.button.colors.normal;
706
- let originalHover = this.formatting.button.colors.hover;
707
- let originalPressed = this.formatting.button.colors.pressed;
741
+ update() {
742
+ if (!this.active) return;
743
+
744
+ // Apply gravity if enabled
745
+ this.applyGravity();
746
+
747
+ let vel = this.velocity[0];
748
+ let angle = this.velocity[1];
749
+
750
+ if (this.velocityMode == "polar") {
751
+ let linked = false;
752
+ if (!/unlinked/i.test(this.rvm) && /linked/i.test(this.rvm)) {
753
+ this.velocity[1] = this.rotation;
754
+ linked = true;
755
+ }
756
+
757
+ let rot = linked ?
758
+ (this.rotationMode == "degrees" ? (this.rotation) : _p5.prototype.radians(this.rotation)) :
759
+ (this.rotationMode == "degrees" ? (this.velocity[1]) : (this.velocity[1]));
708
760
 
709
- let normalBrightness = this.getBrightness(originalNormal);
710
- let hoverBrightness = this.getBrightness(originalHover);
711
- let pressedBrightness = this.getBrightness(originalPressed);
761
+ if(isNaN(rot)){
762
+ vel = 0;
763
+ rot = 0;
764
+ }
712
765
 
713
- let hoverScale = normalBrightness !== 0 ? hoverBrightness / normalBrightness : 0.86;
714
- let pressedScale = normalBrightness !== 0 ? pressedBrightness / normalBrightness : 0.75;
766
+ let vx = vel * _p5.prototype.cos(rot);
767
+ let vy = vel * _p5.prototype.sin(rot);
768
+
769
+ this.velocityMatrix = [vx, vy];
715
770
 
716
- hover = this.scaleColor(normal, hoverScale);
717
- pressed = this.scaleColor(normal, pressedScale);
771
+ // Don't apply horizontal movement if gravity is enabled and we're grounded with friction
772
+ if (!(this.gravity.enabled && this.gravity.grounded && this.gravity.friction > 0)) {
773
+ this.x += vx;
774
+ }
775
+
776
+ // Vertical movement is handled by gravity when enabled
777
+ if (!this.gravity.enabled) {
778
+ this.y += vy;
779
+ }
718
780
  } else {
719
- hover = color(hover);
720
- pressed = color(pressed);
781
+ // Cartesian velocity mode
782
+ if (!(this.gravity.enabled && this.gravity.grounded && this.gravity.friction > 0)) {
783
+ this.x += vel;
784
+ }
785
+ if (!this.gravity.enabled) {
786
+ this.y += angle;
787
+ }
721
788
  }
722
789
 
723
- let normalColor = color(normal);
724
- let disabledColor = disabled !== null ? color(disabled) : null;
725
-
726
- this.formatting.button.colors.normal = normalColor;
727
- this.formatting.button.colors.hover = hover;
728
- this.formatting.button.colors.pressed = pressed;
729
- if (disabledColor !== null) {
730
- this.formatting.button.colors.disabled = disabledColor;
731
- }
790
+ // Update parent scene reference
791
+ this.updateParentScene();
732
792
 
733
- return this;
734
- }
735
-
736
- textStyle(color, size) {
737
- if (color !== undefined) this.formatting.button.text.color = color;
738
- if (size !== undefined) this.formatting.button.text.size = size;
739
- return this;
740
- }
741
-
742
- Disable(disabled = true) {
743
- this.isDisabled = disabled;
744
- return this;
793
+ MALCgameObjects[this.objectInstance] = this;
745
794
  }
746
795
 
747
- click(call) {
748
- if(typeof call == "function" && this.events.pressed()){
749
- call();
750
- }
751
- return this.events.clicked();
752
- }
753
- }
754
-
755
- // ========== SCENE CLASS ==========
756
- class Scene {
757
- static scenes = [];
758
- static activeScene = "blank";
759
- static started = false;
760
- static sceneHistory = [];
761
- static historyLimit = 10;
762
-
763
- static update() {
764
- this.started = true;
765
- this.scenes = MALCScene;
796
+ render() {
797
+ if (!this.active) return;
766
798
 
767
- let activeSceneFound = false;
799
+ this.scripts.forEach(s => {
800
+ if(typeof s == "function")s(this);
801
+ });
768
802
 
769
- this.scenes.forEach(S => {
770
- if (S.id == this.activeScene) {
771
- activeSceneFound = true;
772
-
773
- this.scenes.forEach(s => {
774
- if (s != S) {
775
- s._active = false;
776
- s.active = false;
777
-
778
- s.objects.forEach(o => {
779
- if (o && typeof o.active !== 'undefined') o.active = false;
780
- });
781
- }
782
- });
783
-
784
- S.active = true;
785
-
786
- if (!S._active) {
787
- S.activated = MALC.time.getTime();
788
- if (typeof S.onActivate == 'function') S.onActivate();
789
- }
790
-
791
- S._active = true;
792
- S.timeActive = MALC.time.getTime() - S.activated;
793
-
794
- S.objects.forEach(o => {
795
- if (o && typeof o.active !== 'undefined') o.active = true;
796
- });
797
-
798
- push();
799
- if (window.camera && typeof camera.render == 'function') {
800
- camera.render();
801
- }
802
-
803
- S.render();
804
-
805
- pop();
803
+ let outline = this.formatting.outline;
804
+ let hb = this.hitbox;
805
+
806
+ // Draw debug hitbox if enabled
807
+ if (this.debug) {
808
+ _p5.prototype.push();
809
+ _p5.prototype.translate(this.x, this.y);
810
+ _p5.prototype.rectMode(_p5.prototype.CENTER);
811
+ if (this.rotationMode == "degrees") _p5.prototype.angleMode(_p5.prototype.DEGREES);
812
+ _p5.prototype.rotate(this.rotation + hb.rotation);
813
+
814
+ _p5.prototype.stroke("#00FF27");
815
+ _p5.prototype.strokeWeight(hb.outline);
816
+ _p5.prototype.noFill();
817
+ _p5.prototype.rect(hb.x, hb.y, this.width + hb.width, this.height + hb.height);
818
+
819
+ // Draw gravity indicator if enabled
820
+ if (this.gravity.enabled) {
821
+ _p5.prototype.stroke(0, 255, 0, 100);
822
+ _p5.prototype.line(0, 0, 0, this.gravity.velocity * 5);
806
823
  }
807
- });
824
+
825
+ _p5.prototype.pop();
826
+ }
808
827
 
809
- if (!activeSceneFound && this.activeScene != "blank") {
810
- console.warn(`Scene "${this.activeScene}" not found, switching to blank`);
811
- this.activeScene = "blank";
828
+ if (!this.visible) return;
829
+
830
+ _p5.prototype.push();
831
+ _p5.prototype.translate(this.x, this.y);
832
+ _p5.prototype.rectMode(_p5.prototype.CENTER);
833
+ if (this.rotationMode == "degrees") _p5.prototype.angleMode(_p5.prototype.DEGREES);
834
+ _p5.prototype.rotate(this.rotation);
835
+
836
+ if (outline[0]) {
837
+ _p5.prototype.strokeWeight(outline[1]);
838
+ _p5.prototype.stroke(outline[2]);
839
+ } else {
840
+ _p5.prototype.noStroke();
812
841
  }
842
+
843
+ _p5.prototype.fill(this.formatting.color);
844
+ _p5.prototype.rect(0, 0, this.width, this.height);
845
+ _p5.prototype.pop();
813
846
  }
847
+
848
+ // ========== HELPFUL METHODS ==========
814
849
 
815
- static getSceneById(id) {
816
- return MALCScene.find(scene => scene.id == id) || null;
817
- }
818
-
819
- static getActiveScene() {
820
- return this.getSceneById(this.activeScene);
850
+ belongsToScene(sceneId) {
851
+ return this.scenes.includes(sceneId);
821
852
  }
822
853
 
823
- static switchToScene(id, addToHistory = true) {
824
- let scene = this.getSceneById(id);
825
- if (scene) {
826
- if (addToHistory && this.activeScene) {
827
- this.sceneHistory.push(this.activeScene);
828
- if (this.sceneHistory.length > this.historyLimit) {
829
- this.sceneHistory.shift();
830
- }
854
+ addToScene(sceneId) {
855
+ if (!this.scenes.includes(sceneId)) {
856
+ this.scenes.push(sceneId);
857
+ let scene = Scene.getSceneById(sceneId);
858
+ if (scene && !scene.objects.includes(this)) {
859
+ scene.objects.push(this);
831
860
  }
832
- this.activeScene = id;
833
- } else {
834
- console.error(`Cannot switch to scene "${id}" - not found`);
835
861
  }
862
+ return this;
836
863
  }
837
864
 
838
- static goBack() {
839
- if (this.sceneHistory.length > 0) {
840
- let previousScene = this.sceneHistory.pop();
841
- this.switchToScene(previousScene, false);
842
- return true;
865
+ removeFromScene(sceneId) {
866
+ this.scenes = this.scenes.filter(id => id != sceneId);
867
+ let scene = Scene.getSceneById(sceneId);
868
+ if (scene) {
869
+ scene.objects = scene.objects.filter(obj => obj != this);
843
870
  }
844
- return false;
845
- }
846
-
847
- static getAllScenes() {
848
- return [...MALCScene];
849
- }
850
-
851
- static getScenesWithObject(object) {
852
- return MALCScene.filter(scene => scene.objects.includes(object));
871
+ return this;
853
872
  }
854
873
 
855
- static getScenesByTag(tag) {
856
- return MALCScene.filter(scene => scene.hasTag(tag));
857
- }
858
-
859
- constructor(id, backgroundColor, ...scripts) {
860
- MALCScene.forEach(s => {
861
- if (s.id == id || typeof id != "string") {
862
- throw new Error(`Scenes must have unique IDs and be strings. Duplicate/Invalid ID: "${id}"`);
874
+ removeFromAllScenes() {
875
+ this.scenes.forEach(sceneId => {
876
+ let scene = Scene.getSceneById(sceneId);
877
+ if (scene) {
878
+ scene.objects = scene.objects.filter(obj => obj != this);
863
879
  }
864
880
  });
865
-
866
- this.id = id;
867
- this.backColor = backgroundColor;
868
- this.scripts = scripts;
869
-
870
- this.objects = [];
871
- this.uiPlanes = [];
872
-
873
- this.active = false;
874
- this._active = false;
875
- this.activated = 0;
876
- this.timeActive = -1;
877
-
878
- this.tags = [];
879
- this.paused = false;
880
- this.transition = null;
881
- this.onActivateCallbacks = [];
882
- this.onDeactivateCallbacks = [];
883
- this.onUpdateCallbacks = [];
884
-
885
- this.sceneInstance = MALCScene.length;
886
- MALCScene.push(this);
881
+ this.scenes = [];
882
+ return this;
887
883
  }
888
884
 
889
- render() {
890
- if (this.paused) return;
891
-
892
- if (this.transition) {
893
- this.applyTransition();
894
- }
895
-
896
- background(this.backColor);
897
-
898
- this.scripts.forEach(exe => {
899
- if (typeof exe == "function") exe(this);
900
- });
901
-
902
- this.onUpdateCallbacks.forEach(cb => {
903
- if (typeof cb == "function") cb(this);
904
- });
905
-
906
- this.objects.forEach(o => {
907
- if (o && typeof o.update == "function") {
908
- o.update(true);
909
- }
910
- if (o && typeof o.render == "function") {
911
- o.render();
912
- }
913
- });
914
-
915
- if (typeof UIPlanes !== 'undefined' && UIPlanes.length > 0) {
916
- UIPlanes.forEach(ui => {
917
- if (ui && typeof ui.belongsToScene == "function" && ui.belongsToScene(this.id)) {
918
- ui.render();
919
- }
920
- });
921
- }
922
-
923
- this.uiPlanes.forEach(ui => {
924
- if (ui && typeof ui.render == "function") {
925
- ui.render();
885
+ updateParentScene() {
886
+ if (Scene.activeScene) {
887
+ let activeScene = Scene.getSceneById(Scene.activeScene);
888
+ if (activeScene && this.belongsToScene(activeScene.id)) {
889
+ this.parentScene = activeScene;
926
890
  }
927
- });
928
-
929
- MALCScene[this.sceneInstance] = this;
891
+ }
930
892
  }
931
893
 
932
- applyTransition() {
933
- if (!this.transition || !this.transition.active) return;
934
-
935
- this.transition.progress += 1/60;
894
+ distanceTo(target) {
895
+ let dx = target.x !== undefined ? target.x - this.x : target[0] - this.x;
896
+ let dy = target.y !== undefined ? target.y - this.y : target[1] - this.y;
897
+ return Math.sqrt(dx * dx + dy * dy);
898
+ }
899
+
900
+ collidesWith(other) {
901
+ return (this.x < other.x + other.width &&
902
+ this.x + this.width > other.x &&
903
+ this.y < other.y + other.height &&
904
+ this.y + this.height > other.y);
905
+ }
906
+
907
+ directionTo(x, y, err = 0.5) {
908
+ let pa = [x - this.x, y - this.y];
936
909
 
937
- if (this.transition.progress >= this.transition.duration) {
938
- this.transition.active = false;
939
- this.transition = null;
940
- return;
941
- }
910
+ let angle = (x && y) ? _p5.prototype.atan(pa[1]/pa[0]) : this.rotation;
911
+ var quads = [
912
+ pa[0] < -err && pa[1] > err,
913
+ pa[0] < -err && pa[1] < -err,
914
+ pa[0] > err && pa[1] > err,
915
+ pa[0] > err && pa[1] < -err,
916
+ (pa[0] < err && pa[0] > -err),
917
+ (pa[1] < err && pa[1] > -err),
918
+ ];
942
919
 
943
- let t = this.transition.progress / this.transition.duration;
920
+ let da = (_p5.prototype.atan(pa[1]/pa[0]) * 180)/Math.PI;
944
921
 
945
- push();
946
- switch(this.transition.type) {
947
- case "fade":
948
- fill(0, 255 * (1 - t));
949
- rect(0, 0, width, height);
950
- break;
951
- case "slide":
952
- translate(width * (1 - t), 0);
953
- break;
954
- }
955
- pop();
956
- }
957
-
958
- addObject(object) {
959
- if (object && !this.objects.includes(object)) {
960
- this.objects.push(object);
961
- if (typeof object.addToScene == "function") {
962
- object.addToScene(this.id);
922
+ if((pa[1] > -err && pa[1] < err) && (pa[0] > -err && pa[0] < err)){
923
+ angle = NaN;
924
+ } else if(quads[0]){
925
+ angle = da+180;
926
+ } else if(quads[1]){
927
+ angle = da+180;
928
+ } else if(quads[2]){
929
+ angle = da;
930
+ } else if(quads[3]){
931
+ angle = da;
932
+ } else if(quads[4]){
933
+ if(pa[1] < -err) {
934
+ angle = -90;
935
+ } else if(pa[1] > err) {
936
+ angle = 90;
937
+ }
938
+ } else if(quads[5]){
939
+ if(pa[0] < -err) {
940
+ angle = 180;
941
+ } else if(pa[0] > err) {
942
+ angle = 0;
963
943
  }
964
944
  }
965
- return this;
945
+
946
+ return angle;
966
947
  }
967
948
 
968
- addObjects(objects) {
969
- objects.forEach(obj => this.addObject(obj));
949
+ pointTo(target) {
950
+ this.rotation = this.directionTo(target);
970
951
  return this;
971
952
  }
972
953
 
973
- removeObject(object) {
974
- this.objects = this.objects.filter(obj => obj != object);
975
- if (object && typeof object.removeFromScene == "function") {
976
- object.removeFromScene(this.id);
977
- }
954
+ setPosition(x, y) {
955
+ this.x = x;
956
+ this.y = y;
978
957
  return this;
979
958
  }
980
959
 
981
- clearObjects() {
982
- this.objects.forEach(obj => {
983
- if (obj && typeof obj.removeFromScene == "function") {
984
- obj.removeFromScene(this.id);
985
- }
986
- });
987
- this.objects = [];
960
+ move(dx, dy) {
961
+ this.x += dx;
962
+ this.y += dy;
988
963
  return this;
989
964
  }
990
965
 
991
- getObjects(filter) {
992
- if (typeof filter == "function") {
993
- return this.objects.filter(filter);
994
- } else if (filter == "button") {
995
- return this.objects.filter(obj => obj instanceof Button);
996
- } else if (filter == "gameObject") {
997
- return this.objects.filter(obj => obj instanceof gameObject);
966
+ setVelocity(speed, x, y, err = 0.5){
967
+ let angle = this.directionTo(x,y,err);
968
+
969
+ if(isNaN(angle)){
970
+ angle = 0;
971
+ speed = 0;
998
972
  }
999
- return this.objects;
973
+
974
+ this.velocity = [speed, angle];
975
+ return this.velocity;
1000
976
  }
1001
977
 
1002
- getObjectById(id) {
1003
- return this.objects.find(obj => obj && obj.id == id);
978
+ destroy() {
979
+ this.removeFromAllScenes();
980
+ let index = MALCgameObjects.indexOf(this);
981
+ if (index > -1) {
982
+ MALCgameObjects.splice(index, 1);
983
+ }
1004
984
  }
1005
985
 
1006
- addUIPlane(uiPlane) {
1007
- if (uiPlane && !this.uiPlanes.includes(uiPlane)) {
1008
- this.uiPlanes.push(uiPlane);
1009
- if (typeof uiPlane.addToScene == "function") {
1010
- uiPlane.addToScene(this.id);
1011
- }
986
+ clone() {
987
+ let clone = new gameObject(this.x, this.y, this.width, this.height, ...this.scenes);
988
+ clone.rotation = this.rotation;
989
+ clone.rotationMode = this.rotationMode;
990
+ clone.velocity = [...this.velocity];
991
+ clone.velocityMode = this.velocityMode;
992
+ clone.rvm = this.rvm;
993
+ clone.formatting = JSON.parse(JSON.stringify(this.formatting));
994
+ clone.gravity = JSON.parse(JSON.stringify(this.gravity));
995
+ clone.debug = this.debug;
996
+ clone.hitbox = JSON.parse(JSON.stringify(this.hitbox));
997
+ return clone;
998
+ }
999
+
1000
+ screenToWorld(screenX, screenY) {
1001
+ if (window.camera && typeof window.camera.screenToWorld == "function") {
1002
+ return window.camera.screenToWorld(screenX, screenY);
1012
1003
  }
1013
- return this;
1004
+ return { x: screenX, y: screenY };
1014
1005
  }
1015
1006
 
1016
- removeUIPlane(uiPlane) {
1017
- this.uiPlanes = this.uiPlanes.filter(ui => ui != uiPlane);
1018
- if (uiPlane && typeof uiPlane.removeFromScene == "function") {
1019
- uiPlane.removeFromScene(this.id);
1020
- }
1021
- return this;
1007
+ isOnScreen() {
1008
+ if (!window.camera) return true;
1009
+
1010
+ let cameraPos = window.camera.getOrientation();
1011
+ let screenRight = cameraPos[0] + window.camera.width;
1012
+ let screenBottom = cameraPos[1] + window.camera.height;
1013
+
1014
+ return (this.x + this.width/2 > cameraPos[0] &&
1015
+ this.x - this.width/2 < screenRight &&
1016
+ this.y + this.height/2 > cameraPos[1] &&
1017
+ this.y - this.height/2 < screenBottom);
1022
1018
  }
1019
+ }
1020
+
1021
+ // ========== BUTTON CLASS ==========
1022
+ class Button extends gameObject {
1023
+ static buttons = [];
1023
1024
 
1024
- clearUIPlanes() {
1025
- this.uiPlanes.forEach(ui => {
1026
- if (ui && typeof ui.removeFromScene == "function") {
1027
- ui.removeFromScene(this.id);
1025
+ static updateButton() {
1026
+ this.startedButtons = true;
1027
+ this.buttons = MALCbuttons;
1028
+
1029
+ this.buttons.forEach(b => {
1030
+ if (!b.active) return;
1031
+
1032
+ b.isHovered = b.events.hover();
1033
+
1034
+ if (MALCbuttons.every(b => !b.events.hover())) {
1035
+ _p5.prototype.cursor();
1036
+ } else if (b.isHovered) {
1037
+ _p5.prototype.cursor(b.cursor);
1028
1038
  }
1029
1039
  });
1030
- this.uiPlanes = [];
1031
- return this;
1032
1040
  }
1033
1041
 
1034
- addScript(script) {
1035
- if (typeof script == "function" && !this.scripts.includes(script)) {
1036
- this.scripts.push(script);
1037
- }
1038
- return this;
1042
+ static getButtonByIndex(index) {
1043
+ return MALCbuttons[index] || null;
1039
1044
  }
1040
1045
 
1041
- removeScript(script) {
1042
- this.scripts = this.scripts.filter(s => s != script);
1043
- return this;
1046
+ static getHoveredButton() {
1047
+ return MALCbuttons.find(b => b.active && b.events.hover());
1044
1048
  }
1045
1049
 
1046
- clearScripts() {
1047
- this.scripts = [];
1048
- return this;
1050
+ static getPressedButton() {
1051
+ return MALCbuttons.find(b => b.active && b.events.pressed());
1049
1052
  }
1050
1053
 
1051
- onActivate(callback) {
1052
- if (typeof callback == "function") {
1053
- this.onActivateCallbacks.push(callback);
1054
+ constructor(x = 0, y = 0, w = 20, h = 20, displayText = "Button", ...scenes) {
1055
+ super(x, y, w, h, ...scenes);
1056
+
1057
+ this.formatting.button = {
1058
+ hover: 220,
1059
+ clicked: 190,
1060
+ text: {
1061
+ color: 0,
1062
+ size: 14,
1063
+ style:"normal",
1064
+ display: displayText,
1065
+ },
1066
+ colors: {
1067
+ normal: 255,
1068
+ hover: 220,
1069
+ pressed: 190,
1070
+ disabled: 150
1071
+ }
1072
+ };
1073
+
1074
+ this.cursor = "pointer";
1075
+ this.isHovered = false;
1076
+ this.isPressed = false;
1077
+ this.isDisabled = false;
1078
+ this.clickCooldown = 100;
1079
+ this.lastClickTime = 0;
1080
+ this.cooldownActive = false;
1081
+
1082
+ this.events = {
1083
+ hover: (err = 0) => {
1084
+ return !this.isDisabled && (
1085
+ window.mouse.x < this.x + this.width / 2 + err &&
1086
+ window.mouse.x > this.x - (this.width / 2 + err) &&
1087
+ window.mouse.y < this.y + this.height / 2 + err &&
1088
+ window.mouse.y > this.y - (this.height / 2 + err)
1089
+ );
1090
+ },
1091
+ pressed: () => {
1092
+ return this.events.hover() && window.mouse.down;
1093
+ },
1094
+ clicked: () => {
1095
+ let wasPressed = this.wasPressed;
1096
+ let isHovering = this.events.hover();
1097
+ let mouseReleased = !window.mouse.down && wasPressed;
1098
+
1099
+ this.wasPressed = window.mouse.down && isHovering;
1100
+
1101
+ return mouseReleased && isHovering;
1102
+ }
1103
+ };
1104
+
1105
+ this.wasPressed = false;
1106
+ this.onClick = null;
1107
+
1108
+ this.buttonIndex = MALCbuttons.length;
1109
+ MALCbuttons.push(this);
1110
+ }
1111
+
1112
+ update(boolean) {
1113
+ super.update(boolean);
1114
+
1115
+ if (this.cooldownActive) {
1116
+ let currentTime = Date.now();
1117
+ if (currentTime - this.lastClickTime >= this.clickCooldown) {
1118
+ this.cooldownActive = false;
1119
+ }
1054
1120
  }
1055
- return this;
1121
+
1122
+ this.isHovered = this.events.hover();
1123
+ this.isPressed = this.events.pressed();
1124
+
1125
+ if (this.events.clicked() && this.onClick && !this.isDisabled && !this.cooldownActive) {
1126
+ this.onClick(this);
1127
+ this.lastClickTime = Date.now();
1128
+ this.cooldownActive = true;
1129
+ }
1130
+
1131
+ MALCbuttons[this.buttonIndex] = this;
1056
1132
  }
1057
-
1058
- onDeactivate(callback) {
1059
- if (typeof callback == "function") {
1060
- this.onDeactivateCallbacks.push(callback);
1133
+
1134
+ render() {
1135
+ if (!this.active) return;
1136
+
1137
+ let btnFormat = this.formatting.button;
1138
+ let buttonColor;
1139
+
1140
+ if (this.isDisabled) {
1141
+ buttonColor = btnFormat.colors.disabled;
1142
+ } else if (this.isPressed) {
1143
+ buttonColor = btnFormat.colors.pressed;
1144
+ } else if (this.isHovered) {
1145
+ buttonColor = btnFormat.colors.hover;
1146
+ } else {
1147
+ buttonColor = btnFormat.colors.normal;
1061
1148
  }
1062
- return this;
1149
+
1150
+ let originalColor = this.formatting.color;
1151
+ this.formatting.color = buttonColor;
1152
+
1153
+ super.render();
1154
+
1155
+ if(!this.visible) return;
1156
+
1157
+ _p5.prototype.push();
1158
+ _p5.prototype.translate(this.x, this.y);
1159
+ if (this.rotationMode == "degrees") _p5.prototype.angleMode(_p5.prototype.DEGREES);
1160
+ _p5.prototype.rotate(this.rotation);
1161
+
1162
+ _p5.prototype.textStyle(btnFormat.text.style);
1163
+ _p5.prototype.textSize(btnFormat.text.size);
1164
+ _p5.prototype.fill(btnFormat.text.color);
1165
+
1166
+ // Use colored text if available, otherwise use normal text
1167
+ if (_p5.prototype.coloredText) {
1168
+ _p5.prototype.coloredText(btnFormat.text.display, 0, 0, _p5.prototype.CENTER, _p5.prototype.CENTER);
1169
+ } else {
1170
+ _p5.prototype.text(btnFormat.text.display, 0, 0);
1171
+ }
1172
+
1173
+ _p5.prototype.pop();
1174
+
1175
+ this.formatting.color = originalColor;
1063
1176
  }
1064
1177
 
1065
- onUpdate(callback) {
1066
- if (typeof callback == "function") {
1067
- this.onUpdateCallbacks.push(callback);
1068
- }
1178
+ // ========== BUTTON-SPECIFIC HELPER METHODS ==========
1179
+
1180
+ setText(text) {
1181
+ this.formatting.button.text.display = text;
1069
1182
  return this;
1070
1183
  }
1071
1184
 
1072
- pause() {
1073
- this.paused = true;
1074
- return this;
1185
+ getRGBFromColor(colorInput) {
1186
+ let c = _p5.prototype.color(colorInput);
1187
+ return [_p5.prototype.red(c), _p5.prototype.green(c), _p5.prototype.blue(c)];
1075
1188
  }
1076
1189
 
1077
- resume() {
1078
- this.paused = false;
1079
- return this;
1190
+ getBrightness(colorInput) {
1191
+ let rgb = this.getRGBFromColor(colorInput);
1192
+ return 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
1080
1193
  }
1081
1194
 
1082
- setTransition(type, duration = 1.0) {
1083
- this.transition = {
1084
- type: type,
1085
- duration: duration,
1086
- progress: 0,
1087
- active: true
1088
- };
1089
- return this;
1195
+ scaleColor(baseColor, scaleFactor) {
1196
+ let rgb = this.getRGBFromColor(baseColor);
1197
+ let scaledRGB = rgb.map(val => _p5.prototype.constrain(val * scaleFactor, 0, 255));
1198
+ return _p5.prototype.color(scaledRGB);
1090
1199
  }
1091
1200
 
1092
- addTag(tag) {
1093
- if (!this.tags.includes(tag)) {
1094
- this.tags.push(tag);
1201
+ setColors(normal, hover = null, pressed = null, disabled = null) {
1202
+ if (hover === null && pressed === null) {
1203
+ let originalNormal = this.formatting.button.colors.normal;
1204
+ let originalHover = this.formatting.button.colors.hover;
1205
+ let originalPressed = this.formatting.button.colors.pressed;
1206
+
1207
+ let normalBrightness = this.getBrightness(originalNormal);
1208
+ let hoverBrightness = this.getBrightness(originalHover);
1209
+ let pressedBrightness = this.getBrightness(originalPressed);
1210
+
1211
+ let hoverScale = normalBrightness !== 0 ? hoverBrightness / normalBrightness : 0.86;
1212
+ let pressedScale = normalBrightness !== 0 ? pressedBrightness / normalBrightness : 0.75;
1213
+
1214
+ hover = this.scaleColor(normal, hoverScale);
1215
+ pressed = this.scaleColor(normal, pressedScale);
1216
+ } else {
1217
+ hover = _p5.prototype.color(hover);
1218
+ pressed = _p5.prototype.color(pressed);
1219
+ }
1220
+
1221
+ let normalColor = _p5.prototype.color(normal);
1222
+ let disabledColor = disabled !== null ? _p5.prototype.color(disabled) : null;
1223
+
1224
+ this.formatting.button.colors.normal = normalColor;
1225
+ this.formatting.button.colors.hover = hover;
1226
+ this.formatting.button.colors.pressed = pressed;
1227
+ if (disabledColor !== null) {
1228
+ this.formatting.button.colors.disabled = disabledColor;
1095
1229
  }
1230
+
1096
1231
  return this;
1097
1232
  }
1098
1233
 
1099
- removeTag(tag) {
1100
- this.tags = this.tags.filter(t => t != tag);
1234
+ textStyle(color, size) {
1235
+ if (color !== undefined) this.formatting.button.text.color = color;
1236
+ if (size !== undefined) this.formatting.button.text.size = size;
1101
1237
  return this;
1102
1238
  }
1103
1239
 
1104
- hasTag(tag) {
1105
- return this.tags.includes(tag);
1106
- }
1107
-
1108
- reset() {
1109
- this.clearObjects();
1110
- this.clearUIPlanes();
1111
- this.clearScripts();
1112
- this.onActivateCallbacks = [];
1113
- this.onDeactivateCallbacks = [];
1114
- this.onUpdateCallbacks = [];
1115
- this.tags = [];
1116
- this.paused = false;
1117
- this.transition = null;
1118
- this.timeActive = 0;
1240
+ Disable(disabled = true) {
1241
+ this.isDisabled = disabled;
1119
1242
  return this;
1120
1243
  }
1121
1244
 
1122
- destroy() {
1123
- let index = MALCScene.indexOf(this);
1124
- if (index > -1) {
1125
- MALCScene.splice(index, 1);
1126
- }
1127
-
1128
- this.clearObjects();
1129
- this.clearUIPlanes();
1130
-
1131
- if (Scene.activeScene == this.id) {
1132
- Scene.activeScene = "blank";
1245
+ click(call) {
1246
+ if(typeof call == "function" && this.events.pressed()){
1247
+ call();
1133
1248
  }
1134
- }
1135
-
1136
- clone(newId) {
1137
- let clone = new Scene(newId || this.id + "_copy", this.backColor, ...this.scripts);
1138
- clone.objects = [...this.objects];
1139
- clone.uiPlanes = [...this.uiPlanes];
1140
- clone.tags = [...this.tags];
1141
- return clone;
1142
- }
1143
-
1144
- getInfo() {
1145
- return {
1146
- id: this.id,
1147
- active: this.active,
1148
- timeActive: this.timeActive,
1149
- objectCount: this.objects.length,
1150
- uiPlaneCount: this.uiPlanes.length,
1151
- scriptCount: this.scripts.length,
1152
- tags: this.tags,
1153
- paused: this.paused
1154
- };
1249
+ return this.events.clicked();
1155
1250
  }
1156
1251
  }
1157
1252
 
@@ -1351,20 +1446,20 @@ class UIPlane {
1351
1446
  }
1352
1447
 
1353
1448
  render() {
1354
- push();
1449
+ _p5.prototype.push();
1355
1450
 
1356
1451
  this.applyOrientation();
1357
1452
  this.applyTextFormatting();
1358
1453
 
1359
1454
  if (this.formatting.objectScale !== 1) {
1360
- scale(this.formatting.objectScale);
1455
+ _p5.prototype.scale(this.formatting.objectScale);
1361
1456
  }
1362
1457
 
1363
1458
  if (typeof this.executable == "function") {
1364
1459
  this.executable(this);
1365
1460
  }
1366
1461
 
1367
- pop();
1462
+ _p5.prototype.pop();
1368
1463
  }
1369
1464
 
1370
1465
  applyOrientation() {
@@ -1378,21 +1473,21 @@ class UIPlane {
1378
1473
  } else {
1379
1474
  cameraPos = [window.camera.x || 0, window.camera.y || 0];
1380
1475
  }
1381
- translate(cameraPos[0] + offsetX, cameraPos[1] + offsetY);
1476
+ _p5.prototype.translate(cameraPos[0] + offsetX, cameraPos[1] + offsetY);
1382
1477
  } else {
1383
- translate(offsetX, offsetY);
1478
+ _p5.prototype.translate(offsetX, offsetY);
1384
1479
  }
1385
1480
  } else if (mode.toLowerCase() == "screen") {
1386
- translate(offsetX, offsetY);
1481
+ _p5.prototype.translate(offsetX, offsetY);
1387
1482
  } else if (mode.includes(",")) {
1388
1483
  try {
1389
1484
  let coords = mode.split(",").map(Number);
1390
1485
  if (coords.length >= 2) {
1391
1486
  if (window.camera && typeof window.camera.worldToScreen == "function") {
1392
1487
  let screenPos = window.camera.worldToScreen(coords[0], coords[1]);
1393
- translate(screenPos.x + offsetX, screenPos.y + offsetY);
1488
+ _p5.prototype.translate(screenPos.x + offsetX, screenPos.y + offsetY);
1394
1489
  } else {
1395
- translate(coords[0] + offsetX, coords[1] + offsetY);
1490
+ _p5.prototype.translate(coords[0] + offsetX, coords[1] + offsetY);
1396
1491
  }
1397
1492
  }
1398
1493
  } catch (e) {
@@ -1402,46 +1497,46 @@ class UIPlane {
1402
1497
  }
1403
1498
 
1404
1499
  applyTextFormatting() {
1405
- textSize(this.formatting.txt.base);
1406
- fill(this.formatting.txt.color);
1407
- textAlign(LEFT, TOP);
1500
+ _p5.prototype.textSize(this.formatting.txt.base);
1501
+ _p5.prototype.fill(this.formatting.txt.color);
1502
+ _p5.prototype.textAlign(_p5.prototype.LEFT, _p5.prototype.TOP);
1408
1503
  }
1409
1504
 
1410
1505
  drawText(str, x, y, hAlign = LEFT, vAlign = TOP) {
1411
- push();
1506
+ _p5.prototype.push();
1412
1507
 
1413
1508
  if (str.startsWith("[title]")) {
1414
- textSize(this.formatting.txt.title);
1509
+ _p5.prototype.textSize(this.formatting.txt.title);
1415
1510
  str = str.replace("[title]", "");
1416
1511
  } else if (str.startsWith("[heading]")) {
1417
- textSize(this.formatting.txt.heading);
1512
+ _p5.prototype.textSize(this.formatting.txt.heading);
1418
1513
  str = str.replace("[heading]", "");
1419
1514
  } else if (str.startsWith("[subtitle]")) {
1420
- textSize(this.formatting.txt.subtitle);
1515
+ _p5.prototype.textSize(this.formatting.txt.subtitle);
1421
1516
  str = str.replace("[subtitle]", "");
1422
1517
  } else {
1423
- textSize(this.formatting.txt.base);
1518
+ _p5.prototype.textSize(this.formatting.txt.base);
1424
1519
  }
1425
1520
 
1426
- fill(this.formatting.txt.color);
1427
- textAlign(hAlign, vAlign);
1428
- text(str, x, y);
1521
+ _p5.prototype.fill(this.formatting.txt.color);
1522
+ _p5.prototype.textAlign(hAlign, vAlign);
1523
+ _p5.prototype.text(str, x, y);
1429
1524
 
1430
- pop();
1525
+ _p5.prototype.pop();
1431
1526
  }
1432
1527
 
1433
1528
  drawButton(button, x, y) {
1434
- push();
1529
+ _p5.prototype.push();
1435
1530
 
1436
1531
  if (this.formatting.objectScale !== 1) {
1437
- scale(this.formatting.objectScale);
1532
+ _p5.prototype.scale(this.formatting.objectScale);
1438
1533
  }
1439
1534
 
1440
1535
  if (button && typeof button.render == "function") {
1441
1536
  button.render();
1442
1537
  }
1443
1538
 
1444
- pop();
1539
+ _p5.prototype.pop();
1445
1540
  }
1446
1541
 
1447
1542
  belongsToScene(sceneId) {
@@ -1591,7 +1686,7 @@ class Camera {
1591
1686
  }
1592
1687
 
1593
1688
  let [translateX, translateY] = this.getOrientation();
1594
- translate(-translateX, -translateY);
1689
+ _p5.prototype.translate(-translateX, -translateY);
1595
1690
  }
1596
1691
 
1597
1692
  unlink() {
@@ -2130,14 +2225,14 @@ class GameController {
2130
2225
  back: null,
2131
2226
  primary: null,
2132
2227
  secondary: null,
2133
- leftbumber: null, // Fixed: changed from leftBumber
2134
- rightbumber: null, // Fixed: changed from rightBumber
2135
- lefttrigger: null, // Fixed: changed from leftTrigger
2136
- righttrigger: null, // Fixed: changed from rightTrigger
2228
+ leftbumber: null,
2229
+ rightbumber: null,
2230
+ lefttrigger: null,
2231
+ righttrigger: null,
2137
2232
  view: null,
2138
2233
  menu: null,
2139
- leftstick: null, // Fixed: changed from leftStick
2140
- rightstick: null, // Fixed: changed from rightStick
2234
+ leftstick: null,
2235
+ rightstick: null,
2141
2236
  up: null,
2142
2237
  down: null,
2143
2238
  left: null,
@@ -2262,100 +2357,6 @@ function refreshLoop() {
2262
2357
  });
2263
2358
  }
2264
2359
 
2265
- // ========== COLORED TEXT FUNCTION ==========
2266
- p5.prototype._parseColoredText = function(str) {
2267
- const lines = str.split('\n');
2268
- const result = [];
2269
-
2270
- for (let i = 0; i < lines.length; i++) {
2271
- const line = lines[i];
2272
- const parts = this._parseColoredLine(line);
2273
-
2274
- result.push(...parts);
2275
-
2276
- if (i < lines.length - 1) {
2277
- result.push({
2278
- text: '\n',
2279
- color: null,
2280
- isNewline: true
2281
- });
2282
- }
2283
- }
2284
-
2285
- return result;
2286
- };
2287
-
2288
- p5.prototype._parseColoredLine = function(str) {
2289
- const regex = /\\([^|\\\n]+)\|([^|]+)\|/g;
2290
- const parts = [];
2291
- let lastIndex = 0;
2292
- let match;
2293
-
2294
- while ((match = regex.exec(str)) !== null) {
2295
- if (match.index > lastIndex) {
2296
- parts.push({
2297
- text: str.substring(lastIndex, match.index),
2298
- color: null
2299
- });
2300
- }
2301
-
2302
- parts.push({
2303
- text: match[2],
2304
- color: match[1]
2305
- });
2306
-
2307
- lastIndex = match.index + match[0].length;
2308
- }
2309
-
2310
- if (lastIndex < str.length) {
2311
- parts.push({
2312
- text: str.substring(lastIndex),
2313
- color: null
2314
- });
2315
- }
2316
-
2317
- return parts.length ? parts : [{ text: str, color: null }];
2318
- };
2319
-
2320
- p5.prototype.coloredText = function(str, x, y, horizontal = LEFT, vertical = BASELINE, maxWidth) {
2321
- const parts = this._parseColoredText(str);
2322
- let currentX = x;
2323
- let currentY = y;
2324
-
2325
- const originalFill = this.drawingContext.fillStyle;
2326
- const originalAlign = this.drawingContext.textAlign;
2327
- const originalBaseline = this.drawingContext.textBaseline;
2328
-
2329
- this.textAlign(horizontal, vertical);
2330
-
2331
- for (const part of parts) {
2332
- if (part.isNewline) {
2333
- currentX = x;
2334
- currentY += this.textLeading() || this.textSize() * 1.2;
2335
- continue;
2336
- }
2337
-
2338
- if (part.color) {
2339
- try {
2340
- this.fill(part.color);
2341
- } catch (e) {
2342
- this.fill(originalFill);
2343
- }
2344
- }
2345
-
2346
- this.text(part.text, currentX, currentY, maxWidth);
2347
- currentX += this.textWidth(part.text);
2348
- }
2349
-
2350
- this.fill(originalFill);
2351
- if (originalAlign && originalBaseline) {
2352
- this.drawingContext.textAlign = originalAlign;
2353
- this.drawingContext.textBaseline = originalBaseline;
2354
- }
2355
-
2356
- return this;
2357
- };
2358
-
2359
2360
  // ========== FPS ACCESSOR ==========
2360
2361
  function getFPS() {
2361
2362
  return fps;
@@ -2366,7 +2367,7 @@ const helpDocs = {
2366
2367
  // Game Engine Overview
2367
2368
  overview: `
2368
2369
  MALC Game Engine - A comprehensive 2D game engine for p5.js
2369
- Version: 1.0.0
2370
+ Version: 1.0.3
2370
2371
 
2371
2372
  Core Features:
2372
2373
  - Scene management system
@@ -2520,8 +2521,8 @@ const helpDocs = {
2520
2521
  isButtonPressed: "Check button by index",
2521
2522
  getButtonValue: "Get analog button value"
2522
2523
  },
2523
- buttonNames: ["select", "back", "primary", "secondary", "leftBumber", "rightBumber",
2524
- "leftTrigger", "rightTrigger", "view", "menu", "leftStick", "rightStick",
2524
+ buttonNames: ["select", "back", "primary", "secondary", "leftbumber", "rightbumber",
2525
+ "lefttrigger", "righttrigger", "view", "menu", "leftstick", "rightstick",
2525
2526
  "up", "down", "left", "right", "home"]
2526
2527
  }
2527
2528
  },
@@ -2542,10 +2543,10 @@ const helpDocs = {
2542
2543
  }
2543
2544
 
2544
2545
  // 2. Create a scene
2545
- let gameScene = new Scene("game", 220);
2546
+ let gameScene = new MALC.Scene("game", 220);
2546
2547
 
2547
2548
  // 3. Create a game object with gravity
2548
- let player = new gameObject(100, 100, 50, 50, "game")
2549
+ let player = new MALC.gameObject(100, 100, 50, 50, "game")
2549
2550
  .enableGravity()
2550
2551
  .setGravity({ mass: 1, bounce: 0.3 });
2551
2552
 
@@ -2558,7 +2559,7 @@ const helpDocs = {
2558
2559
 
2559
2560
  // ========== MALC MAIN OBJECT ==========
2560
2561
  const MALC = {
2561
- version: "1.0.0",
2562
+ version: "1.0.3",
2562
2563
 
2563
2564
  // Core classes
2564
2565
  gameObject: gameObject,
@@ -2675,7 +2676,7 @@ const MALC = {
2675
2676
 
2676
2677
  // Initialize the engine
2677
2678
  init: function(canvasX, canvasY) {
2678
- createCanvas(canvasX, canvasY);
2679
+ _p5.prototype.createCanvas(canvasX, canvasY);
2679
2680
 
2680
2681
  this.time = new Date();
2681
2682
  this.startTime = this.time.getTime();
@@ -2687,25 +2688,49 @@ const MALC = {
2687
2688
  this.mouse = new MouseHandler();
2688
2689
  window.mouse = this.mouse;
2689
2690
 
2691
+ // Add coloredText method to p5 instance safely
2692
+ if (!_p5.prototype.coloredText) {
2693
+ _p5.prototype.coloredText = function(str, x, y, horizontal, vertical, maxWidth) {
2694
+ renderColoredText(this, str, x, y, horizontal || LEFT, vertical || BASELINE, maxWidth);
2695
+ return this;
2696
+ };
2697
+ }
2698
+
2690
2699
  // Start FPS tracking
2691
2700
  refreshLoop();
2692
2701
 
2693
2702
  // Create default scenes
2694
2703
  new Scene("blank", 70);
2695
2704
  new Scene("loading", 50, function(self) {
2696
- textSize(24);
2697
- let timed = (self.timeActive / 250 % 4);
2698
- let dots = "";
2699
-
2700
- if (timed < 1) dots = ".";
2701
- else if (timed < 2) dots = "..";
2702
- else if (timed < 3) dots = "...";
2703
-
2704
- coloredText(`\\lime|Loading Game${dots}| `, 120, 200, LEFT, CENTER);
2705
- textSize(16);
2706
-
2707
- let num = (Math.floor(self.timeActive / 100) / 10);
2708
- coloredText(`\\red|${ Math.round((10 - num) * 10) / 10 + ((num + "").length < 2 ? ".0" : "")}|`, 200, 225, CENTER, CENTER);
2705
+ try {
2706
+ _p5.prototype.textSize(24);
2707
+ let timed = (self.timeActive / 250 % 4);
2708
+ let dots = "";
2709
+
2710
+ if (timed < 1) dots = ".";
2711
+ else if (timed < 2) dots = "..";
2712
+ else if (timed < 3) dots = "...";
2713
+
2714
+ if (_p5.prototype.coloredText) {
2715
+ _p5.prototype.coloredText(`\\lime|Loading Game${dots}| `, 120, 200, _p5.prototype.LEFT, _p5.prototype.CENTER);
2716
+ } else {
2717
+ _p5.prototype.text(`Loading Game${dots}`, 120, 200);
2718
+ }
2719
+
2720
+ _p5.prototype.textSize(16);
2721
+
2722
+ let num = (Math.floor(self.timeActive / 100) / 10);
2723
+ let percentText = `${ Math.round((10 - num) * 10) / 10 + ((num + "").length < 2 ? ".0" : "")}`;
2724
+
2725
+ if (_p5.prototype.coloredText) {
2726
+ _p5.prototype.coloredText(`\\red|${percentText}|`, 200, 225, _p5.prototype.CENTER, _p5.prototype.CENTER);
2727
+ } else {
2728
+ _p5.prototype.text(percentText, 200, 225);
2729
+ }
2730
+ } catch (e) {
2731
+ // Fallback if coloredText fails
2732
+ _p5.prototype.text(`Loading Game...`, 120, 200);
2733
+ }
2709
2734
  });
2710
2735
 
2711
2736
  Scene.activeScene = "loading";
@@ -2720,11 +2745,11 @@ const MALC = {
2720
2745
  this.timer = this.time - this.startTime;
2721
2746
 
2722
2747
  if (this.mouse) {
2723
- this.mouse.rawX = mouseX;
2724
- this.mouse.rawY = mouseY;
2725
- this.mouse.x = this.mouse.rawX + camera.getOrientation()[0];
2726
- this.mouse.y = this.mouse.rawY + camera.getOrientation()[1];
2727
- this.mouse.down = mouseIsPressed;
2748
+ this.mouse.rawX = _p5.prototype.mouseX;
2749
+ this.mouse.rawY = _p5.prototype.mouseY;
2750
+ this.mouse.x = this.mouse.rawX + window.camera.getOrientation()[0];
2751
+ this.mouse.y = this.mouse.rawY + window.camera.getOrientation()[1];
2752
+ this.mouse.down = _p5.prototype.mouseIsPressed;
2728
2753
  }
2729
2754
 
2730
2755
  controller.update();
@@ -2734,8 +2759,8 @@ const MALC = {
2734
2759
 
2735
2760
  this.fps = fps;
2736
2761
 
2737
- if (typeof camera.render == "function") {
2738
- camera.render();
2762
+ if (typeof window.camera.render == "function") {
2763
+ window.camera.render();
2739
2764
  }
2740
2765
  Scene.update();
2741
2766
  }