UNPKG

piton-engine

Version:
1,100 lines (1,093 loc) 34.5 kB
// 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