Skip to content

Commit 801d63d

Browse files
authored
Add support for game over / restarting the game (google#150)
* Add support for gameover / restarting the game - Pulled everything that needs to be reset into a level class - Updating many references - Trigger the shared gameover screen - Create a new level when the game is restarted - Keep the main loop running the whole time
1 parent 2272c80 commit 801d63d

4 files changed

Lines changed: 124 additions & 54 deletions

File tree

static/scenes/railroad/js/game.js

Lines changed: 56 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,34 @@
22

33
goog.provide('app.Game');
44

5-
goog.require('app.Constants');
65
goog.require('app.Scene');
76
goog.require('app.Present');
7+
goog.require('app.Constants');
8+
goog.require('app.Level');
89
goog.require('app.shared.Scoreboard');
9-
goog.require('app.systems.CameraSystem');
10-
goog.require('app.systems.ElvesSystem');
11-
goog.require('app.systems.RaycasterSystem');
12-
goog.require('app.systems.PresentSystem');
10+
goog.require('app.shared.Gameover');
11+
12+
/**
13+
* Maximum time allowed between updates. If the player's computer was locked or
14+
* the window was inactive, we could potentially have a very large delta between
15+
* frames. This keeps the game somewhat paused when the user is inactive.
16+
*/
17+
const maxTimeStep = 0.5;
1318

1419
class Game {
1520

1621
constructor() {
1722
this.paused = false;
23+
24+
this.scoreboard = new app.shared.Scoreboard(this, null, app.Constants.LEVEL_COUNT);
25+
this.gameoverView = new app.shared.Gameover(this);
26+
27+
this.renderer = new THREE.WebGLRenderer();
28+
this.renderer.setSize(window.innerWidth, window.innerHeight);
29+
this.renderer.shadowMap.enabled = true;
30+
31+
this.level = undefined;
32+
1833
this.previousSeconds = Date.now() / 1000;
1934
}
2035

@@ -30,37 +45,28 @@ class Game {
3045
* game.
3146
*/
3247
async start(container) {
33-
this.renderer = new THREE.WebGLRenderer();
34-
this.renderer.setSize(window.innerWidth, window.innerHeight);
35-
this.renderer.shadowMap.enabled = true;
3648
container.appendChild(this.renderer.domElement);
3749

3850
await Promise.all([
3951
app.Scene.preload(),
4052
app.Present.preload(),
4153
]);
4254

43-
this.placeholderScene = new app.Scene();
44-
this.camera = this.placeholderScene.getCamera();
45-
this.scoreboard = new app.shared.Scoreboard(this, undefined, app.Constants.NUM_LEVELS);
46-
this.elvesSystem = new app.ElvesSystem(this.camera, this.placeholderScene);
47-
this.presentSystem = new app.PresentSystem(this.placeholderScene);
48-
this.raycasterSystem = new app.RaycasterSystem(this.renderer, this.camera, this.placeholderScene, this.scoreboard);
49-
5055
this.setUpListeners();
5156
this.mainLoop();
5257

53-
window.addEventListener('resize', () => {
54-
this.camera.aspect = window.innerWidth / window.innerHeight;
55-
this.camera.updateProjectionMatrix();
56-
this.renderer.setSize(window.innerWidth, window.innerHeight);
57-
console.log(this.camera);
58-
});
58+
this.startLevel();
59+
}
60+
61+
startLevel() {
62+
if (this.level) {
63+
this.level.cleanUp();
64+
}
65+
this.level = new Level(this.renderer, (score) => this.scoreboard.addScore(score));
5966
}
6067

6168
pause() {
6269
this.paused = true;
63-
this.previousSeconds = null;
6470
}
6571

6672
resume() {
@@ -70,65 +76,67 @@ class Game {
7076
}
7177
this.paused = false;
7278
this.previousSeconds = Date.now() / 1000;
73-
this.mainLoop();
7479
}
7580

7681
restart() {
77-
if (!this.paused) {
78-
console.warn('Game must be paused before it can be restarted');
79-
return;
80-
}
81-
console.log('TODO');
82+
this.startLevel();
8283
this.paused = false;
83-
this.mainLoop();
84+
this.scoreboard.restart()
8485
}
8586

8687
gameover() {
87-
console.error('TODO');
88+
this.paused = true;
89+
this.gameoverView.show();
8890
}
8991

9092
mainLoop() {
91-
if (this.paused) {
92-
return;
93-
}
9493
this.update();
9594
this.render();
9695

9796
requestAnimationFrame(() => this.mainLoop());
9897
}
9998

10099
render() {
101-
this.renderer.render(this.placeholderScene.scene, this.camera);
100+
if (this.paused) {
101+
return;
102+
}
103+
104+
if (this.level) {
105+
this.level.render(this.renderer);
106+
}
102107
}
103108

104109
/**
105110
* Handles the main logic for the game by making each system update.
106111
*/
107112
update() {
108113
const nowSeconds = Date.now() / 1000;
109-
const deltaSeconds = nowSeconds - this.previousSeconds;
110-
this.placeholderScene.update(deltaSeconds);
111-
this.elvesSystem.update(deltaSeconds);
112-
this.presentSystem.update(deltaSeconds);
113-
this.scoreboard.onFrame(deltaSeconds);
114+
const deltaSeconds = Math.min(nowSeconds - this.previousSeconds, maxTimeStep);
115+
116+
if (!this.paused && this.level) {
117+
this.level.update(deltaSeconds);
118+
this.scoreboard.onFrame(deltaSeconds);
119+
}
120+
114121
this.previousSeconds = nowSeconds;
115-
116122
}
117123

118124
setUpListeners() {
119-
this.clickListener = this.renderer.domElement.addEventListener('click', (click) => {
125+
this.renderer.domElement.addEventListener('click', (click) => {
120126
this.handleClick(click);
121127
});
128+
129+
window.addEventListener('resize', () => {
130+
this.renderer.setSize(window.innerWidth, window.innerHeight);
131+
if (this.level) {
132+
this.level.scene.updateCameraSize();
133+
}
134+
});
122135
}
123136

124137
handleClick(clickEvent) {
125-
const nowSeconds = Date.now() / 1000;
126-
const intersections = this.raycasterSystem.cast(clickEvent);
127-
128-
// Test present shot
129-
// const aim = this.placeholderScene.getCameraPosition(nowSeconds + 30);
130-
if (intersections.length > 0) {
131-
this.presentSystem.shoot(intersections[0].point);
138+
if (!this.paused && this.level) {
139+
this.level.handleClick(clickEvent);
132140
}
133141
}
134142
}

static/scenes/railroad/js/level.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
goog.provide('app.Level');
2+
3+
goog.require('app.Scene');
4+
goog.require('app.systems.CameraSystem');
5+
goog.require('app.systems.ElvesSystem');
6+
goog.require('app.systems.RaycasterSystem');
7+
goog.require('app.systems.PresentSystem');
8+
9+
/**
10+
* Handles everything that lives within one level.
11+
*
12+
* For each new level, and when the game is restarted, a new instance will be created.
13+
*/
14+
class Level {
15+
16+
/**
17+
* @param {*} renderer ThreeJS renderer.
18+
* @param { function(number):void } addScore Function for adding to the game's score.
19+
*/
20+
constructor(renderer, addScore) {
21+
this.scene = new app.Scene();
22+
this.camera = this.scene.getCamera();
23+
this.elvesSystem = new app.ElvesSystem(this.camera, this.scene);
24+
this.presentSystem = new app.PresentSystem(this.scene);
25+
this.raycasterSystem = new app.RaycasterSystem(renderer, this.camera, this.scene, addScore);
26+
}
27+
28+
cleanUp() {
29+
// For the moment, it seems like nothing is actually needed here.
30+
}
31+
32+
render(renderer) {
33+
renderer.render(this.scene.scene, this.camera);
34+
}
35+
36+
update(deltaSeconds) {
37+
this.scene.update(deltaSeconds);
38+
this.elvesSystem.update(deltaSeconds);
39+
this.presentSystem.update(deltaSeconds);
40+
}
41+
42+
handleClick(clickEvent) {
43+
const intersections = this.raycasterSystem.cast(clickEvent);
44+
45+
if (intersections.length > 0) {
46+
this.presentSystem.shoot(intersections[0].point);
47+
}
48+
}
49+
}
50+
51+
app.Level = Level;

static/scenes/railroad/js/scene.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ class Scene {
5353
this.mixer.update(deltaSeconds);
5454
}
5555

56+
updateCameraSize() {
57+
this.camera.aspect = window.innerWidth / window.innerHeight;
58+
this.camera.updateProjectionMatrix();
59+
}
60+
5661
getCamera() {
5762
return this.camera;
5863
}

static/scenes/railroad/js/systems/raycaster.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ goog.provide('app.systems.RaycasterSystem');
22

33
class RaycasterSystem {
44

5-
constructor(renderer, camera, placeholderScene, scoreboard) {
5+
/**
6+
* @param {*} renderer ThreeJS renderer.
7+
* @param {*} camera ThreeJS camera.
8+
* @param { app.Scene } placeholderScene Our scene class
9+
* @param { function(number):void } addScore Function for adding to the game's score.
10+
*/
11+
constructor(renderer, camera, scene, addScore) {
612
this.renderer = renderer;
713
this.camera = camera;
8-
this.placeholderScene = placeholderScene;
9-
this.scoreboard = scoreboard;
14+
this.scene = scene;
15+
this.addScore = addScore;
1016
this.raycaster = new THREE.Raycaster();
1117
}
1218

@@ -23,7 +29,7 @@ class RaycasterSystem {
2329
ray.x = (clickEvent.clientX / this.renderer.domElement.width) * 2 - 1;
2430
ray.y = -(clickEvent.clientY / this.renderer.domElement.height) * 2 + 1;
2531
this.raycaster.setFromCamera(ray, this.camera);
26-
return this.raycaster.intersectObjects(this.placeholderScene.scene.children, true);
32+
return this.raycaster.intersectObjects(this.scene.scene.children, true);
2733
}
2834

2935
updateScore(intersections) {
@@ -35,10 +41,10 @@ class RaycasterSystem {
3541
if (object.userData.clickable.type === 'elf') {
3642
score += 1;
3743
} else if (object.userData.clickable.type === 'ice') {
38-
this.placeholderScene.setTimeScale(0.5);
44+
this.scene.setTimeScale(0.5);
3945
}
4046
}
41-
this.scoreboard.addScore(score);
47+
this.addScore(score);
4248
}
4349
}
4450

0 commit comments

Comments
 (0)