Source: func/GameAssetManager.js

// GameAssetManager
//
/**
 * @typedef {number | string} ImageAssetId UniqIdentifer
 * @example
 * "picture_sprite" or "asciifont" or "mycharactor" etc
 * @description
 * 同じIDグループ内で重複しない任意の数値や文字列を設定する<br>\
 * @todo
 * 手間を省くために自動で生成するuniqIDManagerとか作ってもよいかもしれない
 */

/**
 * @typedef {number | string} AudioAssetId UniqIdentifer
 * @example
 * "ring_sound" or "explose_effect" or "click1" etc
 * @description
 * 同じIDグループ内で重複しない任意の数値や文字列を設定する<br>\
 */

/**
 * @summary ゲームアセット管理 
 * Imageやaudioオブジェクトを管理 
 * Image/Audio Object
 * @example
 *  game.asset.imageLoad( id , url ); //戻り値 Imageオブジェクト
 *	game.asset.soundLoad( id , url ); //戻り値 audioオブジェクト(拡張子なしで)
 * 
 *	game.asset.image[ id ];
 *	game.asset.sound[ id ];
 *
 *	game.asset.*.ready //true:ロード完了 false:ロード未完了または失敗
 * 
 * @Todo JSON/TEXT data 
 * @Todo (Sprite Animation pattern/ tilemap data)
 * @Todo DelayLoad
 * @description
 * ゲームで使用する画像や音声などのアセットを管理するクラスです。<br>\
 * アセットのロード、ロード状態の確認、そしてIDによるアクセスを<br>\
 * 一元的に提供します。
 */
class GameAssetManager {

    /**
     * Imageオブジェクトリスト
     * @member
     * @type {imageAsset[]} 
    */
    image;

    /**
     * audioオブジェクトリスト
     * @member
     * @type {audioAsset[]} 
     */
    sound;

    constructor() {

        //========= image asset
        const img_ = [];

        /**
         * @method
         * @param {ImageAssetId} id UniqId(割り当てたい任意の数字/文字列)
         * @param {URI} uri ディレクトリパス
         * @returns {Image} Imageオブジェクト
         * @description
         * 指定されたURIから画像アセットを非同期でロードします。
         * 一意のIDを割り当て、ロード完了ステータスを追跡しながら
         * ロードされたImageオブジェクトを返します。
         */
        this.imageLoad = function (id, uri) {

            img_[id] = new imageAsset(uri);
            return img_[id].img;
        };

        /**
         * {array} Imageオブジェクト
         */
        this.image = img_;

        /**
         * イメージアセットコンテナClass (内部クラス)
         * @class GameAssetManager.imageAsset
         * @param {URI} uri ディレクトリパス
         * @description 
         * 画像アセットの情報を保持するコンテナクラスです。<br>\
         * 画像のURI、ロード状態(ready)、および実際のImageオブジェクトを管理し、<br>\
         * ロードが完了したかどうかを確認する機能を提供します。
         */
        class imageAsset {
            /**
             * @type {Image}
             */
            img;
            /**
             * loadcheck complate status 
             * @type {boolean}
             */
            ready;
            /**
             * Image uri
             * @type {string}
             */
            uri;
            /**
             * @param {URI} uri ディレクトリパス
             */
            constructor(uri) {

                this.uri = uri;
                this.ready = false;
                this.img = new Image();
                this.img.src = uri;
                /**
                 * @method
                 * @returns {boolean} ロード成否
                 * @description
                 * 画像アセットのロードが完了したかどうかをチェックします。<br>\
                 * `Image.complete`プロパティを利用してロード成否を判断し、<br>\
                 * `ready`ステータスを更新します。
                 */
                this.loadcheck = function () {
                    this.ready = this.img.complete; //alert("load "+uri);
                    return this.img.complete;
                };
            }
        };

        //========== Audio Asset 
        const snd_ = [];

        /**
         * @param {AudioAssetId} id UniqId(割り当てたい任意の数字/文字列)
         * @param {URI} uri ディレクトリパス/拡張子無しで指定
         * @returns {Audio} Audioオブジェクト
         * @description
         * 指定されたURIから音声アセットをロードします。<br>\
         * ブラウザが再生可能な形式(MP3またはOGG)を自動判別し<br>\
         * 一意のIDで音声オブジェクトを管理します。
         */
        this.soundLoad = function (id, uri) {

            snd_[id] = new audioAsset(uri);

            return snd_[id].sound;
        };

        /**
         * {array} Audioオブジェクト
         */
        this.sound = snd_;

        /**
         * AudioアセットコンテナClass(内部クラス)
         * @class GameAssetManager.audioAsset
         * @param {URI} uri ディレクトリパス
         * @description 
         * 音声アセットの情報を保持するコンテナクラスです。<br>\
         * 音声のURI、ロード状態(ready)、再生位置(pos)<br>\
         * および実際のAudioオブジェクトを管理します。
         */
        class audioAsset {
            /**
             * Audioオブジェクト
             * @member
             * @type {Audio}
             */
            sound;
            /**
             * loadcheck complate status 
             * @member
             * @type {boolean}
             */
            ready;
            /**
             * Audio uri
             * @member
             * @type {string}
             */
            uri;
            /**
             * Audio play position
             * @member
             * @type {number}
             */
            pos;

            /**
             * 
             * @param {URI} uri 
             */
            constructor(uri) {

                let ext = ".mp3";
                if ((new Audio()).canPlayType("audio/ogg") == "probably") { ext = ".ogg"; }

                this.ready = false;
                this.sound = new Audio(uri + ext);
                this.sound.addEventListener("loadeddata", function (e) { this.ready = true; });

                this.uri = uri + ext;
                this.pos = 0;

                /**
                 * 
                 * @method
                 * @returns {number} readyState
                 * @example
                 * readyState
                 * メディアファイルの再生準備状態。
                 * 値 定数            	状態
                 * 0 HAVE_NOTHING       メディアファイルの情報がない状態。
                 * 1 HAVE_METADATA  	メディアファイルのメタデータ属性を初期化するのに十分な状態。
                 * 2 HAVE_CURRENT_DATA	現在の再生位置のデータはあるが、続きを再生する分のデータは不十分な状態。
                 * 3 HAVE_FUTURE_DATA	現在の再生位置から続きを再生できるだけのデータがある状態。
                 * 4 HAVE_ENOUGH_DATA	メディアファイルの終わりまで中断せずに再生できる状態。
                 * @see https://developer.mozilla.org/ja/docs/Web/API/HTMLMediaElement/readyState
                 * @description
                 * 音声アセットの再生準備状態をチェックします。<br>\
                 * `HTMLMediaElement.readyState`プロパティを利用して、<br>\
                 * 音声データがどの程度ロードされているかを確認します。
                 */
                this.loadcheck = function () {
                    this.ready = (this.sound.readyState != 0) ? true : false;
                    return this.sound.readyState;
                };
                //this.sound = new Audio(uri + ext);
            }
        };

        //==========   
        /**
         * アセットのロード状態一覧チェック
         * @method
         * @returns {string[]} image/soundの状態一覧をテキストリストで返す
         * @description
         * 現在ロードされている全てのアセットのロード状態一覧をテキストで返します。<br>\
         * 画像と音声それぞれについて、URIとロードの成否、<br>\
         * または音声のreadyStateを表示します。
         */
        this.check = function () {

            let st = [];

            for (let i in img_) {
                let stw = img_[i].uri.split("/", 20);

                st.push("[" + i + "] " + stw[stw.length - 1] + " " + (img_[i].loadcheck() ? "o" : "x"));
                //img でreadyState はIEのみの為、使用しない。
            }

            let rs = ["Noting", "Mata", "Current", "Future", "Ok"];

            for (let i in snd_) {
                let stw = snd_[i].uri.split("/", 20);
                let num = snd_[i].loadcheck();
                st.push("[" + i + "] " + stw[stw.length - 1] + " " + num + "/" + rs[num]); //? "o" : "x") );
            }

            return st;
        };

        /**
         * @method
         * @returns {string} Idリスト(テキスト)
         * @description
         * ロードされている全てのアセットの一意のIDリストをテキストで返します。<br>\
         * 画像と音声アセットのIDが結合された文字列として提供され<br>\
         * 管理されているアセットを一目で確認できます。
         */
        this.namelist = function () {

            let st = [];

            for (let i in img_) {
                st.push(i);
            }

            for (let i in snd_) {
                st.push(i);
            }

            return st;
        };
        //
    }
}