piton-engine
Version:
A typescript game engine
1 lines • 75.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/engine.ts","../src/assetLoader.ts","../src/components.ts","../src/systems/renderingSystem.ts","../src/input.ts","../src/systems/buttonActionSystem.ts","../src/entityTemplates.ts"],"sourcesContent":["import { AssetResources, EngineOptions, LoadedResources, Vector2 } from \"./types\";\r\nimport { EntityId, EntityManager } from \"entix-ecs\";\r\nimport { AssetLoader } from \"./assetLoader\";\r\nimport { Transform, Sprite, EntityActive, Parent, Scene, Children, Rectangle, Shape } from \"./components\";\r\nimport { renderingSystem } from \"./systems/renderingSystem\";\r\nimport { Input } from \"./input\";\r\nimport { buttonActionSystem } from \"./systems/buttonActionSystem\";\r\nexport class Engine {\r\n private canvas: HTMLCanvasElement | null = null;\r\n private ctx: CanvasRenderingContext2D | null = null;\r\n private canvasWidth: number = 0;\r\n private canvasHeight: number = 0;\r\n private canvasDefWidth: number = 680;\r\n private canvasDefHeight: number = 600;\r\n private entityManager: EntityManager | null = null;\r\n private lastFrameTime: number = 0;\r\n private deltaTime: number = 0;\r\n private updateFunctions: Function[] = [];\r\n private resources: AssetResources = {};\r\n private assetLoader: AssetLoader | null = null;\r\n private loadedResources: LoadedResources = {};\r\n private startFunctions: Function[] = [];\r\n private sceneEntities: EntityId[] = [];\r\n private currentSceneId: EntityId | null = null;\r\n private input: Input | null = null;\r\n constructor(options: EngineOptions) {\r\n this.init(options);\r\n this.start().then(() => {\r\n requestAnimationFrame(this.update.bind(this));\r\n });\r\n\r\n };\r\n init(options: EngineOptions) {// used to init all the important libs / files\r\n this.canvas = options.canvas ?? null;\r\n if (!this.canvas) {// create one\r\n const newCanvas: HTMLCanvasElement = document.createElement('canvas');\r\n this.canvas = newCanvas;\r\n document.body.appendChild(this.canvas);\r\n };\r\n this.ctx = this.canvas.getContext('2d');\r\n if (!this.ctx) throw new Error(\"CTX NOT FOUND!\");\r\n this.canvasWidth = options.canvasWidth ?? 0;\r\n if (this.canvasWidth === 0) this.canvasWidth = this.canvasDefWidth;\r\n this.canvasHeight = options.canvasHeight ?? 0;\r\n if (this.canvasHeight === 0) this.canvasHeight = this.canvasDefHeight;\r\n this.canvas.width = this.canvasWidth;\r\n this.canvas.height = this.canvasHeight;\r\n const dpr = window.devicePixelRatio || 1;\r\n this.canvas.width = this.canvasWidth * dpr;\r\n this.canvas.height = this.canvasHeight * dpr;\r\n this.canvas.style.width = `${this.canvasWidth}px`;\r\n this.canvas.style.height = `${this.canvasHeight}px`;\r\n this.ctx.scale(dpr, dpr);\r\n this.entityManager = new EntityManager();\r\n if (!this.entityManager) throw new Error(\"ENTITY MANAGER NOT FOUND!\");\r\n this.resources = options.resources ?? {};\r\n this.assetLoader = new AssetLoader(this.resources);\r\n this.input = new Input(this);\r\n if (!this.input) throw new Error(\"INPUT NOT FOUND!\");\r\n };\r\n async start(): Promise<void> {//other game stuff\r\n try {\r\n const loadedResources = await this.assetLoader?.loadAll();\r\n this.loadedResources = loadedResources!;\r\n console.log(\"RESOURCES LOADED! \", loadedResources);\r\n this.startFunctions.forEach(fn => fn());\r\n }\r\n catch (error) {\r\n console.error(\"FAILED TO LOAD ASSETS:\", error);\r\n }\r\n\r\n };\r\n update(timeStamp: number) {//gameloop\r\n this.deltaTime = (timeStamp - this.lastFrameTime) / 1000;\r\n this.lastFrameTime = timeStamp;\r\n\r\n this.ctx?.clearRect(0, 0, this.canvasWidth, this.canvasHeight);\r\n\r\n renderingSystem(this);\r\n buttonActionSystem(this);\r\n\r\n this.updateFunctions.forEach((fn) => {\r\n fn();\r\n });\r\n\r\n this.input?.resetStates();\r\n\r\n requestAnimationFrame(this.update.bind(this));\r\n };\r\n //GETTER\r\n public getCanvas(): HTMLCanvasElement {\r\n if (!this.canvas) throw new Error(\"CANVAS NOT FOUND!\");\r\n return this.canvas;\r\n };\r\n public getCtx(): CanvasRenderingContext2D {\r\n if (!this.ctx) throw new Error(\"CTX NOT FOUND\");\r\n return this.ctx;\r\n };\r\n public getEntityManager(): EntityManager {\r\n if (!this.entityManager) throw new Error(\"ENTITY MANAGER NOT FOUND!\");\r\n return this.entityManager;\r\n };\r\n public getDeltaTime(): number {\r\n return this.deltaTime;\r\n };\r\n public getImage(key: string): HTMLImageElement {\r\n if (!this.loadedResources?.imagesData || !this.loadedResources.imagesData[key]) {\r\n throw new Error(\"LOADED IMAGE RESOURCE \" + key + \" NOT FOUND!\");\r\n }\r\n return this.loadedResources.imagesData[key];\r\n };\r\n public getAudio(key: string): HTMLAudioElement {\r\n if (!this.loadedResources.audioData || !this.loadedResources.audioData[key]) {\r\n throw new Error(\"LOADED AUDIO RESOURCE \" + key + \" NOT FOUND!\");\r\n }\r\n return this.loadedResources.audioData[key];\r\n };\r\n public getJSON<T = any>(key: string): T {\r\n if (!this.loadedResources.customJsonData || !(key in this.loadedResources.customJsonData))\r\n throw new Error(\"LOADED JSON RESOURCE \" + key + \" NOT FOUND!\");\r\n return this.loadedResources.customJsonData[key] as T;\r\n };\r\n public getSceneByName(name: string): EntityId | null {\r\n for (const id of this.sceneEntities) {\r\n const scene = this.entityManager?.getComponent(id, Scene);\r\n if (scene && scene.name === name) return id;\r\n }\r\n return null;\r\n };\r\n getInput(): Input {\r\n if (!this.input) throw new Error(\"INPUT NOT FOUND!\");\r\n return this.input;\r\n }\r\n //FUNCTIONS\r\n public addUpdateFunction(fn: Function) {\r\n this.updateFunctions.push(fn);\r\n };\r\n public removeUpdateFunction(fn: Function) {\r\n const index: number = this.updateFunctions.indexOf(fn);\r\n if (index > -1) {\r\n this.updateFunctions.splice(index, 1);\r\n }\r\n };\r\n public addStartFunction(fn: Function) {\r\n this.startFunctions.push(fn);\r\n };\r\n public removeStartFunction(fn: Function) {\r\n const index: number = this.startFunctions.indexOf(fn);\r\n if (index > -1) {\r\n this.startFunctions.splice(index, 1);\r\n }\r\n };\r\n public drawSprite(transform: Transform, sprite: Sprite) {// draws a sprite, runs in spriteRenderingSystem(auto handles sprite loading)\r\n if (!this.ctx) throw new Error(\"CTX NOT FOUND IN DRAW SPRITE!\");\r\n const selfRotationInRadians = (Math.PI * sprite.rotation) / 180;\r\n const transformRotationInRadians = (Math.PI * transform.rotation.value) / 180\r\n const totalRotationInRadians = selfRotationInRadians + transformRotationInRadians;\r\n const pos: Vector2 = transform.globalPosition.position;\r\n\r\n this.ctx.save();\r\n\r\n this.ctx.translate(pos.x, pos.y);\r\n\r\n this.ctx.scale(transform.scale.value.x, transform.scale.value.y);\r\n\r\n this.ctx.globalAlpha = sprite.alpha;\r\n\r\n this.ctx.rotate(totalRotationInRadians)\r\n\r\n this.ctx.drawImage(sprite.image, -sprite.width / 2, -sprite.height / 2, sprite.width, sprite.height);\r\n\r\n this.ctx.restore();\r\n };\r\n public isEntityActive(id: EntityId): boolean {//checks whether the entity or it's parent chain is active or not\r\n let currentEntity: EntityId | null = id;\r\n\r\n while (currentEntity) {\r\n const entityActiveComponent: EntityActive | null = this.entityManager?.getComponent(currentEntity, EntityActive) ?? null;\r\n const parentComponent: Parent | null = this.entityManager?.getComponent(currentEntity, Parent) ?? null;\r\n if (!entityActiveComponent) throw new Error(\"ENTITY DOES NOT HAVE ENTITYACTIVE COMPONENT IN ISENTITYACTIVE() \" + id);\r\n if (!parentComponent) throw new Error(\"ENTITY DOES NOT HAVE PARENT COMPONENT IN ISENTITYACTIVE() \" + id);\r\n if (entityActiveComponent.value !== true) return false;// return false\r\n currentEntity = parentComponent.value;\r\n }\r\n return true;\r\n };\r\n public addScene(id: EntityId) {//ads a scene,used upon creating a scene using scene template\r\n this.sceneEntities.push(id);\r\n };\r\n public loadScene(id: EntityId) {//loads a scene\r\n const sceneComponent = this.entityManager?.getComponent(id, Scene);\r\n if (!sceneComponent) throw new Error(\"Scene component not found on entity: \" + id);\r\n\r\n for (const sceneEntity of this.sceneEntities) {// turn of all scenes\r\n const entityActive = this.entityManager?.getComponent(sceneEntity, EntityActive);\r\n if (!entityActive) throw new Error\r\n (\"SCENE DOES NOT HAVE ENTITY ACTIVE COMPONENT! WONT BE ABLE TO SWITCH TO THIS SCENE! \" + sceneEntity);\r\n entityActive.value = false;\r\n }\r\n\r\n if (this.currentSceneId !== null) {\r\n const oldScene = this.entityManager?.getComponent(this.currentSceneId, Scene);\r\n oldScene?.onUnload();\r\n }\r\n\r\n this.currentSceneId = id;\r\n const entityActive = this.entityManager?.getComponent(this.currentSceneId, EntityActive);\r\n if (!entityActive) throw new Error(\"Target scene missing EntityActive component!\");\r\n entityActive.value = true;\r\n sceneComponent.onLoad();\r\n };\r\n public addParent(id: EntityId, parentId: EntityId) {// adds a parent to a entity\r\n if (this.entityManager?.getComponent(id, Scene)) throw new Error// scenes dont have parent component!\r\n (\"CANT ASSIGN PARENT TO A SCENE! \" + id);\r\n const entityParentComponent = this.entityManager?.getComponent(id, Parent);\r\n const parentChildrenComponent = this.entityManager?.getComponent(parentId, Children);\r\n if (!entityParentComponent) throw new Error\r\n (\"ENTITY DOES NOT HAVE PARENT COMPONENT, AND YOU ARE TRYING TO ASSIGN A VALUE! \" + id);\r\n if (!parentChildrenComponent) throw new Error\r\n (\"ENTITY DOES NOT HAVE CHILDREN COMPONENT, AND YOU ARE TRYING TO ASSIGN A VALUE! \" + id);\r\n if (parentChildrenComponent.value.includes(id)) {\r\n console.warn\r\n (\"ENTITY ALREADY HAS THIS PARENT ASSIGNED! ARE YOU ASSIGNING THE SAME PARENT AGAIN ? \" + id);\r\n return;\r\n }\r\n entityParentComponent.value = parentId;\r\n parentChildrenComponent.value.push(id);\r\n\r\n };\r\n isEntityBlockingInput(x: number, y: number, layer: number): boolean {\r\n const em = this.getEntityManager();\r\n let blocked = false;\r\n\r\n // Check all Sprites\r\n em.query('All', { transform: Transform, sprite: Sprite }, (_, { transform, sprite }) => {\r\n if (!sprite.active || !sprite.blocksInput) return;\r\n if (sprite.layer <= layer) return;\r\n\r\n const pos = transform.globalPosition.position;\r\n const width = sprite.width * transform.scale.value.x;\r\n const height = sprite.height * transform.scale.value.y;\r\n const centeredX = pos.x - width / 2;\r\n const centeredY = pos.y - height / 2;\r\n\r\n if (x >= centeredX && x <= centeredX + width && y >= centeredY && y <= centeredY + height) {\r\n blocked = true;\r\n }\r\n });\r\n\r\n // Check all Shapes\r\n em.query('All', { transform: Transform, shape: Shape }, (_, { transform, shape }) => {\r\n if (!shape.active || !shape.blocksInput) return;\r\n if (shape.layer <= layer) return;\r\n\r\n const shapeType = shape.shape;\r\n if (shapeType instanceof Rectangle) {\r\n const width = shapeType.width * transform.scale.value.x;\r\n const height = shapeType.height * transform.scale.value.y;\r\n const pos = transform.globalPosition.position;\r\n const centeredX = pos.x - width / 2;\r\n const centeredY = pos.y - height / 2;\r\n\r\n if (x >= centeredX && x <= centeredX + width && y >= centeredY && y <= centeredY + height) {\r\n blocked = true;\r\n }\r\n }\r\n // Add checks for other shape types if needed (e.g., Circle, Polygon)\r\n });\r\n\r\n return blocked;\r\n }\r\n\r\n};","import { AssetResources, LoadedResources } from \"./types\";\r\n\r\nexport class AssetLoader {\r\n private resources: AssetResources;\r\n constructor(resources: AssetResources = {}) {\r\n this.resources = resources;\r\n };\r\n private async fetchData(url: string) {\r\n try {\r\n const response = await fetch(url);\r\n if (!response.ok) {\r\n throw new Error(`HTTP Error: ${response.status} ${response.statusText}`)\r\n }\r\n return await response.json();\r\n }\r\n catch (error) {\r\n console.error(`❌ Failed to fetch JSON at: ${url}\\n`, error);\r\n throw new Error(`FetchDataError\\nURL: ${url}\\nReason: ${(error as Error).message}`);\r\n }\r\n };\r\n private loadImages(): Promise<Record<string, HTMLImageElement>> {\r\n const imagesUrl: string = this.resources.images_JSON_path ?? '';\r\n if (imagesUrl === '') {\r\n console.warn(\"NO IMAGES PASSED TO LOAD\")\r\n return Promise.resolve({});\r\n };\r\n return new Promise((resolve, reject) => {\r\n this.fetchData(imagesUrl)\r\n .then((data) => {\r\n const keys: string[] = Object.keys(data);\r\n const length: number = keys.length;\r\n let loadCount: number = 0;\r\n let images: Record<string, HTMLImageElement> = {};\r\n\r\n keys.forEach((key) => {\r\n const image = new Image();\r\n image.src = data[key];\r\n images[key] = image;\r\n\r\n image.onload = () => {\r\n loadCount++;\r\n if (loadCount === length) {\r\n resolve(images);\r\n }\r\n };\r\n\r\n image.onerror = () => {\r\n console.error(`❌ Failed to load image: ${data[key]}`);\r\n reject(new Error(`ImageLoadError\\nFile: ${data[key]}`));\r\n }\r\n })\r\n })\r\n .catch((error) => {\r\n console.error(\"❌ Failed during image JSON loading:\", error);\r\n reject(new Error(`ImageLoadingError\\n${(error as Error).message}`));\r\n })\r\n })\r\n\r\n };\r\n private loadAudio(): Promise<Record<string, HTMLAudioElement>> {\r\n const audioUrl: string = this.resources.audio_JSON_path ?? '';\r\n if (audioUrl === '') {\r\n console.warn(\"⚠️ No audio JSON path provided.\");\r\n return Promise.resolve({});\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n this.fetchData(audioUrl)\r\n .then((data) => {\r\n const keys = Object.keys(data);\r\n let loadCount = 0;\r\n const audios: Record<string, HTMLAudioElement> = {};\r\n\r\n keys.forEach((key) => {\r\n const audio = new Audio();\r\n audio.src = data[key];\r\n audio.preload = 'auto';\r\n audios[key] = audio;\r\n\r\n // There’s no onload for <audio> reliably — you can use `canplaythrough` instead\r\n audio.addEventListener(\"canplaythrough\", () => {\r\n loadCount++;\r\n if (loadCount === keys.length) {\r\n resolve(audios);\r\n }\r\n });\r\n\r\n audio.onerror = () => {\r\n console.error(`❌ Failed to load audio: ${data[key]}`);\r\n reject(new Error(`AudioLoadError\\nFile: ${data[key]}`));\r\n };\r\n });\r\n })\r\n .catch((error) => {\r\n console.error(\"❌ Failed during audio JSON loading:\", error);\r\n reject(new Error(`AudioLoadingError\\n${(error as Error).message}`));\r\n });\r\n });\r\n }\r\n\r\n async loadAll() {\r\n const imagesPromise = this.loadImages();\r\n const audioPromise = this.loadAudio();\r\n\r\n const jsonPromises: Promise<any>[] = [];\r\n const jsonKeys: string[] = [];\r\n\r\n if (this.resources.jsons) {\r\n for (const [key, url] of Object.entries(this.resources.jsons)) {\r\n jsonKeys.push(key);\r\n jsonPromises.push(this.fetchData(url));\r\n }\r\n }\r\n\r\n try {\r\n const [imagesData, audioData, ...customJsons] = await Promise.all([\r\n imagesPromise,\r\n audioPromise,\r\n ...jsonPromises,\r\n ]);\r\n\r\n const customJsonData: Record<string, any> = {};\r\n jsonKeys.forEach((key, i) => {\r\n customJsonData[key] = customJsons[i];\r\n });\r\n\r\n return {\r\n imagesData,\r\n audioData,\r\n customJsonData,\r\n };\r\n } catch (error) {\r\n console.error(\"❌ Failed to load all resources:\", error);\r\n throw new Error(`LoadAllResourcesError\\n${(error as Error).message}`);\r\n }\r\n }\r\n}","import { EntityId } from \"entix-ecs\";\r\nimport type { Vector2, GlobalPosition, TransformOptions, ScaleOptions, SpriteOptions, EntityActiveOptions, ParentOptions, SceneOptions, RectangleOptions, CircleOptions, ShapeType, ShapeOptions, TriangleOptions, TextOptions, ButtonOptions } from \"./types\";\r\nexport class Rotation {\r\n public value: number = 0;\r\n constructor(value: number = 0) {\r\n this.value = value ?? 0;\r\n }\r\n};\r\nexport class Scale {\r\n public value: Vector2 = {x:1,y:1};\r\n constructor(options:ScaleOptions = {}){\r\n this.value = options.value ?? {x:1,y:1};\r\n }\r\n};\r\nexport class Transform {\r\n public globalPosition: GlobalPosition;\r\n //public localPosition: LocalPosition;\r\n public rotation: Rotation;\r\n public scale: Scale;\r\n constructor(options: TransformOptions = {}) {\r\n this.globalPosition = {position:options.globalPosition ?? {x:0,y:0}};\r\n //this.localPosition = {position:options.localPosition ?? {x:0,y:0}};\r\n this.rotation = new Rotation(options.rotation?.value);\r\n this.scale = new Scale({value:options.scale?.value});\r\n }\r\n\r\n};\r\nexport class Sprite{\r\n public image: HTMLImageElement;\r\n public width: number;\r\n public height: number;\r\n public alpha: number;\r\n public rotation: number;\r\n public layer:number;\r\n public active:boolean;\r\n public blocksInput:boolean = true;\r\n constructor(options:SpriteOptions){\r\n this.image = options.image;\r\n this.width = options.width;\r\n this.height = options.height;\r\n this.alpha = options.alpha ?? 1;\r\n this.rotation = options.rotation ?? 0;\r\n this.layer = options.layer ?? 0;\r\n this.active = options.active ?? true;\r\n this.blocksInput = options.blocksInput ?? true;\r\n }\r\n};\r\nexport class EntityActive{\r\n public value:boolean = true;\r\n constructor(options:EntityActiveOptions = {}){\r\n this.value = options.value ?? true;\r\n }\r\n};\r\nexport class Parent{\r\n public value:EntityId | null = null;\r\n constructor(options:ParentOptions = {}){\r\n this.value = options.value ?? null;\r\n }\r\n};\r\nexport class Children{// increment through code\r\n public value:EntityId[] = [];\r\n};\r\nexport class Scene{\r\n public name:string = '';\r\n public onLoad:Function;\r\n public onUnload:Function;\r\n constructor(options:SceneOptions){\r\n this.name = options.name;\r\n this.onLoad = options.onLoad ?? (()=>{});\r\n this.onUnload = options.onUnload ?? (()=>{});\r\n\r\n }\r\n};\r\nexport class Shape{\r\n public shape:ShapeType;\r\n public color:string = 'green';\r\n public outlineEnabled:boolean = false;\r\n public outlineWidth:number = 3;\r\n public outlineColor:string = 'black';\r\n public alpha:number = 1;\r\n public active:boolean = true;\r\n public layer:number = 0;\r\n public blocksInput:boolean = true;\r\n constructor(options:ShapeOptions){\r\n this.shape = options.shape;\r\n this.color = options.color ?? 'green';\r\n this.outlineEnabled = options.outlineEnabled ?? false;\r\n this.outlineWidth = options.outlineWidth ?? 3;\r\n this.outlineColor = options.color ?? 'black';\r\n this.alpha = options.alpha ?? 1;\r\n this.active = options.active ?? true;\r\n this.layer = options.layer ?? 0;\r\n this.blocksInput = options.blocksInput ?? true;\r\n }\r\n}; \r\nexport class Rectangle{\r\n public width:number;\r\n public height:number;\r\n public centered:boolean = true;\r\n public rotation:number = 0;\r\n public rounded:boolean = false;\r\n public roundedRadius:number = 5;\r\n constructor(options:RectangleOptions){\r\n this.width = options.width ?? 40;\r\n this.height = options.height ?? 40;\r\n this.centered = options.centered ?? true;\r\n this.rotation = options.rotation ?? 0;\r\n this.rounded = options.rounded ?? false;\r\n this.roundedRadius = options.roundRadius ?? 5;\r\n }\r\n};\r\nexport class Circle{\r\n public radius:number;\r\n constructor(options:CircleOptions){\r\n this.radius = options.radius ?? 40;\r\n }\r\n};\r\nexport class Triangle{\r\n public p1:Vector2;// side 1\r\n public p2: Vector2;// side 2\r\n public p3 : Vector2 // side 3\r\n public centered:boolean = true;\r\n public rotation:number = 0;\r\n constructor(options:TriangleOptions){\r\n this.p1 = options.p1 ?? {x:30,y:0};\r\n this.p2 = options.p2 ?? {x:0,y:59};\r\n this.p3 = options.p3 ?? {x:30,y:59};\r\n this.centered = options.centered ?? true;\r\n this.rotation = options.rotation ?? 0;\r\n }\r\n};\r\nexport class Text{\r\n public content:string = 'text';\r\n public size:number = 16;\r\n public color:string = 'white';\r\n public outlineEnabled:boolean = false;\r\n public outlineWidth:number = 2;\r\n public outlineColor:string = 'black';\r\n public alpha:number = 1;\r\n public active:boolean = true;\r\n public layer:number = 0;\r\n public rotation:number = 0;\r\n public style:string = 'sans-serif';\r\n public maxWidth:number = 200;\r\n constructor(options:TextOptions){\r\n this.content = options.content ?? 'text';\r\n this.size = options.size ?? 16;\r\n this.color = options.color ?? 'white';\r\n this.outlineEnabled = options.outlineEnabled ?? false;\r\n this.outlineWidth = options.outlineWidth ?? 2;\r\n this.outlineColor = options.outlineColor ?? 'black';\r\n this.alpha = options.alpha ?? 1;\r\n this.active = options.active ?? true;\r\n this.layer = options.layer ?? 0;\r\n this.rotation = options.rotation ?? 0;\r\n this.style = options.style ?? 'sans-serif';\r\n this.maxWidth = options.maxWidth ?? 200;\r\n }\r\n};\r\nexport class Button{\r\n public pressArea:Vector2 = {x:100,y:100};\r\n public onJustPressed:Function = ()=>{};\r\n public onPress:Function = ()=>{};\r\n public onJustReleased:Function = ()=>{};\r\n public onJustHovered:Function = ()=>{};\r\n public onHovered:Function = ()=>{};\r\n public onHoveredReleased:Function = ()=>{};\r\n public showPressArea:boolean = false;\r\n public pressAreaShowColor:string = 'green';\r\n public layer:number = 0;\r\n public active:boolean = true;\r\n public changeCursorToPointer:boolean = true;\r\n public isHovered:boolean = false;\r\n constructor(options:ButtonOptions){\r\n this.pressArea = options.pressArea ?? {x:100,y:100};\r\n this.onJustPressed = options.onJustPressed ?? (()=>{});\r\n this.onPress = options.onPress ?? (()=>{});\r\n this.onJustReleased = options.onReleased ?? (()=>{});\r\n this.onJustHovered = options.onJustHovered ?? (()=>{});\r\n this.onHovered = options.onHovered ?? (()=>{});\r\n this.onHoveredReleased = options.onHoveredReleased ??(()=>{})\r\n this.showPressArea = options.showPressArea ?? false;\r\n this.pressAreaShowColor = options.pressAreaShowColor ?? 'green';\r\n this.layer = options.layer ?? 0;\r\n this.active = options.active ?? true;\r\n this.changeCursorToPointer = options.changeCursorToPointer ?? true;\r\n }\r\n};\r\n/*\r\nexport class RectButton{\r\n public button:Button;\r\n public shape:Shape;\r\n public text:Text;\r\n constructor(options:RectButtonOptions){\r\n this.button = options.button ?? new Button({\r\n\r\n });\r\n this.shape = options.shape ?? new Shape({\r\n shape:new Rectangle({\r\n \r\n })\r\n });\r\n this.text = options.text ?? new Text({\r\n\r\n })\r\n };\r\n};\r\n*/","import { EntityId, EntityManager } from \"entix-ecs\";\r\nimport { Engine } from \"../engine\";\r\nimport { Transform, Sprite, EntityActive, Shape, Rectangle, Circle, Triangle, Text, Button } from \"../components\";\r\nimport { Vector2 } from \"../types\";\r\n\r\n//RENDERING\r\n\r\nexport function renderingSystem(engine: Engine) {// some total of all, uses layer filter\r\n const em: EntityManager = engine.getEntityManager();\r\n\r\n const drawCalls: {\r\n layer: number;\r\n drawFn: () => void;\r\n }[] = [];\r\n\r\n for (const id of em.getAllEntities()) {\r\n if (!engine.isEntityActive(id)) continue;\r\n const transform = em.getComponent(id, Transform);\r\n if (!transform) continue;\r\n\r\n // Handle Sprite\r\n const sprite = em.getComponent(id, Sprite);\r\n if (sprite) {\r\n if (!sprite.active) continue;\r\n drawCalls.push({\r\n layer: sprite.layer ?? 0,\r\n drawFn: () => spriteRenderingSystem(engine, id)\r\n });\r\n };\r\n //Handle Shapes\r\n const shape = em.getComponent(id, Shape);\r\n if (shape) {\r\n if (!shape.active) continue;\r\n\r\n const layer: number = shape.layer;\r\n let drawFn: (() => void) | null = null;\r\n\r\n if (shape.shape instanceof Rectangle) {\r\n drawFn = () => rectangleRenderingSystem(engine, id);\r\n }\r\n else if (shape.shape instanceof Circle) {\r\n drawFn = () => circleRenderingSystem(engine, id);\r\n }\r\n else if (shape.shape instanceof Triangle) {\r\n drawFn = () => triangleRenderingSystem(engine, id);\r\n }\r\n if (drawFn) {\r\n drawCalls.push({\r\n layer: layer,\r\n drawFn: drawFn\r\n });\r\n };\r\n }\r\n //Handle Text\r\n const text = em.getComponent(id, Text);\r\n if (text) {\r\n if (!text.active) return;\r\n drawCalls.push({\r\n layer: text.layer,\r\n drawFn: () => textRenderingSystem(engine, id)\r\n })\r\n }\r\n //Handle ButtonPressArea\r\n const button = em.getComponent(id, Button);\r\n if (button) {\r\n if (button.showPressArea) {\r\n drawCalls.push({\r\n layer: button.layer,\r\n drawFn: () => buttonPressAreaRendering(engine, id)\r\n })\r\n }\r\n }\r\n };\r\n\r\n drawCalls.sort((a, b) => a.layer - b.layer);\r\n\r\n // Execute all draw calls\r\n for (const draw of drawCalls) {\r\n draw.drawFn();\r\n }\r\n};\r\n\r\n//RENDERING SPRITE , SUB SYSTEM!\r\nfunction spriteRenderingSystem(engine: Engine, id: EntityId) {\r\n const em: EntityManager = engine.getEntityManager();\r\n const transform = em.getComponent(id, Transform);\r\n const sprite = em.getComponent(id, Sprite);\r\n if (!transform) throw new Error(\"TRANSFORM NULL IN SPRITE RENDERING SYSTEM! \" + id);\r\n if (!sprite) throw new Error(\"SPRITE NULL IN SPRITE RENDERING SYSTEM! \" + id);\r\n engine.drawSprite(transform, sprite);\r\n};\r\n\r\n//RENDERING RECTANGLES, SUB SYSTEM!\r\nfunction rectangleRenderingSystem(engine: Engine, id: EntityId) {\r\n const em: EntityManager = engine.getEntityManager();\r\n const ctx = engine.getCtx();\r\n const transform = em.getComponent(id, Transform);\r\n const shape = em.getComponent(id, Shape);\r\n if (!transform) throw new Error(\"TRANSFORM NULL IN RECTANGLE RENDERING STSTEM !\" + id);\r\n if (!shape) throw new Error(\"SHAPE NULL IN RECTANGLE RENDERING SYSTEM! \" + id);\r\n const rectangle = shape.shape;\r\n if ((rectangle instanceof Rectangle)) {\r\n const x: number = transform.globalPosition.position.x;\r\n const y: number = transform.globalPosition.position.y;\r\n const w: number = rectangle.width;\r\n const h: number = rectangle.height;\r\n const selfRotationInRadians = (Math.PI * rectangle.rotation) / 180;\r\n const transformRotationInRadians = (Math.PI * transform.rotation.value) / 180\r\n const totalRotationInRadians = selfRotationInRadians + transformRotationInRadians;\r\n const offset: Vector2 = {\r\n x: rectangle.centered ? -w / 2 : 0,\r\n y: rectangle.centered ? -h / 2 : 0\r\n }\r\n\r\n ctx.save();\r\n\r\n //SHAPE\r\n ctx.fillStyle = shape.color;\r\n ctx.globalAlpha = shape.alpha;\r\n\r\n ctx.beginPath();\r\n\r\n ctx.translate(x, y);\r\n\r\n ctx.rotate(totalRotationInRadians);\r\n\r\n ctx.scale(transform.scale.value.x, transform.scale.value.y);\r\n\r\n if (rectangle.rounded) {\r\n ctx.roundRect(offset.x, offset.y, w, h, rectangle.roundedRadius);\r\n }\r\n else {\r\n ctx.rect(offset.x, offset.y, w, h);\r\n }\r\n\r\n\r\n ctx.fill();\r\n\r\n ctx.closePath();\r\n\r\n //OUTLINE\r\n\r\n if (shape.outlineEnabled) {\r\n ctx.strokeStyle = shape.outlineColor;\r\n ctx.lineWidth = shape.outlineWidth;\r\n\r\n ctx.beginPath();\r\n\r\n if (rectangle.rounded) {\r\n ctx.roundRect(offset.x, offset.y, w, h, rectangle.roundedRadius);\r\n }\r\n else {\r\n ctx.rect(offset.x, offset.y, w, h);\r\n }\r\n\r\n\r\n ctx.stroke();\r\n\r\n ctx.closePath();\r\n };\r\n\r\n ctx.restore();\r\n }\r\n else {\r\n console.warn(\"SHAPE IS NOT A RECTANGLE BUT IS IN RECTANGLE RENDERING SYSTEM! \" + id);\r\n }\r\n\r\n};\r\n\r\n//RENDERING CIRCLES, SUB SYSTEM! \r\nfunction circleRenderingSystem(engine: Engine, id: EntityId) {\r\n const em: EntityManager = engine.getEntityManager();\r\n const ctx = engine.getCtx();\r\n const transform = em.getComponent(id, Transform);\r\n const shape = em.getComponent(id, Shape);\r\n if (!transform) throw new Error(\"TRANSFORM NULL IN RECTANGLE RENDERING STSTEM !\" + id);\r\n if (!shape) throw new Error(\"SHAPE NULL IN RECTANGLE RENDERING SYSTEM! \" + id);\r\n if ((shape.shape instanceof Circle)) {\r\n const x: number = transform.globalPosition.position.x;\r\n const y: number = transform.globalPosition.position.y;\r\n const r: number = shape.shape.radius;\r\n\r\n ctx.save();\r\n\r\n //SHAPE\r\n\r\n ctx.fillStyle = shape.color;\r\n ctx.globalAlpha = shape.alpha;\r\n\r\n ctx.beginPath();\r\n\r\n ctx.translate(x, y);\r\n\r\n ctx.scale(transform.scale.value.x, transform.scale.value.y);\r\n\r\n ctx.arc(0, 0, r, 0, Math.PI * 2);\r\n\r\n ctx.fill();\r\n\r\n ctx.closePath();\r\n\r\n //OUTLINE\r\n\r\n if (shape.outlineEnabled) {\r\n ctx.strokeStyle = shape.outlineColor;\r\n ctx.lineWidth = shape.outlineWidth;\r\n\r\n ctx.beginPath();\r\n\r\n ctx.arc(0, 0, r, 0, Math.PI * 2);\r\n\r\n ctx.stroke();\r\n\r\n ctx.closePath();\r\n }\r\n\r\n ctx.restore();\r\n }\r\n else {\r\n console.warn(\"SHAPE IS NOT A CIRCLE BUT IS IN RECTANGLE RENDERING SYSTEM! \" + id);\r\n }\r\n};\r\n\r\n//RENDERING TRIANGLES, SUB SYSTEM!\r\n\r\nfunction triangleRenderingSystem(engine: Engine, id: EntityId) {\r\n const em: EntityManager = engine.getEntityManager();\r\n const ctx = engine.getCtx();\r\n const transform = em.getComponent(id, Transform);\r\n const shape = em.getComponent(id, Shape);\r\n if (!transform) throw new Error(\"TRANSFORM NULL IN TRIANGLE RENDERING SYSTEM !\" + id);\r\n if (!shape) throw new Error(\"SHAPE NULL IN TRIANGLE RENDERING SYSTEM! \" + id);\r\n const triangle = shape.shape;\r\n if (!(triangle instanceof Triangle)) {\r\n console.warn(\"SHAPE IS NOT A TRIANGLE BUT IS IN TRIANGLE RENDERING SYSTEM! \" + id);\r\n return;\r\n }\r\n\r\n const pos = transform.globalPosition.position;\r\n const selfRotationInRadians = (Math.PI * triangle.rotation) / 180;\r\n const transformRotationInRadians = (Math.PI * transform.rotation.value) / 180\r\n const totalRotationInRadians = selfRotationInRadians + transformRotationInRadians;\r\n\r\n // Step 1: calculate local triangle points relative to center or p1\r\n let localP1: Vector2, localP2: Vector2, localP3: Vector2;\r\n\r\n if (triangle.centered) {\r\n const centroid = {\r\n x: (triangle.p1.x + triangle.p2.x + triangle.p3.x) / 3,\r\n y: (triangle.p1.y + triangle.p2.y + triangle.p3.y) / 3,\r\n };\r\n\r\n localP1 = { x: triangle.p1.x - centroid.x, y: triangle.p1.y - centroid.y };\r\n localP2 = { x: triangle.p2.x - centroid.x, y: triangle.p2.y - centroid.y };\r\n localP3 = { x: triangle.p3.x - centroid.x, y: triangle.p3.y - centroid.y };\r\n } else {\r\n // treat p1 as origin\r\n localP1 = { x: 0, y: 0 };\r\n localP2 = { x: triangle.p2.x - triangle.p1.x, y: triangle.p2.y - triangle.p1.y };\r\n localP3 = { x: triangle.p3.x - triangle.p1.x, y: triangle.p3.y - triangle.p1.y };\r\n }\r\n\r\n // Step 2: draw using local coordinates, then translate/rotate\r\n ctx.save();\r\n\r\n ctx.fillStyle = shape.color;\r\n ctx.globalAlpha = shape.alpha;\r\n\r\n ctx.translate(pos.x, pos.y);\r\n ctx.rotate(totalRotationInRadians);\r\n ctx.scale(transform.scale.value.x, transform.scale.value.y);\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(localP1.x, localP1.y);\r\n ctx.lineTo(localP2.x, localP2.y);\r\n ctx.lineTo(localP3.x, localP3.y);\r\n ctx.closePath();\r\n ctx.fill();\r\n\r\n if (shape.outlineEnabled) {\r\n ctx.strokeStyle = shape.outlineColor;\r\n ctx.lineWidth = shape.outlineWidth;\r\n\r\n ctx.beginPath();\r\n ctx.moveTo(localP1.x, localP1.y);\r\n ctx.lineTo(localP2.x, localP2.y);\r\n ctx.lineTo(localP3.x, localP3.y);\r\n ctx.closePath();\r\n ctx.stroke();\r\n }\r\n\r\n ctx.restore();\r\n};\r\n\r\n//TEXT RENDERING SYSTEM, SUB SYSTEM!\r\n\r\nfunction textRenderingSystem(engine: Engine, id: EntityId) {\r\n const em: EntityManager = engine.getEntityManager();\r\n const ctx: CanvasRenderingContext2D = engine.getCtx();\r\n const transform = em.getComponent(id, Transform);\r\n const text = em.getComponent(id, Text);\r\n if (!transform) throw new Error(\"TRANSFORM NOT FOUND IN TEXT RENDERING SYSTEM! \" + id);\r\n if (!text) throw new Error(\"TEXT NOT FOUND IN TEXT RENDERING SYSTEM! \" + id);\r\n\r\n const pos: Vector2 = transform.globalPosition.position;\r\n const selfRotationInRadians = (Math.PI * text.rotation) / 180;\r\n const transformRotationInRadians = (Math.PI * transform.rotation.value) / 180\r\n const totalRotationInRadians = selfRotationInRadians + transformRotationInRadians;\r\n\r\n ctx.save();\r\n\r\n ctx.translate(pos.x, pos.y);\r\n ctx.rotate(totalRotationInRadians);\r\n ctx.scale(transform.scale.value.x, transform.scale.value.y);\r\n\r\n //DRAWING TEXT\r\n\r\n ctx.beginPath();\r\n ctx.textAlign = 'center';\r\n ctx.textBaseline = 'middle';\r\n ctx.fillStyle = text.color;\r\n ctx.font = `${text.size}px ${text.style}`;\r\n ctx.fillText(text.content, 0, 0, 50);\r\n ctx.closePath();\r\n\r\n //OUTLINE\r\n\r\n if (text.outlineEnabled) {\r\n ctx.beginPath();\r\n ctx.strokeStyle = text.outlineColor;\r\n ctx.lineWidth = text.outlineWidth;\r\n ctx.font = `${text.size}px ${text.style}`;\r\n ctx.strokeText(text.content, 0, 0, 50);\r\n ctx.closePath();\r\n }\r\n\r\n ctx.restore();\r\n};\r\n\r\n//BUTTON PRESSAREA RENDEING, SUB SYSTEM!\r\n\r\nfunction buttonPressAreaRendering(engine: Engine, id: EntityId) {\r\n const em: EntityManager = engine.getEntityManager();\r\n const ctx: CanvasRenderingContext2D = engine.getCtx();\r\n const transform = em.getComponent(id, Transform);\r\n const button = em.getComponent(id, Button);\r\n if (!transform) throw new Error(\"TRANSFORM NOT FOUND IN BUTTON PRESSAREA RENDERING SYSTEM! \" + id);\r\n if (!button) throw new Error(\"BUTTON NOT FOUND IN BUTTON PRESSAREA RENDERING SYSTEM! \" + id);\r\n const pos: Vector2 = transform?.globalPosition.position;\r\n const w: number = button.pressArea.x;\r\n const h: number = button.pressArea.y;\r\n const rotationInRadians: number = (Math.PI * transform.rotation.value) / 180;\r\n if (button.showPressArea) {\r\n ctx.save();\r\n ctx.translate(pos.x, pos.y);\r\n ctx.rotate(rotationInRadians);\r\n ctx.scale(transform.scale.value.x, transform.scale.value.y);\r\n ctx.beginPath();\r\n ctx.globalAlpha = 0.25;\r\n ctx.fillStyle = button.pressAreaShowColor;\r\n ctx.rect(-w / 2, -h / 2, button.pressArea.x, button.pressArea.y);\r\n ctx.fill();\r\n ctx.closePath();\r\n ctx.restore();\r\n };\r\n}","import { Engine } from \"./engine\";\r\nimport { Vector2 } from \"./types\";\r\nexport class Input {\r\n private engine: Engine;\r\n private position: Vector2 = { x: 0, y: 0 };\r\n private justPressed: boolean = false;//just pressed, used to detect clicks\r\n private pressed: boolean = false;// currently pressing\r\n private justReleased: boolean = false;//just released, used to detect mouse leaves\r\n constructor(engine: Engine) {\r\n this.engine = engine;\r\n\r\n this.start();\r\n };\r\n start() {\r\n this.engine.addUpdateFunction(this.update.bind(this));\r\n this.addListeners();\r\n };\r\n update() {\r\n //console.log(\"JUST PRESSED:\", this.justPressed);\r\n //console.log(\"PRESSED:\", this.pressed);\r\n //console.log(\"JUST RELEASED\", this.justReleased);\r\n //console.log(\"POS\", this.x, this.y);\r\n\r\n };\r\n updatePos(clientX: number, clientY: number) {\r\n const canvas: HTMLCanvasElement = this.engine.getCanvas();\r\n const rect: DOMRect = canvas.getBoundingClientRect();\r\n this.position.x = clientX - rect.left;\r\n this.position.y = clientY - rect.top;\r\n };\r\n addListeners() {\r\n const pressedListeners = ['mousedown', 'touchstart'];\r\n const moveListeners = ['mousemove', 'touchmove'];\r\n const releasedListeners = ['mouseup', 'touchend'];\r\n\r\n pressedListeners.forEach((event) => {\r\n this.engine.getCanvas().addEventListener(event, (e: Event) => {\r\n if (e instanceof MouseEvent) {\r\n this.updatePos(e.clientX, e.clientY);\r\n }\r\n else if (e instanceof TouchEvent) {\r\n this.updatePos(e.touches[0].clientX, e.touches[0].clientY);\r\n }\r\n this.justPressed = true;\r\n this.pressed = true;\r\n }, { passive: true });\r\n });\r\n\r\n moveListeners.forEach((event) => {// dont change press bools\r\n this.engine.getCanvas().addEventListener(event, (e: Event) => {\r\n if (e instanceof MouseEvent) {\r\n this.updatePos(e.clientX, e.clientY);\r\n }\r\n else if (e instanceof TouchEvent) {\r\n this.updatePos(e.touches[0].clientX, e.touches[0].clientY);\r\n }\r\n }, { passive: true });\r\n });\r\n\r\n releasedListeners.forEach((event) => {// dont change press bools\r\n this.engine.getCanvas().addEventListener(event, () => {\r\n this.justReleased = true;\r\n this.pressed = false;\r\n });\r\n });\r\n };\r\n resetStates() {\r\n this.justPressed = false;//reset\r\n this.justReleased = false;//reset\r\n }\r\n isOver(x: number, y: number, w: number, h: number) {\r\n return (\r\n this.position.x >= x &&\r\n this.position.x <= x + w &&\r\n this.position.y >= y &&\r\n this.position.y <= y + h\r\n );\r\n };\r\n //GETTERS\r\n getPosition(): Vector2 {\r\n return this.position;\r\n }\r\n getJustPressed(): boolean {\r\n return this.justPressed;\r\n };\r\n getPressed(): boolean {\r\n return this.pressed;\r\n };\r\n getJustReleased(): boolean {\r\n return this.justReleased;\r\n };\r\n};","import { EntityManager } from \"entix-ecs\";\r\nimport { Engine } from \"../engine\";\r\nimport { Input } from \"../input\";\r\nimport { Button, Transform } from \"../components\";\r\nimport { Vector2 } from \"../types\";\r\n\r\nexport function buttonActionSystem(engine: Engine) {\r\n const em: EntityManager = engine.getEntityManager();\r\n const input: Input = engine.getInput();\r\n const ctx: CanvasRenderingContext2D = engine.getCtx();\r\n let anyButtonHovered: boolean = false;\r\n em.query('All', {\r\n transform: Transform,\r\n button: Button\r\n }, (id, { transform, button }) => {\r\n if (!engine.isEntityActive(id) || !button.active) {\r\n if (button.isHovered) {\r\n button.isHovered = false;\r\n button.onHoveredReleased();\r\n }\r\n return;\r\n };\r\n\r\n const pos: Vector2 = transform.globalPosition.position;\r\n const w: number = button.pressArea.x;\r\n const h: number = button.pressArea.y;\r\n const scaledW:number = w * transform.scale.value.x;\r\n const scaledH:number = h * transform.scale.value.y;\r\n const centeredX: number = pos.x - w / 2;\r\n const centeredY: number = pos.y - h / 2;\r\n \r\n const isHovering = input.isOver(centeredX, centeredY, scaledW, scaledH);\r\n\r\n if (isHovering) {\r\n if(engine.isEntityBlockingInput(input.getPosition().x, input.getPosition().y, button.layer))return;\r\n anyButtonHovered = true;\r\n if (!button.isHovered) {\r\n button.isHovered = true;\r\n button.onJustHovered();\r\n };\r\n button.onHovered();\r\n //JUST PRESSED\r\n if (input.getJustPressed()) {\r\n button.onJustPressed();\r\n };\r\n //PRESSED\r\n if (input.getPressed()) {\r\n button.onPress();\r\n };\r\n //JUST RELEASED\r\n if (input.getJustReleased()) {\r\n button.onJustReleased();\r\n }\r\n }\r\n else {\r\n if (button.isHovered) {\r\n button.isHovered = false;\r\n button.onHoveredReleased();\r\n }\r\n }\r\n\r\n if (button.changeCursorToPointer) {\r\n const canvas = engine.getCanvas();\r\n if (anyButtonHovered) {\r\n canvas.style.cursor = 'pointer';\r\n } else {\r\n canvas.style.cursor = 'default';\r\n }\r\n }\r\n\r\n });\r\n};","import { EntityId, EntityManager } from \"entix-ecs\";\r\nimport { Engine } from \"./engine\";\r\nimport { Children, Circle, EntityActive, Parent, Rectangle, Scene, Shape, Sprite, Transform, Triangle, Text, Button } from \"./components\";\r\nimport { Vector2 } from \"./types\";\r\n\r\nexport class EntityTemplates {\r\n private engine: Engine;\r\n private em: EntityManager;;\r\n constructor(engine: Engine) {\r\n this.engine = engine;\r\n this.em = engine.getEntityManager();\r\n };\r\n createEmptyEntity(parent?: EntityId): EntityId {// creates a entity with basic Transform component\r\n const id: EntityId = this.em.createEntity();// new entity created\r\n this.em.addComponent(id, Transform, new Transform({}));\r\n this.em.addComponent(id, EntityActive, new EntityActive({}));\r\n this.em.addComponent(id, Parent, new Parent());\r\n this.em.addComponent(id, Children, new Children());\r\n\r\n if (parent) {//assign only if a valid parent is passed\r\n this.engine.addParent(id, parent);\r\n }\r\n return id;\r\n };\r\n createSpriteEntity(image: HTMLImageElement, width: number, height: number, parent?: EntityId): EntityId {//creates a basic sprite entity\r\n const id: EntityId = this.createEmptyEntity(parent);\r\n this.em.addComponent(id, Sprite, new Sprite({\r\n image: image,\r\n width: width,\r\n height: height\r\n }));\r\n\r\n return id;\r\n };\r\n createSceneEntity(name: string = 'unnamedScene'): EntityId {// creates a scene entity\r\n const id: EntityId = this.createEmptyEntity();//do not require parent\r\n this.em.addComponent(id, Scene, new Scene({ name: name }));\r\n this.em.addComponent(id, Children, new Children());\r\n this.engine.addScene(id);\r\n return id;\r\n };\r\n createRectangleEntity(width: number, height: number, parent?: EntityId): EntityId {\r\n const id: EntityId = this.createEmptyEntity(parent);\r\n this.em.addComponent(id, Shape, new Shape({\r\n shape: new Rectangle({\r\n width: width,\r\n height: height\r\n })\r\n }));\r\n return id;\r\n };\r\n createCircleEntity(radius: number, parent?: EntityId) {\r\n const id: EntityId = this.createEmptyEntity(parent);\r\n this.em.addComponent(id, Shape, new Shape({\r\n shape: new Circle({\r\n radius: radius\r\n })\r\n }));\r\n return id;\r\n };\r\n createTriangleEntity(p1: Vector2, p2: Vector2, p3: Vector2, parent?: EntityId): EntityId {\r\n const id: EntityId = this.createEmptyEntity(parent);\r\n this.em.addComponent(id, Shape, new Shape({\r\n shape: new Triangle({\r\n p1: p1,\r\n p2: p2,\r\n p3: p3\r\n })\r\n }));\r\n return id;\r\n };\r\n createTextEntity(content: string, parent?: EntityId): EntityId {\r\n const id: EntityId = this.createEmptyEntity(parent);\r\n this.em.addComponent(id, Text, new Text({\r\n content: content\r\n }));\r\n return id;\r\n };\r\n createRectButtonEntity(width: number, height: number, textContent?: string, parent?: EntityId): EntityId {\r\n const id: EntityId = this.createEmptyEntity(parent);\r\n this.em.addComponent(id, Button, new Button(\r\n {\r\n pressArea: { x: width, y: height }\r\n }\r\n ));\r\n this.em.addComponent(id, Shape, new Shape({\r\n shape: new Rectangle({\r\n width: width,\r\n height: height\r\n }),\r\n blocksInput:false\r\n }));\r\n this.em.addComponent(id,Text,new Text({\r\n content:textContent\r\n }));\r\n