senäh

17senäh und so…

HTML/CSS/JS
23. Jul 2011
Kommentare: 0

Ein AssetManager für Spiele

Kategorien: HTML/CSS/JS | 23. Jul 2011 | Kommentare: 0

Heute kommt ein richtig praktisches Code-Beispiel: ein AssetManager. Ein AssetManager ist dazu da, um Assets leicht verwalten zu können. (AssetManager manages assets…) Im Wesentlichen geht es darum Assets (in diesem Beispiel Bilder) einmalig zu laden und diesen Ladevorgang zu organisieren. Dies ist erforderlich, weil man als Entwickler nicht augenblicklich auf Bilder zugreifen kann, sondern auf eine asynchrone Benachrichtigung über den erfolgreiche Ladevorgang warten muss. Wurde ein Bild erfolgreich geladen, kann man es mehrfach verwenden ohne es erneut laden zu müssen.

Großer Dank geht an Seth Ladd auf dessen Tutorial der meiste Code basiert. Er erklärt den Aufbau Schritt-für-Schritt. Ich habe seinen AssetManager lediglich etwas nach meinem Code-Stil umgemünzt. Wer sich generell für HTML5 Games interessiert (und ich nehme an, das tut ihr, weil ihr sonst nicht hier wäret ;)), dann empfehle ich euch noch diese Präsentation von Seth Ladd von der I/O Google 2011. Nun aber zum AssetManager.

AssetManager

Den AssetManager habe ich in einer externen .js ausgelagert. Ihr erstellt eine Instanz des AssetManager und übergibt ihm Pfade zu allen Bildern, die geladen werden sollen. Dies ist zunächst nur eine Lade-Grafik. Sie soll zuerst geladen und sofort angezeigt werden. Erst danach soll das Laden der eigentlichen Assets erfolgen. Auf diese Weise sieht der Nutzer „Aha! Der wird was geladen.“ In einer erweiterten Version des AssetManager könnte man noch einen richtigen Fortschrittsbalken einbauen, aber darauf habe ich erst einmal verzichtet. Sind alle Assets geladen, wird die Lade-Grafik entfernt und die Assets werden auf ein Canvas-Element gezeichnet. Hier zeichne ich dann auch mehrmals ein und die gleiche Grafik auf das Canvas ohne die Grafik jedes Mal erneut laden zu müssen.

So soll es aussehen:

AssetManager

AssetManager

Ihr könnt eure eigenen Bilder verwenden oder diese hier (in meinem Fall in einem Unterordner „/img/“):

Und es folgt der Code:

index.html

<!DOCTYPE html>
<html lang="de">

<!--
You can use this code as you wish,
but please recommend our site http://www.senaeh.de.
@author Philipp Zins/Donald Pipowitch
-->

<head>
    <title>Laden und Cachen von Bilder in HTML5</title>

    <meta charset="UTF-8"/>
    <meta name="description" content="Ein einfacher Test zum Laden und Cachen von Bildern in HTML5."/>
    <meta name="keywords" content="HTML5, Cache, Loading, ApplicationCache"/>
    <meta name="author" content="Philipp Zins/Donald Pipowitch"/>

    <link rel="stylesheet" type="text/css" href="css/style.css"/>

    <script type="text/javascript" src="js/AssetManagerTest.js"></script>
</head>

<body onload="init()">
    <canvas id="canvasAsset">
        Dein Browser unterstützt kein Canvas.
    </canvas>
</body>

</html>

style.css

canvas
{
    z-index: -1;
    position: absolute;
    left: 0px;
    top: 0px;
}

AssetManagerTest.js

/**
 * You can use this code as you wish for non-commercial projects,
 * but please recommend our site http://www.senaeh.de.
 * @author Philipp Zins/Donald Pipowitch
 */

/**
 * "Imports"
 */

document.write('<script type="text/javascript" src="js/AssetManager.js"></script>');

/**
 * Variables
 */
var assetManager;

var canvas;
var context;
var width;
var height;

var loaderImgPath = "img/loader.gif";
var loaderImg;
var pipoImgPath = "img/pipo.png";
var xplain1ImgPath = "img/xplain1.jpg";
var xplain2ImgPath = "img/xplain2.jpg";
var bigpicImgPath = "img/bigpic.jpg";

/**
 * Initialization
 */
function init()
{
    // canvas
    canvas = document.getElementById("canvasAsset");
    width = canvas.width = window.innerWidth;
    height = canvas.height = window.innerHeight;
    context = canvas.getContext("2d");
    // assetManager
    assetManager = new AssetManager();
    assetManager.addAssetPath(loaderImgPath);   // ask for asset
    assetManager.loadAssets(showLoadingScreen, onAssetLoadingError);    // download asset
}

/**
 * Logic
 */
function showLoadingScreen()
{
    loaderImg = assetManager.getAsset(loaderImgPath);   // get asset
    document.body.appendChild(loaderImg);   // add asset to body
    assetManager.addAssetPathArray([pipoImgPath, xplain1ImgPath, xplain2ImgPath, bigpicImgPath]);   // ask for multiple assets
    assetManager.loadAssets(showLoadedImages, onAssetLoadingError);   // download asset
}

function showLoadedImages()
{
    document.body.removeChild(loaderImg);   // remove asset from body
    context.drawImage(assetManager.getAsset(bigpicImgPath), 0, 0);  // add asset to canvas
    context.drawImage(assetManager.getAsset(xplain1ImgPath), 0, 50);  // add asset to canvas
    context.drawImage(assetManager.getAsset(xplain2ImgPath), 400, 300);  // add asset to canvas
    context.drawImage(assetManager.getAsset(pipoImgPath), 30, 450);  // add asset to canvas
    context.drawImage(assetManager.getAsset(pipoImgPath), 150, 220);  // add asset to canvas
    context.drawImage(assetManager.getAsset(pipoImgPath), 70, 110);  // add asset to canvas
    context.drawImage(assetManager.getAsset(pipoImgPath), 200, 290);  // add asset to canvas
}

function onAssetLoadingError()
{
    alert("At least one asset couldn't be loaded!");
}

AssetManager.js

/**
 * You can use this code as you wish for non-commercial projects,
 * but please recommend our site http://www.senaeh.de.
 * @author Philipp Zins/Donald Pipowitch
 */

/**
 * Thanks to http://www.html5rocks.com/en/tutorials/games/assetmanager/,
 * which helped a lot.
 */

/**
 * AssetManager
 * Loads images. Can return loaded images.
 * Ver.: 0.0.1
 */
function AssetManager()
{
    this.successCount = 0;
    this.errorCount = 0;
    this.downloadQueue = [];
    this.downloadQueueLength = 0;
    this.cache = [];
    this.isLoading = false;
    this.onLoadAssetsCompleteCallback = null;
    this.onLoadAssetsErrorCallback = null;

    this.onLoadAssetSuccess = onLoadAssetSuccess;
    this.onLoadAssetError = onLoadAssetError;
    this.onLoadAssetsComplete = onLoadAssetsComplete;
    this.isLoadAssetsComplete = isLoadAssetsComplete;
    this.addAssetPath = addAssetPath;
    this.addAssetPathArray = addAssetPathArray;
    this.loadAssets = loadAssets;
    this.getAsset = getAsset;
}

/**
 * Will be called if an assets was successfully loaded.
 */
function onLoadAssetSuccess()
{
    this.successCount++;
    if (this.isLoadAssetsComplete())
        this.onLoadAssetsComplete();
}

/**
 * Will be called if an assets wasn't successfully loaded.
 */
function onLoadAssetError()
{
    this.errorCount++;
    if (this.isLoadAssetsComplete())
        this.onLoadAssetsComplete();
}

/**
 * Check if all assets were loaded.
 * @return Returns true, if all assets were loaded.
 */
function isLoadAssetsComplete()
{
    return this.downloadQueueLength == this.successCount + this.errorCount;
}

/**
 * Will be called if all assets were loaded.
 */
function onLoadAssetsComplete()
{
    var foundErrors = this.errorCount > 0;
    // reset parameter
    this.isLoading = false;
    this.successCount = 0;
    this.errorCount = 0;
    this.downloadQueue.splice(0, this.downloadQueueLength);
    this.downloadQueue.lengh = 0;
    this.downloadQueueLength = 0;
    // callback
    if (this.onLoadAssetsCompleteCallback !== null && !foundErrors)
        this.onLoadAssetsCompleteCallback();
    else if (this.onLoadAssetsErrorCallback !== null && foundErrors)
        this.onLoadAssetsErrorCallback();
}

/**
 * Add a path to an asset to the download queue.
 * @param path The path to an asset.
 */
function addAssetPath(path)
{
    this.downloadQueue.push(path);

}

/**
 * Add an array of asset-paths to the download queue.
 * @param path The path to an asset.
 */
function addAssetPathArray(pathArray)
{
    this.downloadQueue = this.downloadQueue.concat(pathArray);

}

/**
 * Downloads all assets in the download queue.
 * @param onComplete The function which will be called, if all assets were downloaded.
 */
function loadAssets(onComplete, onError)
{
    if (this.isLoading)
    {
        alert("You're allready downloading assets! Please wait until "
                + "the last download is finished!")
        return;
    }
    this.onLoadAssetsCompleteCallback = onComplete;
    this.onLoadAssetsErrorCallback = onError;
    this.isLoading = true;
    this.downloadQueueLength = this.downloadQueue.length;
    if (this.downloadQueueLength == 0)
        this.onLoadAssetsComplete();
    var that = this;
    for (var i = 0; i < this.downloadQueueLength; i++)
    {
        var path = this.downloadQueue[i];
        if (!this.cache[path])    // load image only if it wasn't loaded before
        {
            var img = new Image();
            img.addEventListener("load", function()
            {
                that.onLoadAssetSuccess()
            }, false);
            img.addEventListener("error", function()
            {
                that.onLoadAssetError()
            }, false);
            img.src = path;
            this.cache[path] = img;
        }
    }
}

/**
 * Get a specific asset from path.
 * @param path The path to an asset.
 * @return Returns the asset, if it was downloaded before.
 */
function getAsset(path)
{
    return this.cache[path];
}

Fazit

Cool, cool. So kommen wir der Erstellung des ersten Spieles näher 😉 Natürlich gibt es noch viel Verbesserungspotential. Sollte ein Bild nicht geladen werden, gibt es im Augenblick nur eine Fehlermeldung für den kompletten Ladevorgang ohne Auskunft darüber, welches Bild denn genau nicht geladen werden konnte. Außerdem müsste man den AssetManager auch noch so erweitern, dass man Sounds laden kann. Auf jeden Fall bietet die „Klasse“ eine gute Basis für alle, die einen eigenen AssetManager entwerfen möchten.

Was fehlt? Die Live-Vorschau im Browser 😉

Autor: Pipo

...kommt ursprünglich aus der Designerecke, ist aber im Laufe seines Studiums in die Webentwicklung gestolpert. Kann sich seit dem nicht so richtig entscheiden wo er hingehört und wagt deswegen vielleicht die Flucht nach vorne in ein komplett neues Gebiet. Vielleicht Management? Niemand weiß es. Auch er nicht.