diff --git a/src/framework/entity.js b/src/framework/entity.js index 7855b535a2f..e2948d60d5d 100644 --- a/src/framework/entity.js +++ b/src/framework/entity.js @@ -379,6 +379,36 @@ class Entity extends GraphNode { }); } + /** + * Search the entity and all of its descendants for the first script instance of specified type. + * + * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @returns {import('./script/script-type.js').ScriptType|undefined} A script instance of specified type, if the entity or any of its descendants + * has one. Returns undefined otherwise. + * @example + * // Get the first found "playerController" instance in the hierarchy tree that starts with this entity + * var controller = entity.findScript("playerController"); + */ + findScript(nameOrType) { + const entity = this.findOne(node => node.c?.script?.has(nameOrType)); + return entity?.c.script.get(nameOrType); + } + + /** + * Search the entity and all of its descendants for all script instances of specified type. + * + * @param {string|Class} nameOrType - The name or type of {@link ScriptType}. + * @returns {import('./script/script-type.js').ScriptType[]} All script instances of specified type in the entity or any of its + * descendants. Returns empty array if none found. + * @example + * // Get all "playerController" instances in the hierarchy tree that starts with this entity + * var controllers = entity.findScripts("playerController"); + */ + findScripts(nameOrType) { + const entities = this.find(node => node.c?.script?.has(nameOrType)); + return entities.map(entity => entity.c.script.get(nameOrType)); + } + /** * Get the GUID value for this Entity. * diff --git a/test/framework/entity.test.mjs b/test/framework/entity.test.mjs index 33fd834c400..4bbd61bc05e 100644 --- a/test/framework/entity.test.mjs +++ b/test/framework/entity.test.mjs @@ -675,6 +675,151 @@ describe('Entity', function () { }); + describe('#findScript', function () { + + it('finds script on single entity', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const script = e.findScript('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('returns undefined when script is not found', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + const script = root.findScript('myScript'); + expect(script).to.be.undefined; + }); + + it('returns undefined when script component is not found', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + const script = root.findScript('myScript'); + expect(script).to.be.undefined; + }); + + it('finds script on child entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + child.script.create('myScript'); + const script = root.findScript('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('finds script on grandchild entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + grandchild.addComponent('script'); + grandchild.script.create('myScript'); + const script = root.findScript('myScript'); + expect(script).to.be.an.instanceof(MyScript); + }); + + it('does not find script on parent entity', function () { + createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + root.script.create('myScript'); + const script = child.findScript('myScript'); + expect(script).to.be.undefined; + }); + + }); + + describe('#findScripts', function () { + + it('finds scripts on single entity', function () { + const MyScript = createScript('myScript'); + const e = new Entity(); + e.addComponent('script'); + e.script.create('myScript'); + const scripts = e.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + + it('returns empty array when no scripts are found', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + const scripts = root.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('returns empty array when no script component are found', function () { + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + const scripts = root.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + it('finds scripts on child entity', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + child.addComponent('script'); + child.script.create('myScript'); + const scripts = root.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(1); + expect(scripts[0]).to.be.an.instanceof(MyScript); + }); + + it('finds scripts on 3 entity hierarchy', function () { + const MyScript = createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + const grandchild = new Entity(); + root.addChild(child); + child.addChild(grandchild); + root.addComponent('script'); + root.script.create('myScript'); + child.addComponent('script'); + child.script.create('myScript'); + grandchild.addComponent('script'); + grandchild.script.create('myScript'); + const scripts = root.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(3); + expect(scripts[0]).to.be.an.instanceof(MyScript); + expect(scripts[1]).to.be.an.instanceof(MyScript); + expect(scripts[2]).to.be.an.instanceof(MyScript); + }); + + it('does not find scripts on parent entity', function () { + createScript('myScript'); + const root = new Entity(); + const child = new Entity(); + root.addChild(child); + root.addComponent('script'); + root.script.create('myScript'); + const scripts = child.findScripts('myScript'); + expect(scripts).to.be.an('array'); + expect(scripts.length).to.equal(0); + }); + + }); + describe('#removeComponent', function () { it('removes a component from the entity', function () { diff --git a/utils/types-fixup.mjs b/utils/types-fixup.mjs index c078be9cd07..be5dff3c16a 100644 --- a/utils/types-fixup.mjs +++ b/utils/types-fixup.mjs @@ -4,6 +4,7 @@ import fs from 'fs'; const regex = /Class<(.*?)>/g; const paths = [ './types/framework/components/script/component.d.ts', + './types/framework/entity.d.ts', './types/framework/script/script-attributes.d.ts', './types/framework/script/script-registry.d.ts', './types/framework/script/script.d.ts' @@ -16,6 +17,10 @@ paths.forEach((path, index) => { if (index === 0) { dts += ` import { ScriptType } from '../../script/script-type.js'; +`; + } else if (index === 1) { + dts += ` +import { ScriptType } from './script/script-type.js'; `; } else { dts += `