Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*jslint nomen:false*/
- /*global console*/
- /*:
- * @author David Wendt (fantranslation.org)
- * @plugindesc Makes your game responsive. v0.2.1
- *
- * @param ArtScale
- * @desc The intended scale of your art assets. (RPG Maker MV default: 1.5)
- * @default 1.5
- *
- * @param MinWidth
- * @desc The minimum width of the playfield, in virtual pixel units.
- * @default 544
- *
- * @param MinHeight
- * @desc The minimum height of the playfield, in virtual pixel units.
- * @default 416
- *
- * @param MaxWidth
- * @desc The maximum width of the playfield, in virtual pixel units.
- * @default 816
- *
- * @param MaxHeight
- * @desc The maximum height of the playfield, in virtual pixel units.
- * @default 624
- *
- * @param DesignFPS
- * @desc FPS that the game was originally designed for.
- * @default 60
- *
- * @param UpdateFrameCap
- * @desc Maximum number of updates to run per animation frame.
- * @default 4
- *
- * @param EnableResolutionIndependence
- * @desc Render the game at the resolution of your display & scale assets up.
- * @default true
- *
- * @param DebugFPS
- * @desc FPS that the game should run at, for debugging purposes.
- * @default undefined
- *
- * @param DebugBreakOnNonadaptive
- * @desc If "true", execution will halt on nonadaptive code.
- * @default false
- *
- * @param DebugLogMessages
- * @desc If "true", log classes containing legacy code needing to be updated.
- * @default false
- *
- * @help
- *
- * Adjusts various aspects of an RPG Maker MV game engine to improve
- * flexibility in different ways. Adjusts viewport size, window sizing, and
- * update frequency to ensure various the game adapts to various corner cases.
- * For example, this plugin can adjust your game's playfield to fit the screen
- * viewport it has, such that there are no letterboxing bars and the scale of
- * rendered art assets corresponds to some physical quantity.
- *
- * = Adaptive Viewport =
- *
- * This plugin's main advantage is that you can design games with detailed
- * art for high-density displays without forcing players on lower-density
- * monitors to view excessively large sprites. Art is scaled up or down as
- * needed such that the final result is at a visually appropriate size for any
- * monitor.
- *
- * You can also define a minimum size for your playfield. Viewports smaller
- * than this will have their art assets shrunk further to ensure a minimum
- * amount of virtual screen space is available to the user interface.
- * Additionally, a maximum size can also be defined to prevent assets from
- * becoming too small and the playfield too large. If these two parameters are
- * in conflict, the maximum size will override the minimum size. (Such a
- * situation may happen if the viewport is not tall enough for the minimum
- * height, but wide enough to exceed maximum width.)
- *
- * Maximum and minimum size defaults are set to the sizes of the default
- * RPG Maker MV and VX Ace viewports, respectively. You may wish to adjust them
- * based on your target platform. In that case, please keep in mind that the
- * units for these parameters are specified in virtual units, so if you increase
- * your ArtScale, the sizes specified here do not need to be increased. Or, if
- * you reduce ArtScale (say if you liked MV's larger sprites), you don't need to
- * change these either.
- *
- * Please take caution when setting ArtScale as it increases or decreases
- * the size of the whole game. Settings above 1.5 are highly discouraged unless
- * you have already made the necessary code and design changes to increase the
- * size of UI elements and text before using this plugin. Please do not make
- * those changes, however, if you have not already, as we have a better approach
- * involving true high-resolution support.
- *
- * This plugin unlocks the high-resolution support built into PIXI.js, the
- * 2D graphics library used by RPG Maker MV. What this means is that the actual
- * viewport presented to the user will match the resolution of their device, and
- * existing low-resolution assets will be scaled to the selected art asset size.
- * The only practical effect of this, however, is that text will render at
- * higher resolution when needed. An additional plugin is required to support
- * high-resolution or vector art asset loading.
- *
- * (Note that "high resolution" does not mean "larger physical size" as RPG
- * Maker tends to assume.)
- *
- * In the event that the high-resolution support is causing a bug in your
- * game, you may disable it by setting EnableResolutionIndependence to false,
- * which will refrain from increasing the WebGL/canvas resolution. Note that
- * this setting is independent from the ArtScale setting which is used to
- * calculate viewport resolution.
- *
- * This plugin creates a new method for Scenes and Windows called layout.
- * This method is called to force the target object to adjust it's contents to
- * fit a different screen size. Default implementations for the base game's
- * scenes and windows will be created. Additional scenes and windows in other
- * plugins or your own code should have layout methods created for them if the
- * defaults are unsuitable. The default implementations will use existing layout
- * properties where available. Developers of custom Window or Scene classes
- * should take a look at what this plugin does to ensure nothing breaks when it
- * is enabled.
- *
- * = Adaptive Framerate =
- *
- * The plugin additionally supports adjusting the speed of game logic to
- * be framerate independent. As RPG Maker MV was not designed for this at all,
- * we accomplish this by firing multiple update calls to catch up with where
- * the game should be. Effectively, frames are reinterpreted as a physical unit
- * of time equal to the reciprocal of the DesignFPS rate, or 1/60 seconds.
- *
- * This facility is limited by the UpdateFrameCap, which prevents the game
- * from running ridiculous numbers of updates in situations where the game
- * hasn't been running at all. (According to web standards, the browser is
- * permitted to ramp down framerates or even cease updates for an arbitrary
- * amount of time.) It sets an effective minimum FPS floor equal to the
- * DesignFPS divided by the UpdateFrameCap.
- *
- * Please note that this functionality does not allow us to update at a
- * faster clip than 60 FPS, even if your monitor supports it. We cannot update
- * half a frame's worth of time with the standard RPG Maker MV codebase.
- * However, we can modify existing update functions to support adaptive update
- * rates.
- *
- * To do this, you must alter the relevant update functions to accept a
- * numerical parameter indicating how many updates are intended to be processed
- * at once. This parameter MUST be allowed to be provided as a fraction and
- * your update function must scale all animations by that amount. Because we
- * provide frame counts as a multiple of the design framerate, you do not need
- * to adjust your existing design parameters to match - e.g. if you set a
- * character to move 10 pixels across the viewport per frame, and you designed
- * and tested this existing functionality at 60FPS, you merely need to multiply
- * by the provided number to get the number of pixels that the character should
- * move.
- *
- * Alongside accepting a frame count as a parameter, your update function
- * should also return the amount of frames actually consumed. This is used to
- * allow update functions to control the actual flow of update calls over time.
- *
- * Note that your code should not attempt to call other functions that
- * advance the flow of time on it's own. Instead, this module exports a
- * function called "force_frame_adaptive" which accepts the current frameCount,
- * the desired update function, and the object it's to be called on. It will
- * then call that function, either once for frame_adaptive code, or multiple
- * times for legacy code, to ensure that the flow of time remains consistent.
- *
- * You can find this function in SixLoves_Responsive; if not present, you
- * should not provide your own code. If your plugin is intended to run in both
- * environments - repsonsive and non-responsive - then you should, in the case
- * where the plugin is missing, use a fallback function that merely calls the
- * code without any special legacy handling. You should not attempt to
- * duplicate the legacy code handling in this plugin as it is subject to change
- * and fairly complex.
- *
- * Legacy autodetect relies on properly marking frame-adaptive update
- * functions by use of a special variable on the function object, like so:
- *
- * MySceneClass.prototype.update.frame_adaptive = true
- *
- * This necessitates writing frame-adaptive code in a flexible manner that
- * supports being called from non-frame-adaptive code. Look at the source code
- * of this plugin for an example of this style.
- *
- * Please note that this property is only inherited in the case where the
- * function is unchanged from parent to child class. Overriding the function,
- * either by declaring a new one on the child class's prototype or replacing
- * the existing one on the parent class's prototype, will erase this property.
- * Update functions must individually opt-in to this scheme. If you override a
- * frame_adaptive function, you must also redeclare your frame_adaptive-ness.
- *
- * For testing your own code, you may want to change DebugFPS to something
- * other than the default "undefined", which will lock the game to an arbitrary
- * FPS of your choosing. This will cause it to explicitly run faster or slower.
- * For example, on a 60fps monitor, a DebugFPS of 30 should run half as fast
- * and a DebugFPS of 120 should run twice as fast.
- *
- * In exceptionally rare cases the design FPS may have been altered from
- * the stock 60FPS. For example, a developer who already adjusted their code to
- * work on faster or slower monitors may need to use this plugin. In this case
- * you may change the DesignFPS to speed up or slow down the game at all
- * possible refresh rates. Please note that you should probably not do this.
- */
- this.SixLoves_Responsive = this.SixLoves_Responsive || {};
- (function (root, module) {
- "use strict";
- var parameters = root.PluginManager.parameters('SixLoves_Responsive'),
- enableResolutionIndependence = parameters.EnableResolutionIndependence === "false" ? false : true,
- artScale = Number(parameters.ArtScale || 1.5),
- minWidth = Number(parameters.MinWidth || 544),
- minHeight = Number(parameters.MinHeight || 416),
- maxWidth = Number(parameters.MaxWidth || 816),
- maxHeight = Number(parameters.MaxHeight || 624),
- designFPS = Number(parameters.DesignFPS || 60),
- /* Debug shit */
- debugFPS = (parameters.DebugFPS !== undefined &&
- parameters.DebugFPS !== "undefined") ? Number(parameters.DebugFPS) : undefined,
- debugBreakOnNonAdaptive = (parameters.DebugBreakOnNonadaptive === undefined ||
- parameters.DebugBreakOnNonadaptive === "false") ? false : true,
- debugLogMessages = (parameters.DebugLogMessages === undefined ||
- parameters.DebugLogMessages === "false") ? false : true,
- updateFrameCap = Number(parameters.UpdateFrameCap || 3),
- nonAdaptive = [],
- nonResponsive = [],
- /* Preceding implementations of patched code. */
- _SceneManager_initGraphics = root.SceneManager.initGraphics,
- _Graphics_centerElement = root.Graphics._centerElement,
- /* The additional zoom factor from ArtScale pixels to physical pixels.
- * So, for example...
- *
- * DPI scale ArtScale pixelScaleDiscrepancy
- * 1x 1x 1x
- * 1x 1.5x 0.6666x
- * 1.5x 1.5x 1x
- * 2x 1.5x 1.3333x
- */
- pixelScaleDiscrepancy;
- if (debugFPS !== undefined) {
- console.warn("Debugging framerate is on. You should turn this off if you are not testing update logic.");
- }
- /* == FRAME-RATE ADAPTIVE GAME LOGIC == */
- /* Helper function that repeatedly executes an update callback to emulate
- * adaptive behavior.
- */
- function force_frame_adaptive(frameCount, updateCallback, updateThis) {
- var framesExecuted = 0,
- constructorName;
- if (updateCallback.frame_adaptive === true) {
- framesExecuted = updateCallback.apply(updateThis, [frameCount]);
- //For whatever reason, a particular bit of code may not actually
- //contain any frame-scalable effects but we still want to avoid
- //running it multiple times. This bit here lets us mark code as
- //frame-adaptive without changing it if we want.
- if (framesExecuted === undefined) {
- return frameCount;
- }
- return framesExecuted;
- } else {
- if (debugBreakOnNonAdaptive) {
- debugger;
- }
- if (nonAdaptive.indexOf(updateThis.constructor.name) === -1) {
- nonAdaptive.push(updateThis.constructor.name);
- }
- }
- frameCount = Math.ceil(frameCount);
- while (framesExecuted < frameCount) {
- framesExecuted += 1;
- updateCallback.apply(updateThis);
- }
- return framesExecuted;
- }
- /* Returns the number of physical pixels per ArtScale pixel.
- *
- * Exposed as SixLoves_Responsive.get_artscale_pixel_ratio
- */
- function get_artscale_pixel_ratio() {
- return pixelScaleDiscrepancy;
- }
- /* Code which adjusts the gameloop to run at the same physical speed
- * regardless of the rate at which the browser fires animation frames.
- */
- root.SceneManager.update = function (timeStamp) {
- var frameSkip,
- actualFrameSkip = 0;
- //Clear the debugging list of nonadaptive functions.
- nonAdaptive = [];
- //Ensure we have a timestamp to compare to.
- //We can't run any adaptive code until we have a delta.
- if (this.lastTimeStamp === undefined) {
- this.lastTimeStamp = timeStamp;
- return this.requestUpdate();
- }
- //accumulatedExcessTime is used to keep track of time that hasn't
- //been consumed by the underlying frame update functions.
- if (this.accumulatedExcessTime === undefined) {
- this.accumulatedExcessTime = 0;
- }
- //This tells us how many updates need to run since the last run.
- frameSkip = (timeStamp - this.lastTimeStamp) / 1000 * designFPS;
- //Also, add in any excess time not accounted for by the previous update
- //and apply our update cap.
- frameSkip = Math.min(frameSkip + this.accumulatedExcessTime,
- module.updateFrameCap);
- if (debugFPS !== undefined) {
- frameSkip = designFPS / debugFPS;
- }
- try {
- this.tickStart();
- this.updateInputData();
- actualFrameSkip = force_frame_adaptive(frameSkip, this.updateMain, this);
- this.tickEnd();
- } catch (e) {
- this.catchException(e);
- }
- if (nonAdaptive.length > 0 && debugLogMessages) {
- console.warn("Please update classes " + nonAdaptive.join(", ") + " to support frame adaptive code.");
- }
- //Store any frame time that the update function didn't use.
- this.accumulatedExcessTime = Math.max(frameSkip - actualFrameSkip, 0);
- this.lastTimeStamp = timeStamp;
- };
- root.SceneManager.update.frame_adaptive = true;
- /* Adjust updateMain to also support frame_adaptive behavior.
- *
- * Note that if this function is NOT frame_adaptive, it WILL cause the game
- * to constantly spin and request more and more animation frames, making
- * the game spin out of control and die very quickly.
- */
- root.SceneManager.updateMain = function (frameCount) {
- if (frameCount === undefined) {
- frameCount = 1;
- }
- this.changeScene();
- frameCount = force_frame_adaptive(frameCount, this.updateScene, this);
- this.renderScene();
- this.requestUpdate();
- return frameCount;
- };
- root.SceneManager.updateMain.frame_adaptive = true;
- /* Adjust updateScene to support frame_adaptive scenes.
- *
- * This also changes the scene starting logic a bit: scene starting always
- * takes one update call regardless of how many frames actually passed.
- */
- root.SceneManager.updateScene = function (frameCount) {
- if (frameCount === undefined) {
- frameCount = 1;
- }
- if (this._scene) {
- if (!this._sceneStarted && this._scene.isReady()) {
- this._scene.start();
- this._sceneStarted = true;
- this.onSceneStart();
- }
- if (this.isCurrentSceneStarted()) {
- frameCount = force_frame_adaptive(frameCount, this._scene.update, this._scene);
- }
- }
- return frameCount;
- };
- root.SceneManager.updateScene.frame_adaptive = true;
- /* == RESPONSIVE VIEWPORT LOGIC == */
- /* Code which resizes the Graphics viewport to match the screen, and scale
- * art assets down to their appropriate physical size.
- * (e.g. it allows high-DPI assets to be high-DPI when needed)
- */
- function adapt_to_viewport() {
- var finalScale = artScale,
- displayScale = window.devicePixelRatio || 1,
- cssWidth = window.innerWidth,
- cssHeight = window.innerHeight;
- if (cssWidth < minWidth || cssHeight < minHeight) {
- finalScale = artScale / Math.min(cssWidth / minWidth,
- cssHeight / minHeight);
- }
- if (cssWidth > maxWidth || cssHeight > maxHeight) {
- finalScale = artScale / Math.max(cssWidth / maxWidth,
- cssHeight / maxHeight);
- }
- //Allow turning off full DPI support
- if (enableResolutionIndependence) {
- pixelScaleDiscrepancy = displayScale / finalScale;
- } else {
- pixelScaleDiscrepancy = 1;
- }
- root.Graphics.width = Math.round(cssWidth * finalScale);
- root.Graphics.height = Math.round(cssHeight * finalScale);
- root.Graphics.boxWidth = Math.round(cssWidth * finalScale);
- root.Graphics.boxHeight = Math.round(cssHeight * finalScale);
- root.Graphics.scale = 1 / finalScale;
- nonResponsive = [];
- if (root.SceneManager._scene) {
- if (root.SceneManager._scene.layout) {
- root.SceneManager._scene.layout();
- if (nonResponsive.length > 0 && debugLogMessages) {
- console.warn("Classes " + nonResponsive.join(", ") + " do not have a .layout method; some portions of the current scene may not render properly until the next scene transition.");
- }
- } else {
- if (debugLogMessages) {
- console.warn("The current scene " + root.SceneManager._scene.constructor.name + " does not have a .layout method; the game will not render properly until the next scene transition.");
- }
- }
- }
- }
- /* Monkey-patch the Scene Manager to fill the screen.
- */
- root.SceneManager.initGraphics = function () {
- _SceneManager_initGraphics.apply(this);
- adapt_to_viewport();
- };
- window.addEventListener("resize", adapt_to_viewport);
- function layout_all(children) {
- var i;
- for (i = 0; i < children.length; i += 1) {
- if (children[i].layout) {
- children[i].layout();
- } else {
- if (nonResponsive.indexOf(children[1].constructor.name) === -1) {
- nonResponsive.push(children[1].constructor.name);
- }
- }
- }
- }
- /* Adjust Window_Base to allocate window contents with a properly sized bitmap.
- *
- * This is the last piece in resolution-independence; windows will be able
- * to draw content correctly...
- */
- root.Window_Base.prototype.createContents = function() {
- this.contents = new Bitmap(this.contentsWidth(), this.contentsHeight(), pixelScaleDiscrepancy);
- this.resetFontSettings();
- };
- /* ...after these patches to Bitmap which will reframe literally all window
- * drawing operations in terms of virtual/CSS pixels instead of physical
- * pixels.
- */
- root.Bitmap.prototype.initialize = (function (old_impl) {
- return function (width, height, drawingScale) {
- var childArgs = Array.prototype.concat.call(arguments);
- if (drawingScale === undefined) {
- drawingScale = 1;
- } else {
- //debugger;
- }
- childArgs[0] = Math.floor(width * drawingScale);
- childArgs[1] = Math.floor(height * drawingScale);
- old_impl.apply(this, childArgs);
- this._context.scale(drawingScale, drawingScale);
- this._baseTexture.resolution = drawingScale;
- }
- }(root.Bitmap.prototype.initialize));
- /* Oh, and we also have to catch anyone resizing bitmaps, too. */
- root.Bitmap.prototype.resize = function (width, height, drawingScale) {
- if (drawingScale === undefined) {
- drawingScale = 1;
- }
- width = Math.floor(Math.max(width || 0, 1) * drawingScale);
- height = Math.floor(Math.max(height || 0, 1) * drawingScale);
- this._canvas.width = width;
- this._canvas.height = height;
- this._baseTexture.width = width;
- this._baseTexture.height = height;
- this._baseTexture.resolution = drawingScale;
- };
- /* There's what I -think- is a bug in PIXI where setFrame
- * sets the texture crop without taking resolution into
- * account, so let's update the crop ourselves.
- *
- * Unfortunately the 2.x versions of PIXI are no longer updated so this is
- * just gonna be our little secret. Also, I'm fairly sure this is not what
- * PIXI upstream would prefer we do but I stopped caring.
- */
- PIXI.Texture.prototype.setFrame = function(frame)
- {
- this.noFrame = false;
- this.frame = frame;
- this.width = frame.width;
- this.height = frame.height;
- this.crop.x = frame.x;
- this.crop.y = frame.y;
- this.crop.width = frame.width;
- this.crop.height = frame.height;
- if (!this.trim && (frame.x + frame.width > this.baseTexture.width || frame.y + frame.height > this.baseTexture.height))
- {
- throw new Error('Texture Error: frame does not fit inside the base Texture dimensions ' + this);
- }
- //It's only -after- that check that we can do this:
- this.frame.width *= this.baseTexture.resolution;
- this.frame.height *= this.baseTexture.resolution;
- this.crop.width *= this.baseTexture.resolution;
- this.crop.height *= this.baseTexture.resolution;
- this.valid = frame && frame.width && frame.height && this.baseTexture.source && this.baseTexture.hasLoaded;
- if (this.trim)
- {
- this.width = this.trim.width;
- this.height = this.trim.height;
- this.frame.width = this.trim.width;
- this.frame.height = this.trim.height;
- }
- if (this.valid) this._updateUvs();
- };
- /* Finally, we have to configure our rendering canvas to be the physical
- * resolution of the viewport, and not artscale units
- *
- * Also because high-DPI never really crossed canvas's mind we have to add
- * some CSS to force the browser to shrink the canvas back down (or up, if
- * ArtScale is larger than the actual pixel ratio)
- */
- root.Graphics._updateCanvas = function() {
- var psd = pixelScaleDiscrepancy ? pixelScaleDiscrepancy : 1;
- this._canvas.width = this._width * psd;
- this._canvas.height = this._height * psd;
- this._canvas.style.zIndex = 1;
- this._canvas.style.maxWidth = "100%";
- this._canvas.style.maxHeight = "100%";
- this._canvas.style.minWidth = "100%";
- this._canvas.style.minHeight = "100%";
- //this._centerElement(this._canvas);
- //Our lies are incompatible with this API
- this._canvas.style.position = 'absolute';
- this._canvas.style.margin = 'auto';
- this._canvas.style.top = 0;
- this._canvas.style.left = 0;
- this._canvas.style.right = 0;
- this._canvas.style.bottom = 0;
- };
- /* Tell the PIXI renderer about the pixel scale discrepancy so that it uses
- * the extra resolution we've given/taken to/from it
- */
- root.Graphics._updateRenderer = function() {
- var psd = pixelScaleDiscrepancy ? pixelScaleDiscrepancy : 1;
- if (this._renderer) {
- this._renderer.resolution = psd;
- this._renderer.renderSession.resolution = psd;
- this._renderer.resize(this._width, this._height);
- }
- };
- module.layout_all = layout_all;
- module.force_frame_adaptive = force_frame_adaptive;
- module.get_artscale_pixel_ratio = get_artscale_pixel_ratio;
- module.updateFrameCap = updateFrameCap;
- module.status = "loaded";
- module.version = "0.2.1";
- }(this, this.SixLoves_Responsive));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement