首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用于小型游戏的资产加载器

用于小型游戏的资产加载器
EN

Code Review用户
提问于 2016-10-10 19:37:38
回答 1查看 46关注 0票数 4

我做了一个小游戏,只是为了我自己的练习。在编写过程中,我编写了一个资产加载器,它(顾名思义)加载资产。但是我的主要知识和精力都在游戏上,所以现在我想知道如何改进资产加载器。

好的一面是:不管我做了什么改变,它都做得很好。缺点是:它是根据我一半发明的特定文件格式量身定做的。

因此,任何建议都将不胜感激。

代码语言:javascript
复制
/*
    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.
EN

回答 1

Code Review用户

发布于 2016-10-11 01:08:46

您的加载逻辑知道顺序。LoadTheLevel知道SaveTheFileStringSaveTheFileString知道ParseTheLevelStringParseTheLevelString知道LoadCode等等。如果您需要在此管道中添加另一个操作,则必须接触其他两个操作的代码。虽然听起来并没有那么糟糕,但它常常会导致多米诺骨牌效应,不得不重建整个序列。

考虑使用承诺代替。它仍然是相同的延续风格,但具有更多的结构,并将您的API与流逻辑分离。所有组件函数都需要知道的是,它应该返回一个用值解析或用错误拒绝的承诺。它不会知道其他函数,也不知道操作的顺序。

代码语言:javascript
复制
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.
  });

另一个问题是很难对代码进行推理。考虑一下这个片段:

代码语言:javascript
复制
'.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是什么。虽然这在技术上是有意义的,但很难贯彻到底。

与上面一样,我强烈建议您将流程和逻辑保留在操作的调用方。使组件函数不知情且相互独立。

代码语言:javascript
复制
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中,selectedDownloaderdownloadedItemParser将不知道彼此之间的关系,也不知道它们被调用的顺序。他们所知道的是,他们接收数据,做一些事情,并返回一些东西。对这些函数的输入和对它们的输出所做的操作取决于上面的流逻辑。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/143831

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档