/************************************************************************ * GameObject.js ************************************************************************ * Copyright (c) 2021 Pedro Tonini Rosenberg Schneider. * * This file is part of Pandora. * * Pandora is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Pandora is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Pandora. If not, see . *************************************************************************/ /** * The {@code GameObject} class represents a minimal structure for any object in the * game. All objects added to the tree, either as a root or a child of an object * on the tree must be or inherit from GameObject. * * ! All GameObjects need to be inside the tree to do anything (can be added as a child * ! of another GameObject on the tree or as a root). * * @author Pedro Schneider * * @class */ class GameObject { /** * Creates an empty GameObject, with default properties. * * @param {String} name name of the new GameObject. * * @constructor */ constructor(name) { this.id = 0; // Global unique indentifier for the object. this.name = name; // Set the name for the object. this.children = []; // List of children. this.parented = false; // Does this GameObject have a parent? this.parent = null; // Who is the parent? null if orphan. this.isOnTree = false; // Is this GameObject on the tree? this.isRoot = false; // Is this GameObject a root object? this.signals = []; // List of signals. this.initSignals(); GameHandler.instanceGameObject(this); } /** * Returns the list of children of this GameObject. * * @returns {Array} array containing a reference to all of this GameObject's * children. */ getChildren() { return this.children; } /** * Query for a child of this GameObject with the child's index. * * ! The index refers to the order you added the children to this * ! GameObject, starting at 0. * * @param {String} idx index of the desired child on the GameObject's * children list. * * @returns {GameObject} a reference to the child with the given index, or null if * no child has that index. */ getChildByIndex(idx) { if (idx >= 0 && idx < this.children.length) return this.children[idx]; return null; } /** * Query for a child of this GameObject with the child's id. * * @param {String} id id of the desired child on the GameObject's * children list. * * @returns {GameObject} a reference to the child with the given id, or null if * no child has that id. */ getChildById(id) { for (let i = 0; i < this.children.length; i++) { if (this.children[i].id == id) return this.children[i]; } return null; } /** * Query for a child of this GameObject with the child's name. * * @param {String} name name of the desired child. * * @returns {GameObject} a reference to the child with the given name, or null * if no child has that name. */ getChildByName(name) { for (let i = 0; i < this.children.length; i++) if (this.children[i].name == name) return this.children[i]; return null; } /** * Get a reference to this GameObject's parent. * * @returns {GameObject} a reference to this GameObject's parent if it * exists, null if it doesn't. */ getParent() { if (!this.parented) return null; return this.parent; } /** * Add a new signal to this GameObject. * * @param {String} name name for the new signal. */ addSignal(name) { this.signals.push(new Signal(name)); } /** * Connect another GameObject to one of this GameObject's signals. * * @param {String} signalName name of the signal to be connected. * @param {GameObject} target reference to the GameObject that wants * to be connected to this signal. * @param {String} callback name of the method to be called every * time this signal is emited. */ connect(signalName, target, callback) { for (let i = 0; i < this.signals.length; i++) { if (this.signals[i].name == signalName) { this.signals[i].targets.push(target); this.signals[i].callbacks.push(callback); return; } } } /** * Emits one of this GameObject's signals. * * @param {String} signalName name of the signal to be emited. * @param {...any} params parameters the connected callbacks * should receive. */ emitSignal(signalName, ...params) { for (let i = 0; i < this.signals.length; i++) { if (this.signals[i].name == signalName) { for (let j = 0; j < this.signals[i].callbacks.length; j++) this.signals[i].targets[j][this.signals[i].callbacks[j]](...params); return; } } } /** * Add the GameObject as a child of this GameObject. * * @param {GameObject} child reference to the GameObject to be * added as a child. */ addChild(child) { child.parent = this; child.parented = true; this.children.push(child); if (this.isOnTree) child.setup(); } /** * Remove a child of this GameObject by its index. This action does not * delete the child GameObject; if this is the functionality you want, * freeing a game object from memory automatically removes it from its * parent. * * ! The index refers to the order you added the children to this * ! GameObject, starting at 0. * * @param {number} idx index of the child to be removed. */ removeChildByIndex(idx) { if (idx >= 0 && idx < this.children.length) this.children.splice(idx, 1); } /** * Remove a child of this GameObject by its id. This action does not * delete the child GameObject; if this is the functionality you want, * freeing a game object from memory automatically removes it from its * parent. * * @param {number} id id of the child to be removed. */ removeChildById(id) { for (let i = 0; i < this.children.length; i++) { if (this.children[i].id == id) { this.children.splice(i, 1); return; } } } /** * Remove a child of this GameObject by its name. This action does not * delete the child GameObject; if this is the functionality you want, * freeing a game object from memory automatically removes it from its * parent. * * @param {String} name name of the child to be removed. */ removeChildByName(name) { for (let i = 0; i < this.children.length; i++) { if (this.children[i].name == name) { this.children.splice(i, 1); return; } } } /** * De-parents the GameObject from its parent, or removes it from the root of the * tree if orphan, and recursively marks this GameObject's and all of its children's * memory for garbage collection. */ free() { if (this.parented) this.getParent().removeChildById(this.id); else if (this.isRoot) GameHandler.removeRootObjectById(this.id); this.destroy(); } /** * Recursively marks this GameObject's and all of its children's * memory for garbage collection. */ destroy() { for (let i = 0; i < this.children.length; i++) this.children[i].destroy(); for (var prop in this) this[prop] = null; } /** * Caller for the _initSignals() callback */ initSignals() { this._initSignals(); } /** * Caller for the _setup() callback. Recursively calls itself for all * of this GameObject's children. */ setup() { this.isOnTree = true; this._setup(); for (let i = 0; i < this.children.length; i++) { this.children[i].setup(); } } /** * Serves as a caller to this GameObject's _update method and recursively updates all of * this GameObject's children. * * ! This method only exists to modularize the code, and should not be used by the user. * * @param {number} delta ellapsed seconds since the last frame. */ updateChildren(delta) { this._update(delta); for (let i = 0; i < this.children.length; i++) { this.children[i].update(delta); } } /** * Caller for the _update(delta) callback. Recrusively calls itself for * all of this GameOject's children. * * @param {number} delta ellapsed seconds since the last frame. */ update(delta) { this.updateChildren(delta); } /** * Caller for the _draw(delta, db) callback. Recursively calls itself for * all of this GameObject's children. * * ! This method only exists to modularize the code, and should not be used by the user. * * @param {number} delta ellapsed seconds since the last frame. * @param {p5.Graphics} db secondary buffer to draw on. */ drawChildren(delta, db) { this._draw(delta, db); for (let i = 0; i < this.children.length; i++) this.children[i].draw(delta, db); } /** * Caller for the _draw(delta, db) callback. Recursively calls itself for * all of this GameObject's children. * * @param {number} delta ellapsed seconds since the last frame. * @param {p5.Graphics} db secondary buffer to draw on. */ draw(delta, db) { this.drawChildren(delta, db); } /** * ! This function should be overriden, it provides no default functionality. * This function is called once when the GameObject is created and should declare * any and all signals the user wants for the GameObject. * * @callback */ _initSignals() { } /** * ! This function should be overriden, it provides no default functionality. * This function is called once when the GameObject is added to the tree (as a child * of another GameObject or as a root). * * @callback */ _setup() { } /** * ! This function should be overriden, it provides no default functionality. * This function is called once at the start of every frame and should be used for * any logic that doesn't have anything to do with drawing graphics to the screen buffer. * * @param {number} delta ellapsed seconds since the last frame. * * @callback */ _update(delta) { } /** * ! This function should be overriden, it provides no default functionality. * This function is called once at the start of every frame after all update() calls * have been completed and should be used for any logic that results in something * beeing drawn to the screen buffer. * * @param {number} delta ellapsed seconds since the last frame. * @param {p5.Graphics} db secondary buffer to draw on. * * @callback */ _draw(delta, db) { } }