Skip to content

Commit

Permalink
[#1091] refactoring ofgame into an Instantiable Application objec…
Browse files Browse the repository at this point in the history
…t, with `game` now being the default instance of it

this not yet though possible to create a different Application instance, as references to the "default" game are hardcoded a bit everywhere and need more rework for instances to work independently.
  • Loading branch information
obiot committed Jul 9, 2022
1 parent 3f5ef9e commit 54a689f
Show file tree
Hide file tree
Showing 21 changed files with 293 additions and 287 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
### Changed
- Core: full ES6 refactoring of `me.device`, and API clean-up (@see https://github.com/melonjs/melonJS/wiki/Upgrade-Guide#120x-to-130x-stable)
- Loader: `onload` and `onerror` callbacks are now optionals when directly loading assets (easier with base64 encoded assets)
- Game: refactoring of`game` into an Instantiable `Application` object, with `game` now being the default instance of it (#1091)

### Fixed
- Loader: fix loading/preloading of base64 audio assets, and base64 encoded FontFace
Expand Down
228 changes: 228 additions & 0 deletions src/application/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { renderer } from "./../video/video.js";
import * as event from "./../system/event.js";
import timer from "./../system/timer.js";
import state from "./../state/state.js";
import World from "./../physics/world.js";

/**
* @classdesc
* An Application represents a single melonJS game.
* An Application is responsible for updating (each frame) all the related object status and draw them.
* @see game
*/
class Application {
constructor() {
/**
* a reference to the current active stage "default" camera
* @public
* @type {Camera2d}
*/
this.viewport = null;

/**
* a reference to the game world, <br>
* a world is a virtual environment containing all the game objects
* @public
* @type {World}
*/
this.world = null;

/**
* when true, all objects will be added under the root world container.<br>
* When false, a `me.Container` object will be created for each corresponding groups
* @public
* @type {boolean}
* @default true
*/
this.mergeGroup = true;

/**
* Specify the property to be used when sorting renderables.
* Accepted values : "x", "y", "z"
* @public
* @type {string}
* @default "z"
*/
this.sortOn = "z";

/**
* Last time the game update loop was executed. <br>
* Use this value to implement frame prediction in drawing events,
* for creating smooth motion while running game update logic at
* a lower fps.
* @public
* @type {DOMHighResTimeStamp}
* @name lastUpdate
* @memberof Application
*/
this.lastUpdate = 0;

// to know when we have to refresh the display
this.isDirty = true;

// always refresh the display when updatesPerSecond are lower than fps
this.isAlwaysDirty = false;

// frame counter for frameSkipping
// reset the frame counter
this.frameCounter = 0;
this.frameRate = 1;

// time accumulation for multiple update calls
this.accumulator = 0.0;
this.accumulatorMax = 0.0;
this.accumulatorUpdateDelta = 0;

// min update step size
this.stepSize = 1000 / 60;
this.updateDelta = 0;
this.lastUpdateStart = null;
this.updateAverageDelta = 0;
}

/**
* init the game instance (create a physic world, update starting time, etc..)
*/
init() {
this.world = new World();
this.lastUpdate = globalThis.performance.now();
event.emit(event.GAME_INIT, this);
}

/**
* reset the game Object manager
* destroy all current objects
*/
reset() {
// point to the current active stage "default" camera
var current = state.current();
if (typeof current !== "undefined") {
this.viewport = current.cameras.get("default");
}

// publish reset notification
event.emit(event.GAME_RESET);

// Refresh internal variables for framerate limiting
this.updateFrameRate();
}

/**
* Fired when a level is fully loaded and all renderable instantiated. <br>
* Additionnaly the level id will also be passed to the called function.
* @example
* // call myFunction () everytime a level is loaded
* me.game.onLevelLoaded = this.myFunction.bind(this);
*/
onLevelLoaded() {};

/**
* Update the renderer framerate using the system config variables.
* @see timer.maxfps
* @see World.fps
*/
updateFrameRate() {
// reset the frame counter
this.frameCounter = 0;
this.frameRate = ~~(0.5 + 60 / timer.maxfps);

// set step size based on the updatesPerSecond
this.stepSize = (1000 / this.world.fps);
this.accumulator = 0.0;
this.accumulatorMax = this.stepSize * 10;

// display should always re-draw when update speed doesn't match fps
// this means the user intends to write position prediction drawing logic
this.isAlwaysDirty = (timer.maxfps > this.world.fps);
}

/**
* Returns the parent container of the specified Child in the game world
* @param {Renderable} child
* @returns {Container}
*/
getParentContainer(child) {
return child.ancestor;
}

/**
* force the redraw (not update) of all objects
*/
repaint() {
this.isDirty = true;
}

/**
* update all objects related to this game active scene/stage
* @param {number} time current timestamp as provided by the RAF callback
* @param {Stage} stage the current stage
*/
update(time, stage) {
// handle frame skipping if required
if ((++this.frameCounter % this.frameRate) === 0) {
// reset the frame counter
this.frameCounter = 0;

// publish notification
event.emit(event.GAME_BEFORE_UPDATE, time);

this.accumulator += timer.getDelta();
this.accumulator = Math.min(this.accumulator, this.accumulatorMax);

this.updateDelta = (timer.interpolation) ? timer.getDelta() : this.stepSize;
this.accumulatorUpdateDelta = (timer.interpolation) ? this.updateDelta : Math.max(this.updateDelta, this.updateAverageDelta);

while (this.accumulator >= this.accumulatorUpdateDelta || timer.interpolation) {
this.lastUpdateStart = globalThis.performance.now();

// game update event
if (state.isPaused() !== true) {
event.emit(event.GAME_UPDATE, time);
}

// update all objects (and pass the elapsed time since last frame)
this.isDirty = stage.update(this.updateDelta) || this.isDirty;

this.lastUpdate = globalThis.performance.now();
this.updateAverageDelta = this.lastUpdate - this.lastUpdateStart;

this.accumulator -= this.accumulatorUpdateDelta;
if (timer.interpolation) {
this.accumulator = 0;
break;
}
}

// publish notification
event.emit(event.GAME_AFTER_UPDATE, this.lastUpdate);
}
}

/**
* draw the active scene/stage associated to this game
* @param {Stage} stage the current stage
*/
draw(stage) {
if (renderer.isContextValid === true && (this.isDirty || this.isAlwaysDirty)) {
// publish notification
event.emit(event.GAME_BEFORE_DRAW, globalThis.performance.now());

// prepare renderer to draw a new frame
renderer.clear();

// render the stage
stage.draw(renderer);

// set back to flag
this.isDirty = false;

// flush/render our frame
renderer.flush();

// publish notification
event.emit(event.GAME_AFTER_DRAW, globalThis.performance.now());
}
}
}

export default Application;
10 changes: 5 additions & 5 deletions src/camera/camera2d.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as event from "./../system/event.js";
import pool from "./../system/pooling.js";
import Renderable from "./../renderable/renderable.js";
import {clamp, toBeCloseTo} from "./../math/math.js";
import { world } from "./../game.js";
import game from "./../game.js";


// some ref shortcut
Expand Down Expand Up @@ -615,7 +615,7 @@ class Camera2d extends Renderable {
localToWorld(x, y, v) {
// TODO memoization for one set of coords (multitouch)
v = v || pool.pull("Vector2d");
v.set(x, y).add(this.pos).sub(world.pos);
v.set(x, y).add(this.pos).sub(game.world.pos);
if (!this.currentTransform.isIdentity()) {
this.invCurrentTransform.apply(v);
}
Expand All @@ -639,7 +639,7 @@ class Camera2d extends Renderable {
if (!this.currentTransform.isIdentity()) {
this.currentTransform.apply(v);
}
return v.sub(this.pos).add(world.pos);
return v.sub(this.pos).add(game.world.pos);
}

/**
Expand Down Expand Up @@ -706,7 +706,7 @@ class Camera2d extends Renderable {

this.preDraw(renderer);

container.preDraw(renderer);
container.preDraw(renderer, this);

// draw all objects,
// specifying the viewport as the rectangle area to redraw
Expand All @@ -715,7 +715,7 @@ class Camera2d extends Renderable {
// draw the viewport/camera effects
this.drawFX(renderer);

container.postDraw(renderer);
container.postDraw(renderer, this);

this.postDraw(renderer);

Expand Down
Loading

0 comments on commit 54a689f

Please sign in to comment.