RMMZ Disposing Sprites and Managing Memory: Am I doing it right?

Wavelength

MSD Strong
Global Mod
Joined
Jul 22, 2014
Messages
5,603
Reaction score
5,071
First Language
English
Primarily Uses
RMVXA
I'm starting to get very involved with MZ plugin making now and while I have been able to create some nifty functionality, I think I'm lacking on the fundamentals, especially when it comes to sprites and how to handle them. In particular, I am worried that I am missing steps along the way and that, in the long run, I will be tanking the technical performance of people who use my plugins.

And I would really appreciate any guidance on whether my approach will lead to any memory leaks or other significant technical issues.

Here's a simple scene I made last month, mostly just as a learning experience. The scene simply shows a background, allows the player to create a new faerie sprite onscreen with each left-click, and allows the player to remove all faerie sprites with a right-click. It was partially based off of the Game Over scene code, and it uses Scene_Base as its "parent class".

Code:
function Scene_Zero() {
    this.initialize(...arguments);
}

Scene_Zero.prototype = Object.create(Scene_Base.prototype);
Scene_Zero.prototype.constructor = Scene_Zero;

Scene_Zero.prototype.initialize = function() {
    Scene_Base.prototype.initialize.call(this);
};

Scene_Zero.prototype.create = function() {
    Scene_Base.prototype.create.call(this);
    this.playZeroMusic();
    this.createBackground();
    this.sprites = [];
};

Scene_Zero.prototype.update = function() {
    if (this.isActive() && !this.isBusy()) {
        if (Input.isTriggered("ok") || TouchInput.isTriggered()) {
           SoundManager.playUseSkill();
           var faesprite = new Sprite();
           faesprite.bitmap = ImageManager.loadEnemy("Sylph");
           this.addChild(faesprite);
           this.sprites.push(faesprite);
           faesprite._hue = Number(Math.random() * 360);
           faesprite.x = Number(100 + Math.random() * 600);
           faesprite.y = Number(100 + Math.random() * 400);
           faesprite._updateColorFilter();
        } else if (Input.isTriggered("cancel") || TouchInput.isCancelled()) {
            for (fs of this.sprites) {
                // REMOVE CHILD
                this.removeChild(fs);
                // DESTROY SPRITE
                fs.destroy();
            }
            this.sprites = [];
        }
    }
    Scene_Base.prototype.update.call(this);
};

Scene_Zero.prototype.terminate = function() {
    Scene_Base.prototype.terminate.call(this);
    AudioManager.stopAll();
};

Scene_Zero.prototype.playZeroMusic = function() {
    AudioManager.stopBgm();
    AudioManager.stopBgs();
};

Scene_Zero.prototype.createBackground = function() {
    this._backSprite = new Sprite();
    this._backSprite.bitmap = ImageManager.loadTitle1("Mountain");
    this.addChild(this._backSprite);
};
In particular, a few of the things that I'm doing:
  • To create both the background and the faeries, I create a new sprite, then assign them a bitmap property using the ImageManager.loadwhatever method, then add the new sprite to the scene as its Child - is this a wise way to do it?
  • When it's time to delete all the faerie sprites (due to a right-click), I iterate through the array of sprites, remove each as a Child from the scene, and then run the sprite's destroy() method - will this process free up all of the memory that the sprite was consuming, or do I need to do something else?
  • I haven't added an actual way to exit the Scene yet, but assume that the terminate() function will run when the player leaves the scene - will the terminate() function automatically dispose of the this._backSprite sprite and free up its memory, or do I need to explicitly destroy it or do something else? How about existing faerie sprites that weren't cleared via right-click - will those be automatically disposed when the scene is terminated?
Any other advice about how to better create, handle, and destroy sprites would be most welcome, as well.
 

caethyril

^_^
Veteran
Joined
Feb 21, 2018
Messages
2,059
Reaction score
1,483
First Language
EN
Primarily Uses
RMMZ
  1. Create sprite, set image, add to container...seems OK to me! Note that you can assign a bitmap to a Sprite via its constructor, e.g.
    JavaScript:
    var faesprite = new Sprite(ImageManager.loadEnemy('Sylph'))

  2. I'm not certain of exactly what happens internally, but destroying a sprite should tell the app that all the sprite's values are now disposable and will not be accessed again. It should suffice to prevent associated memory leaks. :)

  3. Scene_Base.prototype.terminate does nothing. :kaoblush: That said, the SceneManager destroys the previous scene before starting the next one, so I don't think you need to explicitly destroy each sprite~
    JavaScript:
    SceneManager.onBeforeSceneStart = function() {
        if (this._previousScene) {
            this._previousScene.destroy();
            this._previousScene = null;
        }
        if (Graphics.effekseer) {
            Graphics.effekseer.stopAll();
        }
    };
    ...and scenes inherit from Stage, which destroys its children when it gets destroyed:
    JavaScript:
    Stage.prototype.destroy = function() {
        const options = { children: true, texture: true };
        PIXI.Container.prototype.destroy.call(this, options);
    };
    JavaScript:
    /**
     * Removes all internal references and listeners as well as removes children from the display list.
     * Do not use a Container after calling `destroy`.
     *
     * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options
     *  have been set to that value
     * @param {boolean} [options.children=false] - if set to true, all the children will have their destroy
     *  method called as well. 'options' will be passed on to those calls.
     * @param {boolean} [options.texture=false] - Only used for child Sprites if options.children is set to true
     *  Should it destroy the texture of the child sprite
     * @param {boolean} [options.baseTexture=false] - Only used for child Sprites if options.children is set to true
     *  Should it destroy the base texture of the child sprite
     */
    Container.prototype.destroy = function destroy (options)
    {
    	DisplayObject.prototype.destroy.call(this);
    
    	this.sortDirty = false;
    
    	var destroyChildren = typeof options === 'boolean' ? options : options && options.children;
    
    	var oldChildren = this.removeChildren(0, this.children.length);
    
    	if (destroyChildren)
    	{
    		for (var i = 0; i < oldChildren.length; ++i)
    		{
    			oldChildren[i].destroy(options);
    		}
    	}
    };
 

Wavelength

MSD Strong
Global Mod
Joined
Jul 22, 2014
Messages
5,603
Reaction score
5,071
First Language
English
Primarily Uses
RMVXA
@caethyril Thank you!! That is extremely helpful information you provided - I was hesitant to move forward with the parts of my plugins that required creating/removing sprites, and it sounds like as long as I am adding the new sprite objects to the scene (stage) as children, I can go ahead and create them without worrying that they will cause memory leaks or bad performance way down the line.

As a quick follow-up, I believe I understand the flow you are describing in #3, with each scene eventually calling the destroy() method for _previousScene before it "starts" - but I am a little unclear about when this scene startup logic will be run:
That said, the SceneManager destroys the previous scene before starting the next one, so I don't think you need to explicitly destroy each sprite~
If I do something like a SceneManager.goto(another scene, like a menu); or a SceneManager.pop(); - will that run the logic that destroys the previous scene (including its sprites and other children), or do I need to do something different in order to get the
onBeforeSceneStart() method to automatically run?
 

caethyril

^_^
Veteran
Joined
Feb 21, 2018
Messages
2,059
Reaction score
1,483
First Language
EN
Primarily Uses
RMMZ
If I do something like a SceneManager.goto(another scene, like a menu); or a SceneManager.pop(); - will that run the logic that destroys the previous scene (including its sprites and other children)
Short answer: yes. :)

(The following code excerpts can all be found in rmmz_managers.js.)

SceneManager.pop goes through goto; goto sets the _nextScene property to a new instance of the next scene:
JavaScript:
SceneManager.goto = function(sceneClass) {
    if (sceneClass) {
        this._nextScene = new sceneClass();
    }
    if (this._scene) {
        this._scene.stop();
    }
};

SceneManager.pop = function() {
    if (this._stack.length > 0) {
        this.goto(this._stack.pop());
    } else {
        this.exit();
    }
};
The SceneManager calls its updateMain method each game cycle (frame). Amongst other things, this calls a couple of relevant methods:
  • changeScene
    • If appropriate, this terminates the current scene and replaces it with _nextScene. onSceneTerminate is called to copy the previous scene into _previousScene for later reference.
  • updateScene
    • If the scene has started, this updates it;
    • If it hasn't started yet, onBeforeSceneStart is called then the scene is started.
JavaScript:
SceneManager.updateMain = function() {
    this.updateFrameCount();
    this.updateInputData();
    this.updateEffekseer();
    this.changeScene();
    this.updateScene();
};

SceneManager.isSceneChanging = function() {
    return this._exiting || !!this._nextScene;
};

SceneManager.onSceneTerminate = function() {
    this._previousScene = this._scene;
    this._previousClass = this._scene.constructor;
    Graphics.setStage(null);
};

SceneManager.changeScene = function() {
    if (this.isSceneChanging() && !this.isCurrentSceneBusy()) {
        if (this._scene) {
            this._scene.terminate();
            this.onSceneTerminate();
        }
        this._scene = this._nextScene;
        this._nextScene = null;
        if (this._scene) {
            this._scene.create();
            this.onSceneCreate();
        }
        if (this._exiting) {
            this.terminate();
        }
    }
};

SceneManager.updateScene = function() {
    if (this._scene) {
        if (this._scene.isStarted()) {
            if (this.isGameActive()) {
                this._scene.update();
            }
        } else if (this._scene.isReady()) {
            this.onBeforeSceneStart();
            this._scene.start();
            this.onSceneStart();
        }
    }
};
You may recognise onBeforeSceneStart: it's the method I quoted in my previous post, showing where the previous scene gets destroyed. :kaopride:

(I think the meaning of the isStarted and isReady scene methods seen in this second spoiler are fairly intuitive; for more details on each scene's requirements you can search rmmz_scenes.js.)
 

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Posts

Latest Profile Posts

being able to transition music seamlessly is amazing. my main character's theme has lyrics and it goes from vocal version to istrumental when he leaves his room. and vice versa. and thanks to a plugin, the transition is seamless. I love it.
All of the auditions for our game are so good, I'm so glad I'm not in charge of choosing completely. lol I just can't with some of them. I listen to them on repeat. XD
Working on a Patch to integrate AlphaABS into random generated Dungeons. Was a bit tricky, but the outcome is a real Life Changer!!

Will release it the next days!
Been pretty busy... but I got a better headset for recording and listening today. Dad says that he'll consider taking me to Great Wolf Lodge for the first time once I finish summer classes (my final semester!) I'll be taking my plushie friends with me: Bendy, Lolbit, and Helen Henny. (I WUV PLUSHIES!)
SF_Monster3,4 added!

Forum statistics

Threads
105,651
Messages
1,015,304
Members
137,327
Latest member
Mezzy
Top