Skip to content

Commit b49056e

Browse files
committed
add: audio, input, scene, game loop, assets loader
1 parent b058bf9 commit b49056e

File tree

11 files changed

+1045
-387
lines changed

11 files changed

+1045
-387
lines changed

src/assets.ts

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { AudioPlayer } from "./audio";
2+
import { Game } from "./game";
3+
import { IAsset as IAsset, IAssets } from "./interface";
4+
5+
/**
6+
* AssetsLoader class for loading game assets (JSON, audio, images).
7+
* Supports asynchronous loading with progress tracking and error handling.
8+
*/
9+
class AssetsLoader {
10+
private assets: IAssets;
11+
private totalAsset: number = 0;
12+
private loadedAssets: number = 0;
13+
private onProgress: ((progress: number, loaded: number, total: number) => void) | null = null;
14+
private onComplete: ((assets: IAssets) => void) | null = null;
15+
private onError: ((error: { name: string; url: string; error: any }) => void) | null = null;
16+
private game: Game
17+
/**
18+
* Creates an instance of AssetsLoader.
19+
* Initializes the asset storage and counters.
20+
*/
21+
constructor(game: Game) {
22+
this.game = game
23+
this.assets = {
24+
json: {},
25+
audio: {},
26+
images: {}
27+
};
28+
}
29+
30+
/**
31+
* Sets the callback for loading progress updates.
32+
* @param callback - Function called with progress (0.0 to 1.0), loaded count, and total count.
33+
*/
34+
setProgressCallback(callback: (progress: number, loaded: number, total: number) => void): void {
35+
this.onProgress = callback;
36+
}
37+
38+
/**
39+
* Sets the callback for when all assets are loaded.
40+
* @param callback - Function called with the loaded assets.
41+
*/
42+
setCompleteCallback(callback: (assets: IAssets) => void): void {
43+
this.onComplete = callback;
44+
}
45+
46+
/**
47+
* Sets the callback for handling loading errors.
48+
* @param callback - Function called with error details (name, url, error).
49+
*/
50+
setErrorCallback(callback: (error: { name: string; url: string; error: any }) => void): void {
51+
this.onError = callback;
52+
}
53+
54+
/**
55+
* Loads a JSON file asynchronously.
56+
* @param name - The unique identifier for the JSON asset.
57+
* @param url - The URL of the JSON file.
58+
*/
59+
loadJson(name: string, url: string): void {
60+
this.totalAsset++;
61+
fetch(url)
62+
.then(response => response.json())
63+
.then(data => {
64+
this.assets.json[name] = data;
65+
this.assetLoaded();
66+
})
67+
.catch(error => this.handleError(name, url, error));
68+
}
69+
70+
/**
71+
* Loads an audio file asynchronously.
72+
* @param name - The unique identifier for the audio asset.
73+
* @param url - The URL of the audio file.
74+
*/
75+
loadAudio(name: string, url: string): void {
76+
this.totalAsset++;
77+
const audio = new Audio();
78+
audio.src = url;
79+
audio.oncanplaythrough = () => {
80+
this.assets.audio[name] = new AudioPlayer(audio, this.game.audio.audioContext);
81+
this.assetLoaded();
82+
audio.oncanplaythrough = null; // Clear event
83+
};
84+
audio.onerror = () => this.handleError(name, url, 'Audio load error');
85+
}
86+
87+
/**
88+
* Loads an image file asynchronously.
89+
* @param name - The unique identifier for the image asset.
90+
* @param url - The URL of the image file.
91+
*/
92+
loadImage(name: string, url: string): void {
93+
this.totalAsset++;
94+
const img = new Image();
95+
img.src = url;
96+
img.onload = () => {
97+
this.assets.images[name] = this.game.render.texture.textureFromSource(img);
98+
this.assetLoaded();
99+
};
100+
img.onerror = () => this.handleError(name, url, 'Image load error');
101+
}
102+
103+
/**
104+
* Loads multiple assets from a list.
105+
* @param assetList - Array of assets to load.
106+
*/
107+
loadAssets(assetList: IAsset[]): void {
108+
assetList.forEach(asset => {
109+
switch (asset.type) {
110+
case 'json':
111+
this.loadJson(asset.name, asset.url);
112+
break;
113+
case 'audio':
114+
this.loadAudio(asset.name, asset.url);
115+
break;
116+
case 'image':
117+
this.loadImage(asset.name, asset.url);
118+
break;
119+
default:
120+
console.warn(`Unknown asset type: ${asset.type}`);
121+
}
122+
});
123+
}
124+
125+
/**
126+
* Handles the completion of an asset load.
127+
* Updates progress and triggers completion callback if all assets are loaded.
128+
*/
129+
private assetLoaded(): void {
130+
this.loadedAssets++;
131+
if (this.onProgress) {
132+
const progress = this.loadedAssets / this.totalAsset;
133+
this.onProgress(progress, this.loadedAssets, this.totalAsset);
134+
}
135+
if (this.loadedAssets === this.totalAsset && this.onComplete) {
136+
this.onComplete(this.assets);
137+
}
138+
}
139+
140+
/**
141+
* Handles errors during asset loading.
142+
* @param name - The name of the asset.
143+
* @param url - The URL of the asset.
144+
* @param error - The error object or message.
145+
*/
146+
private handleError(name: string, url: string, error: any): void {
147+
if (this.onError) {
148+
this.onError({ name, url, error });
149+
} else {
150+
console.error(`Failed to load asset ${name} from ${url}:`, error);
151+
}
152+
}
153+
154+
/**
155+
* Retrieves a loaded asset by type and name.
156+
* @param type - The type of asset ('json', 'audio', or 'images').
157+
* @param name - The name of the asset.
158+
* @returns The loaded asset or undefined if not found.
159+
*/
160+
get<T extends keyof IAssets>(type: T, name: string): IAssets[T][string] | undefined {
161+
return this.assets[type]?.[name];
162+
}
163+
164+
/**
165+
* Retrieves a loaded JSON asset by name.
166+
* @param name - The name of the JSON asset.
167+
* @returns The loaded JSON data or undefined if not found.
168+
*/
169+
getJSON(name: string): any | undefined {
170+
return this.assets.json[name];
171+
}
172+
173+
/**
174+
* Retrieves a loaded audio asset by name.
175+
* @param name - The name of the audio asset.
176+
* @returns The loaded AudioPlayer instance or undefined if not found.
177+
*/
178+
getAudio(name: string): AudioPlayer | undefined {
179+
return this.assets.audio[name];
180+
}
181+
182+
/**
183+
* Retrieves a loaded texture (image) asset by name.
184+
* @param name - The name of the texture asset.
185+
* @returns The loaded texture or undefined if not found.
186+
*/
187+
getTexture(name: string): any | undefined {
188+
return this.assets.images[name];
189+
}
190+
}
191+
192+
export default AssetsLoader;

src/audio.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { ISound } from "./interface";
2+
3+
/**
4+
* Audio class for managing individual audio elements.
5+
* Encapsulates an HTMLAudioElement and its Web Audio API nodes.
6+
*/
7+
export class AudioPlayer {
8+
public element: HTMLAudioElement;
9+
public source: AudioNode | null;
10+
public gainNode: GainNode;
11+
private audioContext: AudioContext;
12+
13+
/**
14+
* Creates an instance of Audio.
15+
* @param audioElement - The HTMLAudioElement to manage.
16+
* @param audioContext - The Web Audio API context.
17+
*/
18+
constructor(audioElement: HTMLAudioElement, audioContext: AudioContext) {
19+
this.element = audioElement;
20+
this.audioContext = audioContext;
21+
this.source = null;
22+
this.gainNode = this.audioContext.createGain();
23+
}
24+
25+
/**
26+
* Plays the audio with optional looping.
27+
* @param loop - Whether the audio should loop (default: false).
28+
*/
29+
async play(loop: boolean = false): Promise<void> {
30+
try {
31+
this.element.loop = loop;
32+
this.source = this.audioContext.createMediaElementSource(this.element);
33+
this.source.connect(this.gainNode);
34+
this.gainNode.connect(this.audioContext.destination);
35+
await this.element.play();
36+
} catch (error) {
37+
console.error(`Error playing audio:`, error);
38+
}
39+
}
40+
41+
/**
42+
* Pauses the audio.
43+
*/
44+
pause(): void {
45+
this.element.pause();
46+
}
47+
48+
/**
49+
* Stops the audio and resets playback position.
50+
*/
51+
stop(): void {
52+
this.element.pause();
53+
this.element.currentTime = 0;
54+
if (this.source) {
55+
this.source.disconnect();
56+
this.source = null;
57+
}
58+
}
59+
60+
/**
61+
* Sets the volume for the audio.
62+
* @param volume - Volume level (0.0 to 1.0).
63+
*/
64+
setVolume(volume: number): void {
65+
this.gainNode.gain.value = Math.max(0, Math.min(1, volume));
66+
}
67+
68+
/**
69+
* Cleans up resources associated with the audio.
70+
*/
71+
destroy(): void {
72+
this.stop();
73+
this.gainNode.disconnect();
74+
}
75+
}
76+
77+
/**
78+
* AudioManager class for managing game audio, including background music (BGM) and sound effects (SFX).
79+
* Delegates audio-specific responsibilities to AudioPlayer instances.
80+
*/
81+
class AudioManager {
82+
audioContext: AudioContext;
83+
84+
constructor() {
85+
this.audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
86+
}
87+
88+
destroy(): void {
89+
this.audioContext.close();
90+
}
91+
}
92+
93+
export default AudioManager;

0 commit comments

Comments
 (0)