piton-engine
Version:
A typescript game engine
1,100 lines (1,093 loc) • 34.5 kB
JavaScript
// src/engine.ts
import { EntityManager } from "entix-ecs";
// src/assetLoader.ts
var AssetLoader = class {
resources;
constructor(resources = {}) {
this.resources = resources;
}
async fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status} ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`\u274C Failed to fetch JSON at: ${url}
`, error);
throw new Error(`FetchDataError
URL: ${url}
Reason: ${error.message}`);
}
}
loadImages() {
const imagesUrl = this.resources.images_JSON_path ?? "";
if (imagesUrl === "") {
console.warn("NO IMAGES PASSED TO LOAD");
return Promise.resolve({});
}
;
return new Promise((resolve, reject) => {
this.fetchData(imagesUrl).then((data) => {
const keys = Object.keys(data);
const length = keys.length;
let loadCount = 0;
let images = {};
keys.forEach((key) => {
const image = new Image();
image.src = data[key];
images[key] = image;
image.onload = () => {
loadCount++;
if (loadCount === length) {
resolve(images);
}
};
image.onerror = () => {
console.error(`\u274C Failed to load image: ${data[key]}`);
reject(new Error(`ImageLoadError
File: ${data[key]}`));
};
});
}).catch((error) => {
console.error("\u274C Failed during image JSON loading:", error);
reject(new Error(`ImageLoadingError
${error.message}`));
});
});
}
loadAudio() {
const audioUrl = this.resources.audio_JSON_path ?? "";
if (audioUrl === "") {
console.warn("\u26A0\uFE0F No audio JSON path provided.");
return Promise.resolve({});
}
return new Promise((resolve, reject) => {
this.fetchData(audioUrl).then((data) => {
const keys = Object.keys(data);
let loadCount = 0;
const audios = {};
keys.forEach((key) => {
const audio = new Audio();
audio.src = data[key];
audio.preload = "auto";
audios[key] = audio;
audio.addEventListener("canplaythrough", () => {
loadCount++;
if (loadCount === keys.length) {
resolve(audios);
}
});
audio.onerror = () => {
console.error(`\u274C Failed to load audio: ${data[key]}`);
reject(new Error(`AudioLoadError
File: ${data[key]}`));
};
});
}).catch((error) => {
console.error("\u274C Failed during audio JSON loading:", error);
reject(new Error(`AudioLoadingError
${error.message}`));
});
});
}
async loadAll() {
const imagesPromise = this.loadImages();
const audioPromise = this.loadAudio();
const jsonPromises = [];
const jsonKeys = [];
if (this.resources.jsons) {
for (const [key, url] of Object.entries(this.resources.jsons)) {
jsonKeys.push(key);
jsonPromises.push(this.fetchData(url));
}
}
try {
const [imagesData, audioData, ...customJsons] = await Promise.all([
imagesPromise,
audioPromise,
...jsonPromises
]);
const customJsonData = {};
jsonKeys.forEach((key, i) => {
customJsonData[key] = customJsons[i];
});
return {
imagesData,
audioData,
customJsonData
};
} catch (error) {
console.error("\u274C Failed to load all resources:", error);
throw new Error(`LoadAllResourcesError
${error.message}`);
}
}
};
// src/components.ts
var Rotation = class {
value = 0;
constructor(value = 0) {
this.value = value ?? 0;
}
};
var Scale = class {
value = { x: 1, y: 1 };
constructor(options = {}) {
this.value = options.value ?? { x: 1, y: 1 };
}
};
var Transform = class {
globalPosition;
//public localPosition: LocalPosition;
rotation;
scale;
constructor(options = {}) {
this.globalPosition = { position: options.globalPosition ?? { x: 0, y: 0 } };
this.rotation = new Rotation(options.rotation?.value);
this.scale = new Scale({ value: options.scale?.value });
}
};
var Sprite = class {
image;
width;
height;
alpha;
rotation;
layer;
active;
blocksInput = true;
constructor(options) {
this.image = options.image;
this.width = options.width;
this.height = options.height;
this.alpha = options.alpha ?? 1;
this.rotation = options.rotation ?? 0;
this.layer = options.layer ?? 0;
this.active = options.active ?? true;
this.blocksInput = options.blocksInput ?? true;
}
};
var EntityActive = class {
value = true;
constructor(options = {}) {
this.value = options.value ?? true;
}
};
var Parent = class {
value = null;
constructor(options = {}) {
this.value = options.value ?? null;
}
};
var Children = class {
// increment through code
value = [];
};
var Scene = class {
name = "";
onLoad;
onUnload;
constructor(options) {
this.name = options.name;
this.onLoad = options.onLoad ?? (() => {
});
this.onUnload = options.onUnload ?? (() => {
});
}
};
var Shape = class {
shape;
color = "green";
outlineEnabled = false;
outlineWidth = 3;
outlineColor = "black";
alpha = 1;
active = true;
layer = 0;
blocksInput = true;
constructor(options) {
this.shape = options.shape;
this.color = options.color ?? "green";
this.outlineEnabled = options.outlineEnabled ?? false;
this.outlineWidth = options.outlineWidth ?? 3;
this.outlineColor = options.color ?? "black";
this.alpha = options.alpha ?? 1;
this.active = options.active ?? true;
this.layer = options.layer ?? 0;
this.blocksInput = options.blocksInput ?? true;
}
};
var Rectangle = class {
width;
height;
centered = true;
rotation = 0;
rounded = false;
roundedRadius = 5;
constructor(options) {
this.width = options.width ?? 40;
this.height = options.height ?? 40;
this.centered = options.centered ?? true;
this.rotation = options.rotation ?? 0;
this.rounded = options.rounded ?? false;
this.roundedRadius = options.roundRadius ?? 5;
}
};
var Circle = class {
radius;
constructor(options) {
this.radius = options.radius ?? 40;
}
};
var Triangle = class {
p1;
// side 1
p2;
// side 2
p3;
// side 3
centered = true;
rotation = 0;
constructor(options) {
this.p1 = options.p1 ?? { x: 30, y: 0 };
this.p2 = options.p2 ?? { x: 0, y: 59 };
this.p3 = options.p3 ?? { x: 30, y: 59 };
this.centered = options.centered ?? true;
this.rotation = options.rotation ?? 0;
}
};
var Text = class {
content = "text";
size = 16;
color = "white";
outlineEnabled = false;
outlineWidth = 2;
outlineColor = "black";
alpha = 1;
active = true;
layer = 0;
rotation = 0;
style = "sans-serif";
maxWidth = 200;
constructor(options) {
this.content = options.content ?? "text";
this.size = options.size ?? 16;
this.color = options.color ?? "white";
this.outlineEnabled = options.outlineEnabled ?? false;
this.outlineWidth = options.outlineWidth ?? 2;
this.outlineColor = options.outlineColor ?? "black";
this.alpha = options.alpha ?? 1;
this.active = options.active ?? true;
this.layer = options.layer ?? 0;
this.rotation = options.rotation ?? 0;
this.style = options.style ?? "sans-serif";
this.maxWidth = options.maxWidth ?? 200;
}
};
var Button = class {
pressArea = { x: 100, y: 100 };
onJustPressed = () => {
};
onPress = () => {
};
onJustReleased = () => {
};
onJustHovered = () => {
};
onHovered = () => {
};
onHoveredReleased = () => {
};
showPressArea = false;
pressAreaShowColor = "green";
layer = 0;
active = true;
changeCursorToPointer = true;
isHovered = false;
constructor(options) {
this.pressArea = options.pressArea ?? { x: 100, y: 100 };
this.onJustPressed = options.onJustPressed ?? (() => {
});
this.onPress = options.onPress ?? (() => {
});
this.onJustReleased = options.onReleased ?? (() => {
});
this.onJustHovered = options.onJustHovered ?? (() => {
});
this.onHovered = options.onHovered ?? (() => {
});
this.onHoveredReleased = options.onHoveredReleased ?? (() => {
});
this.showPressArea = options.showPressArea ?? false;
this.pressAreaShowColor = options.pressAreaShowColor ?? "green";
this.layer = options.layer ?? 0;
this.active = options.active ?? true;
this.changeCursorToPointer = options.changeCursorToPointer ?? true;
}
};
// src/systems/renderingSystem.ts
function renderingSystem(engine) {
const em = engine.getEntityManager();
const drawCalls = [];
for (const id of em.getAllEntities()) {
if (!engine.isEntityActive(id)) continue;
const transform = em.getComponent(id, Transform);
if (!transform) continue;
const sprite = em.getComponent(id, Sprite);
if (sprite) {
if (!sprite.active) continue;
drawCalls.push({
layer: sprite.layer ?? 0,
drawFn: () => spriteRenderingSystem(engine, id)
});
}
;
const shape = em.getComponent(id, Shape);
if (shape) {
if (!shape.active) continue;
const layer = shape.layer;
let drawFn = null;
if (shape.shape instanceof Rectangle) {
drawFn = () => rectangleRenderingSystem(engine, id);
} else if (shape.shape instanceof Circle) {
drawFn = () => circleRenderingSystem(engine, id);
} else if (shape.shape instanceof Triangle) {
drawFn = () => triangleRenderingSystem(engine, id);
}
if (drawFn) {
drawCalls.push({
layer,
drawFn
});
}
;
}
const text = em.getComponent(id, Text);
if (text) {
if (!text.active) return;
drawCalls.push({
layer: text.layer,
drawFn: () => textRenderingSystem(engine, id)
});
}
const button = em.getComponent(id, Button);
if (button) {
if (button.showPressArea) {
drawCalls.push({
layer: button.layer,
drawFn: () => buttonPressAreaRendering(engine, id)
});
}
}
}
;
drawCalls.sort((a, b) => a.layer - b.layer);
for (const draw of drawCalls) {
draw.drawFn();
}
}
function spriteRenderingSystem(engine, id) {
const em = engine.getEntityManager();
const transform = em.getComponent(id, Transform);
const sprite = em.getComponent(id, Sprite);
if (!transform) throw new Error("TRANSFORM NULL IN SPRITE RENDERING SYSTEM! " + id);
if (!sprite) throw new Error("SPRITE NULL IN SPRITE RENDERING SYSTEM! " + id);
engine.drawSprite(transform, sprite);
}
function rectangleRenderingSystem(engine, id) {
const em = engine.getEntityManager();
const ctx = engine.getCtx();
const transform = em.getComponent(id, Transform);
const shape = em.getComponent(id, Shape);
if (!transform) throw new Error("TRANSFORM NULL IN RECTANGLE RENDERING STSTEM !" + id);
if (!shape) throw new Error("SHAPE NULL IN RECTANGLE RENDERING SYSTEM! " + id);
const rectangle = shape.shape;
if (rectangle instanceof Rectangle) {
const x = transform.globalPosition.position.x;
const y = transform.globalPosition.position.y;
const w = rectangle.width;
const h = rectangle.height;
const selfRotationInRadians = Math.PI * rectangle.rotation / 180;
const transformRotationInRadians = Math.PI * transform.rotation.value / 180;
const totalRotationInRadians = selfRotationInRadians + transformRotationInRadians;
const offset = {
x: rectangle.centered ? -w / 2 : 0,
y: rectangle.centered ? -h / 2 : 0
};
ctx.save();
ctx.fillStyle = shape.color;
ctx.globalAlpha = shape.alpha;
ctx.beginPath();
ctx.translate(x, y);
ctx.rotate(totalRotationInRadians);
ctx.scale(transform.scale.value.x, transform.scale.value.y);
if (rectangle.rounded) {
ctx.roundRect(offset.x, offset.y, w, h, rectangle.roundedRadius);
} else {
ctx.rect(offset.x, offset.y, w, h);
}
ctx.fill();
ctx.closePath();
if (shape.outlineEnabled) {
ctx.strokeStyle = shape.outlineColor;
ctx.lineWidth = shape.outlineWidth;
ctx.beginPath();
if (rectangle.rounded) {
ctx.roundRect(offset.x, offset.y, w, h, rectangle.roundedRadius);
} else {
ctx.rect(offset.x, offset.y, w, h);
}
ctx.stroke();
ctx.closePath();
}
;
ctx.restore();
} else {
console.warn("SHAPE IS NOT A RECTANGLE BUT IS IN RECTANGLE RENDERING SYSTEM! " + id);
}
}
function circleRenderingSystem(engine, id) {
const em = engine.getEntityManager();
const ctx = engine.getCtx();
const transform = em.getComponent(id, Transform);
const shape = em.getComponent(id, Shape);
if (!transform) throw new Error("TRANSFORM NULL IN RECTANGLE RENDERING STSTEM !" + id);
if (!shape) throw new Error("SHAPE NULL IN RECTANGLE RENDERING SYSTEM! " + id);
if (shape.shape instanceof Circle) {
const x = transform.globalPosition.position.x;
const y = transform.globalPosition.position.y;
const r = shape.shape.radius;
ctx.save();
ctx.fillStyle = shape.color;
ctx.globalAlpha = shape.alpha;
ctx.beginPath();
ctx.translate(x, y);
ctx.scale(transform.scale.value.x, transform.scale.value.y);
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
if (shape.outlineEnabled) {
ctx.strokeStyle = shape.outlineColor;
ctx.lineWidth = shape.outlineWidth;
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
}
ctx.restore();
} else {
console.warn("SHAPE IS NOT A CIRCLE BUT IS IN RECTANGLE RENDERING SYSTEM! " + id);
}
}
function triangleRenderingSystem(engine, id) {
const em = engine.getEntityManager();
const ctx = engine.getCtx();
const transform = em.getComponent(id, Transform);
const shape = em.getComponent(id, Shape);
if (!transform) throw new Error("TRANSFORM NULL IN TRIANGLE RENDERING SYSTEM !" + id);
if (!shape) throw new Error("SHAPE NULL IN TRIANGLE RENDERING SYSTEM! " + id);
const triangle = shape.shape;
if (!(triangle instanceof Triangle)) {
console.warn("SHAPE IS NOT A TRIANGLE BUT IS IN TRIANGLE RENDERING SYSTEM! " + id);
return;
}
const pos = transform.globalPosition.position;
const selfRotationInRadians = Math.PI * triangle.rotation / 180;
const transformRotationInRadians = Math.PI * transform.rotation.value / 180;
const totalRotationInRadians = selfRotationInRadians + transformRotationInRadians;
let localP1, localP2, localP3;
if (triangle.centered) {
const centroid = {
x: (triangle.p1.x + triangle.p2.x + triangle.p3.x) / 3,
y: (triangle.p1.y + triangle.p2.y + triangle.p3.y) / 3
};
localP1 = { x: triangle.p1.x - centroid.x, y: triangle.p1.y - centroid.y };
localP2 = { x: triangle.p2.x - centroid.x, y: triangle.p2.y - centroid.y };
localP3 = { x: triangle.p3.x - centroid.x, y: triangle.p3.y - centroid.y };
} else {
localP1 = { x: 0, y: 0 };
localP2 = { x: triangle.p2.x - triangle.p1.x, y: triangle.p2.y - triangle.p1.y };
localP3 = { x: triangle.p3.x - triangle.p1.x, y: triangle.p3.y - triangle.p1.y };
}
ctx.save();
ctx.fillStyle = shape.color;
ctx.globalAlpha = shape.alpha;
ctx.translate(pos.x, pos.y);
ctx.rotate(totalRotationInRadians);
ctx.scale(transform.scale.value.x, transform.scale.value.y);
ctx.beginPath();
ctx.moveTo(localP1.x, localP1.y);
ctx.lineTo(localP2.x, localP2.y);
ctx.lineTo(localP3.x, localP3.y);
ctx.closePath();
ctx.fill();
if (shape.outlineEnabled) {
ctx.strokeStyle = shape.outlineColor;
ctx.lineWidth = shape.outlineWidth;
ctx.beginPath();
ctx.moveTo(localP1.x, localP1.y);
ctx.lineTo(localP2.x, localP2.y);
ctx.lineTo(localP3.x, localP3.y);
ctx.closePath();
ctx.stroke();
}
ctx.restore();
}
function textRenderingSystem(engine, id) {
const em = engine.getEntityManager();
const ctx = engine.getCtx();
const transform = em.getComponent(id, Transform);
const text = em.getComponent(id, Text);
if (!transform) throw new Error("TRANSFORM NOT FOUND IN TEXT RENDERING SYSTEM! " + id);
if (!text) throw new Error("TEXT NOT FOUND IN TEXT RENDERING SYSTEM! " + id);
const pos = transform.globalPosition.position;
const selfRotationInRadians = Math.PI * text.rotation / 180;
const transformRotationInRadians = Math.PI * transform.rotation.value / 180;
const totalRotationInRadians = selfRotationInRadians + transformRotationInRadians;
ctx.save();
ctx.translate(pos.x, pos.y);
ctx.rotate(totalRotationInRadians);
ctx.scale(transform.scale.value.x, transform.scale.value.y);
ctx.beginPath();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = text.color;
ctx.font = `${text.size}px ${text.style}`;
ctx.fillText(text.content, 0, 0, 50);
ctx.closePath();
if (text.outlineEnabled) {
ctx.beginPath();
ctx.strokeStyle = text.outlineColor;
ctx.lineWidth = text.outlineWidth;
ctx.font = `${text.size}px ${text.style}`;
ctx.strokeText(text.content, 0, 0, 50);
ctx.closePath();
}
ctx.restore();
}
function buttonPressAreaRendering(engine, id) {
const em = engine.getEntityManager();
const ctx = engine.getCtx();
const transform = em.getComponent(id, Transform);
const button = em.getComponent(id, Button);
if (!transform) throw new Error("TRANSFORM NOT FOUND IN BUTTON PRESSAREA RENDERING SYSTEM! " + id);
if (!button) throw new Error("BUTTON NOT FOUND IN BUTTON PRESSAREA RENDERING SYSTEM! " + id);
const pos = transform?.globalPosition.position;
const w = button.pressArea.x;
const h = button.pressArea.y;
const rotationInRadians = Math.PI * transform.rotation.value / 180;
if (button.showPressArea) {
ctx.save();
ctx.translate(pos.x, pos.y);
ctx.rotate(rotationInRadians);
ctx.scale(transform.scale.value.x, transform.scale.value.y);
ctx.beginPath();
ctx.globalAlpha = 0.25;
ctx.fillStyle = button.pressAreaShowColor;
ctx.rect(-w / 2, -h / 2, button.pressArea.x, button.pressArea.y);
ctx.fill();
ctx.closePath();
ctx.restore();
}
;
}
// src/input.ts
var Input = class {
engine;
position = { x: 0, y: 0 };
justPressed = false;
//just pressed, used to detect clicks
pressed = false;
// currently pressing
justReleased = false;
//just released, used to detect mouse leaves
constructor(engine) {
this.engine = engine;
this.start();
}
start() {
this.engine.addUpdateFunction(this.update.bind(this));
this.addListeners();
}
update() {
}
updatePos(clientX, clientY) {
const canvas = this.engine.getCanvas();
const rect = canvas.getBoundingClientRect();
this.position.x = clientX - rect.left;
this.position.y = clientY - rect.top;
}
addListeners() {
const pressedListeners = ["mousedown", "touchstart"];
const moveListeners = ["mousemove", "touchmove"];
const releasedListeners = ["mouseup", "touchend"];
pressedListeners.forEach((event) => {
this.engine.getCanvas().addEventListener(event, (e) => {
if (e instanceof MouseEvent) {
this.updatePos(e.clientX, e.clientY);
} else if (e instanceof TouchEvent) {
this.updatePos(e.touches[0].clientX, e.touches[0].clientY);
}
this.justPressed = true;
this.pressed = true;
}, { passive: true });
});
moveListeners.forEach((event) => {
this.engine.getCanvas().addEventListener(event, (e) => {
if (e instanceof MouseEvent) {
this.updatePos(e.clientX, e.clientY);
} else if (e instanceof TouchEvent) {
this.updatePos(e.touches[0].clientX, e.touches[0].clientY);
}
}, { passive: true });
});
releasedListeners.forEach((event) => {
this.engine.getCanvas().addEventListener(event, () => {
this.justReleased = true;
this.pressed = false;
});
});
}
resetStates() {
this.justPressed = false;
this.justReleased = false;
}
isOver(x, y, w, h) {
return this.position.x >= x && this.position.x <= x + w && this.position.y >= y && this.position.y <= y + h;
}
//GETTERS
getPosition() {
return this.position;
}
getJustPressed() {
return this.justPressed;
}
getPressed() {
return this.pressed;
}
getJustReleased() {
return this.justReleased;
}
};
// src/systems/buttonActionSystem.ts
function buttonActionSystem(engine) {
const em = engine.getEntityManager();
const input = engine.getInput();
const ctx = engine.getCtx();
let anyButtonHovered = false;
em.query("All", {
transform: Transform,
button: Button
}, (id, { transform, button }) => {
if (!engine.isEntityActive(id) || !button.active) {
if (button.isHovered) {
button.isHovered = false;
button.onHoveredReleased();
}
return;
}
;
const pos = transform.globalPosition.position;
const w = button.pressArea.x;
const h = button.pressArea.y;
const scaledW = w * transform.scale.value.x;
const scaledH = h * transform.scale.value.y;
const centeredX = pos.x - w / 2;
const centeredY = pos.y - h / 2;
const isHovering = input.isOver(centeredX, centeredY, scaledW, scaledH);
if (isHovering) {
if (engine.isEntityBlockingInput(input.getPosition().x, input.getPosition().y, button.layer)) return;
anyButtonHovered = true;
if (!button.isHovered) {
button.isHovered = true;
button.onJustHovered();
}
;
button.onHovered();
if (input.getJustPressed()) {
button.onJustPressed();
}
;
if (input.getPressed()) {
button.onPress();
}
;
if (input.getJustReleased()) {
button.onJustReleased();
}
} else {
if (button.isHovered) {
button.isHovered = false;
button.onHoveredReleased();
}
}
if (button.changeCursorToPointer) {
const canvas = engine.getCanvas();
if (anyButtonHovered) {
canvas.style.cursor = "pointer";
} else {
canvas.style.cursor = "default";
}
}
});
}
// src/engine.ts
var Engine = class {
canvas = null;
ctx = null;
canvasWidth = 0;
canvasHeight = 0;
canvasDefWidth = 680;
canvasDefHeight = 600;
entityManager = null;
lastFrameTime = 0;
deltaTime = 0;
updateFunctions = [];
resources = {};
assetLoader = null;
loadedResources = {};
startFunctions = [];
sceneEntities = [];
currentSceneId = null;
input = null;
constructor(options) {
this.init(options);
this.start().then(() => {
requestAnimationFrame(this.update.bind(this));
});
}
init(options) {
this.canvas = options.canvas ?? null;
if (!this.canvas) {
const newCanvas = document.createElement("canvas");
this.canvas = newCanvas;
document.body.appendChild(this.canvas);
}
;
this.ctx = this.canvas.getContext("2d");
if (!this.ctx) throw new Error("CTX NOT FOUND!");
this.canvasWidth = options.canvasWidth ?? 0;
if (this.canvasWidth === 0) this.canvasWidth = this.canvasDefWidth;
this.canvasHeight = options.canvasHeight ?? 0;
if (this.canvasHeight === 0) this.canvasHeight = this.canvasDefHeight;
this.canvas.width = this.canvasWidth;
this.canvas.height = this.canvasHeight;
const dpr = window.devicePixelRatio || 1;
this.canvas.width = this.canvasWidth * dpr;
this.canvas.height = this.canvasHeight * dpr;
this.canvas.style.width = `${this.canvasWidth}px`;
this.canvas.style.height = `${this.canvasHeight}px`;
this.ctx.scale(dpr, dpr);
this.entityManager = new EntityManager();
if (!this.entityManager) throw new Error("ENTITY MANAGER NOT FOUND!");
this.resources = options.resources ?? {};
this.assetLoader = new AssetLoader(this.resources);
this.input = new Input(this);
if (!this.input) throw new Error("INPUT NOT FOUND!");
}
async start() {
try {
const loadedResources = await this.assetLoader?.loadAll();
this.loadedResources = loadedResources;
console.log("RESOURCES LOADED! ", loadedResources);
this.startFunctions.forEach((fn) => fn());
} catch (error) {
console.error("FAILED TO LOAD ASSETS:", error);
}
}
update(timeStamp) {
this.deltaTime = (timeStamp - this.lastFrameTime) / 1e3;
this.lastFrameTime = timeStamp;
this.ctx?.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
renderingSystem(this);
buttonActionSystem(this);
this.updateFunctions.forEach((fn) => {
fn();
});
this.input?.resetStates();
requestAnimationFrame(this.update.bind(this));
}
//GETTER
getCanvas() {
if (!this.canvas) throw new Error("CANVAS NOT FOUND!");
return this.canvas;
}
getCtx() {
if (!this.ctx) throw new Error("CTX NOT FOUND");
return this.ctx;
}
getEntityManager() {
if (!this.entityManager) throw new Error("ENTITY MANAGER NOT FOUND!");
return this.entityManager;
}
getDeltaTime() {
return this.deltaTime;
}
getImage(key) {
if (!this.loadedResources?.imagesData || !this.loadedResources.imagesData[key]) {
throw new Error("LOADED IMAGE RESOURCE " + key + " NOT FOUND!");
}
return this.loadedResources.imagesData[key];
}
getAudio(key) {
if (!this.loadedResources.audioData || !this.loadedResources.audioData[key]) {
throw new Error("LOADED AUDIO RESOURCE " + key + " NOT FOUND!");
}
return this.loadedResources.audioData[key];
}
getJSON(key) {
if (!this.loadedResources.customJsonData || !(key in this.loadedResources.customJsonData))
throw new Error("LOADED JSON RESOURCE " + key + " NOT FOUND!");
return this.loadedResources.customJsonData[key];
}
getSceneByName(name) {
for (const id of this.sceneEntities) {
const scene = this.entityManager?.getComponent(id, Scene);
if (scene && scene.name === name) return id;
}
return null;
}
getInput() {
if (!this.input) throw new Error("INPUT NOT FOUND!");
return this.input;
}
//FUNCTIONS
addUpdateFunction(fn) {
this.updateFunctions.push(fn);
}
removeUpdateFunction(fn) {
const index = this.updateFunctions.indexOf(fn);
if (index > -1) {
this.updateFunctions.splice(index, 1);
}
}
addStartFunction(fn) {
this.startFunctions.push(fn);
}
removeStartFunction(fn) {
const index = this.startFunctions.indexOf(fn);
if (index > -1) {
this.startFunctions.splice(index, 1);
}
}
drawSprite(transform, sprite) {
if (!this.ctx) throw new Error("CTX NOT FOUND IN DRAW SPRITE!");
const selfRotationInRadians = Math.PI * sprite.rotation / 180;
const transformRotationInRadians = Math.PI * transform.rotation.value / 180;
const totalRotationInRadians = selfRotationInRadians + transformRotationInRadians;
const pos = transform.globalPosition.position;
this.ctx.save();
this.ctx.translate(pos.x, pos.y);
this.ctx.scale(transform.scale.value.x, transform.scale.value.y);
this.ctx.globalAlpha = sprite.alpha;
this.ctx.rotate(totalRotationInRadians);
this.ctx.drawImage(sprite.image, -sprite.width / 2, -sprite.height / 2, sprite.width, sprite.height);
this.ctx.restore();
}
isEntityActive(id) {
let currentEntity = id;
while (currentEntity) {
const entityActiveComponent = this.entityManager?.getComponent(currentEntity, EntityActive) ?? null;
const parentComponent = this.entityManager?.getComponent(currentEntity, Parent) ?? null;
if (!entityActiveComponent) throw new Error("ENTITY DOES NOT HAVE ENTITYACTIVE COMPONENT IN ISENTITYACTIVE() " + id);
if (!parentComponent) throw new Error("ENTITY DOES NOT HAVE PARENT COMPONENT IN ISENTITYACTIVE() " + id);
if (entityActiveComponent.value !== true) return false;
currentEntity = parentComponent.value;
}
return true;
}
addScene(id) {
this.sceneEntities.push(id);
}
loadScene(id) {
const sceneComponent = this.entityManager?.getComponent(id, Scene);
if (!sceneComponent) throw new Error("Scene component not found on entity: " + id);
for (const sceneEntity of this.sceneEntities) {
const entityActive2 = this.entityManager?.getComponent(sceneEntity, EntityActive);
if (!entityActive2) throw new Error("SCENE DOES NOT HAVE ENTITY ACTIVE COMPONENT! WONT BE ABLE TO SWITCH TO THIS SCENE! " + sceneEntity);
entityActive2.value = false;
}
if (this.currentSceneId !== null) {
const oldScene = this.entityManager?.getComponent(this.currentSceneId, Scene);
oldScene?.onUnload();
}
this.currentSceneId = id;
const entityActive = this.entityManager?.getComponent(this.currentSceneId, EntityActive);
if (!entityActive) throw new Error("Target scene missing EntityActive component!");
entityActive.value = true;
sceneComponent.onLoad();
}
addParent(id, parentId) {
if (this.entityManager?.getComponent(id, Scene)) throw new Error("CANT ASSIGN PARENT TO A SCENE! " + id);
const entityParentComponent = this.entityManager?.getComponent(id, Parent);
const parentChildrenComponent = this.entityManager?.getComponent(parentId, Children);
if (!entityParentComponent) throw new Error("ENTITY DOES NOT HAVE PARENT COMPONENT, AND YOU ARE TRYING TO ASSIGN A VALUE! " + id);
if (!parentChildrenComponent) throw new Error("ENTITY DOES NOT HAVE CHILDREN COMPONENT, AND YOU ARE TRYING TO ASSIGN A VALUE! " + id);
if (parentChildrenComponent.value.includes(id)) {
console.warn("ENTITY ALREADY HAS THIS PARENT ASSIGNED! ARE YOU ASSIGNING THE SAME PARENT AGAIN ? " + id);
return;
}
entityParentComponent.value = parentId;
parentChildrenComponent.value.push(id);
}
isEntityBlockingInput(x, y, layer) {
const em = this.getEntityManager();
let blocked = false;
em.query("All", { transform: Transform, sprite: Sprite }, (_, { transform, sprite }) => {
if (!sprite.active || !sprite.blocksInput) return;
if (sprite.layer <= layer) return;
const pos = transform.globalPosition.position;
const width = sprite.width * transform.scale.value.x;
const height = sprite.height * transform.scale.value.y;
const centeredX = pos.x - width / 2;
const centeredY = pos.y - height / 2;
if (x >= centeredX && x <= centeredX + width && y >= centeredY && y <= centeredY + height) {
blocked = true;
}
});
em.query("All", { transform: Transform, shape: Shape }, (_, { transform, shape }) => {
if (!shape.active || !shape.blocksInput) return;
if (shape.layer <= layer) return;
const shapeType = shape.shape;
if (shapeType instanceof Rectangle) {
const width = shapeType.width * transform.scale.value.x;
const height = shapeType.height * transform.scale.value.y;
const pos = transform.globalPosition.position;
const centeredX = pos.x - width / 2;
const centeredY = pos.y - height / 2;
if (x >= centeredX && x <= centeredX + width && y >= centeredY && y <= centeredY + height) {
blocked = true;
}
}
});
return blocked;
}
};
// src/entityTemplates.ts
var EntityTemplates = class {
engine;
em;
constructor(engine) {
this.engine = engine;
this.em = engine.getEntityManager();
}
createEmptyEntity(parent) {
const id = this.em.createEntity();
this.em.addComponent(id, Transform, new Transform({}));
this.em.addComponent(id, EntityActive, new EntityActive({}));
this.em.addComponent(id, Parent, new Parent());
this.em.addComponent(id, Children, new Children());
if (parent) {
this.engine.addParent(id, parent);
}
return id;
}
createSpriteEntity(image, width, height, parent) {
const id = this.createEmptyEntity(parent);
this.em.addComponent(id, Sprite, new Sprite({
image,
width,
height
}));
return id;
}
createSceneEntity(name = "unnamedScene") {
const id = this.createEmptyEntity();
this.em.addComponent(id, Scene, new Scene({ name }));
this.em.addComponent(id, Children, new Children());
this.engine.addScene(id);
return id;
}
createRectangleEntity(width, height, parent) {
const id = this.createEmptyEntity(parent);
this.em.addComponent(id, Shape, new Shape({
shape: new Rectangle({
width,
height
})
}));
return id;
}
createCircleEntity(radius, parent) {
const id = this.createEmptyEntity(parent);
this.em.addComponent(id, Shape, new Shape({
shape: new Circle({
radius
})
}));
return id;
}
createTriangleEntity(p1, p2, p3, parent) {
const id = this.createEmptyEntity(parent);
this.em.addComponent(id, Shape, new Shape({
shape: new Triangle({
p1,
p2,
p3
})
}));
return id;
}
createTextEntity(content, parent) {
const id = this.createEmptyEntity(parent);
this.em.addComponent(id, Text, new Text({
content
}));
return id;
}
createRectButtonEntity(width, height, textContent, parent) {
const id = this.createEmptyEntity(parent);
this.em.addComponent(id, Button, new Button(
{
pressArea: { x: width, y: height }
}
));
this.em.addComponent(id, Shape, new Shape({
shape: new Rectangle({
width,
height
}),
blocksInput: false
}));
this.em.addComponent(id, Text, new Text({
content: textContent
}));
return id;
}
createTexturedButtonEntity(image, width, height, parent) {
const id = this.createEmptyEntity(parent);
this.em.addComponent(id, Button, new Button({
pressArea: { x: width, y: height }
}));
this.em.addComponent(id, Sprite, new Sprite({
image,
width,
height,
blocksInput: false
}));
return id;
}
};
export {
AssetLoader,
Button,
Children,
Circle,
Engine,
EntityActive,
EntityTemplates,
Parent,
Rectangle,
Rotation,
Scale,
Scene,
Shape,
Sprite,
Text,
Transform,
Triangle,
renderingSystem
};
//# sourceMappingURL=index.mjs.map