1. 資源と建設1.1 クリエイターリソースファイルの基本エンジンがリソースを解析して読み込む方法を理解する前に、まずこれらのリソース ファイル (画像、プレハブ、アニメーションなど) のルールを理解しましょう。クリエーター プロジェクト ディレクトリの下には、リソース関連のディレクトリがいくつかあります。
アセット ディレクトリでは、作成者は各リソース ファイルとディレクトリに対して同じ名前の .meta ファイルを生成します。メタ ファイルは、リソース バージョン、UUID、およびさまざまなカスタム情報 (エディターの { "バージョン": "1.2.7", "UUID": "a8accd2e-6622-4c31-8a1e-4db5f2b568b5", "optimizationPolicy": "AUTO", // プレハブ作成の最適化戦略 "asyncLoadAssets": false, // 読み込みを遅延するかどうか "readonly": false, 「サブメタ」: {} } ライブラリ ディレクトリの下の imports ディレクトリでは、リソース ファイル名が uuid に変換され、uuid の最初の 2 文字が取得されてディレクトリがグループ化され、保存されます。作成者は、すべてのリソースの uuid とアセット ディレクトリ間のマッピング関係、およびリソースとメタの最終更新タイムスタンプを、以下に示すように、uuid-to-mtime.json という名前のファイルに記述します。 { "9836134e-b892-4283-b6b2-78b5acf3ed45": { 「資産」: 1594351233259, 「メタ」: 1594351616611, "相対パス": "効果" }, "430eccbf-bf2c-4e6e-8c0c-884bbb487f32": { 「資産」: 1594351233254, 「メタ」: 1594351616643, "相対パス": "effects_\__builtin-editor-gizmo-line.effect" }, ... } アセット ディレクトリ内のリソースと比較すると、ライブラリ ディレクトリ内のリソースはメタ ファイルの情報をマージします。ファイル ディレクトリは uuid-to-mtime.json にのみ記録され、ライブラリ ディレクトリはディレクトリに対して何も生成しません。 1.2 リソースの構築プロジェクトがビルドされると、リソースはライブラリ ディレクトリからビルド出力のビルド ディレクトリに移動されます。基本的に、ビルドに関係するシーンとリソース ディレクトリ内のリソース、およびそれらが参照するリソースのみがエクスポートされます。スクリプト リソースは複数の js スクリプトから 1 つの js にマージされ、さまざまな json ファイルも特定のルールに従ってパッケージ化されます。バンドル設定インターフェースとプロジェクトビルドインターフェースでバンドルとプロジェクトを設定できます。 1.2.1 写真、地図帳、自動地図帳
以下に示すように、エディターにインポートされた画像ごとにテクスチャ情報を記述する json ファイルが生成されます。デフォルトでは、プロジェクト内のすべての Texture2D json ファイルが 1 つに圧縮されます。 { "__type__": "cc.Texture2D", 「コンテンツ」: 「0,9729,9729,33071,33071,0,0,1」 } テクスチャの Type プロパティを Sprite に設定すると、Creator は SpriteFrame タイプの json ファイルも自動的に生成します。 1.2.2 プレハブとシーン
シーン リソースはプレハブ リソースと非常によく似ています。どちらもすべてのノード、コンポーネント、およびその他の情報を記述する json ファイルです。 1.2.3 リソースファイルのマージルール Creator が複数のリソースを 1 つの json ファイルにマージすると、config.json の packs フィールドに
以下は、異なるルールに従ってビルドされたファイルです。圧縮せずに生成されたファイル数が最も多いことがわかります。非インラインファイルはインラインファイルより多くなりますが、インラインでは同じファイルが繰り返しインクルードされる可能性があります。たとえば、プレハブeとfは両方とも同じ画像を参照しており、この画像のSpriteFrame.jsonが繰り返しインクルードされます。これらを1つのjsonにマージすると、生成されるファイルは1つだけになります。
ほとんどの場合、デフォルト オプションは適切な選択です。Web プラットフォームの場合は、ネットワーク IO を削減し、パフォーマンスを向上させるために、 2. アセットバンドルの理解と使用2.1 バンドルを作成するAsset Bundle は、Creator 2.4 以降のリソース管理ソリューションです。簡単に言うと、ディレクトリを通じてリソースを計画し、プロジェクトの要件に応じてさまざまなリソースを異なるディレクトリに配置し、ディレクトリを Asset Bundle に構成します。以下の役割を果たすことができます。
アセット バンドルの作成は非常に簡単です。ディレクトリの このドキュメントでは、圧縮について詳しく説明されていません。ここでの圧縮は、zip などの圧縮ではなく、packAssets を使用して複数のリソース json ファイルを 1 つにマージし、io を削減することを指します。 オプションを確認するのは非常に簡単です。本当の鍵は、バンドルをどのように計画するかにあります。計画の原則は、パッケージ サイズを縮小し、起動を高速化し、リソースを再利用することです。サブゲーム、レベルのコピー、システム機能など、ゲームのモジュールに応じてリソースを計画することをお勧めします。 バンドルは、フォルダー内のリソースだけでなく、フォルダーによって参照される他のフォルダー内のリソースも自動的にパッケージ化します (これらのリソースが他のバンドルにない場合)。モジュールに従ってリソースを計画すると、複数のバンドルでリソースを共有するのが簡単になります。共通リソースをバンドルに抽出するか、バンドルの優先度を高く設定してバンドルの依存関係を構築することができます。そうしないと、これらのリソースは複数のバンドルに同時に配置されます (ローカル バンドルの場合は、パッケージ サイズが増加します)。 2.2 バンドルの使用
Bundle の使い方も非常に簡単です。リソースディレクトリ内のリソースであれば、cc.resources.load を使って直接読み込むことができます。 cc.resources.load("テストアセット/プレハブ", function (err, プレハブ) { var newNode = cc.instantiate(プレハブ); cc.director.getScene().addChild(newNode); }); その他のカスタム バンドル (バンドル名を使用してローカル バンドルまたはリモート バンドルをロードできます) の場合は、cc.assetManager.loadBundle を使用してバンドルをロードし、ロードされたバンドル オブジェクトを使用してバンドル内のリソースをロードできます。ネイティブ プラットフォームの場合、バンドルがリモート パッケージとして構成されている場合は、ビルド中にビルド リリース パネルにリソース サーバーのアドレスを入力する必要があります。 cc.assetManager.loadBundle('01_graphics', (err, バンドル) => { バンドルをロードします。 }); ネイティブまたはミニゲーム プラットフォームでは、次のように Bundle を使用することもできます。
// 他のプロジェクトのアセットバンドルを再利用する場合 cc.assetManager.loadBundle('https://othergame.com/remote/01_graphics', (err, bundle) => { バンドルをロードします。 }); // ネイティブ プラットフォーム cc.assetManager.loadBundle(jsb.fileUtils.getWritablePath() + '/pathToBundle/bundleName', (err, bundle) => { // ... }); // WeChat ミニゲームプラットフォーム cc.assetManager.loadBundle(wx.env.USER_DATA_PATH + '/pathToBundle/bundleName', (err, bundle) => { // ... }); その他の注意事項:
3. 新しいリソースフレームワークの分析v2.4 リファクタリング後の新しいフレームワーク コードは、より簡潔で明確です。まず、マクロの観点からリソース フレームワーク全体を理解できます。リソース パイプラインは、フレームワーク全体の中核部分です。リソース読み込みプロセス全体を標準化し、パイプラインのカスタマイズをサポートします。 公開文書
バンドル
パイプライン部分 CCAssetManager.jsはパイプラインを管理し、統一されたロードおよびアンロードインターフェースを提供します。 パイプラインフレームワーク
前処理パイプライン
ダウンロードパイプライン
解析パイプライン
他の
3.1 パイプラインの読み込みCreator は、パイプラインを使用してリソース読み込みプロセス全体を処理します。これの利点は、リソース処理プロセスが分離され、各ステップが個別のパイプラインに分離されることです。パイプラインは簡単に再利用および結合でき、読み込みプロセス全体をカスタマイズするのに便利です。リソースの暗号化など、独自のパイプラインをいくつか作成してパイプラインに追加できます。 AssetManager には、通常の読み込みパイプライン、プリロード パイプライン、リソース パス変換パイプラインの 3 つの組み込みパイプラインがあります。最後のパイプラインは、最初の 2 つのパイプラインにサービスを提供します。 // 通常の読み込み this.pipeline = pipeline.append(preprocess).append(load); // プリロード this.fetchPipeline = fetchPipeline.append(preprocess).append(fetch); // リソース パスを変換します this.transformPipeline = transformPipeline.append(parse).append(combine); 3.1.1 ロードパイプラインを開始する [ロードインターフェース] 次に、最も単純な cc.resource.load など、一般的なリソースがどのようにロードされるかを見てみましょう。bundle.load メソッドでは、cc.assetManager.loadAny が呼び出されます。loadAny メソッドでは、新しいタスクが作成され、通常のロード パイプラインの async メソッドが呼び出されてタスクが実行されます。 ロードするリソースパスはtask.inputに配置され、optionsはtype、bundle、__requestType__などのフィールドを含むオブジェクトであることに注意してください。 // バンドルクラスのロードメソッド load (paths, type, onProgress, onComplete) { var { type, onProgress, onComplete } = parseLoadResArgs(type, onProgress, onComplete); cc.assetManager.loadAny(paths, { __requestType__: RequestType.PATH, type: type, bundle: this.name }, onProgress, onComplete); }, // assetManager の loadAny メソッド loadAny (requests, options, onProgress, onComplete) { var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete); options.preset = options.preset || 'デフォルト'; task = new Task({input: request, onProgress, onComplete: asyncify(onComplete), options}); パイプラインの非同期処理。 }, パイプラインは、前処理とロードの 2 つの部分で構成されます。 preprocess は、preprocess、transformPipeline { parse、combine } のパイプラインで構成されます。preprocess は実際にはサブタスクのみを作成し、それが transformPipeline によって実行されます。通常のリソースをロードする場合、サブタスクの入力とオプションは親タスクと同じです。 subTask を Task.create({input: task.input, options: subOptions}) とします。 task.output = task.source = transformPipeline.sync(subTask); 3.1.2 transformPipelineパイプライン[準備フェーズ] transformPipeline は、parse と combine の 2 つのパイプラインで構成されています。parse の役割は、ロードされる各リソースの RequestItem オブジェクトを生成し、そのリソース情報 (AssetInfo、uuid、config など) を初期化することです。 まず、入力をトラバーサル用の配列に変換します。リソースをバッチで読み込む場合、各アドインはRequestItemを生成します。 入力項目がオブジェクトの場合、最初にオプションを項目にコピーします(実際には、すべての項目はオブジェクトになります。文字列の場合は、最初のステップでオブジェクトに変換されます)。
関数解析(タスク){ //入力を配列に変換します var input = task.input, options = task.options; input = Array.isArray(input) ? input : [ input ]; タスク出力 = []; (var i = 0; i < input.length; i++) の場合 { var 項目 = 入力[i]; var out = RequestItem.create(); if (typeof item === 'string') { // 最初にオブジェクトを作成する アイテム = Object.create(null); 項目[options.__requestType__ || RequestType.UUID] = 入力[i]; } if (typeof item === 'object') { // ローカルオプションはグローバルオプションと重複します // オプションのプロパティをアイテムにコピーします。アドオンは、オプションにはあるがアイテムにはないプロパティをコピーします。cc.js.addon(item, options); if (item.preset) { cc.js.addon(item, cc.assetManager.presets[item.preset]); } for (var key in item) { スイッチ(キー){ // uuid 型リソースの場合、バンドルからリソースの詳細情報を取得します。ケース RequestType.UUID: var uuid = out.uuid = decodeUuid(item.uuid); バンドルがある場合(item.bundle) { var config = bundles.get(item.bundle)._config; var info = config.getAssetInfo(uuid); if (情報 && info.redirect) { if (!bundles.has(info.redirect)) throw new Error(`まずバンドル ${info.redirect} をロードしてください`); config = bundles.get(info.redirect)._config; アセットIDを取得します。 } config は、次の例のように構成されます。 out.info = 情報; } out.ext = item.ext || '.json'; 壊す; ケース '__requestType__': ケース 'ext': ケース 'バンドル': ケース「プリセット」: case 'type': break; ケース RequestType.DIR: // 解凍後、入力リストの末尾に動的に追加します。後続のループではこれらのリソースが自動的に解析されます if (bundles.has(item.bundle)) { var 情報 = []; バンドルを取得します。 (i = 0, l = infos.length; i < l; i++) の場合 { var info = infos[i]; input.push({uuid: info.uuid, __isNative__: false, ext: '.json', bundle: item.bundle}); } } out.recycle(); 出力 = null; 壊す; ケース RequestType.PATH: // PATH 型のリソースは、パスと型に基づいてリソースの詳細情報を取得します if (bundles.has(item.bundle)) { var config = bundles.get(item.bundle)._config; var info = config.getInfoWithPath(item.path, item.type); if (情報 && info.redirect) { if (!bundles.has(info.redirect)) throw new Error(`まずバンドル ${info.redirect} をロードする必要があります`); config = bundles.get(info.redirect)._config; アセット情報を取得します。 } if (!info) { out.recycle(); 新しいエラーをスローします(`バンドル ${item.bundle} に ${item.path} が含まれていません`); } config は、次の例のように構成されます。 out.uuid = info.uuid; out.info = 情報; } out.ext = item.ext || '.json'; 壊す; ケース RequestType.SCENE: // シーンタイプ。バンドル内の設定から getSceneInfo を呼び出して、シーンの詳細情報を取得します。if (bundles.has(item.bundle)) { var config = bundles.get(item.bundle)._config; var info = config.getSceneInfo(item.scene); if (情報 && info.redirect) { if (!bundles.has(info.redirect)) throw new Error(`まずバンドル ${info.redirect} をロードする必要があります`); config = bundles.get(info.redirect)._config; アセット情報を取得します。 } if (!info) { out.recycle(); 新しいエラーをスローします(`バンドル ${config.name} にはシーン ${item.scene} が含まれていません`); } config は、次の例のように構成されます。 out.uuid = info.uuid; out.info = 情報; } 壊す; ケース '__isNative__': ネイティブ関数は、item.__isNative__ を返します。 壊す; ケースリクエストタイプ.URL: アイテムのurlをコピーします。 out.uuid = item.uuid || item.url; out.ext = item.ext || cc.path.extname(item.url); out.isNative = item.__isNative__ !== 未定義ですか? item.__isNative__ : true; 壊す; デフォルト: out.options[key] = item[key]; } if (!out) ブレーク; } } if (!out) 継続します; タスクの出力をプッシュします。 if (!out.uuid && !out.url) throw new Error('不明な入力:' + item.toString()); } null を返します。 } RequestItem の初期情報はバンドルオブジェクトから照会され、バンドル情報はバンドルに付属する config.json ファイルから初期化されます。バンドルがパッケージ化されると、バンドル内のリソース情報が config.json に書き込まれます。 parse メソッドで処理された後、一連の RequestItem が取得されます。多くの RequestItem には AssetInfo や uuid などの情報が付属しています。combine メソッドは各 RequestItem の実際の読み込みパスを構築し、この読み込みパスは最終的に item.url に変換されます。 関数結合(タスク){ var 入力 = タスク.出力 = タスク.入力; (var i = 0; i < input.length; i++) の場合 { var 項目 = 入力[i]; // アイテムにすでに URL が含まれている場合は、これをスキップしてアイテムの URL を直接使用します if (item.url) 継続; var url = ''、base = ''; var config = item.config; // ディレクトリプレフィックスを決定する if (item.isNative) { base = (config && config.nativeBase) ? (config.base + config.nativeBase) : cc.assetManager.generalNativeBase; } それ以外 { base = (config && config.importBase) ? (config.base + config.importBase) : cc.assetManager.generalImportBase; } uuid = item.uuid とします。 var ver = ''; if (item.info) { ネイティブの場合 ver = item.info.nativeVer ? ('.' + item.info.nativeVer): ''; } それ以外 { ver = item.info.ver ? ('.' + item.info.ver): ''; } } // 最終URLを連結する // 醜いハック。WeChatは「myfont.dw213.ttf」のようなフォントの読み込みをサポートしていません。そのため、ディレクトリにハッシュを追加します。 if (item.ext === '.ttf') { url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`; } それ以外 { url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`; } アイテムのURLをコピーします。 } null を返します。 } 3.1.3 ロードパイプライン[ロードプロセス] ロード メソッドは非常にシンプルです。基本的には、新しいタスクを作成し、loadOneAssetPipeline 内の各サブタスクを実行するだけです。 関数ロード(タスク、完了){ タスクが進行している場合 task.progress = {終了: 0、合計: task.input.length}; } var オプション = task.options、進行状況 = task.progress; options.__exclude__ = options.__exclude__ || Object.create(null); タスク出力 = []; forEach(task.input, 関数(item, cb) { // 各入力項目のサブタスクを作成し、実行のために loadOneAssetPipeline に割り当てます。let subTask = Task.create({ 入力: 項目、 進行状況: タスク.onProgress、 オプション、 進捗、 onComplete: 関数 (err, 項目) { err の場合、task.isFinish と cc.assetManager.force が実行され、err が返されます。 タスク出力をプッシュします(アイテム); サブタスクをリサイクルします。 関数 : } }); // サブタスクを実行します。loadOneAssetPipeline はフェッチと解析で構成されます。loadOneAssetPipeline.async(subTask); }、 関数 () { // 各入力が実行された後、関数は最後に実行されます options.__exclude__ = null; タスクが完了したら クリア(タスク、true); task.dispatch('error') を返します。 } タスクのアセットを収集します。 クリア(タスク、true); 終わり(); }); } 関数名が示すように、loadOneAssetPipeline はアセットをロードするためのパイプラインです。これは、フェッチと解析の 2 つのステップに分かれています。 fetchメソッドはリソースファイルをダウンロードするために使用されます。PackManagerはダウンロードの実装を担当します。fetchはダウンロードしたファイルデータをitem.fileに格納します。 parse メソッドは、読み込まれたリソース ファイルを使用可能なリソース オブジェクトに変換するために使用されます。 ネイティブ リソースの場合、解析には parser.parse を呼び出します。このメソッドは、リソース タイプに応じて異なる解析メソッドを呼び出します。
その他のリソース uuid が var loadOneAssetPipeline = 新しいパイプライン('loadOneAsset', [ 関数 fetch (task, done) { var 項目 = タスク.出力 = タスク.入力; var { オプション、isNative、uuid、ファイル } = 項目; var { reload } = オプション; // リソースがアセットにロードされている場合は、直接完了します。 if (file || (!reload && !isNative && asset.has(uuid))) return done(); // ファイルをダウンロードします。これは非同期プロセスです。ファイルがダウンロードされると、item.file に配置され、done ドライバー パイプラインが実行されます。packManager.load(item, task.options, function (err, data) { もし(エラー){ (cc.assetManager.force)の場合{ エラー = null; } それ以外 { cc.error(err.message, err.stack); } データ = null; } アイテム.ファイル = データ; 完了(エラー); }); }, // リソースファイルをリソースオブジェクトに変換するプロセス function parse (task, done) { var item = task.output = task.input、progress = task.progress、exclude = task.options.__exclude__; var { id, ファイル, オプション } = 項目; ネイティブの場合 // ネイティブリソースの場合は、parser.parse を呼び出して処理し、処理したリソースを item.content に格納してプロセスを終了します。parser.parse(id, file, item.ext, options, function (err, asset) { もし(エラー){ 場合 (!cc.assetManager.force) { cc.error(err.message, err.stack); 完了(err)を返します。 } } アイテムのコンテンツ = アセット; task.dispatch('progress', ++progress.finish, progress.total, item); ファイルを削除します。 解析されたIDを削除します。 終わり(); }); } それ以外 { var { uuid } = アイテム; // 非ネイティブリソース、task.options.__exclude__ にある場合は直接終了します if (uuid in exclude) { var { 終了、コンテンツ、エラー、コールバック } = exclude[uuid]; task.dispatch('progress', ++progress.finish, progress.total, item); if (finish || checkCircleReference(uuid, uuid, exclude) ) { コンテンツ && content.addRef(); アイテムのコンテンツ = コンテンツ; 完了(エラー); } それ以外 { callbacks.push({ done, item }); } } それ以外 { // リロードされておらず、アセットにUUIDが含まれている場合 if (!options.reload &&assets.has(uuid)) { var アセット = アセット.get(uuid); // options.__asyncLoadAssets__ が有効になっているか asset.__asyncLoadAssets__ が false の場合、依存関係をロードせずにプロセスが終了します if (options.__asyncLoadAssets__ || !asset.__asyncLoadAssets__) { アイテムのコンテンツ = asset.addRef(); task.dispatch('progress', ++progress.finish, progress.total, item); 終わり(); } それ以外 { loadDepends(タスク、アセット、完了、false); } } それ以外 { // 再読み込みの場合、またはアセットにない場合は、解析して依存関係を読み込みます。parse(id, file, 'import', options, function (err, asset) { もし(エラー){ (cc.assetManager.force)の場合{ エラー = null; } それ以外 { cc.error(err.message, err.stack); } 完了(err)を返します。 } アセット._uuid = uuid; loadDepends(タスク、アセット、完了、true); }); } } } } ]); 3.2 ファイルのダウンロードCreator は、ダウンロード作業を完了するために
// packManager.load の実装 load (item, options, onComplete) { // リソースがパッケージ化されていない場合は、downloader.download を直接呼び出してダウンロードします (download には、ダウンロード済みか読み込み中かの判断も含まれます) if (item.isNative || !item.info || !item.info.packs) return downloader.download(item.id, item.url, item.ext, item.options, onComplete); // ファイルがダウンロードされた場合は直接戻ります if (files.has(item.id)) return onComplete(null, files.get(item.id)); var パック = item.info.packs; // パックがすでにロード中の場合は、コールバックを _loading キューに追加し、ロードが完了した後にコールバックをトリガーします。var pack = packs.find(isLoading); if (pack) return _loading.get(pack.uuid).push({ onComplete, id: item.id }); // 新しいパックをダウンロードする パック = パック[0]; _loading.add(pack.uuid, [{ onComplete, id: item.id }]); url を cc.assetManager._transform(pack.uuid, {ext: pack.ext, bundle: item.config.name}); // パックをダウンロードして解凍し、 downloader.download(pack.uuid, url, pack.ext, item.options, function (err, data) { ファイルを削除します。(pack.uuid); もし(エラー){ cc.error(err.message, err.stack); } // パッケージを解凍します。内部実装には 2 種類の解凍が含まれています。1 つはプレハブやアトラスなどの json 配列の分割と解凍用、もう 1 つは Texture2D のコンテンツ用です。packManager.unpack(pack.packs, data, pack.ext, item.options, function (err, result) { もしエラーが起きたら (結果内の変数ID) { ファイルを追加します(id, result[id]); } } var コールバック = _loading.remove(pack.uuid); (var i = 0, l = callbacks.length; i < l; i++) の場合 { var cb = コールバック[i]; もし(エラー){ cb.onComplete(エラー); 続く; } var データ = 結果[cb.id]; if (!データ) { cb.onComplete(新しいエラー('パッケージからデータを取得できません')); } それ以外 { cb.onComplete(null、データ); } } }); }); } 3.2.1 Webプラットフォームからのダウンロード Web プラットフォームのダウンロード実装は次のとおりです。
ダウンロード (id、url、タイプ、オプション、onComplete) { // ダウンローダー内の対応するダウンロード コールバックのタイプを取得します。let func = downloaders[type] || downloaders['default']; 自分自身 = this とします。 // 繰り返しダウンロードを回避する let file, downloadCallbacks; if (ファイル = files.get(id)) { onComplete(null、ファイル); } // ダウンロード中の場合はキューに追加します。それ以外の場合は、(downloadCallbacks = _downloading.get(id)) { ダウンロードCallbacks.push(onComplete); (i = 0, l = _queue.length; i < l; i++) の場合 { var item = _queue[i]; if (item.id === id) { var 優先度 = options.priority || 0; if (item.priority < 優先度) { 項目の優先度 = 優先度; _queueDirty = true; } 戻る; } } } それ以外 { // ダウンロードして失敗したダウンロードの再試行を設定します var maxRetryCount = options.maxRetryCount || this.maxRetryCount; var maxConcurrency = options.maxConcurrency || this.maxConcurrency; var maxRequestsPerFrame = options.maxRequestsPerFrame || this.maxRequestsPerFrame; 関数プロセス (インデックス、コールバック) { (インデックス === 0)の場合{ _downloading.add(id, [onComplete]); } if(!self.limited)return func(urlappendtimestamp(url)、options、callback); updateTime(); 関数invoke(){ func(urlappendtimestamp(url)、option、function(){ //ダウンロードが終了したら、_totalnumを更新します _totalnum--; if(!_CheckNextPerioD && _Queue.Length> 0){ callinnexttick(handlequeue、maxconcurrency、maxrequestsperframe); _CHECKNEXTPERIOD = true; } callback.apply(これ、引数); }); } if(_totalnum <maxconcurrency && _totalnumthisperiod <maxrequestsperframe){ invoke(); _totalnum ++; _totalnumthisperiod ++; } それ以外 { //リクエストの数が制限に達したら、残りをキャッシュします _queue.push({id、priority:options.priority || 0、invoke}); _QueueDirty = true; if(!_CheckNextPerioD && _Totalnum <maxConcurrency){ callinnexttick(handlequeue、maxconcurrency、maxrequestsperframe); _CHECKNEXTPERIOD = true; } } } // retryが完了したら、ファイルキャッシュにファイルを追加し、_ダウンロードキューから削除し、// retryが完了したら、コールバックを呼び出します function Finale(err、result){ if(!err)files.add(id、result); var callbacks = _downloading.remove(id); for(i = 0、l = callbacks.length; i <l; i ++){ callbacks [i](err、result); } } Retry(Process、MaxretryCount、this.retryinterval、Finale); } } ダウンローダーは、さまざまなリソースタイプに対応するダウンロードメソッドをマップするマップです。主に次のタイプのダウンロード方法が含まれています。 画像クラスのダウンロードイメージ
ファイルクラス。バイナリファイル、JSONファイル、テキストファイルに分割できます
フォントクラスLoadFontはCSSスタイルを構築し、ダウンロードするURLを指定します サウンドクラスのダウンロードAudio
ビデオクラスのダウンロードVideoWebクライアントが直接返されます ScriptDownloadScriptはHTMLスクリプト要素を作成し、SRC属性を指定してダウンロードして実行します バンドルダウンロードバンドルはバンドルのJSONとスクリプトを同時にダウンロードします ダウンロードFileは、XMLHTTPREQUESTを使用してファイルをダウンロードします。 function downloadfile(url、options、onprogress、oncomplete){ var {options、onprogress、oncomplete} = parseparameters(options、onprogress、oncomplete); var xhr = new xmlhttprequest()、errinfo = 'ダウンロード失敗:' + url + '、status:'; xhr.open('GET', url, true); if(options.responsetype!== undefined)xhr.responsetype = options.responsetype; if(options.withcredentials!== undefined)xhr.withcredentials = options.withcredentials; if(options.mimetype!== undefined && xhr.overridemimeType)xhr.OverRidemimeType(options.mimeType); if(options.timeout!== undefined)xhr.timeout = options.timeout; if(option.header){ for(options.headerのvarヘッダー){ xhr.setRequestHeader(Header、Options.Header [Header]); } } xhr.onload = 関数 () { if(xhr.status === 200 || xhr.status === 0){ oncomplete && oncomplete(null、xhr.response); } それ以外 { oncomplete && oncomplete(new Error(errinfo + xhr.status + '(応答なし)')); } }; if(onprogress){ xhr.onprogress = function(e){ if(e.lengthComputable){ OnProgress(E.Loaded、E.Total); } }; } xhr.onerror = function(){ oncomplete && oncomplete(new error(errinfo + xhr.status + '(error)')); }; xhr.ontimeout = function(){ oncomplete && oncomplete(new Error(errinfo + xhr.status + '(Time Out)')); }; xhr.onabort = function(){ oncomplete && oncomplete(new error(errinfo + xhr.status + '(abort)')); }; xhr.send(null); xhrを返します。 } 3.2.2ネイティブプラットフォームをダウンロードします ネイティブプラットフォームのエンジン関連ファイルは Downloader.register({ // JS '.js':downloadscript、 '.jsc':downloadscript、 //画像 '.png':downloadasset、 '.jpg':downloadasset、 ... }); ネイティブプラットフォームでは、ダウンロードをダウンロードするためにリソースをダウンロードする前に、Transformurlが呼び出されます。これは、主にリソースがネットワークリソースであるかどうかを決定します。ダウンロードされていないネットワークリソースのみをダウンロードする必要があります。ダウンロードする必要がない場合は、ファイルの解析場所でファイルを直接読み取ります。 //たとえば、ダウンロードが完了した後、funcが処理します。 // JSONリソースをダウンロードしたい場合、渡されたFUNCはドノージングです。つまり、oncompleteメソッド関数のダウンロード(url、func、options、onfileprogress、oncomplete)を直接呼び出すことができます{ var result = transformurl(url、options); //ローカルファイルの場合は、FUNCを直接指します if(result.inlocal){ func(result.url、options、oncomplete); } //キャッシュ中の場合、更新リソースの最後の時間が使用されます(LRU) else if(result.incache){ cachemanager.updatelasttime(url) func(result.url、options、function(err、data){ もし(エラー){ cachemanager.removecache(url); } oncomplete(err、data); }); } それ以外 { //無効なネットワークリソースについては、downloadfileを呼び出してvar time = date.now()をダウンロードしてください。 var StoragePath = ''; if(options .__ cachebundleroot__){ StoragePath = `$ {cachemanager.cachedir}/$ {options .__ cachebundleroot __}/$ {time} $ {suffix ++} $ {cc.path.extname(url)} '; } それ以外 { StoragePath = `$ {cachemanager.cachedir}/$ {time} $ {suffix ++} $ {cc.path.extname(url)}`; } //ダウンロードファイルを使用してダウンロードしてキャッシュoptersion(url、storagepath、options.header、onfileprogress、function(err、path){ もし(エラー){ oncomplete(err、null); 戻る; } func(パス、オプション、関数(err、data){ もしエラーが起きたら cachemanager.cachefile(url、storagepath、options .__ cachebundleroot__ __); } oncomplete(err、data); }); }); } } 関数transformurl(url、options){ var inlocal = false; var incache = false; //それは通常のマッチングによるURLですか? if(regex.test(url)){ if(options.reload){ {url}を返します; } それ以外 { //キャッシュにあるかどうかを確認します(ローカルディスクキャッシュ) var cache = cachemanager.cachedfiles.get(url); if(cache){ incache = true; url = cache.url; } } } それ以外 { inlocal = true; } return {url、inlocal、incache}; } ダウンロードFileは、ネイティブプラットフォームのJSB_DownLoaderに電話してリソースをダウンロードし、ローカルディスクに保存します。 downloadfile(remoteurl、filepath、header、onprogress、oncomplete){ download.add(remoteurl、{onprogress、oncomplete}); var StoragePath = filepath; if(!StoragePath)StoragePath = tempdir + '/' + performance.now() + cc.path.extname(remoteurl); jsb_downloader.createdownloadfiletask(remoteurl、storagepath、header); }, 3.3ファイル解析LoadOneasSetPipelineでは、リソースは2つのパイプラインを介して処理されます。 Parse Methodでは、Parser.Parseがファイルの内容を渡し、対応するアセットオブジェクトに解析して返すように呼び出されます。 3.3.1 Webプラットフォーム分析 Webプラットフォームの下のParser.Parseは、主に解析されたファイルを管理し、複製された解析を避けるために解析および解析されたファイルのリストを維持します。同時に、パーサーの後のコールバックリストが維持され、実際の解析方法はパーサーアレイにあります。 parse(id、file、type、options、oncomplete){ Parsedasset、解析、Parsehandlerを使用します。 if(parsedasset = parsed.get(id)){ oncomplete(null、parsedasset); } else if(parsing = _parsing.get(id)){ parsing.push(oncomplete); } else if(parsehandler = parsers [type]){ _parsing.add(id、[oncomplete]); parsehandler(file、options、function(err、data){ もし(エラー){ files.remove(id); } else if(!isscene(data)){ parsed.add(id、data); } let callbacks = _parsing.remove(id); for(i = 0、l = callbacks.length; i <l; i ++){ callbacks [i](err、data); } }); } それ以外 { oncomplete(null、file); } } パーサーは、さまざまなタイプのファイルの解析方法をマップします。 注:Parseimportメソッドでは、脱審設定方法は、リソースの依存関係を資産に依存させます。たとえば、プレハブリソースには2つのノードがあり、どちらも同じリソースを参照しています。 //画像形式を解析方法にマッピングするvar parsers = { '.png':parser.parseimage、 '.jpg':parser.parseimage、 '.bmp':parser.parseimage、 '.jpeg':parser.parseimage、 '.gif':parser.parseimage、 '.ico':parser.parseimage、 '.tiff':parser.parseimage、 '.webp':parser.parseimage、 '.image':parser.parseimage、 '.pvr':parser.parsepvrtex、 '.pkm':parser.parsepkmtex、 //オーディオ '.mp3':parser.parseaudio、 '.ogg':parser.parseaudio、 '.wav':parser.parseaudio、 '.m4a':parser.parseaudio、 // Plist '.plist':parser.parseplist、 「インポート」:parser.parseimport }; //画像はアセットオブジェクトに解析されませんが、parseimage(file、options、oncomplete)になります{ if(capabilities.imagebitmap && file instanceof blob){ ImageOptions = {}; imageoptions.imageorientation = option .__ flipy__: 'none'; imageoptions.premultiplyalpha = option .__ premultiplyalpha__: 'none'; createImageBitMap(file、imageoptions).then(function(result){ result.flipy = !! options .__ flipy__; result.premultiplyalpha = !! options .__ premultiplyalpha__; oncomplete && oncomplete(null、result); }、function(err){ oncomplete && oncomplete(err、null); }); } それ以外 { oncomplete && oncomplete(null、file); } }, //アセットオブジェクトの分析は、一般化を介して実装されます。一般的なプロセスは、対応するクラスを解析し、対応するクラスの_deserializeメソッドを呼び出して、データをコピーし、変数を初期化し、依存関係リソースを資産に配置することです。 parseimport(file、options、oncomplete){ if(!file)return oncomplete && oncomplete(new error( 'json is empty')); var result、err = null; 試す { result = Deserialize(file、options); } キャッチ(e){ err = e; } oncomplete && oncomplete(err、result); }, 3.3.2ネイティブプラットフォームの分析 ネイティブプラットフォームでは、さまざまなリソースの解析方法がjsb-loader.jsに再登録されています。 parser.register({ '.png':downloader.downloaddomimage、 '.binary':parsearraybuffer、 '.txt':parsetext、 '.plist':パーセプリスト、 '.font':loadfont、 '.exportjson':Parsejson、 ... }); 画像を解析する方法は、実際にはDownloader.DownLoadDomimageですか?ネイティブプラットフォームをデバッグしましたが、このメソッドはイメージオブジェクトを作成し、この方法で画像をロードすることもできますが、テクスチャオブジェクトはどのように作成されていますか? Texture2Dに対応するJSONファイルを介して、作成者は本物のネイティブテクスチャをロードした後、Texture2Dオブジェクトの_nativeassetに設定されます。 var texture2d = cc.class({ 名前: 'cc.texture2d'、 拡張:require( '../ assets/ccasset')、 ミキシン:[EventTarget]、 プロパティ: _nativeasset:{ 得る () { //多分Webglのプールに戻った this._imageを返します。 }, set(data){ if(data._data){ this.initwithdata(data._data、this._format、data.width、data.height); } それ以外 { this.initwithelement(data); } }, オーバーライド:本当です }, Parsejson、Parsetext、Parsearraybufferなどの実装については、ここでファイルシステムを呼び出してファイルを読み取るだけです。たとえば、ファイルコンテンツを取得した後に使用するためにさらに解析する必要があるリソースはありますか?たとえば、モデルや骨などのリソースはバイナリモデルデータに依存しています。そうです、上記のTexture2dのように、それらは対応する資産リソース自体に配置されます。 // jsb-loader.jsファイルの関数parsetext(url、options、oncomplete){ readtext(url、oncomplete); } 関数parsearraybuffer(url、options、oncomplete){ ReadArrayBuffer(url、oncomplete); } function parsejson(url、options、oncomplete){ readjson(url、oncomplete); } // jsb-fs-utils.js file {in readtext(filepath、oncomplete){ fsutils.readfile(filepath、 'utf8'、oncomplete); }, ReadArrayBuffer(Filepath、oncomplete){ fsutils.readfile(filepath、 ''、oncomplete); }, readjson(filepath、oncomplete){ fsutils.readfile(filepath、 'utf8'、function(err、text){ var out = null; もしエラーが起きたら 試す { out = json.parse(text); } キャッチ(e){ cc.warn( 'json failed:' + e.message); err = new Error(e.message); } } oncomplete && oncomplete(err、out); }); }, 写真アルバムやプレハブなどのリソースはどのように初期化されていますか?作成者は、これらのリソースの対応するタイプがこのタイプに 3.4依存関係の読み込み作成者は、リソースを2つのカテゴリに分割します。通常のリソースと、CC.SpriteFrame、CC.Texture2d、CC.Prefabなどのサブクラスには含まれます。ネイティブリソースには、さまざまな形式のテクスチャ、音楽、フォント、その他のファイルが含まれます。これらのネイティブリソースを直接使用することはできませんが、使用する前に作成者が対応するCC.Assetオブジェクトに変換する必要があります。 作成者では、プレハブは多くのリソースに依存する場合があり、これらの依存関係は、通常の LoadDependsは、依存関係リソースのロードを担当するサブタスクを作成し、実際にロードを実行して、依存関係が完了した後に実行されます。
//指定された資産関数の依存関係をロードしますloaddepends(タスク、アセット、完了、init){ var item = task.input、progress = task.progress; var {uuid、id、options、config} = item; var {__asyncloadassets__、cacheasset} = options; var依存= []; //依存関係の負荷中にリリースがリリースされないように参照カウントを増やします。 getDepends(uuid、asset、object.create(null)、依存、false、__asyncloadassets__、config); task.dispatch( 'progress'、++ progress.finish、progress.total += depents.length、item); var RepeatItem = task.options .__除外__ [uuid] = {content:asset、finide:false、callbacks:[{done、item}]}; let subtask = task.create({ 入力:依存します、 オプション:task.options、 onprogress:task.onprogress、 onerror:task.prototype.recycle、 進捗、 oncomplete:function(err){ // asset.decref && asset.decref(false); Asset .__ AsyncloadAssets___ = __AsyncLoadAssets__; Repeatitem.finish = true; Repeatitem.err = err; もしエラーが起きたら var assets = array.isarray(subtask.output)? //マップを構築して、UUIDからAssetにマップを記録して、var map = object.create(null); for(i = 0、l = assets.length; i <l; i ++){ var Depenasset = assets [i]; depenasset &&(map [depenasset instanceof cc.asset?depenasset._uuid + '@import':uuid + '@native'] = depenasset); } // SetPropertiesを呼び出して、対応する依存関係リソースを資産のメンバー変数に設定するif(!init){ if(asset .__ nativedepend__ &&!asset._nativeasset){ var MissingAsset = setProperties(uuid、asset、map); if(!MissingAsset){ 試す { asset.onload && asset.onload(); } キャッチ(e){ cc.error(e.message、e.stack); } } } } それ以外 { var MissingAsset = setProperties(uuid、asset、map); if(!MissingAsset){ 試す { asset.onload && asset.onload(); } キャッチ(e){ cc.error(e.message、e.stack); } } files.remove(id); parsed.remove(id); キャッシュ(uuid、asset、cacheasset!== undefined?cacheasset:cc.assetmanager.cacheasset); } subtask.recycle(); } //ロードされたこのrepeectItemには多くの場所がある可能性があり、すべてのコールバックを通知する必要がありますvarコールバック= RepeatItem.callbacksを完了する必要があります。 for(var i = 0、l = callbacks.length; i <l; i ++){ var cb = callbacks [i]; asset.addref && asset.addref(); cb.item.content = asset; cb.done(err); } callbacks.length = 0; } }); pipeline.async(subtask); } 3.4.1依存関係分析 getDepends(uuid、data、explude、depend、preload、asyncloadassets、config){ var err = null; 試す { var info = Depentutil.parse(uuid、data); var includenative = true; if(data instance of cc.asset &&(!data .__ nativedepend__ || data._nativeasset))includenative = false; if(!preload){ asyncloadassets =!cc_editor &&(!! asyncloadassets ||(asyncloadassets &&!info.preventdeferredloaddependents)); for(i = 0、l = info.deps.length; i <l; i ++){ dep = info.deps [i]; if(!(dep in exclude)){ 除外[dep] = true; Depens.push({uuid:dep、__ asyncloadassets__:asyncloadassets、bundle:config && config.name}); } } if(includenative &&!asyncloadassets &&!info.preventpreloadnativeobject && info.nativepep){ config &&(info.nativedep.bundle = config.name); Depens.push(info.nativepep); } } それ以外 { for(i = 0、l = info.deps.length; i <l; i ++){ dep = info.deps [i]; if(!(dep in exclude)){ 除外[dep] = true; Depens.push({uuid:dep、bundle:config && config.name}); } } if(includenative && info.nativepep){ config &&(info.nativedep.bundle = config.name); Depens.push(info.nativepep); } } } キャッチ(e){ err = e; } エラーを返します。 }, Depentilは、依存関係リストを制御するシングルトンです。
Depentilは、依存関係のクエリの重複を避けるために_ -Depends Cacheを維持します。 // JSON情報に基づいてリソース依存関係リストを取得します。 var out = null; //シーンまたはプレハブの場合、データは配列、シーン、またはプレハブになります if(array.isarray(json)){ //それが解析され、_dependsに依存関係リストがある場合、それは(this._depends.has(uuid))this._depends.get(uuid)を返す場合に直接返されます out = { //プレハブまたはシナリオについては、_parsedepsfromjsonメソッドを使用して直接deps:cc.asset._parsedepsfromjson(json)、 asyncloadassets:json [0] .asyncloadassets }; } // __Type__が含まれている場合、そのコンストラクターを取得し、JSONから依存関係リソースを探します。 //実際のテストでは、プリロードされたリソースは次のブランチに移動します。 if(this._depends.has(uuid))this._depends.get(uuid); var ctor = js._getclassbyid(json .__ type__); //一部のリソースは、_parsedepsfromjsonと_ parsenativedepfromjsonメソッドを書き直しました//たとえば、cc.texture2d out = { PreventPreloAdnativeObject:ctor.preventpreloadnativeObject、 PreventDeferredLoadDependents:CTOR.PREVENTDEREDLOADDEPENDENTS、 deps:ctor._parsedepsfromjson(json)、 nativedep:ctor._parsenativedepfromjson(json) }; out.nativedep &&(out.nativedep.uuid = uuid); } //既存の資産からDEPを取得します // __Type__フィールドがない場合、対応するCTORを見つけることができません。 if(!cc_editor &&(out = this._depends.get(uuid))&& out.parsedfromexistasset)return; var asset = json; out = { deps:[]、 parsedfromexistasset:true、 PreventPreloAdnativeObject:asset.constructor.preventpreloadnativeObject、 PreventDeferredLoadDependents:Asset.Constructor.PreventDeredLoadDependents }; deps = asset .__依存関係__; for(var i = 0、l = deps.length; i <l; i ++){ var dep = deps [i] .uuid; out.deps.push(dep); } if(asset .__ nativeDepend__){ // asset._nativedepはこのようなオブジェクトを返します{__isnative__:true、uuid:this._uuid、ext:this._native} out.nativedep = asset._nativedep; } } //依存関係を初めて見つけたら、_dependsリスト、キャッシュ依存関係リストに直接配置します this._depends.add(uuid、out); 戻る; } デフォルトの _parsedepsfromjson(json){ var依存= []; parsedependivilly(json、依存); 返品は異なります。 }, _parsenativedepfromjson(json){ if(json._native)return {__isnative__:true、ext:json._native}; null を返します。 } 3.5リソースリリースこのセクションでは、作成者のリソースをリリースする3つの方法とその背後にある実装に焦点を当て、最後にプロジェクトのリソースリークのトラブルシューティング方法を紹介します。 3.5.1 Creator Resource Release クリエーターは、リソースをリリースする次の3つの方法をサポートしています。
3.5.2シーンの自動リリース 新しいシーンが実行されている場合、director.runsceneimmediateメソッドが実行されます。 runsceneimmediate:function(seen、onbeforeloadscene、onlaunched){ //コードを省略します... var oldscene = this._scene; if(!cc_editor){ //リソースを自動的にリリースcc_build && cc_debug && console.time( 'autorelease'); cc.assetmanager._releasemanager._autoreLease(oldscene、seenc、festingnodelist); cc_build && cc_debug && console.timeend( 'autorelease'); } //シーンをアンロードします cc_build && cc_debug && console.time( 'Destroy'); if(cc.isvalid(oldscene)){ oldscene.destroy(); } //コードを省略します... }, _autorleaseの最新バージョンの実装は、古いシーンから永続的なノードの参照を移行し、リソースがリリースされるかどうかを直接呼び出します。 //自動リリースを行います _autoreLease(oldscene、newscene、staveNodes){ //永続的なノードによって依存するすべてのリソースは自動的に追加され、scenedeps.persistdepsに記録されます(i = 0、l = festiveNodes.length; i <l; i ++){ var node = stavesnodes [i]; var scenedeps = Dependutil._Depends.get(NewsCene._ID); var deps = _persistnodedeps.get(node.uuid); for(i = 0、l = deps.length; i <l; i ++){ var Depenasset = assets.get(deps [i]); if(depenasset){ depenasset.addref(); } } if(scenedeps){ !scenedeps.persistdeps &&(scenedeps.persistdeps = []); scenedeps.persistdeps.push.apply(scenedeps.persistdeps、deps); } } //古いシーンの依存関係をリリースします(oldscene){ var children = depentil.getDeps(oldscene._id); for(i = 0、l = childrens.length; i <l; i ++){ asset = assets.get(Childs [i]); Asset && asset.decref(cc_test || oldscene.autorleaseassets); } var依存関係= Dependutil._depends.get(oldscene._id); if(dependencies && dependencies.persistdeps){ var persistdeps = dependencies.persistdeps; for(i = 0、l = persipdeps.length; i <l; i ++){ asset = assets.get(persistdeps [i]); Asset && asset.decref(cc_test || oldscene.autorleaseassets); } } Depentil.Remove(oldscene._id); } }, 3.5.3リファレンスカウントと手動リソースリリース リソースをリリースする2つの方法があります。それらは、リソースリリースを実現するためにリリースマンガーと呼ばれます。リソースリリースの完全なプロセスは、以下の図に大まかに示されています。 // ccasset.jsリファレンスdecref(autorelease){ this._ref--; autorelease!== false && cc.assetmanager._releasemanager.tryrelease(this); これを返します。 } // ccassetmanager.js手動でリソースリリースセット(asset){ lireasemanager.tryrelease(asset、true); }, TryReleaseは、リリースの遅延と強制的なパラメーターがTRUEである場合、
Checkcircularreferenceの返品値は、リソースが他の場所で参照されていることを意味し、この方法は最初に資産を記録し、その後、リソースBとCの両方のリソースの両方で、リソースBとCの両方のリソースの両方で同等です。 Aが実際にAで解放されます。AがBとCで参照されると、カウントは0ではなく、リリースできません。リソースの参照が1を超える内部参照番号によって差し引かれた場合、それは他の場所でまだ参照されており、解放できないことを意味します。 tryrelease(asset、force){ if(!(asset instanceof cc.asset))return; if(force){ lireasemanager._free(asset、force); } それ以外 { _todelete.add(asset._uuid、asset); //次のディレクターの描画が完了したら、フリーエージェットを実行します if(!eventListener){ eventListener = true; cc.director.once(cc.director.event_after_draw、freassets); } } } // free resource_free(asset、force){ _todelete.remove(asset._uuid); if(!cc.isvalid(asset、true))return; if(!force){ if(asset.refcount> 0){ //リソース内の循環参照を確認します(checkcircularreference(asset)> 0)return; } } // assets.remove(asset._uuid); var depend = Dependutil.getDeps(asset._uuid); for(i = 0、l = depens.length; i <l; i ++){ var Depenasset = assets.get(依存者[i]); if(depenasset){ Depenasset.decref(false); releasemanager._free(depenasset、false); } } asset.destroy(); Depentil.Remove(asset._uuid); }, // _todeleteでリソースをリリースし、機能をクリアしますfreeassets(){ eventlistener = false; _todelete.foreach(function(asset){ lireasemanager._free(asset); }); _todelete.clear(); } asset.destroyは何をしますか?リソースオブジェクトはどのようにリリースされますか?テクスチャやサウンドなどのリソースはどのようにリリースされますか? Assetオブジェクト自体には破壊方法がありませんが、Assetオブジェクトが実装するCCOBJECTオブジェクトは、リリースする配列にオブジェクトを配置し、それを prototype.destroy = function(){ if(this._objflags&destroy){ cc.warnid(5000); false を返します。 } if(this._objflags&todestroy){ false を返します。 } this._objflags | = todestroy; objectStodestroy.push(this); if(cc_editor && deferreddestroytimer === null && cc.engine &&!cc.engine._isupdating){ // deferredDestroytimerは、編集モード= setimmediate(deferredDestroy)ですぐに破壊できます。 } true を返します。 }; //ディレクターはこの方法を呼び出しますすべてのフレーム関数deferredDestroy(){{ var deletecount = objectStodestroy.length; for(var i = 0; i <deletecount; ++ i){ var obj = objectStodestroy [i]; if(!(obj._objflags&Destroyed)){ obj._destroyimmediate(); } } // a.ondestroyのb.destroyを呼び出すと、ObjectStodestroyアレイのサイズが変更されます。 objectStodestroy.length = 0; } それ以外 { objectStodestroy.splice(0、deletecount); } if(cc_editor){ DeferredDestroytimer = null; } } //実際のリソースリリースprototype._destroyimmediate = function(){ if(this._objflags&destroy){ cc.errorid(5000); 戻る; } //コールバックを実行するif(this._onpredestroy){ this._onpredestroy(); } if((cc_test?(/* cc_editor mockable*/ function)( 'return!cc_editor'))():!cc_editor)|| cc.engine._isplaying){ this._destruct(); } this._objflags | =破壊。 }; 在這里 prototype._destruct = function () { var ctor = this.constructor; var destruct = ctor.__destruct__; if (!destruct) { destruct = compileDestruct(this, ctor); js.value(ctor, '__destruct__', destruct, true); } destruct(this); }; function compileDestruct (obj, ctor) { var shouldSkipId = obj instanceof cc._BaseNode || obj instanceof cc.Component; var idToSkip = shouldSkipId ? '_id' : null; var key, propsToReset = {}; for (キー in obj) { obj.hasOwnProperty(キー) の場合 { if (key === idToSkip) { 続く; } switch (typeof obj[key]) { ケース '文字列': propsToReset[key] = ''; 壊す; case 'object': ケース '関数': propsToReset[key] = null; 壊す; } } } // Overwrite propsToReset according to Class if (cc.Class._isCCClass(ctor)) { var attrs = cc.Class.Attr.getClassAttrs(ctor); var propList = ctor.__props__; for (var i = 0; i < propList.length; i++) { key = propList[i]; var attrKey = key + cc.Class.Attr.DELIMETER + 'default'; if (attrKey in attrs) { if (shouldSkipId && key === '_id') { 続く; } switch (typeof attrs[attrKey]) { ケース '文字列': propsToReset[key] = ''; 壊す; case 'object': ケース '関数': propsToReset[key] = null; 壊す; case 'undefined': propsToReset[key] = undefined; 壊す; } } } } if (CC_SUPPORT_JIT) { // compile code var func = ''; for (key in propsToReset) { var statement; if (CCClass.IDENTIFIER_RE.test(key)) { statement = 'o.' + key + '='; } それ以外 { statement = 'o[' + CCClass.escapeForJS(key) + ']='; } var val = propsToReset[key]; if (val === '') { val = '""'; } func += (statement + val + ';\n'); } return Function('o', func); } それ以外 { return function (o) { for (var key in propsToReset) { o[key] = propsToReset[key]; } }; } } 那么 // Node的_onPreDestroy _onPreDestroy () { // 調用_onPreDestroyBase方法,實際是調用BaseNode.prototype._onPreDestroy,這個方法下面介紹var destroyByParent = this._onPreDestroyBase(); // 注銷Actions if (ActionManagerExist) { cc.director.getActionManager().removeAllActionsFromTarget(this); } // 移除_currentHovered if (_currentHovered === this) { _currentHovered = null; } this._bubblingListeners && this._bubblingListeners.clear(); this._capturingListeners && this._capturingListeners.clear(); // 移除所有觸摸和鼠標事件監聽if (this._touchListener || this._mouseListener) { eventManager.removeListeners(this); if (this._touchListener) { this._touchListener.owner = null; this._touchListener.mask = null; this._touchListener = null; } if (this._mouseListener) { this._mouseListener.owner = null; this._mouseListener.mask = null; this._mouseListener = null; } } if (CC_JSB && CC_NATIVERENDERER) { this._proxy.destroy(); this._proxy = null; } // 回收到對象池中this._backDataIntoPool(); if (this._reorderChildDirty) { cc.director.__fastOff(cc.Director.EVENT_AFTER_UPDATE, this.sortAllChildren, this); } if (!destroyByParent) { if (CC_EDITOR) { // 確保編輯模式下的,節點的被刪除后可以通過ctrl+z撤銷(重新添加到原來的父節點) this._parent = null; } } }, // BaseNode的_onPreDestroy _onPreDestroy () { var i, len; // 加上Destroying標記this._objFlags |= Destroying; var parent = this._parent; // 根據檢測父節點的標記判斷是不是由父節點的destroy發起的釋放var destroyByParent = parent && (parent._objFlags & Destroying); if (!destroyByParent && (CC_EDITOR || CC_TEST)) { // 從編輯器中移除this._registerIfAttached(false); } // 把所有子節點進行釋放,它們的_onPreDestroy也會被執行var children = this._children; for (i = 0, len = children.length; i < len; ++i) { children[i]._destroyImmediate(); } // 把所有的組件進行釋放,它們的_onPreDestroy也會被執行for (i = 0, len = this._components.length; i < len; ++i) { var component = this._components[i]; component._destroyImmediate(); } // 注銷事件監聽,比如otherNode.on(type, callback, thisNode) 注冊了事件// thisNode被釋放時,需要注銷otherNode身上的監聽,避免事件回調到已銷毀的對象上var eventTargets = this.__eventTargets; for (i = 0, len = eventTargets.length; i < len; ++i) { var target = eventTargets[i]; target && target.targetOff(this); } eventTargets.length = 0; // 如果自己是常駐節點,則從常駐節點列表中移除if (this._persistNode) { cc.game.removePersistRootNode(this); } // 如果是自己釋放的自己,而不是從父節點釋放的,要通知父節點,把這個失效的子節點移除掉if (!destroyByParent) { if (parent) { var childIndex = parent._children.indexOf(this); parent._children.splice(childIndex, 1); parent.emit && parent.emit('child-removed', this); } } return destroyByParent; }, // Component的_onPreDestroy _onPreDestroy () { // 移除ActionManagerExist和schedule if (ActionManagerExist) { cc.director.getActionManager().removeAllActionsFromTarget(this); } this.unscheduleAllCallbacks(); // 移除所有的監聽var eventTargets = this.__eventTargets; for (var i = eventTargets.length - 1; i >= 0; --i) { var target = eventTargets[i]; target && target.targetOff(this); } eventTargets.length = 0; // 編輯器模式下停止監控if (CC_EDITOR && !CC_TEST) { _Scene.AssetsWatcher.stop(this); } // destroyComp的實現為調用組件的onDestroy回調,各個組件會在回調中銷毀自身的資源// 比如RigidBody3D組件會調用body的destroy方法,而Animation組件會調用stop方法cc.director._nodeActivator.destroyComp(this); // 將組件從節點身上移除this.node._removeComponent(this); }, 3.5.4 資源釋放的問題 最后我們來聊一聊資源釋放的問題與定位,在加入引用計數后,最常見的問題還是沒有正確增減引用計數導致的內存泄露(循環引用、少調用了decRef或多調用了addRef),以及正在使用的資源被釋放的問題(和內存泄露相反,資源被提前釋放了)。 從目前的代碼來看,如果正確使用了引用計數,新的資源底層是可以避免內存泄露等問題的 この問題を解決するにはどうすればいいでしょうか?首先是定位出哪些資源出了問題,如果是被提前釋放,我們可以直接定位到這個資源,如果是內存泄露,當我們發現問題時程序往往已經占用了大量的內存,這種情況下可以切換到一個空場景,并清理資源,把資源清理完后,可以檢查assets中殘留的資源是否有未被釋放的資源。 要了解資源為什么會泄露,可以通過跟蹤addRef和decRef的調用得到,下面提供了一個示例方法,用于跟蹤某資源的addRef和decRef調用,然后調用資源的dump方法打印出所有調用的堆棧: public static traceObject(obj : cc.Asset) { let addRefFunc = obj.addRef; let decRefFunc = obj.decRef; let traceMap = new Map(); obj.addRef = function() : cc.Asset { let stack = ResUtil.getCallStack(1); let cnt = traceMap.has(stack) ? traceMap.get(stack) + 1 : 1; traceMap.set(stack, cnt); return addRefFunc.apply(obj, arguments); } obj.decRef = function() : cc.Asset { let stack = ResUtil.getCallStack(1); let cnt = traceMap.has(stack) ? traceMap.get(stack) + 1 : 1; traceMap.set(stack, cnt); return decRefFunc.apply(obj, arguments); } obj['dump'] = function() { console.log(traceMap); } } 以上就是剖析CocosCreator新資源管理系統的詳細內容,更多關于CococCreator的資料,請關注123WORDPRESS.COM其他相關文章! 以下もご興味があるかもしれません:
|
<<: MySQL で戻り値ありと戻り値なしのストアド プロシージャを書く 2 つの方法
>>: Dockerが独自のローカルイメージリポジトリを構築するための手順
目次エフェクト表示コードリンクキーコード表形式データコンポーネントのネスト検証方法リセット方法完全な...
この記事では、アコーディオンを実装するためのjQueryの具体的なコードを参考までに紹介します。具体...
この記事では、MySQL 8.0.12のインストールされていないバージョンを設定して起動するための具...
最近、データベースについて学び始めました。最初にやったことは、データベースとは何か、データベースとデ...
最近、PHP で Web ページを書いているときに、エンコードを UTF-8 に設定しました。しかし...
1. クリアフローティング法1前の親要素の高さを設定します。注: エンタープライズ開発では、可能であ...
序文MySQL 5.7.11 以降、MySQL は、別の表領域に格納された InnoDB テーブルの...
目次1. ホームページ制作1. ダウンロードアプリの制作2. ナビゲーションバーの制作3. カルーセ...
この記事の例では、画像デジタル時計を実現するためのJSの具体的なコードを参考までに共有しています。具...
遅いログクエリ機能スロー ログ クエリの主な機能は、設定された時間しきい値を超える SQL ステート...
質問。モバイルショッピングモールシステムでは、ページの上部に検索ボックスがよく見られます。ブロガーは...
目次1. はじめに2. axiosインターセプターを使用してフロントエンドログを出力する1. はじめ...
1. MySQL 自己接続MySQL では、情報を照会するときに自分自身に接続 (自己接続) する必...
この記事の例では、参考までにvueタイムラインコンポーネントの具体的な実装コードを共有しています。具...
これは新しいバージョンではもう不可能なようで、推奨されません。そうでない場合は、ソフト リンクを直接...