JUMP INTO JAVASCRIPT PART 2

The teardown series continues, finally!

  • Trihan
  • 01/28/2017 04:29 PM
  • 7821 views
Hey, it's only been...what, a year and 3 months? I'm totally on schedule...

Since I'm done with the Sprite classes on Slip into Ruby now, I figured I'd switch things up a bit and give y'all what you've been waiting so long for. It's time for part 2 of


Jump into Javascript

Thanks for the image, kentona! I couldn't be bothered adding text to it. XD

So part 1 left off just after the end of Scene_Base in rpg_scenes.js. We're going to continue with Scene_Boot.

Scene_Boot

function Scene_Boot() {
    this.initialize.apply(this, arguments);
}


The constructor for Scene_Boot just applies the initialize method, passing in this and an array of arguments, same as with Scene_Base.

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


Again just like with Scene_Base, set create a new Object to serve as Scene_Boot's prototype, passing in the prototype of Scene_Base, then set the constructor of Scene_Boot's prototype to the Scene_Boot function above.

Scene_Boot.prototype.initialize = function() {
    Scene_Base.prototype.initialize.call(this);
    this._startDate = Date.now();
};


The initialize function of Scene_Boot's prototype calls Scene_Base's prototype's initialize function, passing in "this", and then sets the _startDate instance variable to the current date. This tells us when the project was started, and is used later to determine a timeout for loading the font.

[NEW CONCEPT: DATE.NOW()]
The built-in function Date.now() returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC. The Date object has other static functions, but this is the one you'll use most often.


Scene_Boot.prototype.create = function() {
    Scene_Base.prototype.create.call(this);
    DataManager.loadDatabase();
    ConfigManager.load();
    this.loadSystemWindowImage();
};


Unlike the create method of Scene_Base's prototype, the one for Scene_Boot does actually have some code in it.

First, we call the create function of Scene_Base's prototype passing in "this". Then, we call the ststic loadDatabase function of DataManager, then we call the load function of ConfigManager, and finally we call the current instance's loadSystemWindowImage function. This loads the database, configuration, and windowskin.

Scene_Boot.prototype.loadSystemWindowImage = function() {
    ImageManager.loadSystem('Window');
};


The loadSystemWindowImage function just calls the loadSystem function of ImageManager, passing in the string 'Window'.

Scene_Boot.loadSystemImages = function() {
    ImageManager.loadSystem('IconSet');
    ImageManager.loadSystem('Balloon');
    ImageManager.loadSystem('Shadow1');
    ImageManager.loadSystem('Shadow2');
    ImageManager.loadSystem('Damage');
    ImageManager.loadSystem('States');
    ImageManager.loadSystem('Weapons1');
    ImageManager.loadSystem('Weapons2');
    ImageManager.loadSystem('Weapons3');
    ImageManager.loadSystem('ButtonSet');
};


This function loads system images and consists of a series of calls to loadSystem passing in various strings. Once we get to looking at ImageManager we'll see what these strings are doing.

Scene_Boot.prototype.isReady = function() {
    if (Scene_Base.prototype.isReady.call(this)) {
        return DataManager.isDatabaseLoaded() && this.isGameFontLoaded();
    } else {
        return false;
    }
};


The isReady function first checks to see if calling Scene_Base's prototype's isReady function returns true when given "this" as an argument. If so, we return true only if the isDatabaseLoaded function of DataManager AND the isGameFontLoaded of Scene_Boot return true, and false otherwise. We also return false if the call to isReady returned false. Basically, once both the database and game font are loaded, the game is considered ready to start.

[NEW CONCEPT: LOGICAL AND]
If you want to have a particular line of code only run if a number of other conditions are met, you can combine them together with logical AND (&&). In the case of isReady, we're calling two functions and then returning a value based on the result. The possible outcomes are:

Function 1 returns true, function 2 returns true: return true
Function 1 returns true, function 2 returns false: return false
Function 1 returns false, function 2 returns true: return false
Function 1 returns false, function 2 returns false: return false

As you can see, the only circumstance in which this expression will return true is if both the other functions return true when called. You can combine any number of logical comparisons this way by appending additional &&s.


Scene_Boot.prototype.isGameFontLoaded = function() {
    if (Graphics.isFontLoaded('GameFont')) {
        return true;
    } else {
        var elapsed = Date.now() - this._startDate;
        if (elapsed >= 20000) {
            throw new Error('Failed to load GameFont');
        }
    }
};


The isGameFontLoaded function first checks whether Graphics.isFontLoaded returns true when the string 'GameFont' is provided as the argument, and returns true if this is the case.

If that call returns false, set set a variable called elapsed to the return value of Date.now() minus the instance's _startDate variable. This tells us how many milliseconds have elapsed since the project was started.

If 20,000 or more milliseconds have passed, we throw a new Error with the caption 'Failed to load GameFont'.

[NEW CONCEPT: THROWING ERRORS]
Sometimes, despite our best efforts, runtime errors can occur in our games. Occasionally there are circumstances where we'll want to show the user an error has occurred with some manual code, which is where the Error object comes in. Although it does have other parameters, all you're really going to use in MV is "message" which is a user-readable description of what went wrong.

In the case of isGameFontLoaded, after 20,000ms it's pretty clear that something's gone drastically wrong with loading the font, so we create a new Error object with a message to that effect. This will be displayed on the screen to show the player that something's not quite right.


Scene_Boot.prototype.start = function() {
    Scene_Base.prototype.start.call(this);
    SoundManager.preloadImportantSounds();
    if (DataManager.isBattleTest()) {
        DataManager.setupBattleTest();
        SceneManager.goto(Scene_Battle);
    } else if (DataManager.isEventTest()) {
        DataManager.setupEventTest();
        SceneManager.goto(Scene_Map);
    } else {
        this.checkPlayerLocation();
        DataManager.setupNewGame();
        SceneManager.goto(Scene_Title);
        Window_TitleCommand.initCommandPosition();
    }
    this.updateDocumentTitle();
};


The first thing we do in the start function of Scene_Boot's prototype is call the start function of Scene_Base's prototype, passing in "this". Then, we call the preloadImportantSounds function of SoundManager.

If calling the isBattleTest function of DataManager returns true, we call DataManager's setupBattleTest function, then call the goto function of SceneManager passing in Scene_Battle as the argument.

Otherwise, if the isEventTest function of DataManager returns true, we call DataManager's setupEventTest function, then call the goto function of SceneManager passing in Scene_Map as the argument.

Otherwise, we call the instance's checkPlayerLocation function, then the setupNewGame function of DataManager, then the goto function of SceneManager passing in Scene_Title as the argument, and finally the initCommandPosition function of Window_TitleCommand.

Regardless of the outcome of the if statement, we call the instance's updateDocumentTitle function.

This basically starts up the project appropriately depending on whether we're running a battle test, testing an event, or playing the game normally.

Scene_Boot.prototype.updateDocumentTitle = function() {
    document.title = $dataSystem.gameTitle;
};


The updateDocumentTitle function sets the title property of document (which is basically an object representing the game window) to the gameTitle property of the global $dataSystem object.

Scene_Boot.prototype.checkPlayerLocation = function() {
    if ($dataSystem.startMapId === 0) {
        throw new Error('Player\'s starting position is not set');
    }
};


The checkPlayerLocation function checks to see whether the startMapId property of $dataSystem is equal to 0, and if so throws a new Error with a message saying the player's starting position is not set.

[NEW CONCEPT: ===]
Usually, value comparisons are done using == (varName == 1 for example). This is a type-conversion comparison, where the operands will be converted if they are not the same type. === is a strict comparison operator, and requires the operands to be the same type to return true in any circumstances.

To illustrate the different, 1 == "1" will return true because both operands represent the same value, while 1 === "1" will return false because one is an integer and the other is a string.

Some Javascript programmers recommend always using ===, because == can result in some interesting behaviours due to the complicated rules by which types are converted.


Scene_Title

function Scene_Title() {
    this.initialize.apply(this, arguments);
}


Nothing to say about the constructor, it works exactly the same way as the other classes we've seen.

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


Likewise with the creation and setting of the prototype and its constructor.

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


...and the initialize function...

Scene_Title.prototype.create = function() {
    Scene_Base.prototype.create.call(this);
    this.createBackground();
    this.createForeground();
    this.createWindowLayer();
    this.createCommandWindow();
};


The create function does do some new stuff, but it's still just calling instance functions. In this case, we call the create function of Scene_Base's prototype, then createBackground, then createForeground, then createWindowLayer, and finally createCommandWindow.

Scene_Title.prototype.start = function() {
    Scene_Base.prototype.start.call(this);
    SceneManager.clearStack();
    this.centerSprite(this._backSprite1);
    this.centerSprite(this._backSprite2);
    this.playTitleMusic();
    this.startFadeIn(this.fadeSpeed(), false);
};


The start function of Scene_Title's prototype calls the start function of Scene_Base's prototype, then calls the clearStack function of SceneManager, then the instance's centerSprite function twice, passing in the _backSprite1 instance variable then the _backSprite2 instance variable. Then it calls the instance's playTitleMusic function, and finally the instance's startFadeIn function passing in its own fadeSpeed and false as arguments.

Scene_Title.prototype.update = function() {
    if (!this.isBusy()) {
        this._commandWindow.open();
    }
    Scene_Base.prototype.update.call(this);
};


The update function of Scene_Title's prototype first checks to see if the instance's isBusy function returns false; if so, the _commandWindow instance property calls its open function. Regardless of the result of the if statement, we then call the update function of Scene_Base's prototype, passing in "this".

In other words, if the boot scene isn't currently busy, open the command window. Either way, update the scene.

Scene_Title.prototype.isBusy = function() {
    return this._commandWindow.isClosing() || Scene_Base.prototype.isBusy.call(this);
};


isBusy returns the result of a logical OR on calls to the isClosing function of _commandWindow and the isBusy function of Scene_Base's prototype. In other words, the title scene is busy if the command window is closing or the base scene is busy, and it's not busy otherwise.

[NEW CONCEPT: LOGICAL OR]
Similar to logical AND, a logical OR will return true if any of the operands are true, and false if both are false. The possible outcomes for the isBusy function are:

isClosing = true, isBusy = true: return true
isClosing = true, isBusy = false: return true
isClosing = false, isBusy = true: return true
isClosing = false, isBusy = false: return false


Scene_Title.prototype.terminate = function() {
    Scene_Base.prototype.terminate.call(this);
    SceneManager.snapForBackground();
};


The terminate function of Scene_Title's prototype first calls the terminate function of Scene_Base's prototype, and then the snapForBackground function of SceneManager.

Scene_Title.prototype.createBackground = function() {
    this._backSprite1 = new Sprite(ImageManager.loadTitle1($dataSystem.title1Name));
    this._backSprite2 = new Sprite(ImageManager.loadTitle2($dataSystem.title2Name));
    this.addChild(this._backSprite1);
    this.addChild(this._backSprite2);
};


The createBackground function sets the _backSprite1 instance variable to a new Sprite, passing in the result of the loadTitle1 function of ImageManager (which itself passes in the title1Name property of $dataSystem). We set _backSprite2 pretty much the same way, then add both _backSprite1 and _backSprite2 as children of the scene.

Scene_Title.prototype.createForeground = function() {
    this._gameTitleSprite = new Sprite(new Bitmap(Graphics.width, Graphics.height));
    this.addChild(this._gameTitleSprite);
    if ($dataSystem.optDrawTitle) {
        this.drawGameTitle();
    }
};


The createForeground function sets the _gameTitleSprite instance variable to a new Sprite, passing in a new bitmap of width Graphics.width and height Graphics.height, which just creates a bitmap the size of the screen. This sprite is added as a child of the scene. If the optDrawTitle property of $dataSystem is true (which it will be if you checked the "Draw Game Title" box in the System tab of the database), we call the drawGameTitle function.

Scene_Title.prototype.drawGameTitle = function() {
    var x = 20;
    var y = Graphics.height / 4;
    var maxWidth = Graphics.width - x * 2;
    var text = $dataSystem.gameTitle;
    this._gameTitleSprite.bitmap.outlineColor = 'black';
    this._gameTitleSprite.bitmap.outlineWidth = 8;
    this._gameTitleSprite.bitmap.fontSize = 72;
    this._gameTitleSprite.bitmap.drawText(text, x, y, maxWidth, 48, 'center');
};


The drawGameTitle function declares some local variables: x, set to 20; y, set to a quarter of the screen height; maxWidth, set to the width of the screen minus twice the x (40); and text, set to the gameTitle property of $dataSystem.

_gameTitleSprite's bitmap's outlineColor property is set to 'black'; its outlineWidth to 8; its fontSize to 72; and then we call its drawText function, passing in text, x, y, maxWidth, 48, and 'center'. This draws the game title starting 20 pixels to the right and a quarter of the way down the screen, at font size 72, with an 8-pixel black outline, up to 20 pixels away from the right of the screen, and 48 pixels high.

Note that due to the title only being drawn on a single line (by default, at least) having a really, really long game title may result in it being squashed to the point of unreadability.

Scene_Title.prototype.centerSprite = function(sprite) {
    sprite.x = Graphics.width / 2;
    sprite.y = Graphics.height / 2;
    sprite.anchor.x = 0.5;
    sprite.anchor.y = 0.5;
};


The centerSprite function takes a sprite object as a parameter; the sprite's x and y are set to half the width and height of the screen, then their anchor x and y are set to 0.5 (in other words, in the centre of the sprite). This causes the sprite to be positioned exactly in the centre of the screen.

A sprite's anchor property is the MV equivalent of ox/oy from VX Ace.

Scene_Title.prototype.createCommandWindow = function() {
    this._commandWindow = new Window_TitleCommand();
    this._commandWindow.setHandler('newGame',  this.commandNewGame.bind(this));
    this._commandWindow.setHandler('continue', this.commandContinue.bind(this));
    this._commandWindow.setHandler('options',  this.commandOptions.bind(this));
    this.addWindow(this._commandWindow);
};


The createCommandWindow function sets the _commandWindow instance variable to a new instance of Window_TitleCommand. We call the new command window's setHandler function three times: the first time, passing in 'newGame' and this.commandNewGame.bind(this), the second time passing in 'continue' and this.commandContinue.bind(this) and the third time passing in 'options' and this.commandOptions.bind(this). Then, we call the addWindow function of the scene, passing in the instance's _commandWindow variable. This creates the command window for the title scene and binds its various options to the functions that will be called when they're selected.

[NEW CONCEPT: BIND]
From the official Javascript documentation:

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Basically, in this case, it creates a new "copy" of the specified functions so that they can be called by the window when the associated option is chosen.


Scene_Title.prototype.commandNewGame = function() {
    DataManager.setupNewGame();
    this._commandWindow.close();
    this.fadeOutAll();
    SceneManager.goto(Scene_Map);
};


The commandNewGame function is the one that will be called when the player selects "New Game" from the title menu. First, we call the setupNewGame function of DataManager, then the close function of _commandWindow, then the instance's fadeOutAll function, and finally the goto function of SceneManager passing in Scene_Map.

This sets up the game (variables, data structures etc.), closes the command window, fades out the screen, then passes control to the map scene.

Scene_Title.prototype.commandContinue = function() {
    this._commandWindow.close();
    SceneManager.push(Scene_Load);
};


The commandContinue function is the one that will be called when the player chooses "Continue" from the title menu. First, calls the close function of _commandwindow, then the push function of SceneManager passing in Scene_Load as an argument. We'll see this in more detail once we actually get on to looking at rpg_managers.js, but basically this does the same thing as goto with the exception of adding the scene to the stack (this is so that the player can cancel out of the load game scene and have it correctly go back to the title).

Scene_Title.prototype.commandOptions = function() {
    this._commandWindow.close();
    SceneManager.push(Scene_Options);
};


The commandOptions function is the one that is called when the player chooses "Options" from the title menu. First, we call close on _commandWindow, then call SceneManager's push function passing in Scene_Options. Similarly to the previous function, this closes the command window then adds the options scene to the stack and passes control to it.

Scene_Title.prototype.playTitleMusic = function() {
    AudioManager.playBgm($dataSystem.titleBgm);
    AudioManager.stopBgs();
    AudioManager.stopMe();
};


The playTitleMusic function calls the playBgm function from AudioManager passing in the titleBgm property of $dataSystem, then calls AudioManager's stopBgs function, and finally its stopMe function. This starts playing the BGM set in the System tab of the database under "Title" and stops any BGS or ME that might be playing.

I've introduced a few new Javascript concepts with this one, so I think I'm going to leave it there for part 2. That'll let me jump in fresh into Scene_Map, which is quite a large one.

My plan for this series is to go through the files in a somewhat "logical" order where the next file looks at the not-covered classes that first came up in the episode before it (which is why I looked at rpg_scenes.js after rpg_main.js) but if you find this confusing or think there's a better order for me to cover things in, feel free to suggest it. The layout is somewhat different from VX Ace since the scripts are kind of smooshed up into single files rather than each having its own section in a script editor, so the order we look at things in is going to be correspondingly looser.

I'm also going to be looking at the "base" classes, because absolutely nothing in MV's code is off-limits. This is eventually going to get really complex though (when we start looking at Graphics we'll be getting into stuff like OpenGL, for example) so I don't want to do that until we have a slightly more solid base knowledge of Javascript to work from. Again, if you'd rather dive right in to the juicy stuff, let me know.

Until next time!