INT
INT

Reputation: 912

Single canvas sprite animation works, but not if there's two canvases

Working with some animated sprites with the canvas element.

I've managed to setup a test case that works splendidly, but only with one canvas element, there will be any given number of animating sprites in the end.

Can't figure out why?

Working demo.
Broken demo.

  <canvas id="video1"></canvas>
  <input type="button" onclick="PS.draw(1)" value="Play">
  <input type="button" onclick="PS.stop(1)" value="Stop">

  <canvas id="video2"></canvas>
  <input type="button" onclick="PS.draw(2)" value="Play">
  <input type="button" onclick="PS.stop(2)" value="Stop">

  <script>
  var PS = {

    init: function(id) { // setup the premise for the canvases, this data is needed to animate the sprites.
      var sprites = [];
      var context = document.getElementById(src.entries[id].file.ios.filename);
      $(context).attr('width', src.entries[id].file.ios.width);
      $(context).attr('height', src.entries[id].file.ios.height);
      context = context.getContext('2d');

      var array = src.entries[id].file.ios.sprite;
      for (var i = 0; i < array.length; i++) {
        sprites[i] = new Sprite(array[i], { frameW: src.entries[id].file.ios.width, frameH: src.entries[id].file.ios.height, interval: src.entries[id].file.ios.framerate, useTimer: false }); // sprite.js, associates each sprite with given settings.
      }

      var activeSprite = sprites[0];
      var framesDrawn = 0;
      var f = 0;
      var sprite = 0;

      //var filename = src.entries[id].file.ios.filename
      window['sprite' + id] = {
        "context" : context, 
        "activeSprite" : activeSprite, 
        "framesDrawn" : framesDrawn, 
        "f" : f,
        "sprites" : sprites, 
        "sprite" : sprite, 
        "framerate" : src.entries[id].file.ios.framerate, 
        "frames" : src.entries[id].file.ios.frames,
      };
    },

    /*
      ^ How do I pass this on to draw()? Global vars?
    */

    draw: function(id) {
      //var filename = src.entries[id].file.ios.filename;
      //console.log(window.filename.context);
      //console.log(window.sprite+id);

      window['sprite' + id ].player = setTimeout(function() { // I'm here to throttle to approriate framerate
        requestAnimationFrame(function() {
          PS.draw(id);
        });

        window['sprite' + id ].framesDrawn++;
        window['sprite' + id ].f++;
        window['sprite' + id ].activeSprite.draw(window['sprite' + id ].context, 0, 0);

        if(window['sprite' + id ].f == window['sprite' + id ].frames) { // you've reached the total frames of animation, reset to first spritesheet
          window['sprite' + id ].sprite = 0;
          window['sprite' + id ].activeSprite = window['sprite' + id ].sprites[window['sprite' + id ].sprite];
          window['sprite' + id ].framesDrawn = 0;
          window['sprite' + id ].f = 0;     
        }

        if(window['sprite' + id ].framesDrawn == 50) { // each sprite contains 50 images, thus change to new spritesheet
          window['sprite' + id ].sprite++;
          window['sprite' + id ].activeSprite = window['sprite' + id ].sprites[window['sprite' + id ].sprite];
          window['sprite' + id ].framesDrawn = 0;
        }

      }, 1000 / window['sprite' + id ].framerate);
    },

    stop: function(id) {
      //var filename = src.entries[id].file.ios.filename;
      clearTimeout(window['sprite' + id ].player); // some other better way?
    }
  }

  var src ={
    "entries":{
      "1":{
        "file":{
          "ios":{
            "filename":"video1",
            "sprite":["https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite1.jpg","https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite2.jpg","https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite3.jpg"],
            "frames":"115",
            "framerate":"23.976",
            "width":"426",
            "height":"240"
          }
        }
      },
      "2":{
        "file":{
          "ios":{
            "filename":"video2",
            "sprite":["https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite1.jpg","https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite2.jpg","https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite3.jpg"],
            "frames":"115",
            "framerate":"23.976",
            "width":"426",
            "height":"240"
          }
        }
      },
    }
  }

  setTimeout(function() {
    PS.init(1);
    PS.init(2);
  },500)  
  </script>

Upvotes: 0

Views: 286

Answers (1)

Rhumborl
Rhumborl

Reputation: 16609

I haven't been able to work out why exactly, but the problem is the way that sprite.js uses caching and preloading of images to initialise the Sprite. Because both canvas elements are displaying the same set of images, they are conflicting with each other when the second canvas is initialised, so it gets crippled somehow.

The easiest solution seems to be to just disable Sprite image caching:

// override Sprite caching to prevent images conflicting
Sprite.getImageFromCache = function(src) {
    //return images[src] ? images[src] : null;
    return null;
};

Sprite.saveImageToCache = function(src, image) {
    //images[src] = image;
};

Here is an updated fiddle showing it working. http://jsfiddle.net/xbcw7sbb/4/

Note that I also renamed some of your variables to make it a bit more understandable what is what, and attached the filename as a data() object into the canvas to keep it all together.

Plus there was an issue with clicking Play twice causing the animation loop to run double in the canvas, so I created a start(id) method, which only calls draw() if it is not running. draw() itself only needs the context (your filename object), so you can pass that in each time so you don't have to retrieve it from other places on each loop.

/* override Sprite caching to prevent images conflicting */
// (original code commented)

Sprite.getImageFromCache = function(src) {
    //return images[src] ? images[src] : null;
    return null;
};

Sprite.saveImageToCache = function(src, image) {
    //images[src] = image;
};


var PS = {

    init: function (id) {
        // setup the premise for the canvases,
        // this data is needed to animate the sprites.
        var sprites = [];
        var ios = src.entries[id].file.ios;
        var canvas = document.getElementById(ios.filename);
        $(canvas).attr('width', ios.width);
        $(canvas).attr('height', ios.height);
        var context = canvas.getContext('2d');

        var imgList = ios.sprite;
        for (var i = 0; i < imgList.length; i++) {
            sprites[i] = new Sprite(imgList[i], {
                frameW: ios.width,
                frameH: ios.height,
                interval: ios.framerate,
                useTimer: false
            }); // sprite.js, associates each sprite with given settings.
        }

        // attach the context to the canvas so we can retrieve it again
        $(canvas).data('spritecontext', {
            "context": context,
                "activeSprite": sprites[0],
                "framesDrawn": 0,
                "f": 0,
                "sprites": sprites,
                "sprite": 0,
                "framerate": ios.framerate,
                "frames": ios.frames
        });
    },

    start: function(id) {
        var canvasId = src.entries[id].file.ios.filename;
        var canvas = $('#' + canvasId);
        var spritecontext = canvas.data('spritecontext');
        if(!spritecontext.player) {
            PS.draw(id, spritecontext);
        }
    },

    draw: function (id, spritecontext) {
        spritecontext.player = setTimeout(function () {
            // I'm here to throttle to approriate framerate
            requestAnimationFrame(function () {
                PS.draw(id, spritecontext);
            });

            spritecontext.framesDrawn++;
            spritecontext.f++;
            spritecontext.activeSprite.draw(spritecontext.context, 0, 0);

            if (spritecontext.f == spritecontext.frames) {
                // you've reached the total frames of animation,
                // reset to first spritesheet
                spritecontext.sprite = 0;
                spritecontext.activeSprite = spritecontext.sprites[spritecontext.sprite];
                spritecontext.framesDrawn = 0;
                spritecontext.f = 0;
            }

            if (spritecontext.framesDrawn == 50) {
                // each sprite contains 50 images, thus change to new spritesheet
                spritecontext.sprite++;
                spritecontext.activeSprite = spritecontext.sprites[spritecontext.sprite];
                spritecontext.framesDrawn = 0;
            }

        }, 1000 / 23.976);
    },

    stop: function (id) {
        var canvasId = src.entries[id].file.ios.filename;
        var canvas = $('#' + canvasId);
        var spritecontext = canvas.data('spritecontext');
        // some other better way? - probably not
        clearTimeout(spritecontext.player);
        // so we know its stopped
        spritecontext.player = null;
    }
}

var src = {
    "entries": {
        "1": {
            "file": {
                "ios": {
                    "filename": "video1",
                        "sprite":
    ["https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite1.jpg",
     "https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite2.jpg",
     "https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite3.jpg"],
                        "frames": "115",
                        "framerate": "23.976",
                        "width": "426",
                        "height": "240"
                }
            }
        },
        "2": {
            "file": {
                "ios": {
                    "filename": "video2",
                    "sprite": 
    ["https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite1.jpg",
     "https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite2.jpg",
     "https:\/\/dl.dropboxusercontent.com\/u\/3618143\/sprite3.jpg"],
                        "frames": "115",
                        "framerate": "23.976",
                        "width": "426",
                        "height": "240"
                }
            }
        },
    }
}

setTimeout(function () {
    PS.init(1);
    PS.init(2);
}, 500);

Upvotes: 1

Related Questions