Trihan

Speedy Scripter
Veteran
Joined
Apr 12, 2012
Messages
3,418
Reaction score
2,513
First Language
English
Primarily Uses
RMMZ
Hello, sports fans! Today Trilobytes is bringing you a SPECIAL BONUS TUTORIAL that covers a topic I don't think anyone has really covered much yet: how to actually make your own plugins.

@GoodSelf, this one's for you. ;)

Your First Plugin
There isn't really much to the "skeleton" of a plugin. Here it is:

Code:
//=============================================================================
// PluginName.js
//=============================================================================
/*:
 * @plugindesc Description of your plugin
 *
 * @author Your name
 *
 * @help
 *
 * Anything users might need to know about using your plugin.
 *
 * TERMS OF USE
 * What people who use your plugin are allowed to do with it.
 *
 * COMPATIBILITY
 * Any compatibility issues you know of or that have been brought to your attention, such as not being able to use this plugin with one of Galv's for example. This will be most common when you have a plugin that does the same or a similar thing to someone else's.
 */
 
 (function() {
    
 })();

You can totally go ahead and save that as MyPlugin.js in the plugins folder of your game, and if you double click in the plugin manager window and click the drop-down menu you'll see MyPlugin! You can select it and add it to your game and turn it on and everything. It doesn't do anything yet, but by god it's YOUR plugin and you made it all by yourself! Pat yourself on the back, you've earned it.

"But Trihan!" I hear you cry. "I want a materia system. How do I get from this poor excuse for a barebones skeleton to slotting all kinds of neat stuff into my armour?!"

That's a great question, random tutorial reader, and I'll tell you how. But I'm not going to tell you how to make a materia system, unfortunately. The most important thing, and the obvious next step, is...

Figure out How it's Done Already
If you're writing a plugin, it's because there's something about the core MV engine that you want to change. And if there's something you want to change, there's a place where the thing you want to change is already done. And if there's a place where it's already done, there's a place where you can read the code that does it.

So step 1 after making your template: find out how the thing you want to change is done already. Unless you're changing the behaviour of someone else's plugin, you'll probably find whatever you're looking for in the relevant file of the js folder:

For things like adding new sound effect shortcut functions, changing how save data is handled, or modifying battle turn order, you'll probably be looking in rpg_managers.js.

If you want to add a stamina property to actors, give items a durability, or add a new plugin command, you'll be doing something to the classes in rpg_objects.js.

If you want to make a bestiary, move the windows in the menu around or change the help text in the debug menu, it'll probably be in rpg_scenes.js.

If you want to change side-view battlers to use 6 frames instead of 3, draw rain differently or make balloon animations play more slowly, you'll want rpg_sprites.js.

If you want to change the alignment of the text in the gold window, make faces smaller or add a new slash command for messages, look no further than rpg_windows.js.

The most common things that people will change with plugins are objects, scenes and windows. It's not often that people mess with managers (the notable exception being to change the way DataManager loads databases for preloading notetags), and it's a bad idea to dick around with sprites unless you really know what you're doing.

Think About Connected Classes
It's possible that the plugin you want to write will involve changing more than one class. If you want to add a minimap on the map, for example, you're not only going to have to create a window class that actually draws the minimap, but you'll have to tell the map scene that it needs to draw one of those windows as well.

It's pretty much a 100% given that any time you want to show a new window, you're going to have to make edits to the scene you want to show it in, otherwise it'll do precisely squat. Once you understand that, writing plugins seems a lot less scary.

Start GRADUALLY Replacing Things
Here's a crazy notion: you don't have to write a complete plugin all at once. There's nothing wrong with adding little bits of functionality, making sure they work the way you want them to under all the test cases you can think of, and THEN move on to the next little bit. In fact, this makes more sense: if you only make gradual changes, you'll have a much better idea of where you went wrong when things eventually mess up. And believe me, they will mess up. They will mess up often and sometimes in spectacular ways you couldn't possibly imagine, and that's just part of programming. You'll get used to it. :)

Let's take our minimap window. Rather than trying to figure out all the actual minimap stuff, it makes sense to first make sure we can 1. create the window the minimap will be in, and 2. show it on the map scene. So the first thing we want to do is make the simplest window we can:

Code:
function Window_MyWindow() {
      this.initialize.apply(this, arguments);
    }
    
    Window_MyWindow.prototype = Object.create(Window_Base.prototype);
    Window_MyWindow.prototype.constructor = Window_MyWindow;
  
    Window_MyWindow.prototype.initialize = function(x, y, width, height) {
        Window_Base.prototype.initialize.call(this, x, y, width, height);
    };

This is, again, pretty much the bare minimum we need to create our own window class. We could now create a new one of these in a scene, and that's exactly what we're going to do. Now the map scene already exists, so it's not like we can write a new class to place our window on the map. We're going to have to replace some of the default code: if you look at rpg_scenes.js, you'll notice that pretty much all of the default scenes have functions called createXWindow, so we'll put our window in there! In order to overwrite a function in Javascript, all we have to do is redeclare it and put our own code inside, like so:

Code:
Scene_Map.prototype.createAllWindows = function() {
    this.createMessageWindow();
    this.createScrollTextWindow();
    this.createMinimapWindow();
};

Now, when the map scene goes to create all windows, it's going to see the change we made and call createMinimapWindow as well. But wait! If you try this now, you'll get an error saying the function is undefined, and...well, of course it is, you haven't written it yet. Let's fix that.

Code:
Scene_Map.prototype.createMinimapWindow = function() {
    this._minimapWindow = new Window_MyWindow(0, 0, 200, 200);
    this.addChild(this._minimapWindow);
};

Adding a new window to a scene consists of two things: assigning a new instance of a window class to a variable belonging to the scene, and adding that window as a child of the scene. Because the initialize function of our custom window class takes in an x, y, width and height, we have to put those in when we create a new one as well. This will create a new Window_MyWindow at coordinates (0, 0), 200 pixels wide and 200 pixels tall. It'll probably look stupid there, but that's where finetuning and tweaking the numbers come in. The important part is that if you play the game now, you'll see an extra window. Score!

A Function By Any Other Name
That's all well and good, as createAllWindows is pretty short and rewriting it isn't a huge deal. But what if someone else's plugin adds windows to the map too, or what if we're adding something to a really long function? Surely it's pretty inefficient to just plaster our code all over whatever was there before?

And you're right, it is. If only there were a way we could preserve whatever code was already there and just sort of tack our addition on...something like...aliasing.

See, the great thing about Javascript is that functions are just objects like everything else. And subsequently, we can store functions in variables, like so:

Code:
var _Scene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;

What this is doing is saying "Take the function createAllWindows from Scene_Map's prototype and assign it to a variable called _Scene_Map_createAllWindows". Sweet, huh?

What this means, of course, is that when we overwrite createAllWindows we have a copy of the original as well:

Code:
var _Scene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;

    Scene_Map.prototype.createAllWindows = function() {
        _Scene_Map_createAllWindows.call(this);
        this.createMinimapWindow();
    };

So now, instead of completely overwriting things, we're storing the original code in a variable, calling that function inside our overwritten one (which will do whatever createAllWindows used to do before we came along) and after it's finished doing that, it'll create our minimap window as well. We've added additional functionality to the map while still allowing for any other changes made by other plugins! Of course, it is sometimes impossible to alias a function if you're significantly changing something about the way it works or need to modify a value it's returning before it returns it, but most of the time you'll be able to take advantage of this to minimise compatibility issues between your plugin and someone else's.

How Refreshing!
So now when we test play we've got a great big empty box on the screen. Good job, hero! Now what? Well, you obviously want to show something IN the box. The process of actually creating a minimap image is beyond the scope of this tutorial, but any time you want to modify the contents of a window, you'll want to modify its refresh function. Let's demonstrate this by putting some text in the window.

Code:
Window_MyWindow.prototype.refresh = function() {

        this.drawText("This is a test message", 0, 0, this.contentsWidth(), this.lineHeight());
    };

This is basically saying "When I'm refreshed, draw the following message in the window at coordinates (0, 0) allotting a width equal to the width of the window contents and a height that's enough to draw one line of text."

But if you run your game now, it still shows a blank window. What gives? Well you've told it what to do WHEN it refreshes, but refresh is just a function like any other and still has to be called, so we'll have to add a call to it in the window's constructor:

Code:
Window_MyWindow.prototype.initialize = function(x, y, width, height) {

        Window_Base.prototype.initialize.call(this, x, y, width, height);
        this.refresh();
    };

And now if you run your game, you'll see "This is a test message" appearing in your new window.

And now, our final plugin, which proudly creates a new map window and shows some random text in it, looks like this:

Code:
//=============================================================================
// PluginName.js
//=============================================================================
/*:
 * @plugindesc Description of your plugin
 *
 * @author Your name
 *
 * @help
 *
 * Anything users might need to know about using your plugin.
 *
 * TERMS OF USE
 * What people who use your plugin are allowed to do with it.
 *
 * COMPATIBILITY
 * Any compatibility issues you know of or that have been brought to your attention, such as not being able to use this plugin with one of Galv's for example. This will be most common when you have a plugin that does the same or a similar thing to someone else's.
 */
 
 (function() {
    
    function Window_MyWindow() {
     this.initialize.apply(this, arguments);
    }
    
    Window_MyWindow.prototype = Object.create(Window_Base.prototype);
    Window_MyWindow.prototype.constructor = Window_MyWindow;
    
    Window_MyWindow.prototype.initialize = function(x, y, width, height) {
        Window_Base.prototype.initialize.call(this, x, y, width, height);
        this.refresh();
    };
    
    Window_MyWindow.prototype.refresh = function() {
        this.drawText("This is a test message", 0, 0, this.contentsWidth(), this.lineHeight());
    };
    
    var _Scene_Map_createAllWindows = Scene_Map.prototype.createAllWindows;
    Scene_Map.prototype.createAllWindows = function() {
        _Scene_Map_createAllWindows.call(this);
        this.createMinimapWindow();
    };
    
    Scene_Map.prototype.createMinimapWindow = function() {
        this._minimapWindow = new Window_MyWindow(0, 0, 200, 200);
        this.addChild(this._minimapWindow);
    };
    
 })();


So What Was the Point of This?

The concepts I've covered here won't write a full plugin for you. They won't teach you Javascript and they won't even make you a delicious slice of toast. The nerve! What I hope it WILL do is demystify exactly what a plugin IS (it's literally just a collection of overwrites to core code combined with new variables/functions) and how you can structure one to try writing your own. It should hopefully also point out that it's okay to test, find out what's not happening the way it should, and then think about what little bit of code you need to add to make that thing happen, then move on to the next thing you want to happen. And before you know it, you've got a materia system.

If anyone has any questions about what I've covered here or wants to ask anything more in-depth about plugin development, please feel free to post here.
 

LTN Games

Indie Studio
Veteran
Joined
Jun 25, 2015
Messages
708
Reaction score
636
First Language
English
Primarily Uses
RMMV
Great article! It's a great start for newbies to get an idea of the basic flow of a plugin. I find there are a lot of tutorials showing how to make a plugin but not many explaining why things are done the way they are or explain the basic logic behind programming a plugin.
 

StrawberrySmiles

The Talking Fruit
Veteran
Joined
Nov 23, 2012
Messages
1,771
Reaction score
576
First Language
English
Primarily Uses
RMMV
This seems super helpful!! Thanks for writing something like this!
 

mlogan

Global Moderators
Global Mod
Joined
Mar 18, 2012
Messages
16,038
Reaction score
8,904
First Language
English
Primarily Uses
RMMV
I plan to look over this and your Jumping into Javascript once I finally get my computer situation sorted out. Thanks for making these helpful resources!
 

mogwai

1984
Veteran
Joined
Jun 10, 2014
Messages
875
Reaction score
596
First Language
English
Primarily Uses
RMMV
Is the anonymous function to keep all your var's from muddying up the window scope? I like making window variables so I can use them later on in other functions and stuff. No biggies, just my preference.

I can't think of any disadvantages of having 400+ window variables, other than it makes it hard to dir through the window for anyone else who doesn't know your script. That's actually the only way to make a script go in Greasemonkey, what I mostly learned javascript from.
 

LTN Games

Indie Studio
Veteran
Joined
Jun 25, 2015
Messages
708
Reaction score
636
First Language
English
Primarily Uses
RMMV
Is the anonymous function to keep all your var's from muddying up the window scope? I like making window variables so I can use them later on in other functions and stuff. No biggies, just my preference.

The IIFE (Immediately Invoked Function Expression) is to keep everything out of global scope, yes. There is no reason to be making global variables in the window scope, it's bad practise. Especially bad for plugin developers. The more you pollute the window scope the more likely you will get a conflict with another variable, which may have nothing to do with your plugins but whichever plugins currently loaded int the browser and using window scope as well.

Using an IFFE does it exactly what you would expect but without polluting the window scope. It's recommended to do this for every plugin.
PHP:
  ;(function () {
    // Global within this IFFE, but private to window
    var global = {}

  // Need access to window? No problem, it's still available.
   console.log(window)

// All these functions are available only within this IIFE
    function random () {}

    function another () {}

    function randomClass () {}

  })()
 

mogwai

1984
Veteran
Joined
Jun 10, 2014
Messages
875
Reaction score
596
First Language
English
Primarily Uses
RMMV
That sounds intelligent, but if you were going to make a variable that could possibly conflict with the window scope, that's already bad practice and confusing too.
PHP:
(function(){
  var alert = function(){
    // do anything other than window.alert
    // Why would you do this?
  };
})();
 

LTN Games

Indie Studio
Veteran
Joined
Jun 25, 2015
Messages
708
Reaction score
636
First Language
English
Primarily Uses
RMMV
The only time I see using the window scope valid is when you use a namespace. It's not the native window variables anyone needs to worry about it's when we have 5 plugin developers all creating global variables. Imagine if all 5 decided to make a plugin which uses similar classes and functions. Maybe 2 developers end up using the same variable name as the others, now there is an incompatibility which is easily avoided.
 

mogwai

1984
Veteran
Joined
Jun 10, 2014
Messages
875
Reaction score
596
First Language
English
Primarily Uses
RMMV
An alias is still in the global scope from your anonymous function, no matter what.
And even in the window scope you can still follow fundamentally the
PHP:
window.myPlugin = {};
window.myPlugin.myVariable...
IDGI
Using the same global is as likely as writing over alert. And I can use my global in event script.
 
Last edited:

Trihan

Speedy Scripter
Veteran
Joined
Apr 12, 2012
Messages
3,418
Reaction score
2,513
First Language
English
Primarily Uses
RMMZ
I'll put in a section about the IFFE when I get back upstairs, thanks for the discussion guys.
 

GoodSelf

Zhu Li! Do the thing!
Veteran
Joined
Jul 23, 2016
Messages
598
Reaction score
1,134
First Language
English
Primarily Uses
RMMV
Thanks for the shoutout, my friend.
It means so much to me and to the community that you take the time to put out resources like this and share your knowledge and talents with us.

I can't wait to start working with you on the plugin! (That is if I don't make it myself first! :guffaw:)

All the best.
 

LTN Games

Indie Studio
Veteran
Joined
Jun 25, 2015
Messages
708
Reaction score
636
First Language
English
Primarily Uses
RMMV
Using the same global is as likely as writing over alert. And I can use my global in event script.
It's better to keep all your globals in a single object in window than to keep all your globals in the window object. I'm not saying don't do it, I still do it so people can use script calls without having to traverse my global object. Not all my plugins use script calls and it's only ever one variable, so it's not like an entire API is being built in the window object. Doing that is what should be avoided.
Being smart about what you're putting into the window object is better than just using it because you don't see any problems with it, when in fact, there are a ton of issues with building your entire app in the window object.

I'll put in a section about the IFFE when I get back upstairs, thanks for the discussion guys.
Sounds good. I'm excited to see what you write.
 

mogwai

1984
Veteran
Joined
Jun 10, 2014
Messages
875
Reaction score
596
First Language
English
Primarily Uses
RMMV
If you use window anyway, why the wrap?

Just cause I have 400 window variables, doesn't mean they aren't in one window object. So this might be like calling it potato/potato, except I don't have to indent every line of my script more than once.

I never heard of calling it IFFE, but I think what I usually do in my script sans the anonymous function is IFFE too. Does IFFE mean always anonymous function wrap the whole script? Because I think I might agree with it, if IFFE means doing it this way too.
PHP:
myPlugin.myFunction23 = function(){
   var smallVariable = myPlugin.myBigVariable.bigVariableName23;// object array
   // do stuff with smallVariable
}
// because
(function(){
  var smallVariable = myPlugin.myBigVariable.bigVariableName23;// global anyway
   // now it just seems we have redundant functions inside of functions
})();
 

Trihan

Speedy Scripter
Veteran
Joined
Apr 12, 2012
Messages
3,418
Reaction score
2,513
First Language
English
Primarily Uses
RMMZ
It's just good practice really. Wrapping your plugin in an IFFE means that all of the code will be placed into its own execution context when the plugin is run, meaning you don't have to worry about polluting namespaces. I, like other coders, do have my own TLB.whatever variables that I use to hold aliases and custom functions, but the bulk of the actual code will never touch the rest of it. It's not strictly necessary, but it keeps things from becoming too confused. Consider how many plugins the average RM user uses, and then imagine that every one of those plugins just declared its functions, overwrites and aliases in the window object.
 

Quxios

Veteran
Veteran
Joined
Jan 8, 2014
Messages
1,055
Reaction score
788
First Language
English
Primarily Uses
RMMV
Only thing I want to add to this is that new classes should be declared outside the IFFE for compatibility/addon purposes.

Every once in awhile I'll get a compatibility fix request, but the other plugin kept it's classes private and I would have to send steps on how to fix it. But if that plugin ever got updated they would have to reapply the fix everytime.
 

mogwai

1984
Veteran
Joined
Jun 10, 2014
Messages
875
Reaction score
596
First Language
English
Primarily Uses
RMMV
Ah! So you don't run the risk of polluting the window. It's so you don't run the risk of being polluted.
PHP:
var foo = "foo";
(function(){
  var foo = "bar";
  var foobar = function(){
    alert(foo);
  }
  foobar();
})();
 

LTN Games

Indie Studio
Veteran
Joined
Jun 25, 2015
Messages
708
Reaction score
636
First Language
English
Primarily Uses
RMMV
If you use window anyway, why the wrap?
I use one global variable and that is usually for plugins that require script calls to be more advanced. Every other variable declared within the IIFE is safe from polluting global space.
Only thing I want to add to this is that new classes should be declared outside the IFFE for compatibility/addon purposes.
I export all my custom classes to my global object. It's a bit of a nuisance to get if you have no idea about my plugins lol.
PHP:
var customClass = LTN.PluginRegistrar.requireExport('PluginName', 'CustomClass')
 

mogwai

1984
Veteran
Joined
Jun 10, 2014
Messages
875
Reaction score
596
First Language
English
Primarily Uses
RMMV
In the chance I make a twin global, I'll look out for your global. If your variables are in the anonymous function, it's safe from my pollution. You are explaining the pollution concept backwards. My example alerted "bar".

I don't care. This discussion has broke off into a separate tier, and I'm no longer taking part. It's my fault too.
 

Trihan

Speedy Scripter
Veteran
Joined
Apr 12, 2012
Messages
3,418
Reaction score
2,513
First Language
English
Primarily Uses
RMMZ
Yeah, we seem to have gotten off-topic somewhat. :p
 

Latest Threads

Latest Profile Posts

Currently trying to add items in the same style as Kyrise's excellent icons. I like having a large variety of items :3 KyriseandMe.PNG
Create MiniMaps, Sprites for MV Enemies, & Aethereal Plane Battlebacks | RPG Maker News #74

Vtubers Vs MOTHER.png

This might be my next project. You like it?
I'm off to search the master script list for my skill issue. HOORAY! FUN!
Fun fact: Digimon is an isekai :kaoswt2:

Forum statistics

Threads
112,328
Messages
1,067,417
Members
145,964
Latest member
Senkiji
Top