- Joined
- Apr 12, 2012
- Messages
- 6,877
- Reaction score
- 7,962
- 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:
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:
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:
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.
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:
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:
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.
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:
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:
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.
@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.