diff --git a/examples/src/enable.ts b/examples/src/enable.ts new file mode 100644 index 00000000..23293ce9 --- /dev/null +++ b/examples/src/enable.ts @@ -0,0 +1,205 @@ +import { Game, GameObject, resource, RESOURCE_TYPE, Component, System } from "@eva/eva.js"; +import { RendererSystem } from "@eva/plugin-renderer"; +import { Img, ImgSystem } from "@eva/plugin-renderer-img"; +export const name = 'Enable'; +export async function init(canvas) { + + class Move extends Component<{ speed: { x: number, y: number }, myName: string }> { + static componentName = 'Move'; + myName = '' + + speed = { + // 移动速度 + x: 100, + y: 200, + }; + oldSpeed: { x: number, y: number } + init(obj) { + Object.assign(this, obj); + console.log('init', this.myName); + } + onEnable() { + console.log('onEnable', this.myName) + } + onDisable(): void { + console.log('onDisable', this.myName) + } + awake() { + console.log('awake', this.myName); + } + start() { + console.log('start', this.myName); + } + update(e) { + // console.log('update', this.myName) + } + onPause() { + this.oldSpeed = this.speed; + this.speed = { + x: 0, + y: 0, + }; + } + onResume() { + this.speed = this.oldSpeed; + } + } + + // class DemoSystem extends System { + // init() { + // console.log('system init'); + // } + // awake() { + // console.log('system awake'); + // } + // start() { + // console.log('system start'); + // } + // update() { + // // console.log() + // } + // onPause() { + // console.log('system onPause'); + // } + // onResume() { + // console.log('system onResume'); + // } + // } + + resource.addResource([ + { + name: 'heart', + type: RESOURCE_TYPE.IMAGE, + src: { + image: { + type: 'png', + url: '//gw.alicdn.com/bao/uploaded/TB1lVHuaET1gK0jSZFhXXaAtVXa-200-200.png', + }, + }, + preload: false, + }, + ]); + + const game = new Game({ + systems: [ + new RendererSystem({ + canvas, + width: 750, + height: 1000, + }), + new ImgSystem(), + ], + }); + window.game = game + // game.addSystem(new DemoSystem()); + { + const image = new GameObject('image', { + size: { width: 200, height: 200 }, + origin: { x:0, y:0 }, + position: { + x: 0, + y: 0, + }, + }); + window.image = image + image.addComponent( + new Move({ + speed: { + x: 250, + y: 200, + }, + myName: 'aaa' + }), + ) + + image.addComponent( + new Img({ + resource: 'heart' + }) + ) + + game.scene.addChild(image) + const image1 = new GameObject('image1', { + size: { width: 200, height: 200 }, + origin: { x: 0, y: 0 }, + position: { + x: 100, + y: 100, + }, + }); + + image1.addComponent( + new Img({ + resource: 'heart' + }) + ) + image.addChild(image1); + + image1.addComponent( + new Move({ + speed: { + x: 250, + y: 200, + }, + myName: 'bbb' + }), + ) + + setTimeout(() => { + image.setActive(false) + console.log('0 0') + }, 1000) + + setTimeout(() => { + image1.setActive(false) + console.log('0 0') + }, 1500) + + setTimeout(() => { + image.setActive(true) + console.log('1 0') + }, 2000) + + setTimeout(() => { + image.setActive(false) + console.log('0 0') + }, 2500) + + setTimeout(() => { + try { + image1.setActive(true) + } catch(e) { + console.error(e) + } + console.log('error') + }, 3000) + + setTimeout(() => { + image.setActive(true) + console.log('1 0') + }, 3500) + + setTimeout(() => { + image1.setActive(true) + console.log('1 1') + }, 4000) + + setTimeout(() => { + image.setActive(false) + console.log('0 0') + }, 4500) + + setTimeout(() => { + image.setActive(true) + console.log('1 1') + }, 5000) + } + + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + game.pause(); + } else { + game.resume(); + } + }); +} \ No newline at end of file diff --git a/packages/eva.js/lib/core/Component.ts b/packages/eva.js/lib/core/Component.ts index d8ee793e..559825bd 100644 --- a/packages/eva.js/lib/core/Component.ts +++ b/packages/eva.js/lib/core/Component.ts @@ -43,11 +43,11 @@ export function getComponentName>(component } // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ComponentParams {} +export interface ComponentParams { } export interface ComponentConstructor> { componentName: string; - new (params?: ComponentParams): T; + new(params?: ComponentParams): T; } /** @@ -61,6 +61,8 @@ class Component extends EventEmitter { /** Name of this component */ public readonly name: string; + private _enable = false; + /** * Represents the status of the component, If component has started, the value is true * @defaultValue false @@ -85,6 +87,20 @@ class Component extends EventEmitter { this.__componentDefaultParams = params; } + get enable() { + return this._enable; + } + + set enable(val: boolean) { + if (this._enable === val) return + this._enable = val; + if (this._enable) { + this.onEnable?.() + } else { + this.onDisable?.() + } + } + /** * Called during component construction * @param params - optional initial parameters @@ -98,6 +114,8 @@ class Component extends EventEmitter { */ awake?(): void; + onEnable?(): void; + /** * Called after all component's `awake` method has been called * @override @@ -131,6 +149,8 @@ class Component extends EventEmitter { */ onPause?(): void; + onDisable?(): void; + /** * Called while component be destroyed. * @override diff --git a/packages/eva.js/lib/core/GameObject.ts b/packages/eva.js/lib/core/GameObject.ts index f2fb91ca..934ba9f5 100644 --- a/packages/eva.js/lib/core/GameObject.ts +++ b/packages/eva.js/lib/core/GameObject.ts @@ -18,7 +18,7 @@ class GameObject { private _name: string; /** Scene is an abstraction, represent a canvas layer */ - private _scene: Scene; + public _scene: Scene; /** A key-value map for components on this gameObject */ private _componentCache: Record> = {}; @@ -32,6 +32,12 @@ class GameObject { /** GameObject has been destroyed */ public destroyed: boolean = false; + protected _active: boolean = false; + + static _needKeepInactiveGameObjects: number[] = [] + + static _currentProcessGameObjectId: number + /** * Consruct a new gameObject * @param name - the name of this gameObject @@ -69,6 +75,7 @@ class GameObject { return this._name; } + set scene(val: Scene) { if (this._scene === val) return; const scene = this._scene; @@ -94,6 +101,66 @@ class GameObject { return this._scene; } + get active() { + return this._active; + } + + _setActive(val) { + const parent = this.transform?.parent?.gameObject + if (val) { + if (this._active === true) return; + if (parent?.active === false) { + throw `Can't make gameObject ${this.name} active, because parent is inactive.` + return + } + const index = GameObject._needKeepInactiveGameObjects.indexOf(this.id) + + // 这个元素被直接设置成false了,需要直接设置成true才能通过根据父级变化来变化 + if (GameObject._currentProcessGameObjectId !== this.id && index > -1) { + return + } + + if (index > -1) { + GameObject._needKeepInactiveGameObjects.splice(index, 1) + } + + this._active = true + + for (const component of this.components) { + component.enable = true + } + const children = this.transform.children.map(({ gameObject }) => gameObject) + for (const child of children) { + child._setActive(true) + } + } else { + // 直接设置的要放到一个数组里,防止跟随父级变化 + if (GameObject._currentProcessGameObjectId === this.id) { + const index = GameObject._needKeepInactiveGameObjects.indexOf(this.id) + if (index === -1) { + GameObject._needKeepInactiveGameObjects.push(this.id) + } + } + if (this._active === false) return; + + this._active = false + + for (const component of this.components) { + component.enable = false + } + const children = this.transform.children.map(({ gameObject }) => gameObject) + for (const child of children) { + child._setActive(false) + } + } + } + setActive(val) { + if (!this.scene) return + GameObject._currentProcessGameObjectId = this.id + this._setActive(val) + GameObject._currentProcessGameObjectId = undefined + } + /** * Add child gameObject * @param gameObject - child gameobject @@ -110,6 +177,8 @@ class GameObject { } gameObject.transform.parent = this.transform; gameObject.scene = this.scene; + + gameObject.setActive(this.active) } /** @@ -161,6 +230,10 @@ class GameObject { component.awake && component.awake(); + if (this.active) { + component.onEnable?.() + } + return component; } diff --git a/packages/eva.js/lib/core/Transform.ts b/packages/eva.js/lib/core/Transform.ts index 5d41b3b3..3e9f7f00 100644 --- a/packages/eva.js/lib/core/Transform.ts +++ b/packages/eva.js/lib/core/Transform.ts @@ -1,4 +1,4 @@ -import {type, step} from '@eva/inspector-decorator'; +import { type, step } from '@eva/inspector-decorator'; import Component from './Component'; import type { ComponentParams } from './Component'; @@ -77,12 +77,12 @@ class Transform extends Component { this.rotation = params.rotation || this.rotation; } - @type('vector2') @step(1) position: Vector2 = {x: 0, y: 0}; - @type('size') @step(1) size: Size2 = {width: 0, height: 0}; - @type('vector2') @step(0.1) origin: Vector2 = {x: 0, y: 0}; - @type('vector2') @step(0.1) anchor: Vector2 = {x: 0, y: 0}; - @type('vector2') @step(0.1) scale: Vector2 = {x: 1, y: 1}; - @type('vector2') @step(0.1) skew: Vector2 = {x: 0, y: 0}; + @type('vector2') @step(1) position: Vector2 = { x: 0, y: 0 }; + @type('size') @step(1) size: Size2 = { width: 0, height: 0 }; + @type('vector2') @step(0.1) origin: Vector2 = { x: 0, y: 0 }; + @type('vector2') @step(0.1) anchor: Vector2 = { x: 0, y: 0 }; + @type('vector2') @step(0.1) scale: Vector2 = { x: 1, y: 1 }; + @type('vector2') @step(0.1) skew: Vector2 = { x: 0, y: 0 }; @type('number') @step(0.1) rotation: number = 0; set parent(val: Transform) { @@ -134,6 +134,28 @@ class Transform extends Component { clearChildren() { this.children.length = 0; } + + + static changedTransform = [] + + onEnable() { + const index = Transform.changedTransform.indexOf(this) + if (index === -1) { + Transform.changedTransform.push(this) + } + } + onDisable(): void { + const index = Transform.changedTransform.indexOf(this) + if (index === -1) { + Transform.changedTransform.push(this) + } + } + onDestroy(): void { + const index = Transform.changedTransform.indexOf(this) + if (index > -1) { + Transform.changedTransform.splice(index, 1) + } + } } export default Transform; diff --git a/packages/eva.js/lib/game/Game.ts b/packages/eva.js/lib/game/Game.ts index 1f14d0e8..f52a8ba0 100644 --- a/packages/eva.js/lib/game/Game.ts +++ b/packages/eva.js/lib/game/Game.ts @@ -88,6 +88,7 @@ const getAllGameObjects = game => { const gameObjectLoop = (e, gameObjects = []) => { for (const gameObject of gameObjects) { + if (!gameObject.active) continue; for (const component of gameObject.components) { try { triggerStart(component); diff --git a/packages/eva.js/lib/game/Scene.ts b/packages/eva.js/lib/game/Scene.ts index 91e13df6..2a37de92 100644 --- a/packages/eva.js/lib/game/Scene.ts +++ b/packages/eva.js/lib/game/Scene.ts @@ -8,6 +8,8 @@ class Scene extends GameObject { gameObjects: GameObject[] = []; canvas: HTMLCanvasElement; + protected _active = true; + constructor(name, obj?: TransformParams) { super(name, obj); this.scene = this; // gameObject.scene = this diff --git a/packages/plugin-renderer/lib/Transform.ts b/packages/plugin-renderer/lib/Transform.ts index 4e66fdfa..8d9536c8 100644 --- a/packages/plugin-renderer/lib/Transform.ts +++ b/packages/plugin-renderer/lib/Transform.ts @@ -48,9 +48,17 @@ export default class Transform extends EventEmitter { if (container) { sceneInfo.application.stage.removeChildren(); sceneInfo.application.stage.addChild(container); + container.visible = true; } } this.waitChangeScenes = []; + for (const trans of Trans.changedTransform) { + const container = this.containerManager.getContainer(trans?.gameObject?.id) + if (container) { + container.visible = trans.enable + } + } + Trans.changedTransform.length = 0 } componentChanged(changed: ComponentChanged) { if (changed.type === OBSERVER_TYPE.ADD) { @@ -63,6 +71,7 @@ export default class Transform extends EventEmitter { } addContainer(changed: ComponentChanged) { const container = new Container(); + container.visible = false; container.name = changed.gameObject.name; this.containerManager.addContainer({ name: changed.gameObject.id,