Skip to content

Commit

Permalink
feat: add loading screen using custom event emitter
Browse files Browse the repository at this point in the history
  • Loading branch information
LiprikON2 committed Jul 2, 2023
1 parent c122e21 commit ff48c63
Show file tree
Hide file tree
Showing 21 changed files with 229 additions and 95 deletions.
2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@
"@capacitor/device": "^5.0.4",
"@dnd-kit/core": "^6.0.8",
"@emotion/styled": "^11.11.0",
"@geckos.io/client": "^2.3.1",
"@mantine/core": "^6.0.14",
"@mantine/form": "^6.0.14",
"@mantine/hooks": "^6.0.14",
"@tanstack/react-query": "^4.29.13",
"core-js": "^3.31.0",
"immer": "^10.0.2",
"lodash": "^4.17.21",
"nanoevents": "^8.0.0",
"navmesh": "^2.3.1",
"phaser": "3.55.2",
"phaser-navmesh": "^2.3.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import "phaser";
import Phaser from "phaser";
import MouseWheelScrollerPlugin from "phaser3-rex-plugins/plugins/mousewheelscroller-plugin.js";
import RotateToPlugin from "phaser3-rex-plugins/plugins/rotateto-plugin.js";
Expand All @@ -8,7 +7,7 @@ import VirtualJoystickPlugin from "phaser3-rex-plugins/plugins/virtualjoystick-p
import ButtonPlugin from "phaser3-rex-plugins/plugins/button-plugin.js";

import { MainScene, PreloadScene } from "~/scenes";
import type { Spaceship } from "~/objects";

const DEFAULT_WIDTH = 1920;
const DEFAULT_HEIGHT = 1080;
// const DEFAULT_WIDTH = 920;
Expand Down Expand Up @@ -80,42 +79,3 @@ export const gameConfig: Phaser.Types.Core.GameConfig = {
gamepad: true,
},
};

export class Game {
config;
game: Phaser.Game;

constructor(config) {
this.config = config;
}

init = async (settings) => {
const whenIsBooted = new Promise((resolve) => {
this.game = new Phaser.Game({
...this.config,
callbacks: { postBoot: () => resolve(true) },
});
this.game["settings"] = settings;
});
await whenIsBooted;

const whenSceneCreated = new Promise((resolve) => {
const MainScene = this.game.scene.keys.MainScene as MainScene;
MainScene.events.on("create", resolve);
});
await whenSceneCreated;

return this;
};
get scene(): MainScene | null {
return (this.game?.scene?.keys?.MainScene as MainScene) ?? null;
}
get player(): Spaceship | null {
return this.scene?.player ?? null;
}
destroy = () => {
this.game.destroy(false);
};
}

export const game = new Game(gameConfig);
9 changes: 9 additions & 0 deletions client/src/game/scripts/core/gameExtended.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class GameExtended extends Phaser.Game {
settings;
outEmitter;
constructor(GameConfig?: Phaser.Types.Core.GameConfig, settings = {}, outEmitter = null) {
super(GameConfig);
this.settings = settings;
this.outEmitter = outEmitter;
}
}
76 changes: 76 additions & 0 deletions client/src/game/scripts/core/gameManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import Phaser from "phaser";
import { createNanoEvents } from "nanoevents";
import type { Emitter } from "nanoevents";

import { MainScene } from "~/scenes";
import type { Spaceship } from "~/objects";
import { GameExtended } from ".";
import { gameConfig } from ".";

interface Events {
loading: (report: { name: string; progress: number }) => void;
}

export class GameManager {
config: Phaser.Types.Core.GameConfig;
game: GameExtended;
emitter: Emitter<Events>;

constructor(config) {
this.config = config;
this.emitter = createNanoEvents();
}

on = (event, callback) => {
return this.emitter.on(event, callback);
};

init = async (settings) => {
console.log("Booting");
const whenIsBooted = new Promise((resolve) => {
this.game = new GameExtended(
{
...this.config,
callbacks: { postBoot: () => resolve(true) },
},
settings,
this.emitter
);
});
await whenIsBooted;
console.log("Booted");
console.log("Creating");

const whenSceneCreated = new Promise((resolve) => {
const MainScene = this.game.scene.keys.MainScene as MainScene;
MainScene.events.on("create", resolve);
});
await whenSceneCreated;

console.log("Created");

return this;
};
initMultiplayer = async (settings) => {};

// TODO use this when ui modals are opened
lockInput = () => {
this.game.input.keyboard.enabled = false;
};
unlockInput = () => {
this.game.input.keyboard.enabled = false;
};

get scene(): MainScene | null {
const mainScene = this.game?.scene?.keys?.MainScene as MainScene;
return mainScene ?? null;
}
get player(): Spaceship | null {
return this.scene?.player ?? null;
}
destroy = () => {
this.game.destroy(false);
};
}

export const gameManager = new GameManager(gameConfig);
3 changes: 3 additions & 0 deletions client/src/game/scripts/core/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./gameConfig";
export * from "./gameExtended";
export * from "./gameManager";
7 changes: 4 additions & 3 deletions client/src/game/scripts/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { game, gameConfig } from "./core/game";

export { game, gameConfig };
export * from "./core";
export * from "./managers";
export * from "./objects";
export * from "./scenes";
2 changes: 2 additions & 0 deletions client/src/game/scripts/scenes/mainScene.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { InputManager, MobManager, SoundManager } from "~/managers";
import { Spaceship, GenericText } from "~/objects";
import type { GameExtended } from "~/game/core";

export default class MainScene extends Phaser.Scene {
game: GameExtended;
inputManager;
soundManager;
mobManager;
Expand Down
32 changes: 26 additions & 6 deletions client/src/game/scripts/scenes/preloadScene.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,41 @@
import type { GameExtended } from "~/game/core";

export default class PreloadScene extends Phaser.Scene {
game: GameExtended;
constructor() {
super({ key: "PreloadScene" });
}

preload() {
// Maps
this.game.outEmitter.emit("loading", { name: "Maps", progress: 1 });
this.load.atlas("map_1-1", "assets/maps/map_1-1.jpg", "assets/maps/map_1-1.json");
this.load.atlas("map_1-2", "assets/maps/map_1-2.webp", "assets/maps/map_1-2.json");

// Ships
this.game.outEmitter.emit("loading", { name: "Ships", progress: 2 });
this.load.atlas({
key: "F5S4",
textureURL: "assets/ships/F5S4.png",
normalMap: "assets/ships/F5S4N.png",
atlasURL: "assets/ships/F5S4.json",
});

// Weapons
this.game.outEmitter.emit("loading", { name: "Weapons", progress: 3 });
this.load.spritesheet("laser", "assets/weapons/lasers/spr_bullet_strip02.png", {
frameWidth: 95,
frameHeight: 68,
});
this.load.image("gatling", "assets/weapons/gatling/projectile.webp");

// Modules
this.game.outEmitter.emit("loading", { name: "Modules", progress: 4 });
this.load.image("shield", "assets/ships/shield_Edit.png");

// Effects
// TODO is it better to use power of 2?
// TODO is it better to use powers of 2?
this.game.outEmitter.emit("loading", { name: "Effects", progress: 7 });
this.load.spritesheet("particles", "assets/effects/particles_1080x1080.png", {
frameWidth: 1080,
frameHeight: 1080,
Expand All @@ -49,10 +59,12 @@ export default class PreloadScene extends Phaser.Scene {
});

// UI
this.game.outEmitter.emit("loading", { name: "UI", progress: 10 });
this.load.image("joystick_1", "assets/ui/joystick_1.svg");
this.load.image("joystick_2", "assets/ui/joystick_2.svg");

// Sound Effects
this.game.outEmitter.emit("loading", { name: "Sound Effects", progress: 15 });
this.load.audio("laser_sound_1", "assets/weapons/lasers/laser1_short.mp3");
this.load.audio("laser_sound_2", "assets/weapons/lasers/laser2_short.mp3");
this.load.audio("laser_sound_3", "assets/weapons/lasers/laser3_short.mp3");
Expand All @@ -65,15 +77,22 @@ export default class PreloadScene extends Phaser.Scene {
this.load.audio("shield_down_sound_1", "assets/ships/shield_powerdown.mp3");

// Music
this.game.outEmitter.emit("loading", { name: "Music", progress: 20 });
this.load.audio("track_1", "assets/music/SMP1_THEME_Cargoship.mp3");
this.load.audio("track_2", "assets/music/SMP1_THEME_Gliese 1214b.mp3");
this.load.audio("track_3", "assets/music/SMP1_THEME_Space caravan.mp3");
this.load.audio("track_4", "assets/music/SMP1_THEME_Voyager.mp3");

this.game.outEmitter.emit("loading", {
name: "Preload Scene",
progress: 80,
});
}

create() {
// Scenes
this.scene.start("MainScene");
this.game.outEmitter.emit("loading", { name: "Main Scene", progress: 90 });

// Animations
this.anims.create({
Expand Down Expand Up @@ -104,6 +123,7 @@ export default class PreloadScene extends Phaser.Scene {
repeat: 0,
hideOnComplete: true,
});
this.game.outEmitter.emit("loading", { name: "Animations", progress: 100 });

/**
* This is how you would dynamically import the mainScene class (with code splitting),
Expand All @@ -112,11 +132,11 @@ export default class PreloadScene extends Phaser.Scene {
* The name of the chunk would be 'mainScene.chunk.js
* Find more about code splitting here: https://webpack.js.org/guides/code-splitting/
*/
// let someCondition = true
// let someCondition = true;
// if (someCondition)
// import(/* webpackChunkName: "mainScene" */ './mainScene').then(mainScene => {
// this.scene.add('MainScene', mainScene.default, true)
// })
// else console.log('The mainScene class will not even be loaded by the browser')
// import(/* webpackChunkName: "mainScene" */ "./mainScene").then((mainScene) => {
// this.scene.add("MainScene", mainScene.default, true);
// });
// else console.log("The mainScene class will not even be loaded by the browser");
}
}
2 changes: 1 addition & 1 deletion client/src/ui/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export const App = () => {
<StyledUI>
{isLoaded && <TopLeft GroupComponent={StyledTopLeftGroup} />}
{isLoaded && <TopRight GroupComponent={StyledTopRightGroup} />}
{mode === "mainMenu" && <Center GroupComponent={StyledCenterGroup} />}
{!isLoaded && <Center GroupComponent={StyledCenterGroup} />}
<Right GroupComponent={StyledRightGroup} />
{isLoaded && <BottomLeft GroupComponent={StyledBottomLeftGroup} />}
<BottomRight GroupComponent={StyledBottomRightGroup} />
Expand Down
7 changes: 7 additions & 0 deletions client/src/ui/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ const theme: Partial<MantineTheme> = {
p: "xl",
},
},
Progress: {
defaultProps: {
color: "cyan",
size: "xl",
style: { width: "100%" },
},
},
},
// TODO
// headings: {
Expand Down
27 changes: 14 additions & 13 deletions client/src/ui/hooks/useGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import type { Spaceship } from "~/objects";
import { produce } from "immer";
import { create } from "zustand";

import { game } from "~/game/core/game";
import type { Game } from "~/game/core/game";
import { gameManager } from "~/game/core/gameManager";
import type { GameManager } from "~/game/core/gameManager";

interface GameStore {
game: Game | null;
gameManager: GameManager | null;
mode: "mainMenu" | "singleplayer" | "multiplayer";
computed: {
isLoading: boolean;
Expand All @@ -21,7 +21,7 @@ interface GameStore {
}

export const useGame = create<GameStore>((set, get) => ({
game: null,
gameManager: null,
mode: "mainMenu",

// https://github.com/pmndrs/zustand/issues/132
Expand All @@ -30,14 +30,14 @@ export const useGame = create<GameStore>((set, get) => ({
return get().mode !== "mainMenu" && !get().computed.isLoaded;
},
get isLoaded() {
return !!get().game?.player;
return !!get().gameManager?.player;
},

get player() {
return get().game?.player;
return get().gameManager?.player;
},
get scene() {
return get().game?.scene;
return get().gameManager?.scene;
},
},

Expand All @@ -47,11 +47,11 @@ export const useGame = create<GameStore>((set, get) => ({
state.mode = "singleplayer";
})
);
const singleplayerGame = await game.init(settings);
const singleplayerGame = await gameManager.init(settings);

set(
produce((state) => {
state.game = singleplayerGame;
state.gameManager = singleplayerGame;
})
);
},
Expand All @@ -61,18 +61,19 @@ export const useGame = create<GameStore>((set, get) => ({
state.mode = "multiplayer";
})
);
const multiplayerGame = await game.init(settings);
const multiplayerGame = await gameManager.init(settings);

set(
produce((state) => {
state.game = multiplayerGame;
state.gameManager = multiplayerGame;
})
);
},
loadMainMenu: () => {
get().game.destroy();
get().gameManager.destroy();
set(
produce((state) => {
state.game = null;
state.gameManager = null;
state.mode = "mainMenu";
})
);
Expand Down
Loading

0 comments on commit ff48c63

Please sign in to comment.