1
\$\begingroup\$

I am playing around adapting the example code at Mozilla Dev Network: Square tilemaps implementation: Scrolling maps to fit with a server I wrote myself. There's a link to the original source code at the bottom of the page.

Instead of hard-coding the map (and its layers) into the js page, I can have a server call deliver the map data in a similar format (a 1-dimensional sequence of numbers up to 256). The way the server (and the js interface) is constructed, I first need to get the map Id from the game that is stored on the server and use that id to get (a section of) the map.

This is the function for doing the data fetch:

function LoadGame(gameid) {
$.getJSON('/api/game/' + gameid)
    .done(function (game) {
        if (game !== null) {
            mapid = game.MapId;
            map = AssetLoader.LoadMapSection(mapid, 0);
        }
        else
            console.log("Could not load game with id: " + gameid);
    })
    .fail(function (jqXHR, status, error) {
        console.log(error);
    });
}

The LoadMapSection function looks like this:

AssetLoader.LoadMapSection = function (mapid, pos) {
    if (!(mapid in this.maps)) {
        this.status = statusEnum.LOADING;
        console.log("Loading map section: " + mapid + " at: " + pos);
        $.getJSON('/api/map/section/' + mapid + '/' + pos + '/32/24')
            .done(function (map) {
                AssetLoader.maps[mapid] = map;
                this.status = statusEnum.DONE;
                console.log("Loaded section of map: " + mapid);
            }).fail(function (jqXHR, status, error) {
                console.log(error);
                this.status = statusEnum.ERROR;
            });
    }
    return map;
}

Since the map data may take some time to arrive I am trying to use an AssetLoader, suggested by MDN, to fetch the map data as well as the spritesheets for displaying the map. So, upon game load, I construed this:

Game.load = function () {
return [
    AssetLoader.LoadImage('atlas-elevation', 'assets/atlas/elevation.png'),
    AssetLoader.LoadImage('atlas-landscape', 'assets/atlas/landscape.png'),
    LoadGame(gameid),
   ];
};

To ensure the asynchronicity and wait for all data to be fetched, the following code has been left unchanged:

Game.run = function (context) {
    this.ctx = context;
    this._previousElapsed = 0;
    var p = this.load();
    Promise.all(p).then(function (loaded) {
        this.init();
        window.requestAnimationFrame(this.tick);
    }.bind(this));
};

So the following init-call is supposed to happen after that:

Game.init = function () {
    Keyboard.listenForEvents(
        [Keyboard.LEFT, Keyboard.RIGHT, Keyboard.UP, Keyboard.DOWN]);
    this.tileAtlas = AssetLoader.GetImage('atlas-elevation');
    this.camera = new Camera(map, 512, 512);
};

However, I keep on running into situations where the map is still "undefined" as soon as the Camera code is hit:

function Camera(map, width, height) {
    //if (map != null) {
        this.x = 0;
        this.y = 0;
        this.width = width;
        this.height = height;
        this.maxX = map.Width * scale - width;
        this.maxY = map.Height * scale - height;
    //}
}

I did declare map at the top of the js file as global variable, so I am not sure where I am going wrong here.

\$\endgroup\$
1
\$\begingroup\$

It appears there may be some confusion regarding promises. From Mozilla's Promise.All documentation:

The Promise.all(iterable) method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.

In your case you are using Promise.All to ensure your game has fully loaded, specifically that all functions returned by Game.load have completed. However in order for this to work each of these functions must return a promise.

The code hasn't been provided for AssetLoader.LoadImage, but looking at LoadGame it is clear that no promise is returned - in fact, nothing is returned.

However because JQuery's getJSON function return a jqXHR object, which implemnets the Promise interface, you can simply make LoadGame return the results of your $.getJSON call. For example:

function LoadGame(gameid) {
    // Note we're returning the Promise.
    return $.getJSON('/api/game/' + gameid)
    .done(function (game) {
        if (game !== null) {
            mapid = game.MapId;
            map = AssetLoader.LoadMapSection(mapid, 0);
        }
        else
            console.log("Could not load game with id: " + gameid);
    })
    .fail(function (jqXHR, status, error) {
        console.log(error);
    });
}

Additionally because AssetLoader.LoadMapSection is also asynchronous you'll need to add this change to that method aswell, and any other nested functions which are asynchronous.

\$\endgroup\$
  • 1
    \$\begingroup\$ That makes a 100% sense! Thanks! Promises (in js) are new to me and I couldn't quite grasp it. I will try it out and mark your answer as solution when I got it to work! \$\endgroup\$ – Fedor Steeman May 1 '18 at 16:36
  • \$\begingroup\$ Sure thing, let me know if you have any questions for need clarification. \$\endgroup\$ – Jacob Persi May 2 '18 at 20:12
  • 1
    \$\begingroup\$ It turns out I wasn't returning any Promises! I will massage the code until it works and let you know. \$\endgroup\$ – Fedor Steeman May 3 '18 at 8:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.