1. Ressourcen und Konstruktion1.1 Grundlagen zur Creator-RessourcendateiBevor wir verstehen, wie die Engine Ressourcen analysiert und lädt, sollten wir zunächst die Regeln dieser Ressourcendateien (Bilder, Prefabs, Animationen usw.) verstehen. Unter dem Erstellerprojektverzeichnis befinden sich mehrere ressourcenbezogene Verzeichnisse:
Im Assets-Verzeichnis generiert der Ersteller für jede Ressourcendatei und jedes Ressourcenverzeichnis eine .meta-Datei mit demselben Namen. Die Metadatei ist eine JSON-Datei, die die Ressourcenversion, die UUID und verschiedene benutzerdefinierte Informationen (festgelegt im { "version": "1.2.7", "uuid": "a8accd2e-6622-4c31-8a1e-4db5f2b568b5", "optimizationPolicy": "AUTO", // Optimierungsstrategie für die Prefab-Erstellung "asyncLoadAssets": false, // Ob das Laden verzögert werden soll "readonly": false, "subMetas": {} } Im Importverzeichnis unter dem Bibliotheksverzeichnis wird der Ressourcendateiname in eine UUID umgewandelt und die ersten beiden Zeichen der UUID werden verwendet, um das Verzeichnis für die Speicherung zu gruppieren. Der Ersteller fügt die Zuordnungsbeziehung zwischen der UUID aller Ressourcen und dem Assets-Verzeichnis sowie den zuletzt aktualisierten Zeitstempel der Ressourcen und Metadaten in eine Datei mit dem Namen uuid-to-mtime.json ein, wie unten gezeigt. { "9836134e-b892-4283-b6b2-78b5acf3ed45": { "Vermögenswert": 1594351233259, "Meta": 1594351616611, "relativerPfad": "Effekte" }, "430eccbf-bf2c-4e6e-8c0c-884bbb487f32": { "Vermögenswert": 1594351233254, "Meta": 1594351616643, "relativer Pfad": "effects\__builtin-editor-gizmo-line.effect" }, ... } Im Vergleich zu den Ressourcen im Assets-Verzeichnis führen die Ressourcen im Bibliotheksverzeichnis die Informationen der Metadatei zusammen. Das Dateiverzeichnis wird nur in uuid-to-mtime.json aufgezeichnet und das Bibliotheksverzeichnis generiert nichts für das Verzeichnis. 1.2 RessourcenkonstruktionNachdem das Projekt erstellt wurde, werden die Ressourcen aus dem Bibliotheksverzeichnis in das Build-Verzeichnis der Build-Ausgabe verschoben. Grundsätzlich werden nur die am Build beteiligten Szenen und die Ressourcen im Ressourcenverzeichnis sowie die Ressourcen, auf die sie verweisen, exportiert. Skriptressourcen werden aus mehreren JS-Skripten zu einem JS zusammengeführt und verschiedene JSON-Dateien werden ebenfalls gemäß bestimmten Regeln verpackt. Wir können Bundles und Projekte in der Bundle-Konfigurationsschnittstelle und der Projekterstellungsschnittstelle einrichten 1.2.1 Bilder, Atlanten und automatische Atlanten
Für jedes in den Editor importierte Bild wird eine JSON-Datei generiert, die die Texturinformationen beschreibt, wie unten gezeigt. Standardmäßig werden alle Texture2D-JSON-Dateien im Projekt zu einer komprimiert. Wenn { "__type__": "cc.Texture2D", "Inhalt": "0,9729,9729,33071,33071,0,0,1" } Wenn Sie die Type-Eigenschaft der Textur auf Sprite setzen, generiert Creator auch automatisch eine JSON-Datei vom Typ „SpriteFrame“. 1.2.2 Prefab und Szene
Szenenressourcen sind Prefab-Ressourcen sehr ähnlich. Sie sind beide JSON-Dateien, die alle Knoten, Komponenten und andere Informationen beschreiben. Wenn 1.2.3 Regeln zum Zusammenführen von Ressourcendateien Wenn Creator mehrere Ressourcen in einer JSON-Datei zusammenführt, finden wir die Informationen
Nachfolgend sind die nach unterschiedlichen Regeln erstellten Dateien aufgeführt. Sie können sehen, dass die Anzahl der ohne Komprimierung generierten Dateien am größten ist. Es gibt mehr Nicht-Inline-Dateien als Inline-Dateien, aber Inline-Dateien können dazu führen, dass dieselbe Datei wiederholt eingefügt wird. Beispielsweise verweisen die beiden Prefabs e und f auf dasselbe Bild, und das SpriteFrame.json dieses Bilds wird wiederholt eingefügt. Wenn sie zu einem JSON zusammengeführt werden, wird nur eine Datei generiert.
Die Standardoption ist in den meisten Fällen eine gute Wahl. Wenn es sich um eine Webplattform handelt, wird empfohlen, 2. Asset Bundle verstehen und verwenden2.1 Erstellen eines BundlesAsset Bundle ist eine Ressourcenverwaltungslösung nach Creator 2.4. Einfach ausgedrückt plant es Ressourcen über Verzeichnisse, legt verschiedene Ressourcen je nach Projektanforderungen in unterschiedliche Verzeichnisse und konfiguriert die Verzeichnisse in Asset Bundles. Es kann die folgenden Rollen spielen:
Das Erstellen eines Asset-Bundles ist sehr einfach. Aktivieren Sie einfach das Kontrollkästchen im Das Dokument enthält keine detaillierte Beschreibung der Komprimierung. Mit Komprimierung ist hier keine Komprimierung wie Zip gemeint, sondern das Zusammenführen mehrerer JSON-Ressourcendateien zu einer einzigen über packAssets, um die io zu reduzieren. Es ist ganz einfach, die Optionen zu prüfen. Der eigentliche Schlüssel liegt in der Planung des Bundles. Die Planungsprinzipien bestehen darin, die Paketgröße zu reduzieren, den Start zu beschleunigen und Ressourcen wiederzuverwenden. Es empfiehlt sich, die Ressourcen entsprechend den Modulen des Spiels zu planen, etwa nach Unterspielen, Levelkopien oder Systemfunktionen. Bundle packt automatisch die Ressourcen im Ordner sowie die Ressourcen in anderen Ordnern, auf die der Ordner verweist (sofern diese Ressourcen nicht in anderen Bundles enthalten sind). Wenn wir Ressourcen nach Modulen planen, können mehrere Bundles problemlos eine Ressource gemeinsam nutzen. Sie können gemeinsame Ressourcen in ein Paket extrahieren oder einem Paket eine höhere Priorität zuweisen und Paketabhängigkeiten erstellen. Andernfalls werden diese Ressourcen gleichzeitig in mehreren Paketen platziert (wenn es sich um ein lokales Paket handelt, erhöht sich dadurch die Paketgröße). 2.2 Verwenden von Bundle
Die Verwendung von Bundle ist ebenfalls sehr einfach. Wenn es sich um eine Ressource im Ressourcenverzeichnis handelt, können Sie sie direkt mit cc.resources.load laden. cc.resources.load("Test-Assets/Prefab", Funktion (err, Prefab) { var neuerNode = cc.instantiate(prefab); cc.director.getScene().addChild(neuer Knoten); }); Wenn es sich um andere benutzerdefinierte Bundles handelt (lokale Bundles oder Remote-Bundles können unter Verwendung des Bundle-Namens geladen werden), können Sie cc.assetManager.loadBundle verwenden, um das Bundle zu laden, und dann das geladene Bundle-Objekt verwenden, um die Ressourcen im Bundle zu laden. Wenn das Bundle für native Plattformen als Remote-Paket konfiguriert ist, müssen Sie während des Erstellens die Adresse des Ressourcenservers in das Build-Release-Panel eingeben. cc.assetManager.loadBundle('01_graphics', (err, bundle) => { bündeln.laden('xxx'); }); Auf nativen oder Minispielplattformen können wir Bundles auch wie folgt verwenden:
// Bei der Wiederverwendung des Asset Bundles anderer Projekte cc.assetManager.loadBundle('https://othergame.com/remote/01_graphics', (err, bundle) => { bündeln.laden('xxx'); }); // Native Plattform cc.assetManager.loadBundle(jsb.fileUtils.getWritablePath() + '/pathToBundle/bundleName', (err, bundle) => { // ... }); // WeChat Mini-Spieleplattform cc.assetManager.loadBundle(wx.env.USER_DATA_PATH + '/pathToBundle/bundleName', (err, bundle) => { // ... }); Sonstige Hinweise:
3. Analyse des neuen RessourcenrahmensDer neue Framework-Code nach der Refaktorierung von v2.4 ist prägnanter und klarer. Wir können das gesamte Ressourcen-Framework zunächst aus einer Makroperspektive verstehen. Die Ressourcen-Pipeline ist der Kern des gesamten Frameworks. Sie standardisiert den gesamten Ressourcen-Ladeprozess und unterstützt die Anpassung der Pipeline. Öffentliche Dokumente
Bündeln
Rohrleitungsteil CCAssetManager.js verwaltet die Pipeline und bietet eine einheitliche Lade- und Entladeschnittstelle Pipeline-Rahmenwerk
Vorbehandlungspipeline
Pipeline herunterladen
Analysepipeline
andere
3.1 VerladeleitungCreator verwendet Pipelines, um den gesamten Ressourcenladevorgang abzuwickeln. Der Vorteil dabei ist, dass der Ressourcenverarbeitungsprozess entkoppelt und jeder Schritt in eine separate Pipeline aufgeteilt wird. Die Pipeline kann problemlos wiederverwendet und kombiniert werden, und es ist für uns bequem, den gesamten Ladevorgang anzupassen. Wir können einige unserer eigenen Pipelines erstellen und sie der Pipeline hinzufügen, z. B. die Ressourcenverschlüsselung. AssetManager verfügt über drei integrierte Pipelines: die normale Ladepipeline, die Vorladepipeline und die Ressourcenpfadkonvertierungspipeline. Die letzte Pipeline bedient die ersten beiden Pipelines. // Normales Laden this.pipeline = pipeline.append(preprocess).append(load); // Dies vorladen.fetchPipeline = fetchPipeline.append(preprocess).append(fetch); // Ressourcenpfad konvertieren this.transformPipeline = transformPipeline.append(parse).append(combine); 3.1.1 Starten Sie die Ladepipeline [Ladeschnittstelle] Als nächstes schauen wir uns an, wie eine allgemeine Ressource geladen wird, beispielsweise das einfachste cc.resource.load. In der Methode bundle.load wird cc.assetManager.loadAny aufgerufen. In der Methode loadAny wird eine neue Aufgabe erstellt und die asynchrone Methode der normalen Ladepipeline wird aufgerufen, um die Aufgabe auszuführen. Beachten Sie, dass der zu ladende Ressourcenpfad in task.input platziert wird und options ein Objekt ist, das Felder wie type, bundle und __requestType__ enthält. // Die Lademethode der Bundle-Klasse load (paths, type, onProgress, onComplete) { var { Typ, bei Fortschritt, bei Abschluss } = parseLoadResArgs(Typ, bei Fortschritt, bei Abschluss); cc.assetManager.loadAny(Pfade, { __requestType__: RequestType.PATH, Typ: Typ, Bundle: this.name }, bei Fortschritt, bei Abschluss); }, // loadAny-Methode des AssetManagers loadAny (Anfragen, Optionen, bei Fortschritt, bei Abschluss) { var { Optionen, bei Fortschritt, bei Abschluss } = parseParameters(Optionen, bei Fortschritt, bei Abschluss); Optionen.Voreinstellung = Optionen.Voreinstellung || „Standard“; let task = neue Task({Eingabe: Anfragen, bei Fortschritt, bei Abschluss: asyncify(bei Abschluss), Optionen}); pipeline.async(Aufgabe); }, Die Pipeline besteht aus zwei Teilen: Vorverarbeitung und Laden. preprocess besteht aus den folgenden Pipelines: preprocess, transformPipeline { parse, combine }. Preprocess erstellt eigentlich nur eine Unteraufgabe, die dann von transformPipeline ausgeführt wird. Beim Laden einer normalen Ressource sind die Eingaben und Optionen der Unteraufgabe dieselben wie bei der übergeordneten Aufgabe. let subTask = Task.create({Eingabe: task.input, Optionen: subOptions}); task.output = task.source = transformPipeline.sync(Untertask); 3.1.2 transformPipeline-Pipeline [Vorbereitungsphase] transformPipeline besteht aus zwei Pipelines, parse und combine. Die Aufgabe von parse besteht darin, für jede zu ladende Ressource ein RequestItem-Objekt zu generieren und deren Ressourceninformationen (AssetInfo, UUID, Konfiguration usw.) zu initialisieren: Konvertieren Sie zunächst die Eingabe in ein Array für die Durchquerung. Wenn Sie Ressourcen in Batches laden, generiert jedes Add-In ein RequestItem Wenn das Eingabeelement ein Objekt ist, kopieren Sie zuerst die Optionen in das Element (tatsächlich ist jedes Element ein Objekt. Wenn es eine Zeichenfolge ist, wird sie im ersten Schritt in ein Objekt konvertiert).
Funktion analysieren (Aufgabe) { //Eingabe in ein Array umwandeln var input = task.input, options = task.options; Eingabe = Array.isArray(Eingabe)? Eingabe: [Eingabe]; task.ausgabe = []; für (var i = 0; i < Eingabelänge; i ++ ) { var Element = Eingabe[i]; var out = RequestItem.create(); wenn (Typ des Elements === 'Zeichenfolge') { //Erstellen Sie zuerst das Objekt Element = Objekt.erstellen(null); Element[Optionen.__Anforderungstyp__ || Anforderungstyp.UUID] = Eingabe[i]; } wenn (Typ des Elements === 'Objekt') { // Lokale Optionen überlappen globale Optionen //Kopiere die Eigenschaften der Optionen in das Element. Das Add-on kopiert die Eigenschaften, die in den Optionen, aber nicht im Element sind. cc.js.addon(Element, Optionen); wenn (Artikel.Vorgabe) { cc.js.addon(Element, cc.assetManager.presets[Element.preset]); } für (var Schlüssel im Element) { Schalter (Schlüssel) { // Ressource vom Typ UUID, detaillierte Informationen zur Ressource aus dem Bundle-Fall RequestType.UUID abrufen: var uuid = out.uuid = decodeUuid(item.uuid); wenn (bundles.has(item.bundle)) { var config = bundles.get(item.bundle)._config; var info = config.getAssetInfo(uuid); wenn (info && info.weiterleitung) { if (!bundles.has(info.redirect)) throw new Error(`Bitte laden Sie zuerst das Bundle ${info.redirect}`); config = bundles.get(info.redirect)._config; info = config.getAssetInfo(uuid); } out.config = Konfiguration; out.info = Info; } out.ext = Element.ext || '.json'; brechen; Fall '__requestType__': Fall 'ext': Fall 'Bündel': Fall 'Vorgabe': Fall 'Typ': Unterbrechung; Fall RequestType.DIR: // Nach dem Entpacken dynamisch an das Ende der Eingabeliste anfügen. Nachfolgende Schleifen werden diese Ressourcen automatisch analysieren, wenn (bundles.has(item.bundle)) { var infos = []; bundles.get(item.bundle)._config.getDirWithPath(item.dir, item.type, infos); für (sei i = 0, l = infos.Länge; i < l; i++) { var info = infos[i]; input.push({uuid: info.uuid, __isNative__: false, ext: '.json', bundle: item.bundle}); } } raus.recyceln(); aus = null; brechen; Fall RequestType.PATH: // Eine Ressource vom Typ PATH ruft detaillierte Informationen über die Ressource basierend auf dem Pfad und Typ ab, wenn (bundles.has(item.bundle)) { var config = bundles.get(item.bundle)._config; var info = config.getInfoWithPath(item.path, item.type); wenn (info && info.weiterleitung) { if (!bundles.has(info.redirect)) throw new Error(`Sie müssen zuerst das Bundle ${info.redirect} laden`); config = bundles.get(info.redirect)._config; info = config.getAssetInfo(info.uuid); } wenn (!info) { raus.recyceln(); neuen Fehler werfen („Bundle ${item.bundle} enthält nicht ${item.path}“); } out.config = Konfiguration; out.uuid = info.uuid; out.info = Info; } out.ext = Element.ext || '.json'; brechen; Fall RequestType.SCENE: // Szenentyp, rufe getSceneInfo aus der Konfiguration im Paket auf, um detaillierte Informationen über die Szene zu erhalten, wenn (bundles.has(item.bundle)) { var config = bundles.get(item.bundle)._config; var info = config.getSceneInfo(item.scene); wenn (info && info.weiterleitung) { if (!bundles.has(info.redirect)) throw new Error(`Sie müssen zuerst das Bundle ${info.redirect} laden`); config = bundles.get(info.redirect)._config; info = config.getAssetInfo(info.uuid); } wenn (!info) { raus.recyceln(); throw new Error(`Bundle ${config.name} enthält nicht Szene ${item.scene}`); } out.config = Konfiguration; out.uuid = info.uuid; out.info = Info; } brechen; Fall '__isNative__': out.isNative = Element.__isNative__; brechen; Fall RequestType.URL: out.url = Artikel.url; out.uuid = Artikel.uuid || Artikel.URL; out.ext = item.ext || cc.Pfad.extname(item.url); out.isNative = item.__isNative__ !== undefiniert ? item.__isNative__ : wahr; brechen; Standard: out.options[Schlüssel] = item[Schlüssel]; } wenn (!out) abbrechen; } } wenn (!out) weitermachen; Aufgabe.Ausgabe.Push(raus); if (!out.uuid && !out.url) throw new Error('unbekannte Eingabe:' + item.toString()); } gibt null zurück; } Die anfänglichen Informationen von RequestItem werden vom Bundle-Objekt abgefragt, und die Bundle-Informationen werden aus der mit dem Bundle gelieferten Datei config.json initialisiert. Wenn das Bundle verpackt wird, werden die Ressourceninformationen im Bundle in config.json geschrieben. Nach der Verarbeitung durch die Parse-Methode erhalten wir eine Reihe von RequestItems, und viele RequestItems enthalten Informationen wie AssetInfo und UUID. Die Combine-Methode erstellt für jedes RequestItem einen echten Ladepfad, und dieser Ladepfad wird schließlich in item.url konvertiert. Funktion kombinieren (Aufgabe) { var Eingabe = Aufgabe.Ausgabe = Aufgabe.Eingabe; für (var i = 0; i < Eingabelänge; i++) { var Element = Eingabe[i]; // Wenn das Element bereits eine URL enthält, überspringen Sie diesen Schritt und verwenden Sie direkt die URL des Elements. wenn (item.url) fortfahren; var url = '', Basis = ''; var config = item.config; // Bestimmen Sie das Verzeichnispräfix, wenn (item.isNative) { Basis = (Konfiguration und Konfiguration.nativeBase)? (Konfiguration.Basis + Konfiguration.nativeBase): cc.assetManager.generalNativeBase; } anders { Basis = (Konfiguration und Konfiguration.ImportBase)? (Konfiguration.Basis + Konfiguration.ImportBase): cc.assetManager.generalImportBase; } lass uuid = item.uuid; var ver = ''; wenn (Artikelinfo) { wenn (item.isNative) { ver = item.info.nativeVer ? ('.' + item.info.nativeVer) : ''; } anders { ver = item.info.ver ? ('.' + item.info.ver) : ''; } } // Verketten Sie die endgültige URL // hässlicher Hack, WeChat unterstützt das Laden von Schriftarten wie „myfont.dw213.ttf“ nicht. Hängen Sie also einen Hash an das Verzeichnis an wenn (item.ext === '.ttf') { url = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}/${item.options.__nativeName__}`; } anders { URL = `${base}/${uuid.slice(0, 2)}/${uuid}${ver}${item.ext}`; } Artikel.URL = URL; } gibt null zurück; } 3.1.3 Ladepipeline [Ladevorgang] Die Lademethode ist sehr einfach. Sie erstellt im Grunde nur eine neue Aufgabe und führt jede Unteraufgabe in loadOneAssetPipeline aus. Funktion laden (Aufgabe, erledigt) { wenn (!task.fortschritt) { task.progress = {Fertig: 0, Gesamt: task.input.length}; } var Optionen = Task.Optionen, Fortschritt = Task.Fortschritt; Optionen.__exclude__ = Optionen.__exclude__ || Objekt.erstellen(null); task.ausgabe = []; fürJedes(task.input, Funktion (item, cb) { // Für jedes Eingabeelement eine Unteraufgabe erstellen und zur Ausführung loadOneAssetPipeline zuweisen let subTask = Task.create({ Eingabe: Artikel, beim Fortschritt: task.onProgress, Optionen, Fortschritt, beiAbgeschlossen: Funktion (Fehler, Element) { wenn (err && !task.isFinish && !cc.assetManager.force) fertig(err); Aufgabe.Ausgabe.Push(Element); subTask.recycle(); cb(); } }); // Unteraufgaben ausführen. loadOneAssetPipeline besteht aus Fetch und Parse. loadOneAssetPipeline.async(subTask); }, Funktion () { // Nachdem jede Eingabe ausgeführt wurde, wird die Funktion zuletzt ausgeführt options.__exclude__ = null; wenn (task.isFinish) { löschen (Aufgabe, wahr); returniere task.dispatch('Fehler'); } Sammeln Sie Assets (Aufgabe). löschen (Aufgabe, wahr); Erledigt(); }); } Wie der Funktionsname schon sagt, ist loadOneAssetPipeline eine Pipeline zum Laden eines Assets. Sie ist in zwei Schritte unterteilt: Abrufen und Analysieren: Die Fetch-Methode wird zum Herunterladen von Ressourcendateien verwendet. PackManager ist für die Download-Implementierung verantwortlich. Fetch legt die heruntergeladenen Dateidaten in item.file ab. Mit der Parse-Methode wird die geladene Ressourcendatei in ein Ressourcenobjekt konvertiert, das wir verwenden können. Rufen Sie für native Ressourcen zum Parsen parser.parse auf. Diese Methode ruft je nach Ressourcentyp unterschiedliche Parsingmethoden auf.
Für andere Ressourcen Wenn die UUID in var loadOneAssetPipeline = neue Pipeline('loadOneAsset', [ Funktion fetch (Aufgabe, erledigt) { var item = task.ausgabe = task.eingabe; var { Optionen, isNative, UUID, Datei } = Element; var { neu laden } = Optionen; // Wenn die Ressource in Assets geladen wurde, vervollständigen Sie sie direkt, wenn (file || (!reload && !isNative && asset.has(uuid))) return done(); // Laden Sie die Datei herunter. Dies ist ein asynchroner Prozess. Nachdem die Datei heruntergeladen wurde, wird sie in item.file abgelegt und die fertige Treiberpipeline wird ausgeführt packManager.load(item, task.options, function (err, data) { wenn (Fehler) { wenn (cc.assetManager.force) { err = null; } anders { cc.error(Fehlernachricht, Fehlerstapel); } Daten = null; } item.file = Daten; fertig (Fehler); }); }, // Der Prozess der Konvertierung von Ressourcendateien in Ressourcenobjekte Funktion parse (task, done) { var item = task.output = task.input, Fortschritt = task.fortschritt, ausschließen = task.optionen.__exclude__; var { id, Datei, Optionen } = Element; wenn (item.isNative) { // Rufen Sie für native Ressourcen parser.parse auf, um sie zu verarbeiten, fügen Sie die verarbeiteten Ressourcen in item.content ein und beenden Sie den Prozess parser.parse(id, file, item.ext, options, function (err, asset) { wenn (Fehler) { wenn (!cc.assetManager.force) { cc.error(Fehlernachricht, Fehlerstapel); Rückgabe erledigt (Fehler); } } Artikel.Inhalt = Vermögenswert; task.dispatch('Fortschritt', ++Fortschritt.fertig, Fortschritt.gesamt, Element); Dateien.entfernen(id); analysiert.entfernen(id); Erledigt(); }); } anders { var { uuid } = Artikel; // Nicht native Ressourcen, wenn in task.options.__exclude__, direkt beenden, wenn (uuid in exclude) { var { Fertigstellen, Inhalt, Fehler, Rückrufe} = Ausschließen[uuid]; task.dispatch('Fortschritt', ++Fortschritt.fertig, Fortschritt.gesamt, Element); wenn (fertig || checkCircleReference(uuid, uuid, ausschließen) ) { Inhalt && Inhalt.addRef(); item.content = Inhalt; fertig (Fehler); } anders { callbacks.push({ erledigt, Element }); } } anders { // Wenn es nicht neu geladen wird und das Asset die UUID enthält wenn (!optionen.reload && asset.has(uuid)) { var asset = asset.get(uuid); // Wenn options.__asyncLoadAssets__ aktiviert ist oder asset.__asyncLoadAssets__ false ist, wird der Prozess beendet, ohne Abhängigkeiten zu laden, if (options.__asyncLoadAssets__ || !asset.__asyncLoadAssets__) { Element.Inhalt = Asset.addRef(); task.dispatch('Fortschritt', ++Fortschritt.fertig, Fortschritt.gesamt, Element); Erledigt(); } anders { loadDepends(Aufgabe, Vermögenswert, erledigt, falsch); } } anders { // Wenn es neu geladen wird oder nicht in den Assets ist, analysiere es und lade die Abhängigkeiten.parse(id, file, 'import', options, function (err, asset) { wenn (Fehler) { wenn (cc.assetManager.force) { err = null; } anders { cc.error(Fehlernachricht, Fehlerstapel); } Rückgabe erledigt (Fehler); } asset._uuid = uuid; loadDepends(Aufgabe, Vermögenswert, erledigt, wahr); }); } } } } ]); 3.2 Dateidownload Der Ersteller verwendet
// Implementierung von packManager.load load (item, options, onComplete) { // Wenn die Ressource nicht gepackt ist, rufen Sie downloader.download direkt auf, um sie herunterzuladen (der Download beurteilt auch, ob sie heruntergeladen wurde oder geladen wird). wenn (item.isNative || !item.info || !item.info.packs) return downloader.download(item.id, item.url, item.ext, item.options, beiAbgeschlossen); // Wenn die Datei heruntergeladen wurde, direkt zurückkehren, if (files.has(item.id)) return onComplete(null, files.get(item.id)); var packs = item.info.packs; // Wenn das Paket bereits geladen wird, fügen Sie den Rückruf zur Warteschlange „_loading“ hinzu und lösen Sie den Rückruf aus, nachdem der Ladevorgang abgeschlossen ist. var pack = packs.find(isLoading); Wenn (pack) return _loading.get(pack.uuid).push({ bei Abschluss, id: item.id }); // Laden Sie ein neues Paket herunter Packung = Packungen[0]; _loading.add(pack.uuid, [{ bei Abschluss, id: item.id }]); let url = cc.assetManager._transform(pack.uuid, {ext: pack.ext, bundle: item.config.name}); // Das Paket herunterladen und entpacken, downloader.download(pack.uuid, url, pack.ext, item.options, function (err, data) { Dateien.entfernen(pack.uuid); wenn (Fehler) { cc.error(Fehlernachricht, Fehlerstapel); } // Paket entpacken, die interne Implementierung umfasst zwei Arten des Entpackens, eine für die Segmentierung und Entpackung von JSON-Arrays wie Prefab und Atlas und die andere für den Inhalt von Texture2D packManager.unpack(pack.packs, data, pack.ext, item.options, function (err, result) { wenn (!err) { für (var id im Ergebnis) { Dateien.Hinzufügen(ID, Ergebnis[ID]); } } var Rückrufe = _loading.remove(pack.uuid); für (var i = 0, l = callbacks.length; i < l; i++) { var cb = Rückrufe[i]; wenn (Fehler) { cb.onComplete(er); weitermachen; } var Daten = Ergebnis[cb.id]; wenn (!data) { cb.onComplete(neuer Fehler('Daten können nicht aus Paket abgerufen werden')); } anders { cb.onComplete(null, Daten); } } }); }); } 3.2.1 Herunterladen von der Web-Plattform Die Download-Implementierung der Webplattform ist wie folgt:
Download (ID, URL, Typ, Optionen, bei Abschluss) { // Den entsprechenden Typ des Download-Callbacks in Downloadern abrufen let func = Downloader[Typ] || Downloader['Standard']; lass self = dies; // Wiederholte Downloads vermeiden let file, downloadCallbacks; wenn (Datei = Dateien.get(id)) { beiAbgeschlossen(null, Datei); } // Beim Herunterladen zur Warteschlange hinzufügen, sonst wenn (downloadCallbacks = _downloading.get(id)) { downloadCallbacks.push(beiAbgeschlossen); für (lass i = 0, l = _Warteschlangenlänge; i < l; i++) { var item = _queue[i]; wenn (item.id === id) { var Priorität = Optionen.Priorität || 0; if (item.priority < priority) { item.priority = Priorität; _queueDirty = wahr; } zurückkehren; } } } anders { // Herunterladen und Wiederholungsversuche für fehlgeschlagene Downloads einrichten var maxRetryCount = options.maxRetryCount || this.maxRetryCount; var maxConcurrency = Optionen.maxConcurrency || diese.maxConcurrency; var maxRequestsPerFrame = Optionen.maxRequestsPerFrame || diese.maxRequestsPerFrame; Funktionsprozess (Index, Rückruf) { wenn (Index === 0) { _downloading.add(id, [beiAbgeschlossen]); } if (! self.Limited) return func (urlAppendTimestamp (URL), Optionen, Rückruf); UpdateTime (); Funktion invoke () { func (urlAppendTimestamp (URL), Optionen, Funktion () { // Beim Herunterladen _totalnum aktualisieren _totalnum--; if (! _checknextPeriod && _queue.length> 0) { CallInnextTick (Handle, MaxConcurrency, MaxRequestSPerFrame); _ChecknextPeriod = true; } Callback.Apply (dies, Argumente); }); } if (_totalnum <maxconcurrency && _totalnumthisperiode <maxRequestSPerFrame) { aufrufen(); _totalnum ++; _totalnumthisperioD ++; } anders { // Wenn die Anzahl der Anfragen Einschränkung erreicht, zwischen dem Rest zwischenstrichen _queue.push ({id, priorität: options.priority || 0, aufgerufen}); _queuedirty = true; if (! _checknextPeriod && _totalnum <maxconcurrency) { CallInnextTick (Handle, MaxConcurrency, MaxRequestSPerFrame); _ChecknextPeriod = true; } } } // Nach Abschluss der Datei den Dateien -Cache hinzufügen, sie aus der Warteschlange _download entfernen und Rückrufe ausführen. Funktionsfinale (Err, Ergebnis) { if (! err) Dateien.add (ID, Ergebnis); var callbacks = _downloading.remove (id); für (sei i = 0, l = callbacks.length; i <l; i ++) { Rückrufe [i] (err, Ergebnis); } } Wiederholung (Prozess, MaxRetryCount, this.retryInterval, Finale); } } Downloader ist eine Karte, die die Download -Methoden, die verschiedenen Ressourcentypen entsprechen, auf der Webplattform entsprechen. Bildklasse DownloadImage
Dateiklasse, die in Binärdateien, JSON -Dateien und Textdateien unterteilt werden kann
Font Class Loadfont baut CSS -Stil und gibt die URL zum Herunterladen an Sound Class Downloadaudio
Der Videoklassen -DownloadVideo -Web -Client kehrt direkt zurück Das Skript -DownloadScript erstellt ein HTML -Skriptelement und gibt sein SRC -Attribut zum Herunterladen und Ausführen an Bündel DownloadBundle lädt gleichzeitig das JSON und die Skripte des Bundles herunter DownloadFile verwendet XMLHTTPrequest, um Dateien herunterzuladen. Funktion DownloadFile (URL, Optionen, Onprogress, OnComplete) { var {Optionen, onProgress, onComplete} = Parseparameter (Optionen, On -Progress, OnComplete); var xhr = neu xmlhttprequest (), errinfo = 'Download fehlgeschlagen:' + url + ', Status:'; xhr.open('GET', URL, wahr); if (options.responsetype! if (options.withcredentials! == undefiniert) xhr.withcredentials = options if (options.mimetype! if (options.timeout! == undefined) xhr.timeout = options.timeout; if (options.Header) { für (var header in options.Header) { xhr.setRequestHeader (Header, Optionen.Header [Header]); } } xhr.onload = Funktion () { if (xhr.status === 200 || xhr.status === 0) { onComplete && onComplete (null, xhr.response); } anders { OnComplete && onComplete (neuer Fehler (errinfo + xhr.status + '(keine Antwort)')); } }; if (onProgress) { xhr.onprogress = function (e) { if (e.LengthComputable) { onProgress (e.Laded, e.total); } }; } xhr.onError = function () { OnComplete && onComplete (neuer Fehler (Errinfo + xhr.status + '(error)')); }; xhr.ontimeout = function () { OnComplete && onComplete (neuer Fehler (errinfo + xhr.status + '(Zeitausgang)')); }; xhr.onabort = function () { OnComplete && onComplete (neuer Fehler (errinfo + xhr.status + '(abort)')); }; xhr.send (null); Rückgabe xhr; } 3.2.2 Native Plattform Download Die in Engine-bezogenen Dateien der nativen Plattform finden Sie im Downloader.register ({{ // JS '.js': downloadScript, '.jsc': downloadScript, // Bilder '.png': downloadasset, '.jpg': downloadasset, ... }); Auf nativen Plattformen werden Methoden wie DownloadAset Download anrufen, um Ressourcen herunterzuladen. Es müssen nur Netzwerkressourcen heruntergeladen werden, die nicht heruntergeladen wurden. Dateien, die nicht heruntergeladen werden müssen, werden direkt gelesen, wo die Datei analysiert wird. // Func übergibt die Verarbeitung nach Abschluss des Downloads. // Wenn Sie eine JSON -Ressource herunterladen möchten, ist der übergebene Func nicht mehr, was bedeutet, direkt die OnComplete -Methode -Funktion Download (URL, Func, Optionen, OnFileProgress, OnComplete) { var result = transformurl (URL, Optionen); // Wenn es sich um eine lokale Datei handelt, zeigen Sie direkt auf Func if (result.inlocal) { func (result.url, option, onComplete); } // Wenn im Cache die letzte gebrauchte Zeit (LRU) der Ressource aktualisieren, aktualisieren Sie sonst if (result.incache) { CACHEMANATER.UPDATELASTTIME (URL) func (result.Url, Optionen, Funktion (Err, Daten) { wenn (Fehler) { CacheManager.Removecache (URL); } onComplete (err, data); }); } anders { // Für Netzwerkressourcen, die nicht heruntergeladen wurden, rufen Sie DownloadFile an, um var time = date.now () herunterzuladen; var StoragePath = ''; if (Optionen .__ CacheBundleroot__) { StoragePath = `$ {CACHEMANAGER.CacheDir}/$ {Optionen .__ CacheBundleroot __}/$ {time} $ {Suffix ++} $ {cc.path.extName (url)}`; } anders { StoragePath = `$ {CACHEMANAGER.CacheDir}/$ {Zeit} $ {Suffix ++} $ {cc.path.extname (url)}`; } // Download und Cache mit DownloadFile DownloadFile (URL, StoragePath, Optionen.Header, OnFileProgress, Funktion (Err, Path) { wenn (Fehler) { onComplete (err, null); zurückkehren; } Func (Pfad, Optionen, Funktion (Err, Daten) { wenn (!err) { CacheManager.CacheFile (URL, StoragePath, Optionen .__ CacheBundleroot__); } onComplete (err, data); }); }); } } Funktionstransformurl (URL, Optionen) { var inlocal = false; var Incache = false; // Überprüfen Sie, ob es sich um eine URL nach regulärem Ausdruck handelt if (regex.test (url)) { if (options.reload) { return {url}; } anders { // Überprüfen Sie, ob es sich im Cache befindet (lokaler Festplatten -Cache) var cache = cacheManager.cachedFiles.get (URL); if (cache) { Incache = true; url = cache.url; } } } anders { inlocal = true; } return {url, inlocal, Incache}; } DownloadFile ruft den JSB_Downloader der nativen Plattform an, um Ressourcen herunterzuladen und auf der lokalen Festplatte zu speichern DownloadFile (Remoteurl, Filepath, Header, Onprogress, OnComplete) { downloading.add (remoteurl, {onProgress, onComplete}); var sporingPath = filepath; if (! StoragePath) StoragePath = tempdir + '/' + Performance.Now () + cc.Path.extName (remoteurl); jsb_downloader.createdownloadFileTask (Remoteurl, StoragePath, Header); }, 3.3 Datei ParsenIn LoadoneasSetPipeline werden Ressourcen von zwei Pipelines verarbeitet: Fetch und Parse ist für das Herunterladen verantwortlich, während Parse für die Parsen von Ressourcen und die Instanziierung von Ressourcenobjekten verantwortlich ist. In der Parse -Methode wird Parser.Parse aufgerufen, um den Dateiinhalt zu übergeben, sie in das entsprechende Anlageobjekt zu analysieren und zurückzugeben. 3.3.1 Webplattformanalyse Die Hauptfunktion von Parser.Parse auf der Webplattform besteht darin, die analysierten Dateien zu verwalten, eine Liste der analysierten und analysierten Dateien zu verwalten und wiederholtes Parsen zu vermeiden. Gleichzeitig wird eine Liste von Rückrufen nach Abschluss der Parsen beibehalten und die tatsächliche Parsingmethode befindet sich im Parsers -Array. analysieren (ID, Datei, Typ, Optionen, OnComplete) { Lassen Sie ParseDasset, Parsen, Parsehandler; if (parseDasset = parsed.get (id)) { OnComplete (NULL, PARSEDASSET); } else if (parsing = _parsing.get (id)) { Parsing.push (onComplete); } else if (ParseHandler = Parsers [Typ]) { _Parsing.add (id, [onComplete]); ParseHandler (Datei, Optionen, Funktion (Err, Daten) { wenn (Fehler) { Dateien.Remove (ID); } sonst if (! isscene (Daten)) { Parsed.add (ID, Daten); } lass callbacks = _parsing.remove (id); für (sei i = 0, l = callbacks.length; i <l; i ++) { Rückrufe [i] (Err, Daten); } }); } anders { onComplete (null, Datei); } } Parsers kartieren die Parsingmethoden verschiedener Arten von Dateien. HINWEIS: In der Parseemport -Methode wird die Deserialisierungsmethode die Ressourcenabhängigkeiten in Asset einbringt. Die Abhängigkeit von wo ein Array ist. Beispielsweise hat eine Prefab -Ressource zwei Knoten, beide auf dieselbe Ressource. // Bildformate zu Parsen -Methoden zuordnen 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, // Audio '.mp3': Parser.Parseaudio, '.ogg': Parser.Parseaudio, '.wav': Parser.Parseaudio, '.m4a': Parser.Parseaudio, // plist '.plist': Parser.Parseplist, 'Import': Parser.Parseymport }; // Das Bild wird nicht in ein Asset -Objekt analysiert, sondern in das entsprechende Bildobjekt Parseimage (Datei, Optionen, OnComplete) { if (fähigkeiten.imagebitmap && Dateiinstanz von Blob) { lass ImageOptions = {}; ImageOptions.imageOrientation = Optionen .__ Flipy__? ImageOptions.Premultiplyalpha = Optionen .__ Premultiplyalpha__? createImageBitMap (Datei, Bildoptionen) .then (Funktion (Ergebnis) { result.flipy = !! Optionen .__ Flipy__; result.premultiplyalpha = !! Optionen .__ Premultiplyalpha__; OnComplete && onComplete (null, Ergebnis); }, function (err) { onComplete && onComplete (err, null); }); } anders { OnComplete && onComplete (null, Datei); } }, // Analyse des Vermögensobjekts wird durch Deserialize implementiert. PARSEIMPORT (Datei, Optionen, OnComplete) { if (! file) return onComplete && onComplete (neuer Fehler ('json ist leer')); var result, err = null; versuchen { result = Deserialize (Datei, Optionen); } fangen (e) { err = e; } onComplete && onComplete (err, Ergebnis); }, 3.3.2 native Plattformanalyse Auf der nativen Plattform registriert JSB-loader.js die Parsing-Methoden verschiedener Ressourcen: Parser.register ({{{ '.png': downloader.downloaddomage,, '. Binär': ParsearrayBuffer, '.txt': paretExt, '.plist': Parseplist, '.font': lastfont, '.Exportjson': Parsejson, ... }); Die Bild -Parsen -Methode ist tatsächlich Downloader.downloaddomage? Nach der Verfolgung der nativen Plattform und der Debugging wird diese Methode in der Tat ein Bildobjekt erstellt und SRC wird angegeben, um das Bild zu laden. Durch die JSON -Datei, die Texture2D entspricht, hat der Ersteller das texture2d -Asset -Objekt bereits erstellt, bevor die native Textur geladen wird. var texture2d = cc.class ({{ Name: 'cc.texture2d', Erweitert: Erfordernst ('../ Assets/Ccasset'), Mixins: [EventTarget], Eigenschaften: _nativeasSet: { erhalten () { // Vielleicht zurück zum Pool in WebGL zurückgekehrt Gibt dies zurück._image; }, set (Daten) { if (data._data) { this.initwithData (data._data, this._format, data.width, data.height); } anders { this.initwithelement (Daten); } }, Override: True }, In Bezug auf die Implementierungen von Parsejson, ParsetExt, ParsearrayBuffer usw. rufen sie einfach das Dateisystem auf, um die Datei zu lesen. Was ist mit einigen Ressourcen, die weiter analysieren müssen, bevor sie nach dem Erhalten des Dateiinhalts verwendet werden können? Beispielsweise stützen sich Modelle, Skelette und andere Ressourcen auf binäre Modelldaten. Das ist richtig, genau wie die oben genannte Textur2d werden sie alle in der entsprechenden Asset -Ressource gesetzt. // In jsb-loader.js Dateifunktion ParsetExt (URL, Optionen, OnComplete) { ReadText (URL, OnComplete); } Funktion ParsearrayBuffer (URL, Optionen, OnComplete) { ReadArrayBuffer (URL, OnComplete); } Funktion Parsejson (URL, Optionen, OnComplete) { Readjson (URL, OnComplete); } // In JSB-FS-UTILS.JS-Datei 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; wenn (!err) { versuchen { out = json.Parse (Text); } fangen (e) { cc.warn ('Read JSON fehlgeschlagen:' + e.message); Err = neuer Fehler (E.Message); } } OnComplete && onComplete (err, out); }); }, Wie werden Ressourcen wie Atlasen und Präfrages initialisiert? Der Schöpfer verwendet weiterhin die Parseeimport -Methode zum Parsen, da der Typ, der diesen Ressourcen entspricht, 3.4 AbhängigkeitslastDer Schöpfer unterteilt Ressourcen in zwei Kategorien, gemeinsame Ressourcen und häufige Ressourcen. Zu den nativen Ressourcen gehören Texturen, Musik, Schriftarten und andere Dateien in verschiedenen Formaten. Im LOADDEPENDS schafft eine Subtask, um abhängige Ressourcen zu laden und die Pipeline zu rufen, um das Laden durchzuführen.
// Laden Sie die Abhängigkeiten der angegebenen Asset -Funktionslastdarsteller (Aufgabe, Vermögenswert, fertiggestellt, init) {laden Sie. var item = task.input, progress = task.progress; var {uUid, id, Optionen, config} = item; var {__asyncloadassets__, CacheasSet} = Optionen; var hängt ab = []; // Erhöhen Sie die Referenzzahl, um zu vermeiden, dass Ressourcen während des Ladens des Ladens von Abhängigkeiten freigegeben werden, und rufen Sie GetDepends an, um abhängige Ressourcen asset.addref && asset.addref () zu erhalten. getDepends (UUID, Asset, Object.create (NULL), hängt davon ab, falsch, __asyncloadassets__, config); Task.dispatch ('Fortschritt', ++ progresh. var repepeItem = task.options .__ Schließen Sie __ [uUid] = {Inhalt: Asset, Finish: False, Callbacks: [{Done, Element}]}; lass subtask = task.create ({{ Eingabe: Kommt darauf ab, Optionen: task.options, On -Progress: Task.onprogress, OnError: Task.Prototype.recycle, Fortschritt, onComplete: function (err) { // Rückruf Nachdem alle Abhängigkeiten Asset geladen wurden. Asset .__ Asyncloadassets__ = __asyncloadassets __; repepitem.finish = true; repepitem.err = err; wenn (!err) { var assets = array.isArray (subtask.output)? // Erstellen Sie eine Karte, um die Zuordnung von UUID zu Asset var map = Object.create (null) aufzuzeichnen. für (sei i = 0, l = assets.length; i <l; i ++) { var abhängig = assets [i]; abhängig && (map [abhängige Instanz von cc.asset? abhängig._uuid + '@import': uUid + '@native'] = abhängigset); } // Call setProperties zum Festlegen der entsprechenden abhängigen Ressourcen auf die Mitgliedsvariablen von assetif (! Init) { if (asset .__ nativedEpend__ &&! asset._nativeasset) { var Misseraset = setProperties (UUID, Asset, MAP); if (! fehlende asset) { versuchen { asset.onload && asset.onload (); } fangen (e) { CC.Error (E.Message, E.Stack); } } } } anders { var Misseraset = setProperties (UUID, Asset, MAP); if (! fehlende asset) { versuchen { asset.onload && asset.onload (); } fangen (e) { CC.Error (E.Message, E.Stack); } } Dateien.Remove (ID); analysiert.remove (id); Cache (UUID, Asset, CacheasSet! == undefiniert? } subtask.recycle (); } // Dieser Wiederholung kann von vielen Orten geladen werden, und alle Rückrufe müssen über die VAR -Rückrufe der Ladeabschluss informiert werden. für (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 Abhängigkeitslösung getDepends (uUid, Daten, ausschließen, hängt davon ab, vorladen, asyncloadassets, config) { var err = null; versuchen { var info = abhängig.Parse (UUID, Daten); var includeNative = true; if (Dateninstanz von cc.asset && (! data .__ nativedEpend__ || data._nativeasSet)) includeNative = false; if (! vorladen) { asyncloadassets =! cc_editor && (!! data.asyncloadassets || (asyncloadassets &&! info.preventDeferred loadDependents)); für (sei i = 0, l = info.deps.length; i <l; i ++) { sei dep = info.deps [i]; if (! (DEP in Excloud)) { [dep] = true ausschließen; abhängigs.push ({uUid: dep, __asyncloadassets__: asyncloadassets, bündel: config && config.name}); } } if (einschließlich &&! config && (info.nativedep.bundle = config.name); abhängigs.push (info.nativedEp); } } anders { für (sei i = 0, l = info.deps.length; i <l; i ++) { sei dep = info.deps [i]; if (! (DEP in Excloud)) { [dep] = true ausschließen; abhängigs.push ({UUID: DEP, Bundle: config && config.name}); } } if (includeNative && info.nativedEp) { config && (info.nativedep.bundle = config.name); abhängigs.push (info.nativedEp); } } } fangen (e) { err = e; } Rückgabefehler; }, Dependutil ist ein Singleton, der die Abhängigkeitsliste steuert.
Dependutil verwaltet auch einen Cache, um wiederholte Abfragen von Abhängigkeiten zu vermeiden. // Erhalten Sie die Ressourcenabhängigkeitsliste basierend auf den JSON -Informationen. var out = null; // Wenn es sich um eine Szene oder ein Vorfeld handelt, sind Daten ein Array, eine Szene oder ein Präfabab if (array.isarray (json)) { // Wenn es analysiert wurde und in _Depends eine Abhängigkeitsliste vorhanden ist, kehren Sie direkt zurück, wenn (this.depends.has (uUid)) diese._Depends.get (uUid) zurückgeben. out = { // Verwenden Sie für Prefab oder Szene direkt die Methode _parseDepsfromjson, um DEPS zurückzugeben: cc.asset._parsedepsfromjson (JSON), Asyncloadassets: JSON [0] .Asyncloadassets }; } // Wenn __Type__ enthalten ist, holen Sie sich seinen Konstruktor und finden Sie abhängige Ressourcen von JSON Get Deps von JSON // In den tatsächlichen Tests folgen die vorinstallierten Ressourcen dem folgenden Zweig. if (this._depends.has (uUid)) return this._depends.get (uUid); var ctor = js._getClassbyId (json .__ Typ__); // Einige Ressourcen schreiben die _parseDepsfromjson und _parsenativedEpfromjson -Methoden um // beispielsweise cc.texture2d out = { PREVEPREPRELOADNATIONOBJECT: Voraussetzungen für vorgefertigte Ladevorschriften: DEPs: ctor._parsedepsfromjson (JSON), nativedp: ctor._parsenativedpfromjson (JSON) }; out.nativedEp && (out.nativedEp.uuid = uUid); } // Holen Sie sich DEPs von einem vorhandenen Vermögenswert // Wenn es kein Feld __Type __ gibt, kann sein entsprechender CTOR nicht gefunden werden, und die Abhängigkeit wird aus dem Feld __DEPEND__ des Vermögenswerts noch {abgenommen { if (! cc_editor && (out = this._depends.get (uUid)) && out.parsedfromexistasset) kehren aus; var asset = json; out = { DEPs: [], Parsedfromexistasset: Richtig, PREVEPRELOADNATIONOBJECT: Asset.Constructor.PreventPreloLeAnativeObject, Voraussetzungen für vorgefertigte Angaben: }; Sei DEPS = Asset .__ hängt __ ab; für (var i = 0, l = deps.length; i <l; i ++) { var dep = dEPs [i] .UUID; out.deps.push (DEP); } if (asset .__ nativedEpend__) { // asset._nativedep wird ein Objekt wie dieses zurückgeben {__isnative__: true, uUid: this._uuid, ext: this._native} out.nativedEp = asset._nativedEp; } } // Wenn eine Abhängigkeit zum ersten Mal gefunden wird, stecken Sie sie direkt in die Liste der Abhängigkeit, Cache -Abhängigkeitsliste this._depends.add (uUid, out); Rückkehr raus; } Die Standardimplementierung von CCassets _parsedepsfromjson (JSON) { var hängt ab = []; ParseDePendRecursiv (JSON, hängt davon ab); Return hängt davon ab; }, _parsenativedEpfromjson (JSON) { if (json._native) return {__isnative__: true, ext: json._native}; gibt null zurück; } 3.5 RessourcenveröffentlichungDieser Abschnitt konzentriert sich auf drei Möglichkeiten, Ressourcen im Ersteller und deren Implementierung freizugeben, und führt schließlich vor, wie Ressourcenlecks im Projekt Fehler beheben können. 3.5.1 Creator Resource Release Der Schöpfer unterstützt die folgenden drei Möglichkeiten zur Veröffentlichung von Ressourcen:
3.5.2 Automatische Szenenveröffentlichung Wenn eine neue Szene ausgeführt wird, wird der Director.RunsceneMediate -Methode ausgeführt. RunSceneMediate: Funktion (Szene, onBeforeLoadscene, Onlaunted) { // Code weggelassen ... var oldscene = this._scene; if (! cc_editor) { // Ressourcen automatisch veröffentlichen cc_build && cc_debug && console.time ('autorelease'); cc.assetmanager._releasemanager._Autorelease (OldScene, Szene, Persistnodelist); Cc_build && cc_debug && console.timeend ('autorelease'); } // Szene entladen Cc_build && cc_debug && console.time ('destroy'); if (cc.isvalid (Oldscene)) { OldScene.Destroy (); } // Code weggelassen ... }, Die neueste Version von _Autorelease ist sehr präzise und unkompliziert. // automatische Veröffentlichung durchführen _Autorelease (Oldscene, Newscene, Persistnodes) { // Alle Ressourcen, von denen anhaltende Knoten abhängen var node = persistnodes [i]; var scedeps = abhängig._Depends.get (newscene._id); var deps = _PersistnodEDeps.get (node.uuid); für (sei i = 0, l = deps.length; i <l; i ++) { var abhängig = assets.get (DEPS [i]); if (abhängig) { abhängig.addref (); } } if (scedeps) { ! scedeps.persistdeps && (scedeps.Persistdeps = []); scedeps.persistdeps.push.apply (scedeps.Persistdeps, DEPS); } } // Veröffentlichen Sie die Abhängigkeiten der alten Szene, wenn (Oldscene) { var Kinder = abhängig.getDeps (oldscene._id); für (sei i = 0, l = Kinder.Length; i <l; i ++) { Sei Asset = assets.get (Kinder [i]); asset && asset.decref (cc_test || oldscene.Autoreleasasets); } var abhängig = abhängig._Depends.get (oldscene._id); if (Abhängigkeiten && Abhängigkeiten.Persistdeps) { var persistDeps = Abhängigkeiten.PersistDeps; für (sei i = 0, l = persistdeps.length; i <l; i ++) { Sei Asset = assets.get (persistDeps [i]); asset && asset.decref (cc_test || oldscene.Autoreleasasets); } } abhängigUtil.remove (oldscene._id); } }, 3.5.3 Referenzzählung und manuelle Ressourcenfreigabe Die verbleibenden zwei Möglichkeiten zur Veröffentlichung von Ressourcen dienen im Wesentlichen, Releasemanager zu rufen. Der vollständige Prozess der Ressourcenfreigabe erfolgt ungefähr wie in der folgenden Abbildung: // ccasset.js reduzieren Referenz Decref (Autorelease) { this._ref--; autorelease! == false && cc.assetmanager._releasemanager.tryRelease (this); gib dies zurück; } // ccassetManager.js freisetzt manuell Ressourcen releaSeasSet (Asset) { releasemanager.tryRelease (Asset, true); }, TryRelease unterstützt zwei Modi: Verspätete Veröffentlichung und erzwungene
Wenn der Rückgabewert von CheckcircularReference größer als 0 ist, wird die Ressourcen von anderen Orten auf alle Orte bezogen, an denen wir hinzufügen. C sind tatsächlich zusammen mit A freizugeben. Wenn die Redewendung einer Ressource abzüglich der Anzahl der internen Referenzen immer noch größer als 1 ist, bedeutet dies, dass sie noch an einem anderen Ort verwiesen wird und nicht veröffentlicht werden kann. TryRelease (Asset, Force) { if (! (Asset Instance von cc.asset)) return; if (Kraft) { releasemanager._free (Asset, Force); } anders { _todelete.add (asset._uuid, asset); // Nach Abschluss der nächsten Direktor -Zeichnung Freeassets ausführen if (! EventListener) { EventListener = true; cc.director.once (cc.director.event_after_draw, freeassets); } } } // Ressourcen_free (Asset, Force) {veröffentlichen { _todelete.remove (asset._uuid); if (! cc.isvalid (Asset, true)) Return; if (! Kraft) { if (asset.refcount> 0) { // Überprüfen Sie, ob zirkuläre Referenzen innerhalb des Vermögenswerts (checkcircularReference (Asset)> 0) zurückgeben werden. } } // aus cache assets.remove (asset._uuid) entfernen; var abhängig = abhängig.getDeps (asset._uuid); für (sei i = 0, l = abhängigs.length; i <l; i ++) { var abhängig = assets.get (Abhängig [i]); if (abhängig) { abhängig.decref (falsch); releasemanager._free (abhängig, false); } } Asset.Destroy (); abhängigUtil.remove (asset._uuid); }, // die Ressourcen in _todelete und löschem Funktion freeassets () {freigeben { EventListener = false; _todelete.foreach (Funktion (Asset) { releasemanager._free (Asset); }); _todelete.clear (); } Was macht Asset.Destroy? Wie werden Ressourcenobjekte veröffentlicht? Wie werden Ressourcen wie Texturen und Sounds veröffentlicht? Das Asset -Objekt selbst hat keine Zerstörungsmethode, aber das von dem Asset -Objekt geerbte CCObject -Objekt implementiert die prototype.destroy = function () { if (this._objflags & zerstört) { CC.Warnid (5000); gibt false zurück; } if (this._objflags & todestroy) { gibt false zurück; } this._objflags | = Todestroy; ObjectStodestroy.push (this); if (cc_editor && deferredDestrytimer === null && cc.engine &&! cc.engine._isupdating) { // kann sofort im Editor -Modus deperredDestryimer = setImmediate (deferredDestroy) zerstört werden; } gibt true zurück; }; // Director ruft diese Methode auf jede Frame -Funktion deferredDestroy () { var deleteCount = ObjectStodestroy.length; für (var i = 0; i <deleteCount; ++ i) { var obj = ObjectStodestroy [i]; if (! (obj._objflags & zerstört)) { obj._destroyimmediate (); } } // Wenn wir in A.Destroy B.Destroy nennen, ändert sich die Größe des ObjectStodestroy -Arrays. ObjectStodestroy.length = 0; } anders { ObjectStodestroy.SPLICE (0, DeleteCount); } if (cc_editor) { DeferredDestryTimer = null; } } // Real Resource Release Prototype._Destroyimmediate = function () { if (this._objflags & zerstört) { CC.Erorid (5000); zurückkehren; } // Callback durchführen if (this._onpredestroy) { this._onpredestroy (); } if (((cc_test? this._Destruct (); } this._objflags | = zerstört; }; 在這里 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 = {}; für (Schlüssel in Objekt) { wenn (obj.hasOwnProperty(Schlüssel)) { if (key === idToSkip) { weitermachen; } switch (typeof obj[key]) { Fall 'Zeichenfolge': propsToReset[key] = ''; brechen; case 'object': Fall 'Funktion': propsToReset[key] = null; brechen; } } } // 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') { weitermachen; } switch (typeof attrs[attrKey]) { Fall 'Zeichenfolge': propsToReset[key] = ''; brechen; case 'object': Fall 'Funktion': propsToReset[key] = null; brechen; case 'undefined': propsToReset[key] = undefined; brechen; } } } } if (CC_SUPPORT_JIT) { // compile code var func = ''; for (key in propsToReset) { var statement; if (CCClass.IDENTIFIER_RE.test(key)) { statement = 'o.' + key + '='; } anders { statement = 'o[' + CCClass.escapeForJS(key) + ']='; } var val = propsToReset[key]; if (val === '') { val = '""'; } func += (statement + val + ';\n'); } return Function('o', func); } anders { 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),以及正在使用的資源被釋放的問題(和內存泄露相反,資源被提前釋放了)。 從目前的代碼來看,如果正確使用了引用計數,新的資源底層是可以避免內存泄露等問題的 Wie kann dieses Problem gelöst werden?首先是定位出哪些資源出了問題,如果是被提前釋放,我們可以直接定位到這個資源,如果是內存泄露,當我們發現問題時程序往往已經占用了大量的內存,這種情況下可以切換到一個空場景,并清理資源,把資源清理完后,可以檢查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其他相關文章! Das könnte Sie auch interessieren:
|
<<: Zwei Möglichkeiten zum Schreiben gespeicherter Prozeduren in MySQL mit und ohne Rückgabewerte
>>: Schritte für Docker zum Erstellen eines eigenen lokalen Image-Repositorys
Implementieren Sie das Vergrößern und Verkleinern...
1. Erstellen Sie eine Planungsaufgabe Anweisung c...
Inhaltsverzeichnis Überblick 1. Erstellen eines R...
1. Das WEB verstehen Webseiten bestehen hauptsäch...
Inhaltsverzeichnis Ein einfaches Komponentenbeisp...
In diesem Artikelbeispiel wird der spezifische Im...
Hier sind einige Beispiele, wie ich diese Eigensch...
ScreenCloud ist eine tolle kleine App, von der Si...
Ich weiß nicht, ob Ihnen beim Erstellen einer Webs...
Inhaltsverzeichnis 1. Problematische SQL-Anweisun...
1. Welche Zeilenformate gibt es? Sie können Ihre ...
Die Größe des Textbereich-Tags ist unveränderlich ...
Nachfragehintergrund: Fügen Sie dynamische GIF-Bi...
In diesem Artikel finden Sie eine Sammlung von Ja...
Inhaltsverzeichnis 1. Verwenden Sie Standardparam...