Skip to content

Commit

Permalink
add loading bar
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewkolos committed Mar 27, 2021
1 parent 87dbde5 commit cc222cf
Show file tree
Hide file tree
Showing 12 changed files with 815 additions and 178 deletions.
523 changes: 520 additions & 3 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"main": "index.js",
"scripts": {
"test": "jest --verbose",
"start": "tsc --noEmit && cp -ru src/assets dist && parcel src/index.html"
"start": "npm run build && parcel src/index.html",
"build": "tsc --noEmit && cp -ru src/assets dist",
"dev": "npm run build && parcel —no-minify src/index.html"
},
"author": "",
"license": "ISC",
Expand Down Expand Up @@ -40,7 +42,9 @@
"@akolos/event-emitter": "^2.0.4",
"@akolos/ts-tween": "^1.0.6",
"bluebird": "^3.7.2",
"card": "^2.5.0",
"clean-webpack-plugin": "^3.0.0",
"client": "0.0.1",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.4",
"nouislider": "^14.6.3",
Expand Down
Binary file modified src/assets/images/card/backside.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 35 additions & 16 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,41 @@ import { Game } from '../game';
import { SoundId } from './audio/sound-id';
import { Renderer } from './renderer/renderer';
import { SuitAssignments } from './renderer/suit-assignments';
import { loadResources } from './resources';
import { Ui } from './ui';
import { AudioService as audio } from './audio/audio-service';
import { CardLike } from 'card/card-like';
import { RendererLoader } from './renderer/renderer-loader';

export class Client {
private acceptGameInputs = false;
private readonly ui = Ui.init();
private lastCardSoundTime = new Date().getTime();
private rendererPromise: Promise<Renderer>;

private constructor(private readonly renderer: Renderer, private game: Game, private readonly suitAssignments: SuitAssignments) {
this.init();
private constructor(private game: Game, private readonly suitAssignments: SuitAssignments) {
this.rendererPromise = RendererLoader.load(this.game, suitAssignments);
this.rendererPromise.then((renderer) => {
this.setUpRenderer(renderer);
this.enableUi(renderer);
});

RendererLoader.on('progressed', (progress, status) => {
this.ui.updateLoadingStatus(progress, status);
})
.on('completed', () => {
this.ui.enablePlaybutton();
});
}

public async getDomElement(): Promise<HTMLElement> {
const renderer = await this.rendererPromise;
return renderer.domElement;
}

private init() {
private setUpRenderer(renderer: Renderer) {
const suitAssignments = this.suitAssignments;

this.renderer
renderer
.on('dealingCards', () => this.acceptGameInputs = false)
.on('cardsDelt', () => this.acceptGameInputs = true)
.on('cardEntered', card => {
Expand Down Expand Up @@ -55,6 +72,14 @@ export class Client {
}
});

renderer.start();

function isCardInP1Hand(card: CardLike, game: Game) {
return card.suit === suitAssignments.player1 && game.cards.inHand.p1.includes(card.rank);
}
}

private enableUi(renderer: Renderer) {
this.ui.on('playButtonClicked', () => {
this.acceptGameInputs = true;
if (!audio.isMusicPlaying) audio.playMusic();
Expand All @@ -64,18 +89,13 @@ export class Client {
})
.on('resetButtonClicked', () => {
this.game = new Game();
this.renderer.setGameToRender(this.game);
renderer.setGameToRender(this.game);
this.ui.hideResultsToast();
this.ui.updateScore(0, 0);
})
.on('volumeChanged', (value: number) => {
audio.soundVolume = audio.musicVolume = value;
});
this.ui.enablePlaybutton();

function isCardInP1Hand(card: CardLike, game: Game) {
return card.suit === suitAssignments.player1 && game.cards.inHand.p1.includes(card.rank);
}
}

private playCardSound(soundId: SoundId.CardFlip | SoundId.CardHitTable) {
Expand All @@ -90,10 +110,9 @@ export class Client {

public static async start(suitAssignments: SuitAssignments): Promise<Client> {
const game = new Game();
const resources = await loadResources();
const renderer = new Renderer(resources, game, suitAssignments);
document.body.appendChild(renderer.domElement);
renderer.start();
return new Client(renderer, game, suitAssignments);
const client = new Client(game, suitAssignments);
const domElement = await client.getDomElement();
document.body.appendChild(domElement);
return client;
}
}
2 changes: 1 addition & 1 deletion src/client/renderer/card-object3d/card-object3d-factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { CardTextureResources } from '../../../client/resources';
import { CardTextureResources } from '../../resources/resources';
import { Card, CardAbbreviation } from '../../../card/card';
import { CardObject3d } from './card-object3d';

Expand Down
38 changes: 38 additions & 0 deletions src/client/renderer/renderer-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { EventEmitter } from '@akolos/event-emitter';
import { ResourceLoader } from '../../client/resources/resource-loader';
import { Game } from '../../game';
import { Renderer } from './renderer';
import { SuitAssignments } from './suit-assignments';

export interface RendererLoaderEvents {
progressed: [progress: number, status: string];
completed: [];
}


export class RendererLoader {

private static instance: Renderer;

private static ee = new EventEmitter<RendererLoaderEvents>();
private static emit = RendererLoader.ee.makeDelegate('emit', RendererLoader);
public static readonly on = RendererLoader.ee.makeDelegate('on', RendererLoader.ee.asProtected());
public static readonly off = RendererLoader.ee.makeDelegate('off', RendererLoader.ee.asProtected());

public static async load(game: Game, suitAssignments: SuitAssignments): Promise<Renderer> {
if (this.instance) return this.instance;

ResourceLoader.on('loadingFile', (url, itemsLoaded, totalItems) => {
const progress = itemsLoaded / totalItems;
RendererLoader.emit('progressed', progress, `Loading file: ${url}`);
});
ResourceLoader.on('completed', () => {
RendererLoader.emit('progressed', 1, 'Done!');
RendererLoader.emit('completed');
});

const resources = await ResourceLoader.load();
return new Renderer(resources, game, suitAssignments);
}

}
3 changes: 2 additions & 1 deletion src/client/renderer/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Suit } from '../../card/suit';
import { CardAnimator } from '../../client/renderer/animation/card-animator';
import { Game, MatchupWinner } from '../../game';
import { Matchup } from '../../game/matchup';
import { CardTextureResources, Resources } from '../resources';
import { CardTextureResources, Resources } from '../resources/resources';
import { CardObject3d, MatchupOutcomeMarker } from './card-object3d/card-object3d';
import { createCardObject3dFactory } from './card-object3d/card-object3d-factory';
import { createScene } from './create-scene';
Expand All @@ -27,6 +27,7 @@ export interface RendererEvents {
const CAMERA_FOV = 70;

export class Renderer extends InheritableEventEmitter<RendererEvents> {

private readonly camera: THREE.PerspectiveCamera;
private readonly scene: THREE.Scene;
private readonly webGlRenderer: THREE.WebGLRenderer;
Expand Down
101 changes: 0 additions & 101 deletions src/client/resources.ts

This file was deleted.

105 changes: 105 additions & 0 deletions src/client/resources/resource-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { EventEmitter } from '@akolos/event-emitter';
import * as THREE from 'three';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { Card, CardAbbreviation } from '../../card/card';
import { objectPromiseAll } from '../../util/object-promise-all';
import { Resources } from './resources';

export interface ResourceLoaderEvents {
starting: [];
completed: [];
loadingFile: [url: string, itemsLoaded: number, totalItems: number];
}

const IMAGE_DIR_PATH = 'images/';
const CARD_DIR_PATH = IMAGE_DIR_PATH + 'card/';
const loadingManager = new THREE.LoadingManager();
const textureLoader = new THREE.TextureLoader(loadingManager);

export class ResourceLoader {
private static readonly ee = new EventEmitter<ResourceLoaderEvents>();
public static readonly on = ResourceLoader.ee.makeDelegate('on', ResourceLoader.ee.asProtected());
public static readonly off = ResourceLoader.ee.makeDelegate('off', ResourceLoader.ee.asProtected());

private static resources?: Promise<Resources>;

public static load(): Promise<Resources> {
if (this.resources) return this.resources;

loadingManager.onProgress = (url, loaded, total) => {
console.log('starting a load!', url, loaded, total);
this.ee.emit('loadingFile', url, loaded, total);
};
loadingManager.onLoad = () => {
console.log('loaded!');
this.ee.emit('completed');
};

const resourcesPromiseObj = {
table: loadTable(),
grassTexture: loadTexture(IMAGE_DIR_PATH + 'grass.png'),
cards: {
backSideAlpha: loadTexture(CARD_DIR_PATH + 'backside_alpha.png'),
backSide: loadTexture(CARD_DIR_PATH + 'backside.png'),
frontSideAlpha: loadTexture(CARD_DIR_PATH + 'frontside_alpha.png'),
getFront: loadCardFrontTextures(),
},
};

ResourceLoader.ee.emit('starting');
this.resources = objectPromiseAll(resourcesPromiseObj);
return this.resources;
}

private constructor() {}
}

function loadTable(): Promise<THREE.Object3D> {
const loadModel: Promise<THREE.Object3D> = new Promise((resolve) => {
new OBJLoader(loadingManager).load('models/lowtable.obj', (obj) => {
resolve(obj);
});
});

return new Promise((resolve) => {
Promise.all([loadModel, loadTexture(IMAGE_DIR_PATH + 'wood.png')]).then((value) => {
const object = value[0];
const texture = value[1];
texture.offset.set(0, 0);
texture.repeat.set(24, 24);
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
applyTexture(object.children[0], texture);
object.receiveShadow = true;
resolve(object);
});
});

function applyTexture(obj: THREE.Object3D, texture: THREE.Texture) {
obj.receiveShadow = true;
if (obj instanceof THREE.Mesh) {
(obj.material as THREE.MeshPhongMaterial).map = texture;
}
if (obj instanceof THREE.Object3D) {
obj.children.forEach((c) => applyTexture(c, texture));
}
}
}

const loadCardFrontTextures = async () => {
const cardFilepaths = Card.makeDeckOf().map(c => ({ cardAbbreviation: c.abbreviation, imagePath: getImagePathForCard(c) }));
const texturePromises = cardFilepaths.map((cfp) => loadTexture(cfp.imagePath).then(texture => [cfp.cardAbbreviation, texture] as [CardAbbreviation, THREE.Texture]));
const textures: Array<[CardAbbreviation, THREE.Texture]> = await Promise.all(texturePromises);
const abbrevToTextureMap = new Map(textures);

return (card: Card) => abbrevToTextureMap.get(card.abbreviation)!;
};

function getImagePathForCard(card: Card): string {
return `${CARD_DIR_PATH}${card.rank.name}_of_${card.suit.name}.png`;
}

function loadTexture(path: string): Promise<THREE.Texture> {
return new Promise((resolve) => {
textureLoader.load(path, (texture) => resolve(texture));
});
}
Loading

0 comments on commit cc222cf

Please sign in to comment.