我做了一个小游戏,只是为了我自己的练习。在编写过程中,我编写了一个资产加载器,它(顾名思义)加载资产。但是我的主要知识和精力都在游戏上,所以现在我想知道如何改进资产加载器。
好的一面是:不管我做了什么改变,它都做得很好。缺点是:它是根据我一半发明的特定文件格式量身定做的。
因此,任何建议都将不胜感激。
/*
CPS (Continuation-passing Style) is used a lot here because it makes
the work with callbacks (and async functions) a lot more convienent, but
it needs some time to get your head around it.
https://en.wikipedia.org/wiki/Continuation-passing_style
*/
var gCachedData = [];
var gameLoop;
AssetLoader = {
currentGame: null,
downloaders: {
// These are extentions assosiated with a method to download them.
'.js': function(file, save, count){
var c = function(response){
save(file, response.currentTarget.responseText);
count();
}
gUtil.xhrGet(file, c, 'text');
},
'.png': function(file, save, count){
var c = function(res){
var image = res.target;
var x = {
img: image,
def: {
frame: {x: 0, y: 0, w: image.width, h: image.height},
rotated: false
}
};
save(file, x);
count();
};
var i = new Image();
i.onload = c
i.onerror = function(e){console.log(e)}
i.src = file;
},
'-sprite.png': function(file, save, count){
// This is a sprite, download it and
// download the corrosponding JSON file.
var x = {
img: null,
def: null
}
var enterImagesFromSprite = function(x){
for(var imageName in x.def.frames){
var xx = {
img: x.img,
def: x.def.frames[imageName]
}
save(imageName, xx);
}
count();
}
var cImage = function(res){
x.img = res.target;
if(x.def)
enterImagesFromSprite(x);
};
var cJSON = function(res){
x.def = JSON.parse(res.currentTarget.responseText);
if(x.img)
enterImagesFromSprite(x);
}
var i = new Image();
i.onload = cImage;
i.src = file;
var dot = file.lastIndexOf('.');
var jsonFile = file.substring(0, dot) + '.json';
gUtil.xhrGet(jsonFile, cJSON, 'text');
},
'.wav': function(file, save, count){
var c = function(response){
var audio_context = null;
try{
audio_context = new (window.AudioContext || window.webkitAudioContext)();
}catch(e){
console.log('Not able to play sounds');
}
var x = {
buffer: null,
loaded: false
};
audio_context.decodeAudioData(
response.currentTarget.response,
function(buffer){
x.buffer = buffer;
x.loaded = true;
save(file, x);
count();
}
);
}
gUtil.xhrGet(file, c, 'arraybuffer');
},
'default': function(file, cont){
console.log('We have no handler for this file:', file);
cont();
}
},
loadListOfElements: function(list, callback){
if((!list) || list.length == 0){
callback();
return;
}
var loader = {
elementsLeft: list.length,
cb: callback
};
function saveDataToCache(filename, object){
if((filename != undefined) && (object != undefined))
gCachedData[filename] = object;
}
function countAsset(){
--loader.elementsLeft;
if(loader.elementsLeft == 0)
loader.cb();
}
for(var i=0; i<list.length; i++){
file = list[i]
var extension;
if(file.endsWith('-sprite.png')){
extension = '-sprite.png';
}else{
var dot = file.lastIndexOf('.');
extension = file.substring(dot);
}
if(this.downloaders[extension]){
this.downloaders[extension](file, saveDataToCache, countAsset);
}else{
console.log('We have no handler for this file:', file);
}
}
},
loadLevel: function(i){
if(this.currentGame){
this.unloadLevel();
}
// The following things should happen in that order:
// 1. Load the code
// 2. Eval the code (with inner order defined in the json file)
// 3. Load assets.
// 4. Start the game.
var LoadTheLevel = function(filename, cont){
var c = (function(filename, cont){
return SaveTheFileString(filename, cont);
})(filename, cont);
gUtil.xhrGet(filename, c, 'text');
}
var SaveTheFileString = function(filename, cont){
return function(res){
gCachedData[filename] = res.currentTarget.responseText;
ParseTheLevelString(gCachedData[filename], cont);
}
}.bind(this);
var ParseTheLevelString = function(str, cont){
LoadCode(gUtil.parseLevelData(str), cont);
}
var LoadCode = function(json, cont){
var c = (function(cont, levelStruct){
return function(res){
evalTheCode(levelStruct);
LoadAssets(levelStruct.assets, cont);
}
})
this.loadListOfElements(json.code, c(cont, json));
}.bind(this);
var evalTheCode = function(levelStruct){
// Eval by order.
for(var i=0; i<levelStruct.code.length; i++){
eval(gCachedData[levelStruct.code[i]]);
}
}
var LoadAssets = function(list, cont){
this.loadListOfElements(list, cont);
}.bind(this);
var StartTheGame = function(levelSting){
var levelStruct = gUtil.parseLevelData(levelSting);
this.currentGame = new GameEngineClass(levelStruct.world);
this.currentGame.setup(levelStruct.entities, levelStruct.world.camera);
canvas.focus();
gameLoop = setInterval(this.currentGame.update.bind(this.currentGame), 13);
}.bind(this);
var fileName = i + '.json';
if(!gCachedData[fileName]){
LoadTheLevel(fileName, function(){StartTheGame(gCachedData[fileName]);});
}else{
StartTheGame(gCachedData[fileName]);
}
},
unloadLevel: function(){
// TODO: Remove assets or not?
clearInterval(gameLoop);
try{
this.currentGame.prepareGameEnd();
}catch(ignor){}
gameLoop = null;
document.getElementById('canvasDebug').style.display = 'none';
document.getElementById('reloadButton').style.visibility = 'visible';
}
};
var stop = AssetLoader.unloadLevel.bind(AssetLoader); // Just a helper for debugging.发布于 2016-10-11 01:08:46
您的加载逻辑知道顺序。LoadTheLevel知道SaveTheFileString,SaveTheFileString知道ParseTheLevelString,ParseTheLevelString知道LoadCode等等。如果您需要在此管道中添加另一个操作,则必须接触其他两个操作的代码。虽然听起来并没有那么糟糕,但它常常会导致多米诺骨牌效应,不得不重建整个序列。
考虑使用承诺代替。它仍然是相同的延续风格,但具有更多的结构,并将您的API与流逻辑分离。所有组件函数都需要知道的是,它应该返回一个用值解析或用错误拒绝的承诺。它不会知道其他函数,也不知道操作的顺序。
function loadLevel(...){
return new Promise(function(resolve, reject){ ... });
}
function evaluateData(...){
return new Promise(function(resolve, reject){ ... });
}
function loadAssets(...){
return new Promise(function(resolve, reject){ ... });
}
function initializeGame(...){
return new Promise(function(resolve, reject){ ... });
}
// Dummy promise. I just use it to indent the entire sequence.
Promise.resolve()
.then(function(){
return loadLevel(...);
})
.then(function(levelData){
// Do something with levelData
return evaluateData(...);
})
.then(function(evaluatedData){
// Do something with evaluatedData
return loadAssets(...);
})
.then(function(loadedAssets){
// Do something with loadedAssets
return initializeGame(...);
})
.then(function(){
// By now, everything is ready.
})
.catch(function(error){
// Something went horribly wrong somewhere.
});另一个问题是很难对代码进行推理。考虑一下这个片段:
'.js': function(file, save, count){
var c = function(response){
save(file, response.currentTarget.responseText);
count();
}
gUtil.xhrGet(file, c, 'text');
},据我所知,file是一个url,而xhrGet似乎是某种使用GET的AJAX调用。但是save是什么呢?count是什么?何时调用这个.js函数?在代码中它在哪里被调用?这种传递控件的风格使跟踪代码流变得很困难。从通过list循环的循环跳到这个函数,然后查找save是什么,然后返回到这个函数,然后查找count是什么。虽然这在技术上是有意义的,但很难贯彻到底。
与上面一样,我强烈建议您将流程和逻辑保留在操作的调用方。使组件函数不知情且相互独立。
function selectedParser(item){
...
return parsedData;
}
function selectedDownloader(itemData){
return new Promise(function(resolve, reject){
...
});
}
function downloadedItemParser(item){
...
return parsedData;
}
var downloadPromises = list.map(function(item){
// parse the data needed from item, and return it for next operation.
return selectedParser(item);
}).map(function(parsedData){
// Determine the downloader, an call it. Have it return a promise.
return selectedDownloader(parsedData);
});
// Wait for all promises to complete
Promise.all(downloadPromises)
.then(function(downloadedItems){
// downloadedItems is an array of responses from the requests
// in the same order as downloadPromises.
return downloadedItems.map(function(item){
return downloadedItemParser(item);
});
})
.then(function(parsedData){
// we've got finished data. Use and/or cache.
});在这个伪代码selectedParser中,selectedDownloader,downloadedItemParser将不知道彼此之间的关系,也不知道它们被调用的顺序。他们所知道的是,他们接收数据,做一些事情,并返回一些东西。对这些函数的输入和对它们的输出所做的操作取决于上面的流逻辑。
https://codereview.stackexchange.com/questions/143831
复制相似问题