UNPKG

4.8 kBJavaScriptView Raw
1"use strict";
2
3var Camera = require("./camera");
4
5/**
6 * A Scene handles the render loop for the game. Inside of initFunc, simulationFunc, and drawFunc `this` refers to the current scene.
7 * @constructor
8 * @alias Splat.Scene
9 * @param {external:canvas} canvas The canvas to render on.
10 * @param {emptyCallback} initFunc A callback to be called every time the Scene is {@link Splat.Scene#start started}.
11 * @param {simulationCallback} simulationFunc A callback that updates the state of the game's simulation.
12 * @param {drawCallback} drawFunc A callback that draws the game.
13 */
14function Scene(canvas, initFunc, simulationFunc, drawFunc) {
15 /**
16 * The canvas to render on.
17 * @member {external:canvas}
18 * @private
19 */
20 this.canvas = canvas;
21 /**
22 * A callback to be called ever time the Scene is {@link Splat.Scene#start started}.
23 * @member {emptyCallback}
24 * @private
25 */
26 this.initFunc = initFunc;
27 /**
28 * A callback that updates the state of the game's simulation.
29 * @member {simulationCallback}
30 * @private
31 */
32 this.simulationFunc = simulationFunc;
33 /**
34 * A callback that draws the game.
35 * @member {drawCallback}
36 * @private
37 */
38 this.drawFunc = drawFunc;
39
40 /**
41 * The drawing context for {@link Scene#canvas}
42 * @member {external:CanvasRenderingContext2D}
43 * @private
44 */
45 this.context = canvas.getContext("2d");
46 /**
47 * The timestamp of the last frame. Used to determine how many milliseconds elapsed between frames.
48 * @member {number}
49 * @private
50 */
51 this.lastTimestamp = -1;
52 /**
53 * How frequently to run the simulation in Hertz (cycles per second). This should be higher than your expected framerate.
54 * @member {number}
55 * @private
56 */
57 this.simulationFrequencyHz = 180;
58 /**
59 * An accumulator of the leftover time between frames. This lets us run the simulation at a constant framerate independant of the drawing framerate.
60 * @member {number}
61 * @private
62 */
63 this.timeAccumulator = 0;
64 /**
65 * Whether or not the Scene is currently running.
66 * @member {boolean}
67 * @private
68 */
69 this.running = false;
70 /**
71 * A key-value store of named timers. Timers in this object will be automatically {@link Splat.Timer#tick tick()}ed for you when the scene is running..
72 * @member {object}
73 */
74 this.timers = {};
75
76 /**
77 * The Camera used to offset the Scene's drawing.
78 * This Camera's {@link Splat.Entity#move move} and {@link Splat.Camera#draw draw} methods are called automatically for you. The default Camera starts at the origin (0,0).
79 * @member {Splat.Camera}
80 */
81 this.camera = new Camera(0, 0, canvas.width, canvas.height);
82 /**
83 * A flag that enables/disables a frame rate counter in the corner of the screen. This is useful during development.
84 * @member {boolean}
85 */
86 this.showFrameRate = false;
87}
88/**
89 * Start running the scene.
90 */
91Scene.prototype.start = function() {
92 this.lastTimestamp = -1;
93 this.running = true;
94 var scene = this;
95 window.requestAnimationFrame(function(t) { mainLoop(scene, t); });
96};
97/**
98 * Stop running the scene.
99 */
100Scene.prototype.stop = function() {
101 this.running = false;
102};
103/**
104 * Reset the simulation by re-running the {@link Splat.Scene#initFunc}.
105 */
106Scene.prototype.reset = function() {
107 this.initFunc.call(this);
108};
109
110function mainLoop(scene, timestamp) {
111 if (!scene.running) {
112 return;
113 }
114 if (scene.lastTimestamp === -1) {
115 scene.lastTimestamp = timestamp;
116 }
117 var elapsedMillis = timestamp - scene.lastTimestamp;
118 scene.lastTimestamp = timestamp;
119
120 scene.timeAccumulator += elapsedMillis;
121 var simulationMs = Math.floor(1000 / scene.simulationFrequencyHz);
122 while (scene.timeAccumulator > simulationMs) {
123 scene.timeAccumulator -= simulationMs;
124
125 incrementTimers(scene.timers, simulationMs);
126 if (!scene.running) {
127 return;
128 }
129 scene.simulationFunc.call(scene, simulationMs);
130 scene.camera.move(simulationMs);
131 }
132
133 scene.context.save();
134 scene.camera.draw(scene.context);
135 scene.drawFunc.call(scene, scene.context);
136
137 if (scene.showFrameRate) {
138 drawFrameRate(scene, elapsedMillis);
139 }
140
141 scene.context.restore();
142
143 if (scene.running) {
144 window.requestAnimationFrame(function(t) { mainLoop(scene, t); });
145 }
146}
147
148function incrementTimers(timers, elapsedMillis) {
149 for (var i in timers) {
150 if (timers.hasOwnProperty(i)) {
151 timers[i].tick(elapsedMillis);
152 }
153 }
154}
155
156function drawFrameRate(scene, elapsedMillis) {
157 var fps = (1000 / elapsedMillis) |0;
158
159 scene.context.font = "24px mono";
160 if (fps < 30) {
161 scene.context.fillStyle = "#ff0000";
162 } else if (fps < 50) {
163 scene.context.fillStyle = "#ffff00";
164 } else {
165 scene.context.fillStyle = "#00ff00";
166 }
167 var msg = fps + " FPS";
168 var w = scene.context.measureText(msg).width;
169 scene.camera.drawAbsolute(scene.context, function() {
170 scene.context.fillText(msg, scene.canvas.width - w - 50, 50);
171 });
172}
173
174module.exports = Scene;