added audio and sprite loading

This commit is contained in:
Joe Weaver 2020-09-07 15:38:10 -04:00
parent 2093d8e4ab
commit 9e86192bb0
11 changed files with 271 additions and 20 deletions

View File

@ -6,6 +6,7 @@ import Debug from "../Debug/Debug";
import ResourceManager from "../ResourceManager/ResourceManager"; import ResourceManager from "../ResourceManager/ResourceManager";
import Viewport from "../SceneGraph/Viewport"; import Viewport from "../SceneGraph/Viewport";
import SceneManager from "../Scene/SceneManager"; import SceneManager from "../Scene/SceneManager";
import AudioManager from "../Sound/AudioManager";
export default class GameLoop{ export default class GameLoop{
// The amount of time to spend on a physics step // The amount of time to spend on a physics step
@ -39,6 +40,7 @@ export default class GameLoop{
private recorder: Recorder; private recorder: Recorder;
private resourceManager: ResourceManager; private resourceManager: ResourceManager;
private sceneManager: SceneManager; private sceneManager: SceneManager;
private audioManager: AudioManager;
constructor(){ constructor(){
this.maxFPS = 60; this.maxFPS = 60;
@ -68,6 +70,7 @@ export default class GameLoop{
this.recorder = new Recorder(); this.recorder = new Recorder();
this.resourceManager = ResourceManager.getInstance(); this.resourceManager = ResourceManager.getInstance();
this.sceneManager = new SceneManager(this.viewport, this); this.sceneManager = new SceneManager(this.viewport, this);
this.audioManager = AudioManager.getInstance();
} }
private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D { private initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
@ -150,6 +153,7 @@ export default class GameLoop{
this.inputReceiver.update(deltaT); this.inputReceiver.update(deltaT);
this.recorder.update(deltaT); this.recorder.update(deltaT);
this.sceneManager.update(deltaT); this.sceneManager.update(deltaT);
this.resourceManager.update(deltaT);
} }
render(): void { render(): void {

View File

@ -13,6 +13,23 @@ export default class MainScene extends Scene {
loadScene(){ loadScene(){
this.load.tilemap("platformer", "assets/tilemaps/Platformer.json"); this.load.tilemap("platformer", "assets/tilemaps/Platformer.json");
this.load.tilemap("background", "assets/tilemaps/Background.json"); this.load.tilemap("background", "assets/tilemaps/Background.json");
this.load.image("player", "assets/sprites/player.png");
this.load.audio("player_jump", "assets/sounds/jump-3.wav");
this.load.audio("level_music", "assets/sounds/level.wav");
let loadingScreen = this.addLayer();
let box = this.add.graphic(Rect, loadingScreen, new Vec2(200, 300), new Vec2(400, 60));
box.setColor(new Color(0, 0, 0));
let bar = this.add.graphic(Rect, loadingScreen, new Vec2(205, 305), new Vec2(0, 50));
bar.setColor(new Color(0, 200, 200));
this.load.onLoadProgress = (percentProgress: number) => {
bar.setSize(295 * percentProgress, bar.getSize().y);
}
this.load.onLoadComplete = () => {
loadingScreen.disable();
}
} }
startScene(){ startScene(){
@ -22,6 +39,9 @@ export default class MainScene extends Scene {
backgroundTilemap.getLayer().setParallax(0.5, 0.8); backgroundTilemap.getLayer().setParallax(0.5, 0.8);
backgroundTilemap.getLayer().setAlpha(0.5); backgroundTilemap.getLayer().setAlpha(0.5);
// Add the music and start playing it on a loop
this.add.audio("level_music").play(true);
// Add the tilemap // Add the tilemap
this.add.tilemap("platformer", OrthogonalTilemap); this.add.tilemap("platformer", OrthogonalTilemap);
@ -30,10 +50,12 @@ export default class MainScene extends Scene {
// Add a player // Add a player
let player = this.add.physics(Player, mainLayer, "platformer"); let player = this.add.physics(Player, mainLayer, "platformer");
let playerSprite = this.add.graphic(Rect, mainLayer, new Vec2(0, 0), new Vec2(50, 50)); let playerSprite = this.add.sprite("player", mainLayer)
playerSprite.setColor(new Color(255, 0, 0));
player.setSprite(playerSprite); player.setSprite(playerSprite);
// TODO - Should sound playing be handled with events?
let playerJumpSound = this.add.audio("player_jump");
player.jumpSound = playerJumpSound;
this.viewport.follow(player); this.viewport.follow(player);

View File

@ -0,0 +1,34 @@
import CanvasNode from "../CanvasNode";
import ResourceManager from "../../ResourceManager/ResourceManager";
import Vec2 from "../../DataTypes/Vec2";
export default class Sprite extends CanvasNode {
private imageId: string;
private scale: Vec2;
constructor(imageId: string){
super();
this.imageId = imageId;
let image = ResourceManager.getInstance().getImage(this.imageId);
this.size = new Vec2(image.width, image.height);
this.scale = new Vec2(1, 1);
}
getScale(): Vec2 {
return this.scale;
}
setScale(scale: Vec2): void {
this.scale = scale;
}
update(deltaT: number): void {}
render(ctx: CanvasRenderingContext2D): void {
let image = ResourceManager.getInstance().getImage(this.imageId);
let origin = this.getViewportOriginWithParallax();
ctx.drawImage(image,
0, 0, this.size.x, this.size.y,
this.position.x - origin.x, this.position.y - origin.y, this.size.x * this.scale.x, this.size.y * this.scale.y);
}
}

View File

@ -3,6 +3,7 @@ import Vec2 from "./DataTypes/Vec2";
import Debug from "./Debug/Debug"; import Debug from "./Debug/Debug";
import AABB from "./Physics/Colliders/AABB"; import AABB from "./Physics/Colliders/AABB";
import CanvasNode from "./Nodes/CanvasNode"; import CanvasNode from "./Nodes/CanvasNode";
import Audio from "./Sound/Audio";
export default class Player extends PhysicsNode { export default class Player extends PhysicsNode {
velocity: Vec2; velocity: Vec2;
@ -11,6 +12,7 @@ export default class Player extends PhysicsNode {
size: Vec2; size: Vec2;
gravity: number = 7000; gravity: number = 7000;
type: string; type: string;
jumpSound: Audio;
constructor(type: string){ constructor(type: string){
super(); super();
@ -81,6 +83,10 @@ export default class Player extends PhysicsNode {
let vel = new Vec2(0, this.velocity.y); let vel = new Vec2(0, this.velocity.y);
if(this.grounded){ if(this.grounded){
if(dir.y === -1){
// Jumping
this.jumpSound.play();
}
vel.y = dir.y*1800; vel.y = dir.y*1800;
} }

View File

@ -3,11 +3,16 @@ import Tilemap from "../Nodes/Tilemap";
import Queue from "../DataTypes/Queue"; import Queue from "../DataTypes/Queue";
import { TiledTilemapData } from "../DataTypes/Tilesets/TiledData"; import { TiledTilemapData } from "../DataTypes/Tilesets/TiledData";
import StringUtils from "../Utils/StringUtils"; import StringUtils from "../Utils/StringUtils";
import AudioManager from "../Sound/AudioManager";
export default class ResourceManager { export default class ResourceManager {
private static instance: ResourceManager; private static instance: ResourceManager;
private loading: boolean; private loading: boolean;
private justLoaded: boolean;
public onLoadProgress: Function;
public onLoadComplete: Function;
private imagesLoaded: number; private imagesLoaded: number;
private imagesToLoad: number; private imagesToLoad: number;
@ -19,8 +24,17 @@ export default class ResourceManager {
private tilemapLoadingQueue: Queue<{key: string, path: string}>; private tilemapLoadingQueue: Queue<{key: string, path: string}>;
private tilemaps: Map<TiledTilemapData>; private tilemaps: Map<TiledTilemapData>;
private audioLoaded: number;
private audioToLoad: number;
private audioLoadingQueue: Queue<{key: string, path: string}>;
private audioBuffers: Map<AudioBuffer>;
// The number of different types of things to load
private typesToLoad: number;
private constructor(){ private constructor(){
this.loading = false; this.loading = false;
this.justLoaded = false;
this.imagesLoaded = 0; this.imagesLoaded = 0;
this.imagesToLoad = 0; this.imagesToLoad = 0;
@ -31,6 +45,11 @@ export default class ResourceManager {
this.tilemapsToLoad = 0; this.tilemapsToLoad = 0;
this.tilemapLoadingQueue = new Queue(); this.tilemapLoadingQueue = new Queue();
this.tilemaps = new Map(); this.tilemaps = new Map();
this.audioLoaded = 0;
this.audioToLoad = 0;
this.audioLoadingQueue = new Queue();
this.audioBuffers = new Map();
}; };
static getInstance(): ResourceManager { static getInstance(): ResourceManager {
@ -45,7 +64,7 @@ export default class ResourceManager {
this.imageLoadingQueue.enqueue({key: key, path: path}); this.imageLoadingQueue.enqueue({key: key, path: path});
} }
public getImage(key: string){ public getImage(key: string): HTMLImageElement{
return this.images.get(key); return this.images.get(key);
} }
@ -54,30 +73,38 @@ export default class ResourceManager {
} }
public audio(key: string, path: string): void { public audio(key: string, path: string): void {
this.audioLoadingQueue.enqueue({key: key, path: path});
}
public getAudio(key: string): AudioBuffer {
return this.audioBuffers.get(key);
} }
// This one is trickier than the others because we first have to load the json file, then we have to load the images
public tilemap(key: string, path: string): void { public tilemap(key: string, path: string): void {
// Add a function that loads the tilemap to the queue
this.tilemapLoadingQueue.enqueue({key: key, path: path}); this.tilemapLoadingQueue.enqueue({key: key, path: path});
} }
public getTilemap(key: string): TiledTilemapData{ public getTilemap(key: string): TiledTilemapData {
return this.tilemaps.get(key); return this.tilemaps.get(key);
} }
// TODO - Should everything be loaded in order, one file at a time?
loadResourcesFromQueue(callback: Function): void { loadResourcesFromQueue(callback: Function): void {
this.typesToLoad = 3;
this.loading = true; this.loading = true;
// Load everything in the queues. Tilemaps have to come before images because they will add new images to the queue // Load everything in the queues. Tilemaps have to come before images because they will add new images to the queue
this.loadTilemapsFromQueue(() => { this.loadTilemapsFromQueue(() => {
this.loadImagesFromQueue(() => { this.loadImagesFromQueue(() => {
this.loadAudioFromQueue(() => {
// Done loading // Done loading
this.loading = false; this.loading = false;
this.justLoaded = true;
callback(); callback();
}); });
}); });
});
} }
@ -121,7 +148,7 @@ export default class ResourceManager {
private loadImagesFromQueue(onFinishLoading: Function): void { private loadImagesFromQueue(onFinishLoading: Function): void {
this.imagesToLoad = this.imageLoadingQueue.getSize(); this.imagesToLoad = this.imageLoadingQueue.getSize();
this.tilemapsLoaded = 0; this.imagesLoaded = 0;
while(this.imageLoadingQueue.hasItems()){ while(this.imageLoadingQueue.hasItems()){
let image = this.imageLoadingQueue.dequeue(); let image = this.imageLoadingQueue.dequeue();
@ -148,7 +175,47 @@ export default class ResourceManager {
this.imagesLoaded += 1; this.imagesLoaded += 1;
if(this.imagesLoaded === this.imagesToLoad ){ if(this.imagesLoaded === this.imagesToLoad ){
// We're done loading tilemaps // We're done loading images
callback();
}
}
private loadAudioFromQueue(onFinishLoading: Function){
this.audioToLoad = this.audioLoadingQueue.getSize();
this.audioLoaded = 0;
while(this.audioLoadingQueue.hasItems()){
let audio = this.audioLoadingQueue.dequeue();
this.loadAudio(audio.key, audio.path, onFinishLoading);
}
}
private loadAudio(key: string, path: string, callbackIfLast: Function): void {
let audioCtx = AudioManager.getInstance().getAudioContext();
let request = new XMLHttpRequest();
request.open('GET', path, true);
request.responseType = 'arraybuffer';
request.onload = () => {
audioCtx.decodeAudioData(request.response, (buffer) => {
// Add to list of audio buffers
this.audioBuffers.add(key, buffer);
// Finish loading sound
this.finishLoadingAudio(callbackIfLast);
}, (error) =>{
throw "Error loading sound";
});
}
request.send();
}
private finishLoadingAudio(callback: Function): void {
this.audioLoaded += 1;
if(this.audioLoaded === this.audioToLoad){
// We're done loading audio
callback(); callback();
} }
} }
@ -164,4 +231,20 @@ export default class ResourceManager {
}; };
xobj.send(null); xobj.send(null);
} }
private getLoadPercent(): number {
return (this.tilemapsLoaded/this.tilemapsToLoad
+ this.imagesLoaded/this.imagesToLoad
+ this.audioLoaded/this.audioToLoad)
/ this.typesToLoad;
}
public update(deltaT: number): void {
if(this.loading){
this.onLoadProgress(this.getLoadPercent());
} else if(this.justLoaded){
this.justLoaded = false;
this.onLoadComplete();
}
}
} }

View File

@ -0,0 +1,21 @@
import ResourceManager from "../../ResourceManager/ResourceManager";
import AudioManager from "../../Sound/AudioManager";
import Scene from "../Scene";
import Audio from "../../Sound/Audio";
export default class AudioFactory {
private scene: Scene;
private resourceManager: ResourceManager;
private audioManager: AudioManager;
init(scene: Scene){
this.scene = scene;
this.resourceManager = ResourceManager.getInstance();
this.audioManager = AudioManager.getInstance();
}
addAudio = (key: string, ...args: any): Audio => {
let audio = new Audio(key);
return audio;
}
}

View File

@ -1,9 +1,9 @@
import Scene from "../Scene"; import Scene from "../Scene";
import CanvasItem from "../../Nodes/CanvasNode"
import SceneGraph from "../../SceneGraph/SceneGraph"; import SceneGraph from "../../SceneGraph/SceneGraph";
import UIElement from "../../Nodes/UIElement"; import UIElement from "../../Nodes/UIElement";
import Layer from "../Layer"; import Layer from "../Layer";
import Graphic from "../../Nodes/Graphic"; import Graphic from "../../Nodes/Graphic";
import Sprite from "../../Nodes/Sprites/Sprite";
export default class CanvasNodeFactory { export default class CanvasNodeFactory {
private scene: Scene; private scene: Scene;
@ -27,8 +27,8 @@ export default class CanvasNodeFactory {
return instance; return instance;
} }
addSprite = <T extends CanvasItem>(constr: new (...a: any) => T, layer: Layer, ...args: any): T => { addSprite = (imageId: string, layer: Layer, ...args: any): Sprite => {
let instance = new constr(...args); let instance = new Sprite(imageId);
// Add instance to scene // Add instance to scene
instance.setScene(this.scene); instance.setScene(this.scene);

View File

@ -2,20 +2,23 @@ import Scene from "../Scene";
import PhysicsNodeFactory from "./PhysicsNodeFactory"; import PhysicsNodeFactory from "./PhysicsNodeFactory";
import CanvasNodeFactory from "./CanvasNodeFactory"; import CanvasNodeFactory from "./CanvasNodeFactory";
import TilemapFactory from "./TilemapFactory"; import TilemapFactory from "./TilemapFactory";
import AudioFactory from "./AudioFactory";
import PhysicsManager from "../../Physics/PhysicsManager"; import PhysicsManager from "../../Physics/PhysicsManager";
import SceneGraph from "../../SceneGraph/SceneGraph"; import SceneGraph from "../../SceneGraph/SceneGraph";
import Tilemap from "../../Nodes/Tilemap"; import Tilemap from "../../Nodes/Tilemap";
export default class FactoryManager { export default class FactoryManager {
private canvasNodeFactory: CanvasNodeFactory = new CanvasNodeFactory();; private canvasNodeFactory: CanvasNodeFactory = new CanvasNodeFactory();
private physicsNodeFactory: PhysicsNodeFactory = new PhysicsNodeFactory();; private physicsNodeFactory: PhysicsNodeFactory = new PhysicsNodeFactory();
private tilemapFactory: TilemapFactory = new TilemapFactory();; private tilemapFactory: TilemapFactory = new TilemapFactory();
private audioFactory: AudioFactory = new AudioFactory();
constructor(scene: Scene, sceneGraph: SceneGraph, physicsManager: PhysicsManager, tilemaps: Array<Tilemap>){ constructor(scene: Scene, sceneGraph: SceneGraph, physicsManager: PhysicsManager, tilemaps: Array<Tilemap>){
this.canvasNodeFactory.init(scene, sceneGraph); this.canvasNodeFactory.init(scene, sceneGraph);
this.physicsNodeFactory.init(scene, physicsManager); this.physicsNodeFactory.init(scene, physicsManager);
this.tilemapFactory.init(scene, tilemaps, physicsManager); this.tilemapFactory.init(scene, tilemaps, physicsManager);
this.audioFactory.init(scene);
} }
uiElement = this.canvasNodeFactory.addUIElement; uiElement = this.canvasNodeFactory.addUIElement;
@ -23,4 +26,5 @@ export default class FactoryManager {
graphic = this.canvasNodeFactory.addGraphic; graphic = this.canvasNodeFactory.addGraphic;
physics = this.physicsNodeFactory.add; physics = this.physicsNodeFactory.add;
tilemap = this.tilemapFactory.add; tilemap = this.tilemapFactory.add;
audio = this.audioFactory.addAudio;
} }

26
src/Sound/Audio.ts Normal file
View File

@ -0,0 +1,26 @@
import AudioManager from "./AudioManager";
export default class Audio {
private key: string;
private sound: AudioBufferSourceNode;
constructor(key: string){
this.key = key;
}
play(loop?: boolean){
this.sound = AudioManager.getInstance().createSound(this.key);
if(loop){
this.sound.loop = true;
}
this.sound.start();
}
stop(){
if(this.sound){
this.sound.stop();
}
}
}

50
src/Sound/AudioManager.ts Normal file
View File

@ -0,0 +1,50 @@
import ResourceManager from "../ResourceManager/ResourceManager";
export default class AudioManager {
private static instance: AudioManager;
private audioCtx: AudioContext;
private constructor(){
this.initAudio();
}
public static getInstance(): AudioManager {
if(!this.instance){
this.instance = new AudioManager();
}
return this.instance;
}
private initAudio(): void {
try {
window.AudioContext = window.AudioContext;// || window.webkitAudioContext;
this.audioCtx = new AudioContext();
console.log('Web Audio API successfully loaded');
} catch(e) {
console.log('Web Audio API is not supported in this browser');
}
}
public getAudioContext(): AudioContext {
return this.audioCtx;
}
createSound(key: string): AudioBufferSourceNode {
// Get audio buffer
let buffer = ResourceManager.getInstance().getAudio(key);
// creates a sound source
var source = this.audioCtx.createBufferSource();
// tell the source which sound to play
source.buffer = buffer;
// connect the source to the context's destination
// i.e. the speakers
source.connect(this.audioCtx.destination);
return source;
}
}

View File

@ -2,6 +2,7 @@
"files": [ "files": [
"src/main.ts", "src/main.ts",
"src/Player.ts", "src/Player.ts",
"src/PlayerSprite.ts",
"src/DataTypes/Tilesets/TiledData.ts", "src/DataTypes/Tilesets/TiledData.ts",
"src/DataTypes/Tilesets/Tileset.ts", "src/DataTypes/Tilesets/Tileset.ts",
@ -33,9 +34,7 @@
"src/Nodes/UIElements/Button.ts", "src/Nodes/UIElements/Button.ts",
"src/Nodes/UIElements/Label.ts", "src/Nodes/UIElements/Label.ts",
"src/Nodes/CanvasNode.ts", "src/Nodes/CanvasNode.ts",
"src/ColoredCircle.ts",
"src/Nodes/GameNode.ts", "src/Nodes/GameNode.ts",
"src/PlayerSprite.ts",
"src/Nodes/Tilemap.ts", "src/Nodes/Tilemap.ts",
"src/Nodes/UIElement.ts", "src/Nodes/UIElement.ts",
@ -52,6 +51,8 @@
"src/SceneGraph/SceneGraphArray.ts", "src/SceneGraph/SceneGraphArray.ts",
"src/SceneGraph/Viewport.ts", "src/SceneGraph/Viewport.ts",
"src/Sound/AudioManager.ts",
"src/Utils/Color.ts", "src/Utils/Color.ts",
"src/Utils/MathUtils.ts", "src/Utils/MathUtils.ts",
"src/Utils/RandUtils.ts", "src/Utils/RandUtils.ts",