Santa Clara Games

dic 10, 2013

Pixel Perfect using Cocos2D-HTML5

As soon as we started developing "Hassleheart", we had to make sure we could use pixel-perfect sprites in Cocos2D HTML5 and make it as painless as possible.

The basic pixel-perfect challenge comes with making sure Cocos2d doesn't apply antialias effects to your textures. Thankfully, this can be easily done by invoking the setAliasTexParameters() function after you create a Sprite. A piece of cake, right?

Well, in the end, we didn't like this solution too much, finding it impractical to expect programmers to remember to call this function every time they create a sprite. I know myself enough not to trust me.  Sooner or later, I would forget to do this, and the game would look all fuzzy.

Another solution could be to extend the cc.Sprite initialize method in order to use call the setAliasTexParameters function after the sprite is created. Example:

var myCCSprite = cc.Sprite.extend({
  initWithSpriteFrame: function ( texture ) {
    this._super();
    this.getTexture().setAliasTexParameters();
  } 
});
This way, we extend the original CCSprite class and use only the
myCCSprite class to create our sprites. All done, right?  Well, not
quite. What about other Cocos 2D classes such as cc.SpriteFrameCache or
cc.SpriteBatchNode? They still use the original cc.Sprite class, knowing
nothing about our myCCSprite extension :,(

Another approach could be to patch the CCSprite.js file
directly, modifying its initializer method. But that would be far from
elegant, hardcoding the assumption that all games are to be
pixel-perfect and forcing us to merge this change any time the Cocos2D
team releases a new version.

The last and most optimal approach involved modifying the initializer
function at runtime like this:

~~~~ {.prettyprint .lang-js .linenums}
var __CCSprite_initWithSpriteFrame = cc.Sprite.prototype.initWithSpriteFrame;
cc.Sprite.prototype.initWithSpriteFrame = function() {
  var ret = __CCSprite_initWithSpriteFrame.apply(this, arguments);

  if ( ret ) {
    this.getTexture().setAliasTexParameters();
  }

  return ret;
};

By extending the original instance method and building on its prototype, we are able to achieve the most elegant solution because we avoid modifying the original implementation.  Now, if Cocos2D changes its implementation, our code will still support it, while at the same time, we ensure that any Cocos2D class that uses cc.Sprite class will be passing through our extended prototyping of the initWithSpriteFrame method.