前言
資源管理是記憶體優化的一部分,對于大型遊戲,資源管理不明确,很容易出現記憶體不足而閃退的情況。
說到資源也就涉及到了資源劃分,這部分内容可以看另一篇文章《遊戲開發之目錄劃分》。
資料總管需要考慮的情況
- 加載完成的回調
- 加載失敗後的嘗試
- 多個相同請求的處理。
- 未加載成功之前已經删除。
- 資源的使用情況,記數。
- 跨引擎使用。
各個引擎需要提供的輔助類需要實作的接口
/**
* 自定義的資源分類,對應各個引擎中相同的資源。
*/
export enum ResType {
Texture2D,
SpriteFrame,
SpriteAtlas,
Prefab,
Json,
Scene,
Material,
AnimationClip,
Mesh,
Particle2D,//粒子效果
AudioClip,
}
export type ResCallback = (err: any, res: any) => void
/**
* 是否使用引用記數
* 對于一些資源很少的小遊戲不需要清理資源,是以可以設定為false。
*/
export let RECORD_RES_COUNT: boolean = true
/**
* 各個引擎需要提供資源的輔助類需要實作的接口
*/
export default interface ResInterface {
/**
*
* @param url 加載資源
* @param type
* @param callback
*/
loadRes(url: string, type: ResType, callback: ResCallback): void;
/**
* 清理資源
* @param url
*/
release(url: string): void;
/**
* 擷取資源
* @param url
* @param ResType 自定義的資源類型
*/
getRes(url: string, type: ResType): any;
/**
* 獲得資源的依賴資源
* @param url
*/
getDependsRecursively(url: any): any;
}
資源類的封裝和記數處理
import ResHelper from "../../engine/ResHelper";
import { ResType, RECORD_RES_COUNT } from "./ResInterface";
export default class ResItem {
// 全局資源使用計數器。
protected static resCountMap: {} = {};
//嘗試加載次數
private loadCount: number = 0;
//以來資源
protected resources: {} = {};
//使用次數
protected useCount: number = 0;
//資源id
private url: string;
//資源類型
private type: ResType;
//加載是否結束
protected loadFinish: boolean = false;
//資源本身
private res: any;
//需要通知的函數
private callbackList: Function[] = []
constructor(url: string, type?: ResType) {
this.url = url;
this.type = type;
}
addCallback(func: Function) {
this.callbackList.push(func)
}
//是否加載完畢
isDone() {
return this.loadFinish;
}
getUrl() {
return this.url;
}
getType() {
return this.type;
}
getRes() {
if (RECORD_RES_COUNT) {
this.addCount();
}
if (!this.res) {
this.res = ResHelper.instance().getRes(this.url, this.type)
}
return this.res;
}
/**
* 加載完成調用
* @param flag
*/
setLoadingFlag(flag: boolean) {
this.loadFinish = flag;
if (flag) {
while (this.callbackList.length > 0) {
let func = this.callbackList.shift();
func(null, this)
}
}
}
/**
* 由于引擎加載機制,加載完成就已經使用,
*/
cacheRes(res: any) {
this.res = res;
if (RECORD_RES_COUNT) {
let depands = ResHelper.instance().getDependsRecursively(res)
for (let key of depands) {
this.resources[key] = true;
}
//加載成功後直接加1,以免被其他子產品的記載器清理掉。
this.addCount()
}
}
//獲得加載次數
getLoadCount() {
return this.loadCount;
}
//更新加載次數
updateLoadCount() {
this.loadCount++;
}
//獲得使用次數
getUseCount() {
return this.useCount;
}
releaseAll() {
if (RECORD_RES_COUNT) {
while (this.useCount > 0) {
this.release();
}
}
}
release() {
if (RECORD_RES_COUNT) {
if (this.useCount > 0) {
this.subCount();
if (this.useCount == 0) {
return true;
} else {
return false;
}
} else {
return true;
}
}
}
subCount() {
this.useCount --;
let resources: string[] = Object.keys(this.resources);
for (let index = 0; index < resources.length; index++) {
const key = resources[index];
if (ResItem.resCountMap[key] > 0) {
ResItem.resCountMap[key]--;
if (ResItem.resCountMap[key] == 0) {
ResHelper.instance().release(key)
delete this.resources[key];
delete ResItem.resCountMap[key];
}
}
}
}
addCount() {
this.useCount++;
let resources: string[] = Object.keys(this.resources);
for (let index = 0; index < resources.length; index++) {
const key = resources[index];
ResItem.resCountMap[key]++;
}
}
/**
* 删除沒有使用的資源
*/
static removeUnUsedRes() {
let resources: string[] = Object.keys(this.resCountMap);
for (let index = 0; index < resources.length; index++) {
const key = resources[index];
const count = this.resCountMap[key];
if (count === 1) {
// cc.log("removeUnUsedRes uuid " + key + " count " + ResItem.resCountMap[key])
ResHelper.instance().release(key)
delete this.resCountMap[key];
}
}
}
}
資料總管
import ResItem from "./ResItem";
import ResInterface, { ResCallback, ResType } from "./ResInterface";
import ResHelper from "../../engine/ResHelper";
export default class ResLoader {
private helper: ResInterface = null;
constructor() {
this.helper = ResHelper.instance();
}
protected resCache = {}
/**
* 清理單個資源
* @param url
* @param type
*/
releaseRes(url: string, type: ResType) {
let ts = this.getKey(url, type);
let item = this.resCache[ts];
if (item) {
if (item.release()) {
this.resCache[ts] = null;
}
}
}
/**
* 删除所有資源
*/
release() {
console.log(' ResLoader release ================== ')
let resources: string[] = Object.keys(this.resCache);
for (let index = 0; index < resources.length; index++) {
const key = resources[index];
const element: ResItem = this.resCache[key];
if (element) {
element.releaseAll();
this.resCache[key] = null;
} else {
// console.warn("ResLoader release url = is error ",key)
}
}
}
private getKey(url: string, type: ResType) {
let key = url + type;
return key;
}
/**
* 同時加載多個資源。
* @param list 需要加載的資源清單
* @param type 需要加載的資源類型,要求所有資源統一類型
* @param func 加載後的回調
* @param loader 資源加載管理器,預設是全局管理器。
*/
loadArray(list: Array<string>, type: ResType, func: (err: string, process: number) => void) {
let resCount = 0;
for (let index = 0; index < list.length; index++) {
const element = list[index];
this.loadRes(element, type, (err) => {
// 不論是否都加載成功都傳回。
if (err) {
console.log(err);
func(err, resCount / list.length);
return;
}
resCount++;
func(err, resCount / list.length);
});
}
}
getItem(url: string, type: ResType) {
let ts = this.getKey(url, type)
if (this.resCache[ts]) {
return this.resCache[ts]
} else {
let item = new ResItem(url, type);
this.resCache[ts] = item;
}
}
/**
* 加載單個檔案
* @param url
* @param type
* @param callback
*/
loadRes(url: string, type: ResType, callback: (err: string, res: ResItem) => void) {
let ts = this.getKey(url, type);
let item: ResItem = this.resCache[ts]
// cc.log(" loadRes url ",url,' ts ',ts);
if (item && item.isDone()) {
callback(null, item);
return;
} else {
if (item) {
item.addCallback(callback)
return;
} else {
item = new ResItem(url, type);
this.resCache[ts] = item;
}
}
let func: ResCallback = (err: any, res: any) => {
item.updateLoadCount();
if (err) {
if (item.getLoadCount() <= 3) {
console.warn(" item.getLoadCount() =========== ", item.getLoadCount())
this.helper.loadRes(url, type, func);
} else {
console.warn(" res load fail url is " + url);
this.resCache[ts] = null;
callback(err, null);
}
} else {
item.cacheRes(res);
if (this.resCache[ts]) {
item.setLoadingFlag(true)
callback(err, item);
} else {
//處理加載完之前已經删除的資源
item.subCount();
}
}
}
this.helper.loadRes(url, type, func);
}
/**
* 擷取資源的唯一方式
* @param url
* @param type
*/
getRes(url: string, type: ResType) {
let ts = this.getKey(url, type)
let item = this.resCache[ts];
if (item) {
return item.getRes();
} else {
let res = this.helper.getRes(url, type);
if (res) { // 如果其他管理器已經加載了資源,直接使用。
console.log(' 其他加載器已經加載了次資源 ', url)
let item = new ResItem(url, type);
item.cacheRes(item)
this.resCache[ts] = item
return item.getRes();
} else {
console.warn('getRes url ', url, ' ts ', ts)
}
}
return null;
}
}
CocosCreator資源輔助類
import ResInterface, { ResType, ResCallback } from "../cfw/res/ResInterface";
/**
* 各個引擎提供的資源輔助類。需要實作ResInterface接口
*/
export default class ResHelper implements ResInterface {
private static ins: ResInterface;
static instance() {
if (!this.ins) {
this.ins = new ResHelper()
}
return this.ins;
}
/**
* 加載資源
* @param url
* @param type
* @param callback
*/
loadRes(url: string, type: ResType, callback: ResCallback): void {
switch (type) {
case ResType.Prefab:
cc.loader.loadRes(url, cc.Prefab, callback)
break;
case ResType.Texture2D:
cc.loader.loadRes(url, cc.Texture2D, callback)
break;
case ResType.SpriteFrame:
cc.loader.loadRes(url, cc.SpriteFrame, callback)
break;
case ResType.Json:
cc.loader.loadRes(url, cc.JsonAsset, callback)
break;
case ResType.SpriteAtlas:
cc.loader.loadRes(url, cc.SpriteAtlas, callback)
break;
case ResType.Particle2D:
cc.loader.loadRes(url, cc.ParticleAsset, callback)
break;
case ResType.AudioClip:
cc.loader.loadRes(url, cc.AudioClip, callback)
break;
}
}
/**
* 清理資源
* @param url
*/
release(url: string): void {
cc.loader.release(url);
}
getRes(url: string, type: ResType): any {
switch (type) {
case ResType.Prefab:
return cc.loader.getRes(url, cc.Prefab);
case ResType.Texture2D:
return cc.loader.getRes(url, cc.Texture2D);
case ResType.SpriteFrame:
return cc.loader.getRes(url, cc.SpriteFrame);
case ResType.Json:
return cc.loader.getRes(url, cc.JsonAsset);
case ResType.SpriteAtlas:
return cc.loader.getRes(url, cc.SpriteAtlas);
case ResType.Particle2D:
return cc.loader.getRes(url, cc.ParticleAsset)
case ResType.AudioClip:
return cc.loader.getRes(url, cc.AudioClip)
default:
console.error(' getRes error url is ', url, ' type is ', type)
return null;
}
}
getDependsRecursively(res: any): any {
return cc.loader.getDependsRecursively(res)
}
}
結語
歡迎掃碼關注公衆号《微笑遊戲》,浏覽更多内容。
歡迎掃碼關注公衆号《微笑遊戲》,浏覽更多内容。