phaser
                                
                                 phaser copied to clipboard
                                
                                    phaser copied to clipboard
                            
                            
                            
                        Scene/GameObject destruction and Scene Children properties are confusing
I recently had to create a BaseScene, thereby on scene shutdown, it did certain cleanup acts on my custom gameobjects. This was required because the lifecycle of both a scene and a gameobject are not clear, and the scene.children property is not intuitive.
For simplicity sake, let's say I have a custom gameobject (in Typescript) like so :
class MyGameObject extends Phaser.GameObjects.GameObject
{
    mySubscriptions : any[];
}
Whereby mySubscriptions is an array of active listening subscriptions. For example an internal message bus (Which is my actual use case), or in general just things you wish to destroy when the gameobject is destroyed.
The first issue is that...
GameObject's destroy method only gets called if it renders
That is, if I did the following :
class MyGameObject extends  Phaser.GameObjects.GameObject
{
    mySubscriptions : any[];
    destroy(fromScene? : boolean) : void {
        //do something here
        super.destroy(fromScene);
    }
}
The Destroy method will never be called. I'll talk about more why this is later, but it is actually very confusing and IMO, not all that clear.
I will also note that the event for destroy also never gets raised. So this also does not work :
class MyGameObject extends Phaser.GameObjects.GameObject
{
    mySubscriptions : any[];
    constructor() {
        this.on('destroy', () => {
            //Never gets called
        });
     }
}
I tried to find a way around this by instead listening for the destroy method *in the scene and then doing something there. To my surprise, in my scene, children was always empty. For example :
class MyScene extends Phaser.Scene {
  create() {
    this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => {
      this.children //<- ALWAYS EMPTY
    })
  }
}
I came to find that this.children are always empty by the time the scene shuts down. The reason is that before the shutdown event is called, all "children" are wiped out. Which is incredibly frustrating.
So I found a way around this by hooking into the update of the scene and keeping a clone of the children before the shutdown was called. However, to my surprise scene.children didn't have the full list. When I checked the code as to why...
https://github.com/photonstorm/phaser/blob/59fbcc5ca3f45388b833b07f94aefbc25cd1e24e/src/gameobjects/GameObjectFactory.js#L131-L146
When you call AddExisting on a Scene to add a GameObject. If the item does not render it does not get added as a "child". Additionally, if it has a "preUpdate" method, it gets added to a different list. If it does neither of these things, the scene doesn't hold onto the reference at all.
Confusingly, this method accepts a GameObjects.Group of all things which does not render and does not update, so... It accepts it to do nothing with it. Although I understand that accepting it atleast opens up more generic code to accept a gameobject or a group.
This displaylist is essentially what scene.children exposes, the updatelist is not exposed anywhere by default from the scene.
Additionally, Phaser.Scene is not an exported type in the typescript definitions, so this is impossible to patch/extend (Although I could be wrong there.).
In the end, I had to have a solution such as :
export class BaseScene extends Phaser.Scene {
  fullChildren: any[] = [];
  addCustomGameObject(gameObject : Phaser.GameObjects.GameObject) {
    this.fullChildren.push(gameObject);
    this.add.existing(gameObject);
  }
}
Here's what I think needs to happen :
- 
The rationale behind gameobjects not being added to the displayList if they don't render is that it's faster for game loops/cleanup. However these are two different things. I think it makes sense when the project eventually makes it's way to typescript that a gameobject should extend an IDestroyable interface that exposes a destroy method. If an IDestroyable gameobject is added to a scene, it is added to a seperate list to be called when the scene itself is destroyed. 
- 
Irrespective of which lists are used for what, a complete list of all gameobjects added to the scene should be kept and made available during all lifecycle events of a scene, including the destroy event of a scene. This allows any custom code to be run across all gameobjects on a scene at any point. 
- 
The types of Scene (And frankly a million other types) should be exportable and able to be overridden so the "add" method is easily able to be extended (And then call the super add), to handle additional things such as this. 
Game objects on the scene display list or update list are destroyed automatically when the scene is stopped. Any other game objects are not. This is on purpose.
I think the usual way to do what you want is
class MyGameObject extends Phaser.GameObjects.GameObject {
  constructor() {
    // …
    this.scene.events.once("shutdown", () => {
      this.destroy();
    });
  }
}
Alternatives are:
- Add the game object to a group
- If the game object has a preUpdate()method, add it directly to the update list
The lists are available at this.sys.displayList and this.sys.updateList. I agree that this.children is an ambiguous name but the docs do describe it correctly.
Groups are on the update list because they have a preUpdate() method, which they use to update children when runChildUpdate is on.
A Scene does not keep lists of Game Objects unless they become active, i.e. need rendering or updating, to allow you to efficiently pool and re-use resources without filling the display or update lists with redundant entries (which cost time to iterate every frame), this way you're free to cycle objects in and out of the lists as required. But it's up to you to look after the 'master list' of objects.
There's a valid argument for a Scene to keep all objects added to it in yet another list, but at this late stage in the release cycle of Phaser 3, it's too much of a fundamental change to warrant making, and honestly, nothing that has presented itself as an issue in all these years, so I'm going to say it's an edge-case requirement that can be worked around by listening to the key Scene events and hooking into its lifecycle directly (or, I guess, just giving all objects a preUpdate method and using add.existing to get them onto the list - but I wouldn't if there are lots of them)
children is an alias to the Display List. The name is a legacy convenience reference from Phaser 2. To be honest, most devs don't even realise it exists!
You can easily create your own GameObjectFactory (i.e. 'add') methods, for your own custom types, which would give you much more control, for example:
class ClownGameObject extends Phaser.GameObjects.Image {
    constructor (scene, x, y)
    {
        super(scene, x, y, 'clown');
    }
}
class ClownPlugin extends Phaser.Plugins.BasePlugin {
    constructor (pluginManager)
    {
        super(pluginManager);
        //  Register our new Game Object type
        pluginManager.registerGameObject('clown', this.createClown);
    }
    createClown (x, y)
    {
        return this.displayList.add(new ClownGameObject(this.scene, x, y));
    }
}
You don't have to do it via a plugin, you could just call registerGameObject directly and pass it to your own handler, but a plugin might be more flexible. Using the above, a Scene could then offer this.add.clown(x,y), which could add it to whatever lists you need, set-up event listeners, etc.