Skip to content

Commit 54a689f

Browse files
committed
[#1091] refactoring ofgame into an Instantiable Application object, 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.
1 parent 3f5ef9e commit 54a689f

21 files changed

+293
-287
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
### Changed
1010
- Core: full ES6 refactoring of `me.device`, and API clean-up (@see https://github.com/melonjs/melonJS/wiki/Upgrade-Guide#120x-to-130x-stable)
1111
- Loader: `onload` and `onerror` callbacks are now optionals when directly loading assets (easier with base64 encoded assets)
12+
- Game: refactoring of`game` into an Instantiable `Application` object, with `game` now being the default instance of it (#1091)
1213

1314
### Fixed
1415
- Loader: fix loading/preloading of base64 audio assets, and base64 encoded FontFace

src/application/application.js

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import { renderer } from "./../video/video.js";
2+
import * as event from "./../system/event.js";
3+
import timer from "./../system/timer.js";
4+
import state from "./../state/state.js";
5+
import World from "./../physics/world.js";
6+
7+
/**
8+
* @classdesc
9+
* An Application represents a single melonJS game.
10+
* An Application is responsible for updating (each frame) all the related object status and draw them.
11+
* @see game
12+
*/
13+
class Application {
14+
constructor() {
15+
/**
16+
* a reference to the current active stage "default" camera
17+
* @public
18+
* @type {Camera2d}
19+
*/
20+
this.viewport = null;
21+
22+
/**
23+
* a reference to the game world, <br>
24+
* a world is a virtual environment containing all the game objects
25+
* @public
26+
* @type {World}
27+
*/
28+
this.world = null;
29+
30+
/**
31+
* when true, all objects will be added under the root world container.<br>
32+
* When false, a `me.Container` object will be created for each corresponding groups
33+
* @public
34+
* @type {boolean}
35+
* @default true
36+
*/
37+
this.mergeGroup = true;
38+
39+
/**
40+
* Specify the property to be used when sorting renderables.
41+
* Accepted values : "x", "y", "z"
42+
* @public
43+
* @type {string}
44+
* @default "z"
45+
*/
46+
this.sortOn = "z";
47+
48+
/**
49+
* Last time the game update loop was executed. <br>
50+
* Use this value to implement frame prediction in drawing events,
51+
* for creating smooth motion while running game update logic at
52+
* a lower fps.
53+
* @public
54+
* @type {DOMHighResTimeStamp}
55+
* @name lastUpdate
56+
* @memberof Application
57+
*/
58+
this.lastUpdate = 0;
59+
60+
// to know when we have to refresh the display
61+
this.isDirty = true;
62+
63+
// always refresh the display when updatesPerSecond are lower than fps
64+
this.isAlwaysDirty = false;
65+
66+
// frame counter for frameSkipping
67+
// reset the frame counter
68+
this.frameCounter = 0;
69+
this.frameRate = 1;
70+
71+
// time accumulation for multiple update calls
72+
this.accumulator = 0.0;
73+
this.accumulatorMax = 0.0;
74+
this.accumulatorUpdateDelta = 0;
75+
76+
// min update step size
77+
this.stepSize = 1000 / 60;
78+
this.updateDelta = 0;
79+
this.lastUpdateStart = null;
80+
this.updateAverageDelta = 0;
81+
}
82+
83+
/**
84+
* init the game instance (create a physic world, update starting time, etc..)
85+
*/
86+
init() {
87+
this.world = new World();
88+
this.lastUpdate = globalThis.performance.now();
89+
event.emit(event.GAME_INIT, this);
90+
}
91+
92+
/**
93+
* reset the game Object manager
94+
* destroy all current objects
95+
*/
96+
reset() {
97+
// point to the current active stage "default" camera
98+
var current = state.current();
99+
if (typeof current !== "undefined") {
100+
this.viewport = current.cameras.get("default");
101+
}
102+
103+
// publish reset notification
104+
event.emit(event.GAME_RESET);
105+
106+
// Refresh internal variables for framerate limiting
107+
this.updateFrameRate();
108+
}
109+
110+
/**
111+
* Fired when a level is fully loaded and all renderable instantiated. <br>
112+
* Additionnaly the level id will also be passed to the called function.
113+
* @example
114+
* // call myFunction () everytime a level is loaded
115+
* me.game.onLevelLoaded = this.myFunction.bind(this);
116+
*/
117+
onLevelLoaded() {};
118+
119+
/**
120+
* Update the renderer framerate using the system config variables.
121+
* @see timer.maxfps
122+
* @see World.fps
123+
*/
124+
updateFrameRate() {
125+
// reset the frame counter
126+
this.frameCounter = 0;
127+
this.frameRate = ~~(0.5 + 60 / timer.maxfps);
128+
129+
// set step size based on the updatesPerSecond
130+
this.stepSize = (1000 / this.world.fps);
131+
this.accumulator = 0.0;
132+
this.accumulatorMax = this.stepSize * 10;
133+
134+
// display should always re-draw when update speed doesn't match fps
135+
// this means the user intends to write position prediction drawing logic
136+
this.isAlwaysDirty = (timer.maxfps > this.world.fps);
137+
}
138+
139+
/**
140+
* Returns the parent container of the specified Child in the game world
141+
* @param {Renderable} child
142+
* @returns {Container}
143+
*/
144+
getParentContainer(child) {
145+
return child.ancestor;
146+
}
147+
148+
/**
149+
* force the redraw (not update) of all objects
150+
*/
151+
repaint() {
152+
this.isDirty = true;
153+
}
154+
155+
/**
156+
* update all objects related to this game active scene/stage
157+
* @param {number} time current timestamp as provided by the RAF callback
158+
* @param {Stage} stage the current stage
159+
*/
160+
update(time, stage) {
161+
// handle frame skipping if required
162+
if ((++this.frameCounter % this.frameRate) === 0) {
163+
// reset the frame counter
164+
this.frameCounter = 0;
165+
166+
// publish notification
167+
event.emit(event.GAME_BEFORE_UPDATE, time);
168+
169+
this.accumulator += timer.getDelta();
170+
this.accumulator = Math.min(this.accumulator, this.accumulatorMax);
171+
172+
this.updateDelta = (timer.interpolation) ? timer.getDelta() : this.stepSize;
173+
this.accumulatorUpdateDelta = (timer.interpolation) ? this.updateDelta : Math.max(this.updateDelta, this.updateAverageDelta);
174+
175+
while (this.accumulator >= this.accumulatorUpdateDelta || timer.interpolation) {
176+
this.lastUpdateStart = globalThis.performance.now();
177+
178+
// game update event
179+
if (state.isPaused() !== true) {
180+
event.emit(event.GAME_UPDATE, time);
181+
}
182+
183+
// update all objects (and pass the elapsed time since last frame)
184+
this.isDirty = stage.update(this.updateDelta) || this.isDirty;
185+
186+
this.lastUpdate = globalThis.performance.now();
187+
this.updateAverageDelta = this.lastUpdate - this.lastUpdateStart;
188+
189+
this.accumulator -= this.accumulatorUpdateDelta;
190+
if (timer.interpolation) {
191+
this.accumulator = 0;
192+
break;
193+
}
194+
}
195+
196+
// publish notification
197+
event.emit(event.GAME_AFTER_UPDATE, this.lastUpdate);
198+
}
199+
}
200+
201+
/**
202+
* draw the active scene/stage associated to this game
203+
* @param {Stage} stage the current stage
204+
*/
205+
draw(stage) {
206+
if (renderer.isContextValid === true && (this.isDirty || this.isAlwaysDirty)) {
207+
// publish notification
208+
event.emit(event.GAME_BEFORE_DRAW, globalThis.performance.now());
209+
210+
// prepare renderer to draw a new frame
211+
renderer.clear();
212+
213+
// render the stage
214+
stage.draw(renderer);
215+
216+
// set back to flag
217+
this.isDirty = false;
218+
219+
// flush/render our frame
220+
renderer.flush();
221+
222+
// publish notification
223+
event.emit(event.GAME_AFTER_DRAW, globalThis.performance.now());
224+
}
225+
}
226+
}
227+
228+
export default Application;

src/camera/camera2d.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import * as event from "./../system/event.js";
1010
import pool from "./../system/pooling.js";
1111
import Renderable from "./../renderable/renderable.js";
1212
import {clamp, toBeCloseTo} from "./../math/math.js";
13-
import { world } from "./../game.js";
13+
import game from "./../game.js";
1414

1515

1616
// some ref shortcut
@@ -615,7 +615,7 @@ class Camera2d extends Renderable {
615615
localToWorld(x, y, v) {
616616
// TODO memoization for one set of coords (multitouch)
617617
v = v || pool.pull("Vector2d");
618-
v.set(x, y).add(this.pos).sub(world.pos);
618+
v.set(x, y).add(this.pos).sub(game.world.pos);
619619
if (!this.currentTransform.isIdentity()) {
620620
this.invCurrentTransform.apply(v);
621621
}
@@ -639,7 +639,7 @@ class Camera2d extends Renderable {
639639
if (!this.currentTransform.isIdentity()) {
640640
this.currentTransform.apply(v);
641641
}
642-
return v.sub(this.pos).add(world.pos);
642+
return v.sub(this.pos).add(game.world.pos);
643643
}
644644

645645
/**
@@ -706,7 +706,7 @@ class Camera2d extends Renderable {
706706

707707
this.preDraw(renderer);
708708

709-
container.preDraw(renderer);
709+
container.preDraw(renderer, this);
710710

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

718-
container.postDraw(renderer);
718+
container.postDraw(renderer, this);
719719

720720
this.postDraw(renderer);
721721

0 commit comments

Comments
 (0)