JUMP INTO JAVASCRIPT PART 3

The teardown series continues.

  • Trihan
  • 01/30/2017 04:36 PM
  • 7450 views
Hello sports fans! Welcome to another exciting episode/edition/article/can never decide what noun to use of


Jump into Javascript

We'll jump right in to today's lesson where we left off last time, which is a continuation of rpg_scenes.js:

Scene_Map

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


Standard class constructor that calls apply on the initialize function, passing in "this" and arguments. Nothing we haven't already seen.

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


Same with prototype assignment.

Scene_Map.prototype.initialize = function() {
    Scene_Base.prototype.initialize.call(this);
    this._waitCount = 0;
    this._encounterEffectDuration = 0;
    this._mapLoaded = false;
    this._touchCount = 0;
};


The initialize function of Scene_Map's prototype sets a number of instance variables: the wait count for calling the menu is set to 0, the duration of the encounter screen effect is set to 0, a flag for the map being loaded is set to false, and the touch counter for mouse/finger input is set to 0.

Scene_Map.prototype.create = function() {
    Scene_Base.prototype.create.call(this);
    this._transfer = $gamePlayer.isTransferring();
    var mapId = this._transfer ? $gamePlayer.newMapId() : $gameMap.mapId();
    DataManager.loadMapData(mapId);
};


The create function first calls Scene_Base's create function, then sets the _transfer instance variable to the return value from isTransferring in $gamePlayer (whether the player is currently transitioning to a new map), then sets a local variable called mapId to either the new map ID if the player is transferring, or the map ID from $gameMap otherwise. Finally, we call loadMapData in DataManager passing in the map ID, which as the name suggests...loads the map data.

[NEW CONCEPT: TERNARY OPERATOR]
The third line of this function might look confusing, but it's actually quite simple. There's an operator in many languages that's called, among other things, "inline if" or a tertiary/ternary operator, and it's usually written as "condition ? expression to evaluate if true : expression to evaluate if false".

In this case, the condition is "this._transfer" which is essentially checking to see if the _transfer variable is true. If it is, we run the part between the ? and the :, which will call $gamePlayer's newMapId function, which will return the ID of the map the player is transferring to. If _transfer is false, we call $gameMap's mapId function instead, which will return the ID of the map the player is already on.

To explain this construct in a more understandable way, it's equivalent to writing:

var mapId = null;
if (this._transfer == true) {
  mapId = $gamePlayer.newMapId();
} else {
  mapId = $gameMap.mapId();
}


Scene_Map.prototype.isReady = function() {
    if (!this._mapLoaded && DataManager.isMapLoaded()) {
        this.onMapLoaded();
        this._mapLoaded = true;
    }
    return this._mapLoaded && Scene_Base.prototype.isReady.call(this);
};


Scene_Map's isReady function first checks to see whether the _mapLoaded variable is false AND that the isMapLoaded function in DataManager returns true. If both these conditions are true, we call onMapLoaded and set _mapLoaded to true.

Regardless of the result of the if statement, we return the result of checking whether _mapLoaded is true AND Scene_Base's prototype's isReady function returns true when called (in other words, the map scene is considered ready when the map is loaded and the base scene is ready).

Scene_Map.prototype.onMapLoaded = function() {
    if (this._transfer) {
        $gamePlayer.performTransfer();
    }
    this.createDisplayObjects();
};


The onMapLoaded function first checks to see if the _transfer variable returns true; if it does, we call the performTransfer function of $gamePlayer, which handles map transitions. Regardless of the if statement's result, we call createDisplayObjects, which creates the objects that will appear on the map.

Scene_Map.prototype.start = function() {
    Scene_Base.prototype.start.call(this);
    SceneManager.clearStack();
    if (this._transfer) {
        this.fadeInForTransfer();
        this._mapNameWindow.open();
        $gameMap.autoplay();
    } else if (this.needsFadeIn()) {
        this.startFadeIn(this.fadeSpeed(), false);
    }
    this.menuCalling = false;
};


The start function, as with all the other ones we've seen so far, first calls Scene_Base's prototype's start function, passing in the Scene_Map instance.

We call the clearStack function of SceneManager, which clears the recorded stack of scenes we've been through so far (since the map is pretty much the "main" scene and should always be the bottom of the stack while a game is in progress).

Then we check whether _transfer is true; if it is, we call fadeInForTransfer and the open function of _mapNameWindow, which opens the window showing the name of the map. We also call the autoplay function of $gameMap, which starts the BGM/BGS defined in the map settings.

If _transfer is not true, we check whether needsFadeIn is true; if it is, we call startFadeIn passing in two arguments: the result of a call to the fadeSpeed function, and false. As we saw in Scene_Base (which is where the startFadeIn function is defined) these correspond to the "duration" and "white" parameters respectively.

Regardless of the result of the if statement, we set menuCalling to false, which basically tells any function checking its value that we are not currently calling the game menu.

Scene_Map.prototype.update = function() {
    this.updateDestination();
    this.updateMainMultiply();
    if (this.isSceneChangeOk()) {
        this.updateScene();
    } else if (SceneManager.isNextScene(Scene_Battle)) {
        this.updateEncounterEffect();
    }
    this.updateWaitCount();
    Scene_Base.prototype.update.call(this);
};


There are a few things going on in the update function of Scene_Map's prototype. First we have a couple of function calls: to updateDestination, which calculates where the player needs to be based on touch input; and to updateMainMultiply, which is used to fast forward events while the "ok" button is held down.

We then check whether calling isSceneChangeOk returns true, which it will if it's determined that the scene is able to change state (the conditions for which we'll see soon). If it does return true, we call updateScene. Otherwise, if calling SceneManager's isNextScene function with Scene_Battle as an argument returns true (in other words, the next scene to be called is a battle) we call the updateEncounterEffect function.

Regardless of the if statement result, we call updateWaitCount to decrement the number of frames the map is considered "busy" while opening the menu, then call the base update function.

Scene_Map.prototype.updateMainMultiply = function() {
    this.updateMain();
    if (this.isFastForward()) {
        this.updateMain();
    }
};


The updateMainMultiply function first calls updateMain, then if calling the isFastForward function returns true, we call updateMain again. This causes fast forwarding to double the update frequency as we're calling the main update function twice per frame.

Scene_Map.prototype.updateMain = function() {
    var active = this.isActive();
    $gameMap.update(active);
    $gamePlayer.update(active);
    $gameTimer.update(active);
    $gameScreen.update();
};


The updateMain function first sets a local variable called active to the result of calling isActive, then calls the update methods of $gameMap, $gamePlayer, $gameTimer and $gameScreen passing in the value of the active variable. This causes the map/player/timer/screen to only update if the map scene is currently active, which essentially "pauses" the map while processing other scenes.

Scene_Map.prototype.isFastForward = function() {
    return ($gameMap.isEventRunning() && !SceneManager.isSceneChanging() &&
            (Input.isLongPressed('ok') || TouchInput.isLongPressed()));
};


The isFastForward function returns true if a map event is running AND no scene change is taking place AND (the player is long-pressing the "ok" button OR the player is holding down touch input). It will return false if any of these conditions are not met.

[NEW CONCEPT: NESTED LOGIC]
Not really a "new" concept per se but one I felt worth a sidebar anyway. Here we see an example of a nested logic test, where one of the conditions of an AND is itself an OR conditional between two other conditions. You can determine the order in which conditionals are processed by putting groups in brackets. Had these not been in brackets, the OR would have been considered on the same level of importance as the ANDs and so this whole return statement would always return true whenever TouchInput.isLongPressed() is true. When it comes to more complex AND/OR conditionals, it's important to be aware of what order the interpreter is going to process them in, as this can sometimes be a hard-to-spot source of bugs in your code.


Scene_Map.prototype.stop = function() {
    Scene_Base.prototype.stop.call(this);
    $gamePlayer.straighten();
    this._mapNameWindow.close();
    if (this.needsSlowFadeOut()) {
        this.startFadeOut(this.slowFadeSpeed(), false);
    } else if (SceneManager.isNextScene(Scene_Map)) {
        this.fadeOutForTransfer();
    } else if (SceneManager.isNextScene(Scene_Battle)) {
        this.launchBattle();
    }
};


The stop function of Scene_Map first calls the base stop function, then the straighten function of $gamePlayer, which resets the player's character graphic to its "idle" pose, and then calls the close function of _mapNameWindow, which closes the window displaying the map name.

We then check whether the needsSlowFadeOut function returns true, and if so call startFadeOut passing in two arguments: the result of calling slowFadespeed, and false (which, again, correspond with the "duration" and "white" parameters). Otherwise, if the next scene lined up in SceneManager is Scene_Map (in other words, we're moving from the current map to a different one) we call the fadeOutForTransfer function, and otherwise if the next scene lined up is Scene_Battle, we call the launchBattle function.

Scene_Map.prototype.isBusy = function() {
    return ((this._messageWindow && this._messageWindow.isClosing()) ||
            this._waitCount > 0 || this._encounterEffectDuration > 0 ||
            Scene_Base.prototype.isBusy.call(this));
};


The isBusy function returns true if (a message window is active AND the message window is currently closing) OR the menu wait count is greater than 0 OR the encounter effect duration is greater than 0 OR the base isBusy function returns true. It will return false if none of these conditions are met.

Scene_Map.prototype.terminate = function() {
    Scene_Base.prototype.terminate.call(this);
    if (!SceneManager.isNextScene(Scene_Battle)) {
        this._spriteset.update();
        this._mapNameWindow.hide();
        SceneManager.snapForBackground();
    }
    $gameScreen.clearZoom();
    //TODO: Ivan: investigate why is it working, what keeps Scene_Map from freeing stuff
    this.removeChild(this._fadeSprite);
    this.removeChild(this._mapNameWindow);
    this.removeChild(this._windowLayer);
    this.removeChild(this._spriteset);
};


The terminate function first calls the base terminate function.

Then we check whether the next scene is NOT Scene_Battle (in other words, anything but a battle) and if this is the case we call the update function of _spriteset, the hide function of _mapNameWindow, and the snapForBackground function of SceneManager. This updates the map sprites, hides the window displaying the map name, and takes a blurry snapshot of the map (used in scenes like the shop/menu etc. where the background becomes a blurry copy of the game map).

Regardless of the if statement result, we call the clearZoom function of $gameScreen, which sets the screen zoom and scale variables to their defaults.

Finally, we call removeChild four times, passing in _fadeSprite, _mapNameWindow, _windowLayer and _spriteset.

There's a TODO comment from a developer called Ivan regarding Scene_Map not freeing "stuff", which I believe is from the memory leak issue being fixed in a later update. As far as I can tell from my own investigation, the leak was partially due to the way RenderTextures are handled in the snap function, but all the known memory leak issues in the editor itself have apparently been resolved.

Scene_Map.prototype.needsFadeIn = function() {
    return (SceneManager.isPreviousScene(Scene_Battle) ||
            SceneManager.isPreviousScene(Scene_Load));
};


The needsFadeIn function determines whether a fade-in is required; it'll return true if the previous scene was a battle OR the load game screen, and false otherwise.

Scene_Map.prototype.needsSlowFadeOut = function() {
    return (SceneManager.isNextScene(Scene_Title) ||
            SceneManager.isNextScene(Scene_Gameover));
};


The needsSlowFadeOut function determines whether a slow fade-in is required; it'll return true if the next scene is the title screen or game over screen, and false otherwise.

Scene_Map.prototype.updateWaitCount = function() {
    if (this._waitCount > 0) {
        this._waitCount--;
        return true;
    }
    return false;
};


The updateWaitCount function checks whether _waitCount is greater than 0; if it is, we decrement it and return true. Otherwise, we return false. As far as I'm aware, the return values here don't actually do anything. I think the structure for this function was copied over from similar functions in rpg_objects.js and rpg_windows.js, which do actually do something with the return value of the function call.

Scene_Map.prototype.updateDestination = function() {
    if (this.isMapTouchOk()) {
        this.processMapTouch();
    } else {
        $gameTemp.clearDestination();
        this._touchCount = 0;
    }
};


The updateDestination function first checks to see whether isMapTouchOk returns true, and if so calls processMapTouch. If it returns false, we call $gameTemp's clearDestination function and set _touchCount to 0.

Scene_Map.prototype.isMapTouchOk = function() {
    return this.isActive() && $gamePlayer.canMove();
};


The isMapTouchOk function determines whether the game will accept touch input for movement; it returns true if the map scene is currently active AND the player is able to move, and false otherwise.

Scene_Map.prototype.processMapTouch = function() {
    if (TouchInput.isTriggered() || this._touchCount > 0) {
        if (TouchInput.isPressed()) {
            if (this._touchCount === 0 || this._touchCount >= 15) {
                var x = $gameMap.canvasToMapX(TouchInput.x);
                var y = $gameMap.canvasToMapY(TouchInput.y);
                $gameTemp.setDestination(x, y);
            }
            this._touchCount++;
        } else {
            this._touchCount = 0;
        }
    }
};


The processMapTouch function processes player movement by way of touching the destination tile (with a mouse button or finger press on mobile).

First, we check whether the left mouse button or touchscreen has just been pressed OR _touchCount is greater than 0.

If this condition is met, we then check to see whether the player has the mouse button or touchscreen pressed down at this particular moment.

If this condition is also met, we check to see if _touchCount is 0 OR greater than or equal to 15. This will prevent further touch input from being processed for at least 15 frames (half a second) after the initial touch.

If we meet all of these conditions, we set two local variables, x and y, to the results of calling $gameMap's canvasToMapX and canvasToMapY functions respectively, passing them the x and y properties of TouchInput. This converts the coordinates the player touched into the X/Y tile coordinate of that screen position on the map. We then call $gameTemp's setDestination function passing in x and y, which sets the touched tile as the destination to which the player is walking.

Regardless of the current value of _touchCount, we increment it. Then, in the event that touch input is not being pressed, we set _touchCount to 0.

Effectively this means that there is a very brief delay after touching the screen where the game won't process further input, meaning you won't accidentally send your player to a tile you didn't mean to press if you move your finger/pointer slightly. However, after this 15-frame delay, as long as you keep your mouse button/finger pressed down, you can move to new tiles rapidly. If you find that you or people playing your game are still registering accidental inputs during play, you can increase this number a bit.

Scene_Map.prototype.isSceneChangeOk = function() {
    return this.isActive() && !$gameMessage.isBusy();
};


The isSceneChangeOk function determines whether a scene change is currently possible; it will return true if the map scene is active and the message object isn't busy, and false otherwise. This will prevent a scene change while a message window is still being displayed.

Scene_Map.prototype.updateScene = function() {
    this.checkGameover();
    if (!SceneManager.isSceneChanging()) {
        this.updateTransferPlayer();
    }
    if (!SceneManager.isSceneChanging()) {
        this.updateEncounter();
    }
    if (!SceneManager.isSceneChanging()) {
        this.updateCallMenu();
    }
    if (!SceneManager.isSceneChanging()) {
        this.updateCallDebug();
    }
};


The updateScene function...well, updates the scene. First, we call checkGameOver, which checks whether the game is over. Rocket science, this. Anyway, following that check, we check to see whether SceneManager is not changing the scene, and if this is the case we call updateTransferPlayer to process map transitions if one is waiting. Then we have another check that the scene is not changing, where we call updateEncounter if a battle is waiting to start. Then we have another check that the scene is not changing, where we call updatecallMenu if the menu is waiting to open. And finally, we have one final check for the scene not changing, and call updateCallDebug if the debug menu is waiting to open.

This seems like a convoluted way to do it, but basically what it's doing is making sure no scene transition is taking place before trying to go to another one, to avoid scene changes overlapping. The reason the if statements are separate even though they check the same condition is that the update functions being called are calling the goto function of SceneManager, which changes the condition isSceneChanging bases its return value on. So if one of them ends up running, the rest won't run because isSceneChanging will no longer be true.

Scene_Map.prototype.createDisplayObjects = function() {
    this.createSpriteset();
    this.createMapNameWindow();
    this.createWindowLayer();
    this.createAllWindows();
};


The createDisplayObjects function does exactly what it says on the tin. We have four function calls: createSpriteset, createMapNameWindow, createWindowLayer and createAllWindows.

Scene_Map.prototype.createSpriteset = function() {
    this._spriteset = new Spriteset_Map();
    this.addChild(this._spriteset);
};


The createSpriteset function sets the _spriteset instance variable to a new instance of the Spriteset_Map class, then adds it as a child of the scene object.

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


The createAllWindows function is just a wrapper for calls to the createMessageWindow and createScollTextWindow functions. You can probably guess what they do.

Scene_Map.prototype.createMapNameWindow = function() {
    this._mapNameWindow = new Window_MapName();
    this.addChild(this._mapNameWindow);
};


The createMapNameWindow sets _mapNameWindow to a new instance of Window_MapName and adds it as a child of the scene object.

Scene_Map.prototype.createMessageWindow = function() {
    this._messageWindow = new Window_Message();
    this.addWindow(this._messageWindow);
    this._messageWindow.subWindows().forEach(function(window) {
        this.addWindow(window);
    }, this);
};


The createMessageWindow function sets _messageWindow to a new instance of Window_Message, then calls the addWindow function with that variable as its argument. As we saw in the breakdown of Scene_Base, this adds the passed-in window as a child of the scene's _windowLayer.

We then iterate through each subwindow of _messageWindow (the subwindows being the gold window, choice window, number window and item window) and call addWindow on those too to add them to the window layer.

Scene_Map.prototype.createScrollTextWindow = function() {
    this._scrollTextWindow = new Window_ScrollText();
    this.addWindow(this._scrollTextWindow);
};


The createScrollTextWindow function sets _scrollTextWindow to a new instance of Window_ScrollText then calls addWindow with that variable as its argument.

Scene_Map.prototype.updateTransferPlayer = function() {
    if ($gamePlayer.isTransferring()) {
        SceneManager.goto(Scene_Map);
    }
};


The updateTransferPlayer function checks whether the player is transferring to a new map, and if so passes Scene_Map to the SceneManager's goto function.

Scene_Map.prototype.updateEncounter = function() {
   if ($gamePlayer.executeEncounter()) {
       SceneManager.push(Scene_Battle);
   }
};


The updateEncounter function checks whether the player is about to enter combat, and if so passes Scene_Battle to the SceneManager's push function.

Scene_Map.prototype.updateCallMenu = function() {
    if (this.isMenuEnabled()) {
        if (this.isMenuCalled()) {
            this.menuCalling = true;
        }
        if (this.menuCalling && !$gamePlayer.isMoving()) {
            this.callMenu();
        }
    } else {
        this.menuCalling = false;
    }
};


The updateCallMenu function checks whether the menu is enabled; if so, and the menu has been called, menuCalling is set to true.

We then check whether menuCalling is true AND the player isn't moving. If both these conditions are met, we call callMenu.

If the menu is not enabled, we simply set menuCalling to false.

Scene_Map.prototype.isMenuEnabled = function() {
    return $gameSystem.isMenuEnabled() && !$gameMap.isEventRunning();
};


The isMenuEnabled function gets whether the menu is enabled by returning the result of checking if the menu is currently enabled (i.e. hasn't been disabled by an event) AND an event is not running on the map. If both these conditions are met, we return true, and false if not.

Scene_Map.prototype.isMenuCalled = function() {
    return Input.isTriggered('menu') || TouchInput.isCancelled();
};


The isMenuCalled function gets whether the menu key has been pressed; it returns the result of checking whether the 'menu' input key has been pressed OR the touch input has registered a cancellation gesture (right mouse button or 2-finger screen press).

Scene_Map.prototype.callMenu = function() {
    SoundManager.playOk();
    SceneManager.push(Scene_Menu);
    Window_MenuCommand.initCommandPosition();
    $gameTemp.clearDestination();
    this._mapNameWindow.hide();
    this._waitCount = 2;
};


The callMenu function calls the menu. Seriously, sometimes it's just that obvious and there isn't even any point in me explaining it, but I will continue to do so regardless. :P

First, we call the playOk function of SoundManager, which plays the "OK" system sound effect. We push Scene_Menu to SceneManager, which will line it up as the next scene transition, call the initCommandPosition function of Window_MenuCommand, which sets the last command symbol to null ready for input, call $gameTemp's clearDestination function, which sets the player's destination X/Y to null, and finally we hide the map name window and set _waitCount to 2 (this is one of the variables that tell us whether the scene is "busy" or not).

Scene_Map.prototype.updateCallDebug = function() {
    if (this.isDebugCalled()) {
        SceneManager.push(Scene_Debug);
    }
};


The updateCallDebug function calls the debug menu. First we check whether calling isDebugCalled returns true, and if so we push Scene_Debug to SceneManager.

Scene_Map.prototype.isDebugCalled = function() {
    return Input.isTriggered('debug') && $gameTemp.isPlaytest();
};


The isDebugCalled function gets whether the player has activated the debug menu; it returns true if the 'debug' key has been pressed AND we're currently in test play mode. Obviously in the production version of the game you don't want the debug menu to be available (or maybe you do! In which case remove this part of the condition).

Scene_Map.prototype.fadeInForTransfer = function() {
    var fadeType = $gamePlayer.fadeType();
    switch (fadeType) {
    case 0: case 1:
        this.startFadeIn(this.fadeSpeed(), fadeType === 1);
        break;
    }
};


The fadeInForTransfer function is another one of those exactly-what-it-says-on-the-tin deals. We set a local variable called fadeType to the result of calling $gamePlayer's fadeType function, which will either return 0 (black), 1 (white) or 2 (none). We then have a switch statement against the value of fadeType. Case 0 and case 1 will both call the startFadeIn function passing in two arguments: the result of calling fadeSpeed, and a conditional check for fadeType being equal to 1. This will be true if the fade is "white" or false otherwise. Finally, we have a break statement. The break isn't strictly necessary, as explained in the new concept section below.

[NEW CONCEPT: SWITCH]
The switch statement is essentially a shorthand way of combining a number of if/else conditions. The switch expression will be evaluated once and its value compared with the values of each case. If a match is found, the associated block of code will be executed.

In this instance, as case 1 followed directly from case 0, 0 will "fall through" the evaluation and execute the code for the case below it. This means that both 0 and 1 will execute the same code.

The break statement breaks out of the switch and prevents any further code execution. Without it, the interpreter will just continue to the next case and continue running (which is why case 0 falls through to case 1; if there were a break there, 0 would just do nothing and then break out). It's also not strictly necessary to break out of the last case of a switch, because execution will stop there regardless, but it's sometimes a good idea to include it for consistency anyway.

Note that for small cases like this where fallthrough is intentional, something like "if (fadeType === 0 || fadeType === 1) { this.startFadeIn(this.fadeSpeed(), fadeType === 1); } would work just as well. Switch statements really start to shine when there are 3 or more cases to work through.


Scene_Map.prototype.fadeOutForTransfer = function() {
    var fadeType = $gamePlayer.fadeType();
    switch (fadeType) {
    case 0: case 1:
        this.startFadeOut(this.fadeSpeed(), fadeType === 1);
        break;
    }
};


The fadeOutForTransfer function is basically the opposite of fadeInForTransfer. In fact the only difference is that it's calling startFadeOut instead of startFadeIn.

Scene_Map.prototype.launchBattle = function() {
    BattleManager.saveBgmAndBgs();
    this.stopAudioOnBattleStart();
    SoundManager.playBattleStart();
    this.startEncounterEffect();
    this._mapNameWindow.hide();
};


The launchBattle function is called when a battle is about to begin. First, we save the currently-playing BGM and BGS via a static function of BattleManager, then we call stopAudioOnBattleStart, then the playBattleStart function of SoundManager which plays the "Battle Start" sound from the database, then we call startEncounterEffect to begin the screen transition, and finally we hide the map name window.

Scene_Map.prototype.stopAudioOnBattleStart = function() {
    if (!AudioManager.isCurrentBgm($gameSystem.battleBgm())) {
        AudioManager.stopBgm();
    }
    AudioManager.stopBgs();
    AudioManager.stopMe();
    AudioManager.stopSe();
};


The stopAudioOnBattleStart function...come on, three guesses what it does.

First we check whether the isCurrentBgm function of AudioManager returns true when provided with the battleBgm function of $gameSystem as a parameter, which will return true if the currently-playing BGM is the battle theme, and false otherwise. If false, we call the stopBgm function of AudioManager. To put it in layman's terms, "Keep the battle theme going if it's already playing, stop anything else."

Regardless of the if statement's result, we call stopBgs, stopMe and stopSe to stop all other types of audio playback.

Scene_Map.prototype.startEncounterEffect = function() {
    this._spriteset.hideCharacters();
    this._encounterEffectDuration = this.encounterEffectSpeed();
};


The startEncounterEffect function calls the hideCharacters function of _spriteset, which hides all of the character sprites temporarily, and then sets _encounterEffectDuration to the return value of the encounterEffectSpeed function, which as we'll see when we get to that point is hardcoded as 60 (in other words, the effect will take a full second to complete).

Scene_Map.prototype.updateEncounterEffect = function() {
    if (this._encounterEffectDuration > 0) {
        this._encounterEffectDuration--;
        var speed = this.encounterEffectSpeed();
        var n = speed - this._encounterEffectDuration;
        var p = n / speed;
        var q = ((p - 1) * 20 * p + 5) * p + 1;
        var zoomX = $gamePlayer.screenX();
        var zoomY = $gamePlayer.screenY() - 24;
        if (n === 2) {
            $gameScreen.setZoom(zoomX, zoomY, 1);
            this.snapForBattleBackground();
            this.startFlashForEncounter(speed / 2);
        }
        $gameScreen.setZoom(zoomX, zoomY, q);
        if (n === Math.floor(speed / 6)) {
            this.startFlashForEncounter(speed / 2);
        }
        if (n === Math.floor(speed / 2)) {
            BattleManager.playBattleBgm();
            this.startFadeOut(this.fadeSpeed());
        }
    }
};


The updateEncounterEffect function has a fair bit going on, so let's take a careful look at exactly what's happening here.

First, we check whether _encounterEffectduration is greater than 0. The rest of the function is inside this if statement, so will only execute if the condition is met.

First part of the if block is decrementing _encounterEffectDuration, which will bring it closer to 0.

Then we set a local variable called speed to encounterEffectSpeed (which will result in it being 60) and another called n (number of elapsed frames) to speed minus the current duration. Let's say we're in the third iteration of the loop, so duration is 57 at this point. This will give us a value of 3 for n.

We then set a local variable called p (percentage of total duration elapsed) to n divided by speed, which in the first frame will be 3/60 = 0.05.

Then a local variable called q (scale) is set to ((p - 1) * 20 * p + 5) * p + 1. That's a bit confusing, so let's plug in the values we've calculated so far:

((0.05 - 1) * 20 * 0.05 + 5) * 0.05 + 1
(-0.95 * 20 * 0.05 + 5) * 0.05 + 1
4.05 * 0.05 + 1
0.2025 + 1
1.2025

We then set local variables called zoomX and zoomY to the player's screen X and (screen Y - 24).

If n is equal to 2 (which would mean duration is 58, or in other words 2 frames have passed) we call $gameScreen's setZoom function passing in zoomX, zoomY, and 1 as arguments. We then call the snapForBattleBackground function, and startFlashForEncounter passing in half the value of speed (30, or half a second) as an argument.

Regardless of the result of the if statement, we call setZoom passing in zoomX, zoomY and q. Then we check whether n is equal to 1/6th of the speed rounded down to the nearest integer (as speed is hardcoded at 60, this will be true when n is 10) and if so we call startFlashForEncounter passing in half the speed again. Finally, we have another check for n being equal to half speed rounded down (30) at which point we play the battle BGM and call startFadeOut with the return value from fadeSpeed as the argument.

So basically what this does is calculates the percentage of the total effect duration that's elapsed, calculates what the zoom scale should be at that point, and sets the zoom to that scale. What this algorithm actually results in is a brief zoom in and out towards the player, then a slower zoom in up to 6x scale, with an initial flash and another one 10 frames in, then a fade out at 30 frames. I can't fully explain the logic behind the numbers used, but I think it basically calculates how far the zoom should be from 6 (600%) over 20 frames (a third of a second) where it's at its highest zoom in the middle of the sequence before returning to 1, then does the same thing over the remaining 40 frames.

Scene_Map.prototype.snapForBattleBackground = function() {
    this._windowLayer.visible = false;
    SceneManager.snapForBackground();
    this._windowLayer.visible = true;
};


The snapForBattleBackground function takes a blurry snapshot of the current scene by making the window layer invisible, calling SceneManager's snapForBackground function, and making the window layer visible again.

Scene_Map.prototype.startFlashForEncounter = function(duration) {
    var color = [255, 255, 255, 255];
    $gameScreen.startFlash(color, duration);
};


The startFlashForEncounter function begins a full-screen flash effect and takes one parameter, duration. We set a local variable called color to full white (255 in RGB and 255 alpha) then call $gameScreen's startFlash function passing in color and duration as arguments.

Scene_Map.prototype.encounterEffectSpeed = function() {
    return 60;
};


The encounterEffectSpeed function gets the number of frames the encounter start effect lasts, and is hardcoded at 60.

Map is quite a big class, so I think I'll finish up there for now. The next one is Scene_MenuBase anyway, so it makes sense to start with a base class and then do the subclasses.

Until next time!