Function#debounce Useful JavaScript Game Extension #36

I got this from Underscore.js.

###*
Calling a debounced function will postpone its execution until after 
wait milliseconds have elapsed since the last time the function was 
invoked. Useful for implementing behavior that should only happen after 
the input has stopped arriving. For example: rendering a preview of a 
Markdown comment, recalculating a layout after the window has stopped 
being resized...
 
lazyLayout = calculateLayout.debounce(300)
$(window).resize(lazyLayout)
 
@name debounce
@methodOf Function#
@returns {Function} The debounced version of this function.
###
Function::debounce = (wait) ->
  timeout = null
  func = this
 
  return ->
    context = this
    args = arguments
 
    later = ->
      timeout = null
      func.apply(context, args)
 
    clearTimeout(timeout)
    timeout = setTimeout(later, wait)

Already I can see an important use for it. In the code editor we copy out the buffer every keyup. This can lead to a bunch of extra work for the browser when typing. Usually it’s not a big deal because the files are generally small, but on larger files it can add up and cause some lagging. When using debounce the editor will only trigger after enough milliseconds have elapsed since the end of new keyup events. This will keep the IDE from lagging on larger files while you are typing.

Another place debounce is useful is in a game that implements a Halo style health regeneration. Every time the player takes damage a debounced startRegen function could be called. That way the regen will only start after enough time without taking damage has elapsed. In the game version it would probably be better to constrain it to ticks of the engine, or engine elapsed time for more accuracy.

I’m always a big fan of Function functions, they are a great way to reduce boilerplate and solve higher-order problems.

HTML5 JavaScript Pasting Image Data in Chrome

Pasting anything other than simple text into the browser has historically been stupendously impossible. Until now… It’s a miracle, image data pasted into the browser can be retrieved with JavaScript (at least since Chrome 13.0.782.220)! Just use this jQuery plugin and start receiving paste events with images all their sweet gooey image data. This picks up paste events initiated with Ctrl+V, it does not provide arbitrary clipboard access (which is probably sane from a security standpoint). What’s insane is that it used to be so hard to get pasted image data, but that is all behind us now.

A magical wizard

Copy and paste me in this webpage!

Give it a try now, right click this wizard and choose “Copy Image”, then jam Ctrl+V or Command+V. Be amazed (unless you’re not using Chrome, and in that case, get off my lawn). It also works in the pixel editor on PixieEngine. Use the wizard there too, the editor currently only handles small images. Generally there is no size restriction, you can even paste image data from your favorite image editing programs, boot one up and try it out on this page.

# Created by STRd6
# MIT License
# jquery.paste_image_reader.js.coffee
(($) ->
  $.event.fix = ((originalFix) ->
    (event) ->
      event = originalFix.apply(this, arguments)
 
      if event.type.indexOf('copy') == 0 || event.type.indexOf('paste') == 0
        event.clipboardData = event.originalEvent.clipboardData
 
      return event
 
  )($.event.fix)
 
  defaults =
    callback: $.noop
    matchType: /image.*/
 
  $.fn.pasteImageReader = (options) ->
    if typeof options == "function"
      options =
        callback: options
 
    options = $.extend({}, defaults, options)
 
    this.each ->
      element = this
      $this = $(this)
 
      $this.bind 'paste', (event) ->
        found = false
        clipboardData = event.clipboardData
 
        Array::forEach.call clipboardData.types, (type, i) ->
          return if found
 
          if type.match(options.matchType) or clipboardData.items[i].type.match(options.matchType)
            file = clipboardData.items[i].getAsFile()
 
            reader = new FileReader()
 
            reader.onload = (evt) ->
              options.callback.call element,
                dataURL: evt.target.result
                event: evt
                file: file
                name: file.name
 
            reader.readAsDataURL(file)
 
            found = true
 
)(jQuery)

Pretty simple plugin, eh? The first part is extending the copy and paste events in jQuery with the clipboardData object. Once the paste events have been extended with all the clipboard data that Chrome provides we can use that data to extract the image contents.

The meat of the plugin is binding a paste event to all the elements in the selector. When a paste event is triggered we loop through each MIME type until we hit one that claims to be an image. Once we find it we get the corresponding file data and load it as a dataURL. This can be used directly in CSS or passed on to the server and chopped up, base64 decoded, and stored as a regular png.

To use it you choose what element to listen to paste events on (html should get all of them). I haven’t messed around much with scoping it to other elements, but I don’t see why it wouldn’t work.

$("html").pasteImageReader (results) ->
  {filename, dataURL} = results
 
  $("body").css
    backgroundImage: "url(#{dataURL})"

Now when someone pastes a copied image to the page it sets the background to the pasted image. This is just scratching the surface, but the great thing is that you can now capture paste events containing images in pure JS/HTML.

What’s that? CoffeeScript is hot for you to handle? Well here’s the JS version:

// Created by STRd6
// MIT License
// jquery.paste_image_reader.js
(function($) {
  var defaults;
  $.event.fix = (function(originalFix) {
    return function(event) {
      event = originalFix.apply(this, arguments);
      if (event.type.indexOf('copy') === 0 || event.type.indexOf('paste') === 0) {
        event.clipboardData = event.originalEvent.clipboardData;
      }
      return event;
    };
  })($.event.fix);
  defaults = {
    callback: $.noop,
    matchType: /image.*/
  };
  return $.fn.pasteImageReader = function(options) {
    if (typeof options === "function") {
      options = {
        callback: options
      };
    }
    options = $.extend({}, defaults, options);
    return this.each(function() {
      var $this, element;
      element = this;
      $this = $(this);
      return $this.bind('paste', function(event) {
        var clipboardData, found;
        found = false;
        clipboardData = event.clipboardData;
        return Array.prototype.forEach.call(clipboardData.types, function(type, i) {
          var file, reader;
          if (found) {
            return;
          }
          if (type.match(options.matchType) || clipboardData.items[i].type.match(options.matchType)) {
            file = clipboardData.items[i].getAsFile();
            reader = new FileReader();
            reader.onload = function(evt) {
              return options.callback.call(element, {
                dataURL: evt.target.result,
                event: evt,
                file: file,
                name: file.name
              });
            };
            reader.readAsDataURL(file);
            return found = true;
          }
        });
      });
    });
  };
})(jQuery);

Boomstick – Hella Joysticks in JavaScript

Boomstick!

Hey! I know that you want to use joysticks and gamepads in your HTML/JavaScript games, so what are you waiting for? Check out Boomstick and give it a shot.

This cross-platform, multi-browser extension gives JavaScript access to all joysticks on all platforms. Pretty rad.

Right now I’m having great results with it in my game Red Ice. It can support up to 8 joysticks straight away, so don’t worry.

If you use any browser on Windows, then run the Windows installer.

If you’re using Chrome on any OS, bust out the Chrome Extension

Once you’ve got it plug in a joystick and check out the readme for how to access joystick data. Or you could just start developing on PixieEngine which has libraries for joystick handling built in.

But at any rate, use it, abuse it, and let me know what awesome games you make (also if you encounter any bugs or issues.)

Peace!

Operator Overloading in PaperScript

I was looking into how paper.js did their operator overloading and what I found was pretty clever:

var operators = {
  '+': 'add',
  '-': 'subtract',
  '*': 'multiply',
  '/': 'divide',
  '%': 'modulo',
  '==': 'equals',
  '!=': 'equals'
};
 
function $eval(left, operator, right) {
  var handler = operators[operator];
  if (left && left[handler]) {
    var res = left[handler](right);
    return operator == '!=' ? !res : res;
  }
  switch (operator) {
  case '+': return left + right;
  case '-': return left - right;
  case '*': return left * right;
  case '/': return left / right;
  case '%': return left % right;
  case '==': return left == right;
  case '!=': return left != right;
  default:
    throw new Error('Implement Operator: ' + operator);
  }
};

Though technically this is PaperScript and not JavaScript, so they can get around the lack of native JS operator overloading. The PaperScript code is given a minimal compile pass which replaces calls to arithmetic operators with calls to $eval().

// PaperScript
var p = new Point(10, 20);
var r = p * 5;
 
// JavaScript
var p = new Point(10, 20);
var r = $eval(p, "*", 5);

Related PaperScript source

So as long as Point#multiply is defined then the operator is effectively overloaded.

I’m very interested in the implications for PixieEngine and CoffeeScript.

Better window.requestAnimationFrame Shim

There’s been quite a bit of copy/pasting of the compatibility shim for requestAnimationFrame going around on the Net, which is all fine and dandy, but sadly the popular shim isn’t compatible with passing in the timestamp on the setTimeout fallback.

Here’s the improved one:

window.requestAnimationFrame ||= 
  window.webkitRequestAnimationFrame || 
  window.mozRequestAnimationFrame    || 
  window.oRequestAnimationFrame      || 
  window.msRequestAnimationFrame     || 
  (callback, element) ->
    window.setTimeout( ->
      callback(+new Date())
    , 1000 / 60)

Also, a JavaScript version for those of you who have suffered the misfortune of not choosing to use CoffeeScript.

window.requestAnimationFrame || (window.requestAnimationFrame = 
  window.webkitRequestAnimationFrame || 
  window.mozRequestAnimationFrame    || 
  window.oRequestAnimationFrame      || 
  window.msRequestAnimationFrame     || 
  function(callback, element) {
    return window.setTimeout(function() {
      callback(+new Date());
  }, 1000 / 60);
});

Take notice of the param passed to the callback +new Date(). It’s the timestamp that leading implementations pass in.

It has also been rumored that Chrome10 doesn’t pass in the timestamp either, so for super reliability you’ll want to have timestamp ||= +new Date() as the first line of your callback as well.

There were also several issues mentioned in this gist which have not been decisively resolved, so any feedback is certainly welcome. I also decided to just polyfill that ‘ish, because it seems legit enough.

Good luck and Godspeed.

jQuery Plugin: TakeClass

This comes up all the time, you have an element, and you want to select it to be the sole active element from among it’s siblings. With jQuery that is simple enough:

element.addClass("active").siblings().removeClass("active")

Though it is not as simple as it can be and it also fails to chain well. The solution is a super simple jQuery plugin:

(($) ->
  $.fn.takeClass = (name) ->
    this.addClass(name).siblings().removeClass(name)
 
    return this
)(jQuery)

Now you can really go nuts!

newElement.appendTo(layerSelect).takeClass("active").find(".name").mousedown()

Optimizing JSDoc Toolkit for Large JS Files

Recently I was running into a problem with JSDock Toolkit where it would throw an error saying js: exception from uncaught JavaScript throw: java.lang.OutOfMemoryError: Java heap space.

The solution was to add the -s flag so that it would not try and generate a large, marked-up source file for viewing with the docs.

java -jar jsdoc-toolkit/jsrun.jar jsdoc-toolkit/app/run.js gamelib.js -d=docs -n -s

I also added the -n flag to prevent it from documenting methods that didn’t have comments, such as minified jQuery or Box2d, though this was irrelevant in preventing the js: exception from uncaught JavaScript throw: java.lang.OutOfMemoryError: Java heap space error.

Array#wrap Useful JavaScript Game Extension #29

/**
 * Pretend the array is a circle and grab a new array containing length elements. 
 * If length is not given return the element at start, again assuming the array 
 * is a circle.
 *
 * @param {Number} start The index to start wrapping at, or the index of the 
 * sole element to return if no length is given.
 * @param {Number} [length] Optional length determines how long result 
 * array should be.
 * @returns The element at start mod array.length, or an array of length elements, 
 * starting from start and wrapping.
 * @type Object or Array
 */
Array.prototype.wrap = function(start, length) {
  if(length != null) {
    var end = start + length;
    var result = [];
 
    for(var i = start; i < end; i++) {
      result.push(this[i.mod(this.length)]);
    }
 
    return result;
  } else {
    return this[start.mod(this.length)];
  }
};

Simple use:

[1, 2, 3].wrap(-1) => 3

[1, 2, 3].wrap(6) => 1

Or get fancy and tile your kitchen:

["w", "o", "o", "o"].wrap(0, 16) => ["w", "o", "o", "o", "w", "o", "o", "o", "w", "o", "o", "o", "w", "o", "o", "o"]

Array#clear Useful JavaScript Game Extension #28

Here’s a useful little method that empties out an array. Sure you could type myArray.length = 0; but that is kind of weird. For some reason myArray.clear() just makes more sense to me.

/**
* Empties the array of it's contents. It is modified in place.
*
* @type Array
* @returns this, now emptied.
*/
Array.prototype.clear = function() {
  this.length = 0;
  return this;
};

The series continues…

jQuery Drag Image From Desktop Plugin

Here’s the CoffeeScript for a jQuery plugin I wrote that makes accepting images dragged in from the desktop super easy. The event.fix part at the beginning is because jQuery currently doesn’t pass on the dataTransfer attribute of events. Once that’s taken care of we create the plugin.

(($) ->
  $.event.fix = ((originalFix) ->
    (event) ->
      event = originalFix.apply(this, arguments)
 
      if event.type.indexOf('drag') == 0 || event.type.indexOf('drop') == 0
        event.dataTransfer = event.originalEvent.dataTransfer
 
      event
 
  )($.event.fix)
 
  $.fn.dropImageReader = (callback) ->
    stopFn = (event) ->
      event.stopPropagation()
      event.preventDefault()
 
    this.each () ->
      element = this
      $this = $(this)
 
      $this.bind 'dragenter dragover dragleave', stopFn
 
      $this.bind 'drop', (event) ->
        stopFn(event)
 
        Array.prototype.forEach.call event.dataTransfer.files, (file) ->
          imageType = /image.*/
          if !file.type.match(imageType)
            return
 
          reader = new FileReader()
 
          reader.onload = (evt) ->
            callback.call(element, file, evt)
 
          reader.readAsDataURL(file)
 
)(jQuery)

The plugin takes a callback that will be called when any of the matched elements receive an image file via the drop event. Here’s an example usage:

$(".tiles").dropImageReader (file, event) ->
  img = $ "<img/>",
    alt: file.name
    src: event.target.result
    title: file.name
 
  $(this).append img

For image drops all you really care about is the file name and the data url, but if for some reason you need different file results here’s the line to modify: reader.readAsDataURL(file). You can also extend or alter what is passed to the callback if this is too mundane for your needs.

And here’s the JS version for anyone eager to copy/paste:

  (function() {
    $.event.fix = (function(originalFix) {
      return function(event) {
        event = originalFix.apply(this, arguments);
 
        if (event.type.indexOf('drag') === 0 || event.type.indexOf('drop') === 0) {
          event.dataTransfer = event.originalEvent.dataTransfer;
        }
 
        return event;
      };
    })($.event.fix);
 
    $.fn.dropImageReader = function(callback) {
      var stopFn;
 
      stopFn = function(event) {
        event.stopPropagation();
        event.preventDefault();
      };
 
      return this.each(function() {
        var $this, element;
 
        element = this;
        $this = $(this);
 
        $this.bind('dragenter dragover dragleave', stopFn);
 
        $this.bind('drop', function(event) {
          stopFn(event);
 
          Array.prototype.forEach.call(event.dataTransfer.files, function(file) {
            var imageType, reader;
 
            imageType = /image.*/;
            if (!file.type.match(imageType)) {
              return;
            }
 
            reader = new FileReader();
 
            reader.onload = function(evt) {
              return callback.call(element, file, evt);
            };
 
            reader.readAsDataURL(file);
          });
        });
      });
    };
  })(jQuery);

Happy image dropping!