How to Extends / Alias / Overwrite existing class in MZ/ ES6 [Tutorial]

SumRndmDde

Follower of RNGesus
Veteran
Joined
Jul 30, 2013
Messages
225
Reaction score
424
First Language
English
Primarily Uses
RMMV
Sorry if this has been brought up already, but perhaps instead of re-assigning the class, you could use a function to merge the prototypes together? This would retain the behavior of the "ES5/prototype" overwriting system, but allow for the syntax of ES6 classes.

The follow example works even if the "child" class already exists, and modifying the "base" class will still affect the child. Outside of the MergeClass function itself (which you could just copy/paste), you never have to write "prototype". And finally, it automatically stores the old functions into a global namespace variable, so using IIFE doesn't prevent micro-managing things down the line (but it does prevent temporary ES6 classes from clogging up global space). Only downside is the calls to old functions are still somewhat long winded (NAMESPACE.CLASS.FUNC.call(this)), but at least it retains compatibility with "super" for existing child classes. Also, don't know if it works with "get" functions, but those may be impossible to properly overwrite anyway.

I've tested this with both Node.JS (native ES6) and Babel (conversion to ES5). (PasteBin version with syntax colors)

Code:
// ====================================================
// * Base Class
// ====================================================

class Base {
    test_return() {
        return 0;
    }

    test_super() {
        console.log("Base");
    }

    test_this() {
        if(!this._val) this._val = 0;
        return ++this._val;
    }
}

// ====================================================
// * Child Class
// ====================================================

class Child extends Base {
    test_return() {
        return super.test_return() * 2;
    }

    test_super() {
        super.test_super();
        console.log("Child");
    }

    test_this() {
        super.test_this();
        return ++this._val;
    }
}

/*** ---------------------------------------------------------------------------- ***/

// ====================================================
// * Namespace (to store old functions)
// ====================================================

var Dev = Dev || {};
Dev.PluginName = Dev.PluginName || {};

(function($) { // $ = Dev.PluginName

// ====================================================
// * Create Merge Class
// ====================================================

function MergeClass(classObj, es6Class, namespace) {
    // Get Class Name as String (for ex: "Base")
    const className = classObj.prototype.constructor.name;

    // Add Class Name Property to Namespace
    namespace[className] = {};

    // Loop Through ES6 Prototype
    Object.getOwnPropertyNames(es6Class.prototype).forEach(function(name) {

        // Ignore Constructor
        if(name === "constructor") return;

        // If the Function Already Exists, Copy Into Namespace
        if(classObj.prototype[name]) {
            namespace[className][name] = classObj.prototype[name];
        }

        // Add/Overwrite Function Into Original Class
        classObj.prototype[name] = es6Class.prototype[name];
    });
}

// ====================================================
// * Modify Base Class
// ====================================================

class ModifiedBase {
    test_return() {
        return 12;
    }

    test_super() {
        $.Base.test_super.call(this);
        console.log("Base Modified");
    }

    test_this() {
        $.Base.test_this.call(this);
        return ++this._val;
    }
}

// Add Content from ModifiedBase to Base
MergeClass(Base, ModifiedBase, $);

// ====================================================
// * Test Child Class
// ====================================================

const c = new Child();

// normally, this would be 0 since the Base.test_return is 0
// but since Base now returns 12, this prints 24
// base behavior modified while still retaining child behavior
console.log(c.test_return()); // prints 24

// this successfully prints the following (in order):
// Base
// Base Modified
// Child
c.test_super();

// normally, this would return 2, since it is incremented once in both Base and Child
// but now it increments by 3, since an increment is included in modified Base:
console.log(c.test_this()) // prints 3
console.log(c.test_this()) // prints 6

})(Dev.PluginName);
 
Last edited:

Solar_Flare

Veteran
Veteran
Joined
Jun 6, 2020
Messages
533
Reaction score
235
First Language
English
Primarily Uses
RMMV
Interesting.. will have to run a few tests to check this out. from the looks of it, and considering es6 classes are just some sugar on the syntax, I wonder how difficult it would actually be to port a plugin from mv to mz.

Until we can see some of the default code for mz though, we wont really know for sure how were going to have to implement things. :(
Well, there's no need to move all code to the class syntax when porting plugins, so it might be quite easy especially for simpler plugins.

And when you redefine pawn later on, then

Code:
Pawn = class extends Pawn {
    initParams() {
        super.initParams();
    }
}

// is functionally the same as doing

var originalInitParams = Pawn.prototype.initParams;
Pawn.prototype.initParams = function() {
    originalInitParams();
}
I think this is incorrect. My understanding of the syntax suggests that this extension method is actually equivalent to the following code:

JavaScript:
var oldPawn = Pawn; // except in the extends method the oldPawn variable doesn't exist
Pawn = function() {} // I guess if you defined a constructor() function in the class its content would go here?

Pawn.prototype = Object.create(oldPawn.prototype);
Pawn.prototype.constructor = Pawn;

Pawn.prototype.initParams = function() {
    oldPawn.prototype.initParams.call(this);
};
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,158
First Language
English
Primarily Uses
N/A
@Solar_Flare You’re right, that would be the properly equivalent full code.

I mentioned and used a “functionally equivalent” example for brevity because in context of the discussion where the change to the method wouldn’t be picked up by code preceding it, it would be functionally the same. As would your example of course so for consistency I probably should’ve shown the whole thing like that :)
 

Hudell

Dog Lord
Veteran
Joined
Oct 2, 2014
Messages
3,564
Reaction score
3,763
First Language
Java's Crypt
Primarily Uses
RMMZ
Please don't replace existing classes just to modify a method on them. Create a new class with your code and then overwrite the old method with one from your class using prototype.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,802
Reaction score
950
First Language
Chinese
Primarily Uses
N/A
I'm actually thinking about throwing away inheritance altogether in the default MZ codebase and replacing it with composition, while plugins will still use inheritance to add/edit/erase/extend existing class functionalities, for instance:
RMMZ ES6 Extend Base Class.png
The 1st part of this snippet has Parent and Child classes showing that inheritance doesn't work with extending adding/editing/erasing/extending bass class functionalities, and the 2nd part of this snippet has ParentDecorator and ChildDecorator classes showing that the composition approach works for such plugin development needs.
Basically, the only change is replacing "extends BaseClass" in the subclass declararions with the extra code "this.super = new BaseClass()" in the subclass constructor :)

P.S.: This will of course lead to increased memory usage and GC pressure as there are now more objects, so this approach does has its drawbacks :p
 
Last edited:

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,851
First Language
English
@DoubleX Generally inheritance and composition have their uses.
"is a" vs "has a" relationship.

But wait...what's going on here

Code:
class ChildDecorator extends ParentDecorator {
   constructor() {
      this.super = new ParentDecorator();
   }
}

I haven't seen that kind of statement before lol
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,802
Reaction score
950
First Language
Chinese
Primarily Uses
N/A
@DoubleX Generally inheritance and composition have their uses.
"is a" vs "has a" relationship.

But wait...what's going on here

Code:
class ChildDecorator extends ParentDecorator {
   constructor() {
      this.super = new ParentDecorator();
   }
}

I haven't seen that kind of statement before lol
It's my mistake, it should be this(I've already edited my post):
Code:
class ChildDecorator {

   constructor() {
      this.super = new ParentDecorator();
   }

}
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,158
First Language
English
Primarily Uses
N/A
I mean in the interest of discussion, I rarely use class based inheritance or extension anymore personally. In generally I think OOP isn't a good fit for game design anyway, and I keep drifting toward factory patterns and ECS (https://en.wikipedia.org/wiki/Entity_component_system)

But when it comes to writing plugins for RM, this only works in isolation on your own game (which is where I use this) or in plugins not altering core engine behavior, but augmenting it. Once you need to alter core mechanisms as has been discussed a lot in this thread, you will need to use prototype and inheritance.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,851
First Language
English
@Galenmereth Reading about it, I guess Unity is basically designed around ECS, where you create gameObjects and then attach Components to them.

Then it wouldn't be necessary to think of things in terms of inheritance: actors and enemies, while they are both battlers, would simply share the same components and therefore have the same interface, without necessarily being part of the same class hierarchy.

Then you could define other custom battler objects like NPC's, environmental elements, etc, by simply attaching those components to them. Anything can become a battler.
 

Galenmereth

Retired
Veteran
Joined
May 15, 2013
Messages
2,248
Reaction score
2,158
First Language
English
Primarily Uses
N/A
Yeah exactly. And when extending them in plugins, you wouldn't actually have to alter the existing components on the object, you could just attach another component with the added logic and lifecycle methods to adjust the properties you want. There's challenges with this approach too of course, and it requires a very different mindset from how RM has always worked under the hood.
 

DoubleX

Just a nameless weakling
Veteran
Joined
Jan 2, 2014
Messages
1,802
Reaction score
950
First Language
Chinese
Primarily Uses
N/A
How about something like this?
JavaScript:
// Default RMMZ codebase
class DefaultMZStaticClass {
    static constructSuper(...args) {
        const className = Object.getPrototypeOf(this).constructor.name;
        this.super = new DefaultMZStaticClass[className](...args);
    }
    static inherit(Parent, Child) {
        const childProto = Child.prototype, childName = childProto.constructor.name;
        DefaultMZStaticClass[childName] = Parent;
        DefaultMZStaticClass._subclasses[childName] = Child;
        this._inherit(Parent.prototype, childProto);
    }
    static updateBaseClass(Base) {
        Object.entries(DefaultMZStaticClass._subclasses).forEach(([childName, Child]) => {
            const Parent = DefaultMZStaticClass[childName];
            if (Base.prototype.constructor.name !== Parent.prototype.constructor.name) return;
            this._inherit(Base.prototype, Child.prototype);
        });
    }
    static _inherit(parentProto, childProto) {
        Object.getOwnPropertyNames(parentProto).forEach(name => {
            const desc = Object.getOwnPropertyDescriptor(parentProto, name);
            if (!desc || typeof desc.value !== "function") return;
            childProto[name] = childProto[name] || parentProto[name];
        });
    }
}
DefaultMZStaticClass._subclasses = {};

class DefaultMZBaseClass {
    constructor(val) { this._val = val; }
    reusedMethod() { return `Base reused method val: ${this._val}`; }
    overridenMethod() { return "Base overriden method"; }
    unusedMethod() { return "Base unused method"; }
}

class DefaultMZSubclass {
    constructor(val) { DefaultMZStaticClass.constructSuper.call(this, val); }
    reusedMethod() { return `${this.super.reusedMethod()} Sub reused method`; }
    overridenMethod() { return "Sub overriden method"; }
    newMethod() { return `Sub new method val: ${this.super._val}`; }
}
DefaultMZStaticClass.inherit(DefaultMZBaseClass, DefaultMZSubclass);
//

// Plugin A codebase
const PluginADefaultMZBaseClass = DefaultMZBaseClass;
DefaultMZBaseClass = class extends DefaultMZBaseClass {
    reusedMethod() { return `${super.reusedMethod()} Plugin A` };
    overridenMethod() { return "Plugin A Base overriden method"; }
    pluginANewBaseMethod() { return "Plugin A Base plugin a new base method"; };
}
DefaultMZStaticClass.updateBaseClass(DefaultMZBaseClass);

const PluginADefaultMZSubclass = DefaultMZSubclass;
DefaultMZSubclass = class extends DefaultMZSubclass {
    reusedMethod() { return `${super.reusedMethod()} Plugin A`; }
    overridenMethod() { return "Plugin A Sub overriden method"; }
    pluginANewSubMethod() { return "Plugin A Sub plugin a new sub method"; }
}
//

// Plugin B codebase
const PluginBDefaultMZBaseClass = DefaultMZBaseClass;
DefaultMZBaseClass = class extends DefaultMZBaseClass {
    reusedMethod() { return `${super.reusedMethod()} Plugin B` };
    overridenMethod() { return "Plugin B Base overriden method"; }
    pluginBNewBaseMethod() { return "Plugin B Base plugin b new base method"; };
}
DefaultMZStaticClass.updateBaseClass(DefaultMZBaseClass);

const PluginBDefaultMZSubclass = DefaultMZSubclass;
DefaultMZSubclass = class extends DefaultMZSubclass {
    reusedMethod() { return `${super.reusedMethod()} Plugin B`; }
    overridenMethod() { return "Plugin B Sub overriden method"; }
    pluginBNewSubMethod() { return "Plugin B Sub plugin b new sub method"; }
}
//

const base = new DefaultMZBaseClass("parent"), sub = new DefaultMZSubclass("child");
// base.reusedMethod(): Base reused method val: parent Plugin A Plugin B
console.info("base.reusedMethod():", base.reusedMethod());
//
// base.overridenMethod(): Plugin B Base overriden method
console.info("base.overridenMethod():", base.overridenMethod());
//
// base.unusedMethod(): Base unused method
console.info("base.unusedMethod():", base.unusedMethod());
//
// base.pluginANewBaseMethod(): Plugin A Base plugin a new base method
console.info("base.pluginANewBaseMethod():", base.pluginANewBaseMethod());
//
// base.pluginBNewBaseMethod(): Plugin B Base plugin b new base method
console.info("base.pluginBNewBaseMethod():", base.pluginBNewBaseMethod());
//
// sub.reusedMethod(): Base reused method val: child Sub reused method Plugin A Plugin B
console.info("sub.reusedMethod():", sub.reusedMethod());
//
// sub.overridenMethod(): Plugin B Sub overriden method
console.info("sub.overridenMethod():", sub.overridenMethod());
//
// sub.unusedMethod(): Base unused method
console.info("sub.unusedMethod():", sub.unusedMethod());
//
// sub.newMethod(): Sub new method val: child
console.info("sub.newMethod():", sub.newMethod());
//
// sub.pluginANewBaseMethod(): Plugin A Base plugin a new base method
console.info("sub.pluginANewBaseMethod():", sub.pluginANewBaseMethod());
//
// sub.pluginBNewBaseMethod(): Plugin B Base plugin b new base method
console.info("sub.pluginBNewBaseMethod():", sub.pluginBNewBaseMethod());
//
// sub.pluginANewSubMethod(): Plugin A Sub plugin a new sub method
console.info("sub.pluginANewSubMethod():", sub.pluginANewSubMethod());
//
// sub.pluginBNewSubMethod(): Plugin B Sub plugin b new sub method
console.info("sub.pluginBNewSubMethod():", sub.pluginBNewSubMethod());
//
Of course, I won't use it myself, as I'm ok with prototyping, those avoiding prototype like a plauge might want to tinker on this :)
 
Last edited:

??????

Diabolical Codemaster
Veteran
Joined
May 11, 2012
Messages
6,548
Reaction score
3,297
First Language
Binary
Primarily Uses
RMMZ
I think you guys are overthinking this. From the core code structure it doesnt look like mz even uses a modern js class structure, it still uses the same function ClassName(){} syntax as mv. If they didnt change how they define classes, why do we need to change how we alias them? :)
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,851
First Language
English
I think you guys are overthinking this. From the core code structure it doesnt look like mz even uses a modern js class structure, it still uses the same function ClassName(){} syntax as mv. If they didnt change how they define classes, why do we need to change how we alias them? :)

I don't want to type prototype so much lol

There's also the possibility that they might switch to all classes, which leads to this aliasing problem that we have right now.
 

Hudell

Dog Lord
Veteran
Joined
Oct 2, 2014
Messages
3,564
Reaction score
3,763
First Language
Java's Crypt
Primarily Uses
RMMZ
There's no way to alias methods in JS without using prototype
 

??????

Diabolical Codemaster
Veteran
Joined
May 11, 2012
Messages
6,548
Reaction score
3,297
First Language
Binary
Primarily Uses
RMMZ
TBH I still kinda like this structure:
Code:
((klass, parent) => {"use strict";
    klass.funkyname = function() {
        parent.funkyname.apply(this, arguments);
        // do junk
    };
})(GameActor.prototype, GameBattler.prototype);

Also neat enough for aliasing:
Code:
((klass, parent) => {"use strict";
    const funkyname = klass.funkyname;
    klass.funkyname = function() {
        funkyname.apply(this, arguments);
        // do junk
    };
})(GameActor.prototype, GameBattler.prototype);

Compared to some of the other workarounds, that seems almost elegant (for js)
 

CaRa_CrAzY

Undefined Custom Title
Veteran
Joined
Jan 19, 2019
Messages
65
Reaction score
28
First Language
Portuguese
Primarily Uses
Other
TBH I still kinda like this structure:
Code:
((klass, parent) => {"use strict";
    klass.funkyname = function() {
        parent.funkyname.apply(this, arguments);
        // do junk
    };
})(GameActor.prototype, GameBattler.prototype);

Also neat enough for aliasing:
Code:
((klass, parent) => {"use strict";
    const funkyname = klass.funkyname;
    klass.funkyname = function() {
        funkyname.apply(this, arguments);
        // do junk
    };
})(GameActor.prototype, GameBattler.prototype);

Compared to some of the other workarounds, that seems almost elegant (for js)

Following in the same line, what if we create a script for simplifying aliasing. Something kinda AOP-like with before and after pointcuts?

It would simplify the "prototype fest" and maybe standardize the community's approach towards extensions.
The drawback would be that every script would depend on it, but if it becomes a standard, I don't think it would be too much of a problem in the long run.
 
Last edited:

Hudell

Dog Lord
Veteran
Joined
Oct 2, 2014
Messages
3,564
Reaction score
3,763
First Language
Java's Crypt
Primarily Uses
RMMZ
Following in the same line, what if we create a script for simplifying aliasing. Something kinda AOP-like with before and after pointcuts?

It would simplify the "prototype fest" and maybe standardize the community's approach towards extensions.
The drawback would be that every script would depend on it, but if it becomes a standard, I don't think it would be too much of a problem in the long run.
We tried that on MV. If it's not included in the engine nobody will use it.
 

??????

Diabolical Codemaster
Veteran
Joined
May 11, 2012
Messages
6,548
Reaction score
3,297
First Language
Binary
Primarily Uses
RMMZ
As Hudell mentioned, that was tried for mv at launch.
see: https://github.com/mv-secret-team/MVCommons
 

MushroomCake28

KAMO Studio
Global Mod
Joined
Nov 18, 2015
Messages
3,781
Reaction score
4,724
First Language
English
Primarily Uses
RMMZ
@Galenmereth Trying to implement a ECS system in MZ? Wow, that's going to be a lot of trouble, plus I doubt the editor will make it friendly to work in that environment. I mean, you could do it for your own project, but it seems like a lot of work simply to change something that is already functional.
 

Tsukihime

Veteran
Veteran
Joined
Jun 30, 2012
Messages
8,564
Reaction score
3,851
First Language
English
@Galenmereth Trying to implement a ECS system in MZ? Wow, that's going to be a lot of trouble, plus I doubt the editor will make it friendly to work in that environment. I mean, you could do it for your own project, but it seems like a lot of work simply to change something that is already functional.

I attach battlers to arbitrary objects all the time just so they can "become" battlers without necessarily needing to be a battler themselves.

In my grid battle code, I've been experimenting with attaching battler objects to tiles on the ground just so I can give them state animations, perform damage when you step on it, and some other stuff related to battler interaction. That way you could do all your work in the database, and apply it to "environment" effects.
 

Latest Threads

Latest Posts

Latest Profile Posts

Who would have thought making interesting birds would be so difficult/fun?

Randomly spawning anywhere on map (sometimes even flying), having a chance of landing on a different spot on map when flying. Also chance of flying off screen if spooked/sees the player

I think I got it!
Since I started in this world of make games, It's hard for me to enjoy playing a game. It's like, coding and make it work, sounds like I'm playing and have fun o_O
Not sure if it's true (and I won't judge or blame anyone if it is, really), but I heard from confident sources that you guys (and girls) really really like to
How is this new year for everyone? Hope you are well. Still deep in Covid in the US. Its like a vampire and refuses to die with even more strains. I need some holy water to kill it with.
in my game, what's known as HeadSpace is combining with the real world...

Forum statistics

Threads
107,670
Messages
1,031,316
Members
139,791
Latest member
Ender570
Top