SlideShare a Scribd company logo
1 of 44
The Canvas API
 for Rubyists
  Cascadia Ruby, August 3rd, 2012
       Harry Dean Hudson
Who Am I?




 Dean Hudson, @deanero
Bit Twiddler at Massively Fun
We make games with
open web technology.
  (And are hiring).

http://massivelyfun.com
    @massivelyfun
Word * Word
What is this talk?
• Actually less about Canvas API and more
   about patterns in which it can be used...
• No Ruby! (Sorry Rubyists).
• Play along! https://github.com/massivelyfun/
   canvas-playground
• ...in 5 / 7 / 5.
Canvas API:
2D graphics in browser.
   It is quite simple.
Canvas is
• 2D, immediate mode, graphics API
• Well supported in modern browsers
• A collection of drawing calls
• Not Flash!
(function () {

    var ImageData = function (width, height) {

         this.width     = width   || 10;

         this.height = height || 10;

         numPixels = this.width * this.height;

         this.data = new Uint8Array(numPixels * 4);

    };



    var CanvasGradient = function () {};



    CanvasGradient.prototype.addColorStop = function (offset, color) {

         return undefined;

    };



    var CanvasPattern = function (image, repetitionStyle) {

         this.image = image;

         this.repetitionStyle = repetitionStyle;

    };



    var TextMetrics = function (ctx, text) {




                                           this...
         // quick and dirty style

         var fontSize = parseInt(ctx.font),

             chars = text.split().length;

         this.width = fontSize * chars;

    }



    var CanvasRenderingContext2D = function (canvas) {

         this.canvas                         = canvas;

         this.fillStyle                      = "rgb(0,0,0)";

         this.font                           = "10px sans-serif";

         this.globalAlpha                    = 1.0;

         this.globalCompositionOperation = "source-over";

         this.lineCap                        = "butt";

         this.lineJoin                       = "miter";

         this.lineWidth                      = 1.0;

         this.miterLimit                     = 10;

         this.textAlign                      = "start";

         this.textBaseLine                   = "alphabetic";

         this.shadowBlur                     = 0;

         this.shadowColor                    = "rgba(0,0,0,0)";

         this.shadowOffsetX                  = 0;

         this.shadowOffsetY                  = 0;

         this.strokeStyle                    = "rgb(0,0,0)";



         this.__width              = this.canvas.width;

         this.__height             = this.canvas.height;

         this.__imageData          = null; // don't do this until we need it, it's a memory hog.

                                           // new ImageData(this.__width, this.__height);

         this.__curX               = 0;

         this.__curY               = 0;
this.__openSubpath      = true;

     this.__initTime         = new Date();

     this.__lastUpdateTime = null;

     this.__lastFillTime     = null;

     this.__updateCount      = 0;

     this.__fillCount        = 0;

};



CanvasRenderingContext2D.prototype.__update = function () {

     var args = Array.prototype.slice.call(arguments);

     this.__lastUpdateTime = new Date();

     this.__updateCount++;

}

CanvasRenderingContext2D.prototype.__fill = function () {

     var args = Array.prototype.slice.call(arguments);

     this.__lastFillTime = new Date();

     this.__fillCount++;

}



// Stub out the real methods. I'm explicitly returning undefined

// in cases where the API calls for void return, so as to be clear

// about the intent. This is a simple sub-set of the API focused

// on operations for images. TODO: implement transforms and state.

CanvasRenderingContext2D.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) {

     this.__openSubpath      = true;




...plus this...
     return undefined;

};

CanvasRenderingContext2D.prototype.arcTo = function (x1, y1, x2, y2, radius) {

     this.__openSubpath      = true;

     this.__curX = x2;

     this.__curY = y2;

     return undefined;

};

CanvasRenderingContext2D.prototype.beginPath = function () {

     this.__openSubpath = true;

     return undefined;

};

CanvasRenderingContext2D.prototype.bezierCurveTo = function (cpX1, cpY1, cpX2, cpY2, x, y) {

     this.__openSubpath      = true;

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.clearRect = function () {

     return undefined;

};

CanvasRenderingContext2D.prototype.clip = function () {

     return undefined;

};

CanvasRenderingContext2D.prototype.closePath = function () {

     this.__openSubpath = false;

     return undefined;

};

CanvasRenderingContext2D.prototype.createImageData = function () {

     var args = Array.prototype.slice.call(arguments);
CanvasRenderingContext2D.prototype.clip = function () {

     return undefined;

};

CanvasRenderingContext2D.prototype.closePath = function () {

     this.__openSubpath = false;

     return undefined;

};

CanvasRenderingContext2D.prototype.createImageData = function () {

     var args = Array.prototype.slice.call(arguments);



     if (args[0].hasOwnProperty('data') && typeof args[0].data !== 'undefined') {

         return new ImageData(args[0].data.length, 1);

     } else if (typeof args[0] === 'number' && typeof args[1] === 'number') {

         return new ImageData(args[0], args[1]);

     } else {

         throw new Error("Invalid arguments. createImageData() takes 1 or 2 args.");

     }

};

CanvasRenderingContext2D.prototype.createLinearGradient = function (xStart, yStart, xEnd, yEnd) {

     return new CanvasGradient();

};

CanvasRenderingContext2D.prototype.createPattern = function (image, repetitionStyle) {

     return new CanvasPattern(image, repetitionStyle);

};

CanvasRenderingContext2D.prototype.createRadialGradient = function (xStart, yStart, radiusStart, xEnd, yEnd, radiusEnd) {




...plus this...
     return new CanvasGradient();

};

CanvasRenderingContext2D.prototype.drawImage = function () {

     switch(arguments.length) {

     case 3:

         return CanvasRenderingContext2D.prototype.__drawImage3.apply(this, arguments);

     case 5:

         return CanvasRenderingContext2D.prototype.__drawImage5.apply(this, arguments);

     case 9:

         return CanvasRenderingContext2D.prototype.__drawImage9.apply(this, arguments);

     default:

         throw new Error("Invalid number of arguments. drawImage() takes, 3, 5, or 9 args.");

     }

};



// All that contortion and I don't do anything with it. I'm stubbing

// this out in case I want to at some point.

CanvasRenderingContext2D.prototype.__drawImage3 = function (image, dx, dy) {

     return undefined;

};

CanvasRenderingContext2D.prototype.__drawImage5 = function (image, dx, dy, dw, dh) {

     return undefined;

};

CanvasRenderingContext2D.prototype.__drawImage9 = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {

     return undefined;

};

CanvasRenderingContext2D.prototype.fill = function () {

     return undefined;

};

CanvasRenderingContext2D.prototype.fillRect = function (x, y, width, height) {
return retImageData;

};

CanvasRenderingContext2D.prototype.isPointPath = function (x, y) {

     return true;

};

CanvasRenderingContext2D.prototype.lineTo = function (x, y) {

     this.__openSubpath      = true;

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.measureText = function (text) {

     return new TextMetrics(this, text);

};

CanvasRenderingContext2D.prototype.moveTo = function (x, y) {

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.putImageData = function (insertData, dx, dy, sx, sy, sw, sh) {

     if (arguments.length !== 7)

         throw new Error("putImageData requires 7 arguments")



     var imageData = this.__imageData || new ImageData(this.__width,   this.__height),




...plus this...
         startAt     = dx * dy * 4 + dx + 4,

         fromData,

         fromOffset = sx * sy * 4 + sx * 4,

         fromNumPixels = sw * sh * 4 + sw * 4,

         endAt = imageData.length - 1,

         howMany;



     if (typeof fromOffset === 'number' && typeof fromNumPixels === 'number') {

         fromData = insertData.data.slice(fromOffset, fromOffset + fromNumPixels);

     } else {

         fromData = insertData.data;

     }



     startAt + fromData.length > endAt ? howMany = endAt - startAt : howMany = startAt + fromData.length;

     imageData.data.splice(startAt, howMany, fromData);



     return undefined;

};

CanvasRenderingContext2D.prototype.quadraticCurveTo = function (cpX, cpY, x, y) {

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.rect = function (x, y, width, height) {

     this.__curX = x;

     this.__curY = y;

     return undefined;

};

CanvasRenderingContext2D.prototype.restore = function () {

     return undefined;
return undefined;

    };

    CanvasRenderingContext2D.prototype.restore = function () {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.rotate = function (angle) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.save = function () {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.scale = function (sx, sy) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.setTransform = function (a, b, c, d, e, f) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.stroke = function () {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.strokeRect = function (x, y, width, height) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.strokeText = function (text, x, y, max) {

         return undefined;




           ...and this.
    };

    CanvasRenderingContext2D.prototype.transform = function (a, b, c, d, e, f) {

         return undefined;

    };

    CanvasRenderingContext2D.prototype.translate = function (dx, dy) {

         return undefined;

    };



    var Canvas = function () {

         this.width   = 10; // API default is 300 x 150, but that makes

         this.height = 10; // our ImageData a memory hog.

    };



    Canvas.prototype.getContext = function (cxtType) {

         return new CanvasRenderingContext2D(this);

    };



    Canvas.prototype.toDataURL = function () {

         var buf = new Buffer("Say hello to my little friend.");

         return "data:text/plain;base64," + buf.toString('base64');

    };



    module.exports = Canvas;

})();
The CanvasContext:
It has all the calls to draw
  Pixels to the screen.
Simple, no?
cvs = document.getElementById("canvas")
ctx = cvs.getContext("2D")

# ctx has all your draw methods...

ctx.fillText("Hello!", 100, 100, 200)
Simple, no?
cvs = document.getElementById("canvas")
ctx = cvs.getContext("2D")

# ctx has all your draw methods...

ctx.fillText("Hello!", 100, 100, 200)

     You’ll only see this from here out.
dean@dean:~$ grep prototype canvas.js |
                 wc -l
                   38

  You can learn the draw
    API in a weekend!
dean@dean:~$ grep prototype canvas.js |
                 wc -l
                   38

  You can learn the draw
     API in a weekend!
...so, on to bigger things!
Browser event loops
Are not well suited for games
   You must roll your own
Simple Game Loop
 • Handle queued UI/server events
 • Update state of “things” on screen.
 • Re-draw!
# a fake game loop
gameLoop =
  run: ->
    @handleQueuedEvents()
    @update()
    @draw()
    # loop somehow?
setInterval()?
setTimeout()?
NO!
16 ms !=
16.666...ms
    Text

 (60 FPS)
For accurate loops
requestAnimationFrame()
 Will bring you great joy
requestAnimationFrame
 allows the browser to
manage screen updates
      efficiently...
requestAnimationFrame
 allows the browser to
manage screen updates
      efficiently...

 ...but is not uniformly
        supported.
buildRAFPolyfill = ->
  requestAnimationFrame =
    requestAnimationFrame         ||
    webkitRequestAnimationFrame   ||
    mozRequestAnimationFrame      ||
    oRequestAnimationFrame        ||
    msRequestAnimationFrame       ||
    -> (cb, elt)
      setTimeout (cb) ->
        cb(+new Date())
      , 1000 / 60
class GameLoop
  constructor: (cvs) ->
    @ctx       = cvs.getContext("2D")
    @entities = []

  addEntity: (entity) ->
    @entities.push(entity)

  run: () =>
    tick = @frameId = requestAnimationFrame(@run)

    # Update entities
    entity.update(tick) for entity in @entities

    # Draw!
    entity.draw(@ctx) for entity in @entities

  stop: ->
    root.cancelAnimationFrame(@frameId)
class GameLoop
  constructor: (cvs) ->
    @ctx       = cvs.getContext("2D")
    @entities = []

  addEntity: (entity) ->
    @entities.push(entity)

  run: () =>
    tick = @frameId = requestAnimationFrame(@run)

    # Update entities
    entity.update(tick) for entity in @entities

    # Draw!
    entity.draw(@ctx) for entity in @entities

  stop: ->
    root.cancelAnimationFrame(@frameId)

requestAnimationFrame returns a int “id”
Entities and Rects:
The largest things are built
 From just these pieces.
Simple Game
   Entity
• Respond to update call
• Manage position, height, width
• Delegate draw calls
# Game level object
class Entity
  # Rect is a drawing primitive
  constructor: (options = {}) ->
    @rect     = options.rect ? null
    {x, y}    = options
    @position = {x: x, y: y}

  update: (tick) ->
    # override and do something interesting here.

  draw: (ctx) ->
    # delegate to your drawing primitive!
    @rect.draw(ctx)

module.exports = Entity
# Game level object
class Entity
  # Rect is a drawing primitive
  constructor: (options = {}) ->
    @rect     = options.rect ? null
    {x, y}    = options
    @position = {x: x, y: y}

  update: (tick) ->
    # override and do something interesting here.

  draw: (ctx) ->
    # delegate to your drawing primitive!
    @rect.draw(ctx)

module.exports = Entity

         Game level logic, once per tick
# Game level object
class Entity
  # Rect is a drawing primitive
  constructor: (options = {}) ->
    @rect     = options.rect ? null
    {x, y}    = options
    @position = {x: x, y: y}

  update: (tick) ->
    # override and do something interesting here.

  draw: (ctx) ->
    # delegate to your drawing primitive!
    @rect.draw(ctx)

module.exports = Entity

               DELEGATE!!!!!
Simple Draw
        Primitive
• Associated with Entity
• Responds to draw() call
• Has actual CanvasContext2D drawing calls
• You can determine containment here
  (image hit detection, for instance)
#
# Rect: Drawing primitive for canvas.

class Rect
  constructor: (options = {}) ->
    @width    = options?.width ? 1
    @height   = options?.height ? 1

    {x, y} = options

    @position = {x: x, y: y}

  draw: (canvasCtx) ->
    throw new Error("Implement draw()")

module.exports = Rect
#
# Rect: Drawing primitive for canvas.

class Rect
  constructor: (options = {}) ->
    @width    = options?.width ? 1
    @height   = options?.height ? 1

    {x, y} = options

    @position = {x: x, y: y}

  draw: (canvasCtx) ->
    throw new Error("Implement draw()")

module.exports = Rect

      Your *simple* interface
Rect = require "rect"

# Sometimes we just need the simple things.
# Make a simple box subclass.
class Box extends Rect
  constructor: (options = {}) ->
    super(options)

  draw: (ctx) ->
    {x, y} = @position.get()
    ctx.fillRect(x, y, @width, @height)

module.exports = Box
Rect = require "rect"

# Sometimes we just need the simple things.
# Make a simple box subclass.
class Box extends Rect
  constructor: (options = {}) ->
    super(options)

  draw: (ctx) ->
    {x, y} = @position.get()
    ctx.fillRect(x, y, @width, @height)

module.exports = Box


                Implement draw
Rect = require "rect"

# Sometimes we just need the simple things.
# Make a simple box subclass.
class Box extends Rect
  constructor: (options = {}) ->
    super(options)

  draw: (ctx) ->
    {x, y} = @position.get()
    ctx.fillRect(x, y, @width, @height)

module.exports = Box


      Do work on CanvasContext2D
Canvas tests are hard;
The one true way is to cheat.
Stub, Stub, stub the world.
CanvasRenderingContext2D.prototype.__update = function () {
    var args = Array.prototype.slice.call(arguments);
    this.__lastUpdateTime = new Date();
    this.__updateCount++;
}
CanvasRenderingContext2D.prototype.__fill = function () {
    var args = Array.prototype.slice.call(arguments);
    this.__lastFillTime = new Date();
    this.__fillCount++;
}

//   Stub out the real methods. I'm explicitly returning undefined
//   in cases where the API calls for void return, so as to be clear
//   about the intent. This is a simple sub-set of the API focused
//   on operations for images. TODO: implement transforms and state.

CanvasRenderingContext2D.prototype.arcTo = function (x1, y1, x2, y2, radius) {
    this.__openSubpath    = true;
    this.__curX = x2;
    this.__curY = y2;
    return undefined;



                               ...etc.
};
Questions?
Thanks!




dean@massivelyfun.com, @deanero
     http://massivelyfun.com

More Related Content

What's hot

openFrameworks 007 - GL
openFrameworks 007 - GL openFrameworks 007 - GL
openFrameworks 007 - GL roxlu
 
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic LabsTypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic LabsAlfonso Peletier
 
Hidden Gems in Swift
Hidden Gems in SwiftHidden Gems in Swift
Hidden Gems in SwiftNetguru
 
JavaScript Design Patterns
JavaScript Design PatternsJavaScript Design Patterns
JavaScript Design PatternsDerek Brown
 
C# v8 new features - raimundas banevicius
C# v8 new features - raimundas baneviciusC# v8 new features - raimundas banevicius
C# v8 new features - raimundas baneviciusRaimundas Banevičius
 
Exploring Canvas
Exploring CanvasExploring Canvas
Exploring CanvasKevin Hoyt
 
Oxygine 2 d objects,events,debug and resources
Oxygine 2 d objects,events,debug and resourcesOxygine 2 d objects,events,debug and resources
Oxygine 2 d objects,events,debug and resourcescorehard_by
 
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Writing SOLID C++ [gbgcpp meetup @ Zenseact]Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Writing SOLID C++ [gbgcpp meetup @ Zenseact]Dimitrios Platis
 
openFrameworks 007 - graphics
openFrameworks 007 - graphicsopenFrameworks 007 - graphics
openFrameworks 007 - graphicsroxlu
 
JavaScript - i och utanför webbläsaren (2010-03-03)
JavaScript - i och utanför webbläsaren (2010-03-03)JavaScript - i och utanför webbläsaren (2010-03-03)
JavaScript - i och utanför webbläsaren (2010-03-03)Anders Jönsson
 
Functional microscope - Lenses in C++
Functional microscope - Lenses in C++Functional microscope - Lenses in C++
Functional microscope - Lenses in C++Alexander Granin
 
Cursor implementation
Cursor implementationCursor implementation
Cursor implementationvicky201
 

What's hot (20)

openFrameworks 007 - GL
openFrameworks 007 - GL openFrameworks 007 - GL
openFrameworks 007 - GL
 
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic LabsTypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
 
Hidden Gems in Swift
Hidden Gems in SwiftHidden Gems in Swift
Hidden Gems in Swift
 
JavaScript Design Patterns
JavaScript Design PatternsJavaScript Design Patterns
JavaScript Design Patterns
 
Css5 canvas
Css5 canvasCss5 canvas
Css5 canvas
 
C# v8 new features - raimundas banevicius
C# v8 new features - raimundas baneviciusC# v8 new features - raimundas banevicius
C# v8 new features - raimundas banevicius
 
Encoder + decoder
Encoder + decoderEncoder + decoder
Encoder + decoder
 
Exploring Canvas
Exploring CanvasExploring Canvas
Exploring Canvas
 
WebGL 2.0 Reference Guide
WebGL 2.0 Reference GuideWebGL 2.0 Reference Guide
WebGL 2.0 Reference Guide
 
Oxygine 2 d objects,events,debug and resources
Oxygine 2 d objects,events,debug and resourcesOxygine 2 d objects,events,debug and resources
Oxygine 2 d objects,events,debug and resources
 
Composite Pattern
Composite PatternComposite Pattern
Composite Pattern
 
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Writing SOLID C++ [gbgcpp meetup @ Zenseact]Writing SOLID C++ [gbgcpp meetup @ Zenseact]
Writing SOLID C++ [gbgcpp meetup @ Zenseact]
 
Js hacks
Js hacksJs hacks
Js hacks
 
openFrameworks 007 - graphics
openFrameworks 007 - graphicsopenFrameworks 007 - graphics
openFrameworks 007 - graphics
 
JavaScript - i och utanför webbläsaren (2010-03-03)
JavaScript - i och utanför webbläsaren (2010-03-03)JavaScript - i och utanför webbläsaren (2010-03-03)
JavaScript - i och utanför webbläsaren (2010-03-03)
 
Functional microscope - Lenses in C++
Functional microscope - Lenses in C++Functional microscope - Lenses in C++
Functional microscope - Lenses in C++
 
Cursor implementation
Cursor implementationCursor implementation
Cursor implementation
 
Say It With Javascript
Say It With JavascriptSay It With Javascript
Say It With Javascript
 
Pointers
PointersPointers
Pointers
 
P1
P1P1
P1
 

Similar to The Canvas API for Rubyists

javascript Model- Render & canvas sample
javascript Model- Render & canvas samplejavascript Model- Render & canvas sample
javascript Model- Render & canvas sampleHika Maeng
 
Bindings: the zen of montage
Bindings: the zen of montageBindings: the zen of montage
Bindings: the zen of montageKris Kowal
 
Object-Oriented Javascript
Object-Oriented JavascriptObject-Oriented Javascript
Object-Oriented Javascriptkvangork
 
Object-Oriented JavaScript
Object-Oriented JavaScriptObject-Oriented JavaScript
Object-Oriented JavaScriptkvangork
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript IntroductionDmitry Sheiko
 
05 Geographic scripting in uDig - halfway between user and developer
05 Geographic scripting in uDig - halfway between user and developer05 Geographic scripting in uDig - halfway between user and developer
05 Geographic scripting in uDig - halfway between user and developerAndrea Antonello
 
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficientTh 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficientBin Shao
 
Jeroen Vloothuis Bend Kss To Your Will
Jeroen Vloothuis   Bend Kss To Your WillJeroen Vloothuis   Bend Kss To Your Will
Jeroen Vloothuis Bend Kss To Your WillVincenzo Barone
 
Webgl para JavaScripters
Webgl para JavaScriptersWebgl para JavaScripters
Webgl para JavaScriptersgerbille
 
need help with code I wrote. This code is a maze gui, and i need hel.pdf
need help with code I wrote. This code is a maze gui, and i need hel.pdfneed help with code I wrote. This code is a maze gui, and i need hel.pdf
need help with code I wrote. This code is a maze gui, and i need hel.pdfarcotstarsports
 
Using Arbor/ RGraph JS libaries for Data Visualisation
Using Arbor/ RGraph JS libaries for Data VisualisationUsing Arbor/ RGraph JS libaries for Data Visualisation
Using Arbor/ RGraph JS libaries for Data VisualisationAlex Hardman
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ EtsyNishan Subedi
 
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...DroidConTLV
 
Developing web-apps like it's 2013
Developing web-apps like it's 2013Developing web-apps like it's 2013
Developing web-apps like it's 2013Laurent_VB
 
Exploring fractals in CSS, @fronttrends, Warsaw, 2015
Exploring fractals in CSS, @fronttrends, Warsaw, 2015Exploring fractals in CSS, @fronttrends, Warsaw, 2015
Exploring fractals in CSS, @fronttrends, Warsaw, 2015pixelass
 

Similar to The Canvas API for Rubyists (20)

javascript Model- Render & canvas sample
javascript Model- Render & canvas samplejavascript Model- Render & canvas sample
javascript Model- Render & canvas sample
 
Bindings: the zen of montage
Bindings: the zen of montageBindings: the zen of montage
Bindings: the zen of montage
 
Object-Oriented Javascript
Object-Oriented JavascriptObject-Oriented Javascript
Object-Oriented Javascript
 
Object-Oriented JavaScript
Object-Oriented JavaScriptObject-Oriented JavaScript
Object-Oriented JavaScript
 
TypeScript Introduction
TypeScript IntroductionTypeScript Introduction
TypeScript Introduction
 
ES6 Overview
ES6 OverviewES6 Overview
ES6 Overview
 
ES6 in Real Life
ES6 in Real LifeES6 in Real Life
ES6 in Real Life
 
05 Geographic scripting in uDig - halfway between user and developer
05 Geographic scripting in uDig - halfway between user and developer05 Geographic scripting in uDig - halfway between user and developer
05 Geographic scripting in uDig - halfway between user and developer
 
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficientTh 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
Th 0230 turbo_chargeyourui-howtomakeyourandroidu_ifastandefficient
 
Player x 0 y ga.docx
Player x 0 y ga.docxPlayer x 0 y ga.docx
Player x 0 y ga.docx
 
Jeroen Vloothuis Bend Kss To Your Will
Jeroen Vloothuis   Bend Kss To Your WillJeroen Vloothuis   Bend Kss To Your Will
Jeroen Vloothuis Bend Kss To Your Will
 
Webgl para JavaScripters
Webgl para JavaScriptersWebgl para JavaScripters
Webgl para JavaScripters
 
need help with code I wrote. This code is a maze gui, and i need hel.pdf
need help with code I wrote. This code is a maze gui, and i need hel.pdfneed help with code I wrote. This code is a maze gui, and i need hel.pdf
need help with code I wrote. This code is a maze gui, and i need hel.pdf
 
Using Arbor/ RGraph JS libaries for Data Visualisation
Using Arbor/ RGraph JS libaries for Data VisualisationUsing Arbor/ RGraph JS libaries for Data Visualisation
Using Arbor/ RGraph JS libaries for Data Visualisation
 
JavaScript Refactoring
JavaScript RefactoringJavaScript Refactoring
JavaScript Refactoring
 
Sencha Touch
Sencha TouchSencha Touch
Sencha Touch
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
Tricks to Making a Realtime SurfaceView Actually Perform in Realtime - Maarte...
 
Developing web-apps like it's 2013
Developing web-apps like it's 2013Developing web-apps like it's 2013
Developing web-apps like it's 2013
 
Exploring fractals in CSS, @fronttrends, Warsaw, 2015
Exploring fractals in CSS, @fronttrends, Warsaw, 2015Exploring fractals in CSS, @fronttrends, Warsaw, 2015
Exploring fractals in CSS, @fronttrends, Warsaw, 2015
 

More from deanhudson

Seattle.rb 6.4
Seattle.rb 6.4Seattle.rb 6.4
Seattle.rb 6.4deanhudson
 
Mcdm presentations
Mcdm presentationsMcdm presentations
Mcdm presentationsdeanhudson
 
Stupid Canvas Tricks
Stupid Canvas TricksStupid Canvas Tricks
Stupid Canvas Tricksdeanhudson
 
Pointer Events in Canvas
Pointer Events in CanvasPointer Events in Canvas
Pointer Events in Canvasdeanhudson
 
Com546 Final Pres
Com546 Final PresCom546 Final Pres
Com546 Final Presdeanhudson
 
Reading Slides
Reading SlidesReading Slides
Reading Slidesdeanhudson
 

More from deanhudson (6)

Seattle.rb 6.4
Seattle.rb 6.4Seattle.rb 6.4
Seattle.rb 6.4
 
Mcdm presentations
Mcdm presentationsMcdm presentations
Mcdm presentations
 
Stupid Canvas Tricks
Stupid Canvas TricksStupid Canvas Tricks
Stupid Canvas Tricks
 
Pointer Events in Canvas
Pointer Events in CanvasPointer Events in Canvas
Pointer Events in Canvas
 
Com546 Final Pres
Com546 Final PresCom546 Final Pres
Com546 Final Pres
 
Reading Slides
Reading SlidesReading Slides
Reading Slides
 

Recently uploaded

Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebUiPathCommunity
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Commit University
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii SoldatenkoFwdays
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfAlex Barbosa Coqueiro
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsRizwan Syed
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxLoriGlavin3
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationSlibray Presentation
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Enterprise Knowledge
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsPixlogix Infotech
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfRankYa
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.Curtis Poe
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024Lonnie McRorey
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024Stephanie Beckett
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsSergiu Bodiu
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 

Recently uploaded (20)

Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Dev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio WebDev Dives: Streamline document processing with UiPath Studio Web
Dev Dives: Streamline document processing with UiPath Studio Web
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!Nell’iperspazio con Rocket: il Framework Web di Rust!
Nell’iperspazio con Rocket: il Framework Web di Rust!
 
"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko"Debugging python applications inside k8s environment", Andrii Soldatenko
"Debugging python applications inside k8s environment", Andrii Soldatenko
 
Unraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdfUnraveling Multimodality with Large Language Models.pdf
Unraveling Multimodality with Large Language Models.pdf
 
Scanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL CertsScanning the Internet for External Cloud Exposures via SSL Certs
Scanning the Internet for External Cloud Exposures via SSL Certs
 
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptxMerck Moving Beyond Passwords: FIDO Paris Seminar.pptx
Merck Moving Beyond Passwords: FIDO Paris Seminar.pptx
 
Connect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck PresentationConnect Wave/ connectwave Pitch Deck Presentation
Connect Wave/ connectwave Pitch Deck Presentation
 
Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024Designing IA for AI - Information Architecture Conference 2024
Designing IA for AI - Information Architecture Conference 2024
 
The Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and ConsThe Ultimate Guide to Choosing WordPress Pros and Cons
The Ultimate Guide to Choosing WordPress Pros and Cons
 
Search Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdfSearch Engine Optimization SEO PDF for 2024.pdf
Search Engine Optimization SEO PDF for 2024.pdf
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.How AI, OpenAI, and ChatGPT impact business and software.
How AI, OpenAI, and ChatGPT impact business and software.
 
TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024TeamStation AI System Report LATAM IT Salaries 2024
TeamStation AI System Report LATAM IT Salaries 2024
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024What's New in Teams Calling, Meetings and Devices March 2024
What's New in Teams Calling, Meetings and Devices March 2024
 
DevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platformsDevEX - reference for building teams, processes, and platforms
DevEX - reference for building teams, processes, and platforms
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 

The Canvas API for Rubyists

  • 1. The Canvas API for Rubyists Cascadia Ruby, August 3rd, 2012 Harry Dean Hudson
  • 2. Who Am I? Dean Hudson, @deanero Bit Twiddler at Massively Fun
  • 3. We make games with open web technology. (And are hiring). http://massivelyfun.com @massivelyfun
  • 5. What is this talk? • Actually less about Canvas API and more about patterns in which it can be used... • No Ruby! (Sorry Rubyists). • Play along! https://github.com/massivelyfun/ canvas-playground • ...in 5 / 7 / 5.
  • 6. Canvas API: 2D graphics in browser. It is quite simple.
  • 7. Canvas is • 2D, immediate mode, graphics API • Well supported in modern browsers • A collection of drawing calls • Not Flash!
  • 8. (function () { var ImageData = function (width, height) { this.width = width || 10; this.height = height || 10; numPixels = this.width * this.height; this.data = new Uint8Array(numPixels * 4); }; var CanvasGradient = function () {}; CanvasGradient.prototype.addColorStop = function (offset, color) { return undefined; }; var CanvasPattern = function (image, repetitionStyle) { this.image = image; this.repetitionStyle = repetitionStyle; }; var TextMetrics = function (ctx, text) { this... // quick and dirty style var fontSize = parseInt(ctx.font), chars = text.split().length; this.width = fontSize * chars; } var CanvasRenderingContext2D = function (canvas) { this.canvas = canvas; this.fillStyle = "rgb(0,0,0)"; this.font = "10px sans-serif"; this.globalAlpha = 1.0; this.globalCompositionOperation = "source-over"; this.lineCap = "butt"; this.lineJoin = "miter"; this.lineWidth = 1.0; this.miterLimit = 10; this.textAlign = "start"; this.textBaseLine = "alphabetic"; this.shadowBlur = 0; this.shadowColor = "rgba(0,0,0,0)"; this.shadowOffsetX = 0; this.shadowOffsetY = 0; this.strokeStyle = "rgb(0,0,0)"; this.__width = this.canvas.width; this.__height = this.canvas.height; this.__imageData = null; // don't do this until we need it, it's a memory hog. // new ImageData(this.__width, this.__height); this.__curX = 0; this.__curY = 0;
  • 9. this.__openSubpath = true; this.__initTime = new Date(); this.__lastUpdateTime = null; this.__lastFillTime = null; this.__updateCount = 0; this.__fillCount = 0; }; CanvasRenderingContext2D.prototype.__update = function () { var args = Array.prototype.slice.call(arguments); this.__lastUpdateTime = new Date(); this.__updateCount++; } CanvasRenderingContext2D.prototype.__fill = function () { var args = Array.prototype.slice.call(arguments); this.__lastFillTime = new Date(); this.__fillCount++; } // Stub out the real methods. I'm explicitly returning undefined // in cases where the API calls for void return, so as to be clear // about the intent. This is a simple sub-set of the API focused // on operations for images. TODO: implement transforms and state. CanvasRenderingContext2D.prototype.arc = function (x, y, radius, startAngle, endAngle, counterClockwise) { this.__openSubpath = true; ...plus this... return undefined; }; CanvasRenderingContext2D.prototype.arcTo = function (x1, y1, x2, y2, radius) { this.__openSubpath = true; this.__curX = x2; this.__curY = y2; return undefined; }; CanvasRenderingContext2D.prototype.beginPath = function () { this.__openSubpath = true; return undefined; }; CanvasRenderingContext2D.prototype.bezierCurveTo = function (cpX1, cpY1, cpX2, cpY2, x, y) { this.__openSubpath = true; this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.clearRect = function () { return undefined; }; CanvasRenderingContext2D.prototype.clip = function () { return undefined; }; CanvasRenderingContext2D.prototype.closePath = function () { this.__openSubpath = false; return undefined; }; CanvasRenderingContext2D.prototype.createImageData = function () { var args = Array.prototype.slice.call(arguments);
  • 10. CanvasRenderingContext2D.prototype.clip = function () { return undefined; }; CanvasRenderingContext2D.prototype.closePath = function () { this.__openSubpath = false; return undefined; }; CanvasRenderingContext2D.prototype.createImageData = function () { var args = Array.prototype.slice.call(arguments); if (args[0].hasOwnProperty('data') && typeof args[0].data !== 'undefined') { return new ImageData(args[0].data.length, 1); } else if (typeof args[0] === 'number' && typeof args[1] === 'number') { return new ImageData(args[0], args[1]); } else { throw new Error("Invalid arguments. createImageData() takes 1 or 2 args."); } }; CanvasRenderingContext2D.prototype.createLinearGradient = function (xStart, yStart, xEnd, yEnd) { return new CanvasGradient(); }; CanvasRenderingContext2D.prototype.createPattern = function (image, repetitionStyle) { return new CanvasPattern(image, repetitionStyle); }; CanvasRenderingContext2D.prototype.createRadialGradient = function (xStart, yStart, radiusStart, xEnd, yEnd, radiusEnd) { ...plus this... return new CanvasGradient(); }; CanvasRenderingContext2D.prototype.drawImage = function () { switch(arguments.length) { case 3: return CanvasRenderingContext2D.prototype.__drawImage3.apply(this, arguments); case 5: return CanvasRenderingContext2D.prototype.__drawImage5.apply(this, arguments); case 9: return CanvasRenderingContext2D.prototype.__drawImage9.apply(this, arguments); default: throw new Error("Invalid number of arguments. drawImage() takes, 3, 5, or 9 args."); } }; // All that contortion and I don't do anything with it. I'm stubbing // this out in case I want to at some point. CanvasRenderingContext2D.prototype.__drawImage3 = function (image, dx, dy) { return undefined; }; CanvasRenderingContext2D.prototype.__drawImage5 = function (image, dx, dy, dw, dh) { return undefined; }; CanvasRenderingContext2D.prototype.__drawImage9 = function (image, sx, sy, sw, sh, dx, dy, dw, dh) { return undefined; }; CanvasRenderingContext2D.prototype.fill = function () { return undefined; }; CanvasRenderingContext2D.prototype.fillRect = function (x, y, width, height) {
  • 11. return retImageData; }; CanvasRenderingContext2D.prototype.isPointPath = function (x, y) { return true; }; CanvasRenderingContext2D.prototype.lineTo = function (x, y) { this.__openSubpath = true; this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.measureText = function (text) { return new TextMetrics(this, text); }; CanvasRenderingContext2D.prototype.moveTo = function (x, y) { this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.putImageData = function (insertData, dx, dy, sx, sy, sw, sh) { if (arguments.length !== 7) throw new Error("putImageData requires 7 arguments") var imageData = this.__imageData || new ImageData(this.__width, this.__height), ...plus this... startAt = dx * dy * 4 + dx + 4, fromData, fromOffset = sx * sy * 4 + sx * 4, fromNumPixels = sw * sh * 4 + sw * 4, endAt = imageData.length - 1, howMany; if (typeof fromOffset === 'number' && typeof fromNumPixels === 'number') { fromData = insertData.data.slice(fromOffset, fromOffset + fromNumPixels); } else { fromData = insertData.data; } startAt + fromData.length > endAt ? howMany = endAt - startAt : howMany = startAt + fromData.length; imageData.data.splice(startAt, howMany, fromData); return undefined; }; CanvasRenderingContext2D.prototype.quadraticCurveTo = function (cpX, cpY, x, y) { this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.rect = function (x, y, width, height) { this.__curX = x; this.__curY = y; return undefined; }; CanvasRenderingContext2D.prototype.restore = function () { return undefined;
  • 12. return undefined; }; CanvasRenderingContext2D.prototype.restore = function () { return undefined; }; CanvasRenderingContext2D.prototype.rotate = function (angle) { return undefined; }; CanvasRenderingContext2D.prototype.save = function () { return undefined; }; CanvasRenderingContext2D.prototype.scale = function (sx, sy) { return undefined; }; CanvasRenderingContext2D.prototype.setTransform = function (a, b, c, d, e, f) { return undefined; }; CanvasRenderingContext2D.prototype.stroke = function () { return undefined; }; CanvasRenderingContext2D.prototype.strokeRect = function (x, y, width, height) { return undefined; }; CanvasRenderingContext2D.prototype.strokeText = function (text, x, y, max) { return undefined; ...and this. }; CanvasRenderingContext2D.prototype.transform = function (a, b, c, d, e, f) { return undefined; }; CanvasRenderingContext2D.prototype.translate = function (dx, dy) { return undefined; }; var Canvas = function () { this.width = 10; // API default is 300 x 150, but that makes this.height = 10; // our ImageData a memory hog. }; Canvas.prototype.getContext = function (cxtType) { return new CanvasRenderingContext2D(this); }; Canvas.prototype.toDataURL = function () { var buf = new Buffer("Say hello to my little friend."); return "data:text/plain;base64," + buf.toString('base64'); }; module.exports = Canvas; })();
  • 13. The CanvasContext: It has all the calls to draw Pixels to the screen.
  • 14. Simple, no? cvs = document.getElementById("canvas") ctx = cvs.getContext("2D") # ctx has all your draw methods... ctx.fillText("Hello!", 100, 100, 200)
  • 15. Simple, no? cvs = document.getElementById("canvas") ctx = cvs.getContext("2D") # ctx has all your draw methods... ctx.fillText("Hello!", 100, 100, 200) You’ll only see this from here out.
  • 16. dean@dean:~$ grep prototype canvas.js | wc -l 38 You can learn the draw API in a weekend!
  • 17. dean@dean:~$ grep prototype canvas.js | wc -l 38 You can learn the draw API in a weekend! ...so, on to bigger things!
  • 18. Browser event loops Are not well suited for games You must roll your own
  • 19. Simple Game Loop • Handle queued UI/server events • Update state of “things” on screen. • Re-draw!
  • 20. # a fake game loop gameLoop = run: -> @handleQueuedEvents() @update() @draw() # loop somehow?
  • 22. NO!
  • 23. 16 ms != 16.666...ms Text (60 FPS)
  • 24. For accurate loops requestAnimationFrame() Will bring you great joy
  • 25. requestAnimationFrame allows the browser to manage screen updates efficiently...
  • 26. requestAnimationFrame allows the browser to manage screen updates efficiently... ...but is not uniformly supported.
  • 27. buildRAFPolyfill = -> requestAnimationFrame = requestAnimationFrame || webkitRequestAnimationFrame || mozRequestAnimationFrame || oRequestAnimationFrame || msRequestAnimationFrame || -> (cb, elt) setTimeout (cb) -> cb(+new Date()) , 1000 / 60
  • 28. class GameLoop constructor: (cvs) -> @ctx = cvs.getContext("2D") @entities = [] addEntity: (entity) -> @entities.push(entity) run: () => tick = @frameId = requestAnimationFrame(@run) # Update entities entity.update(tick) for entity in @entities # Draw! entity.draw(@ctx) for entity in @entities stop: -> root.cancelAnimationFrame(@frameId)
  • 29. class GameLoop constructor: (cvs) -> @ctx = cvs.getContext("2D") @entities = [] addEntity: (entity) -> @entities.push(entity) run: () => tick = @frameId = requestAnimationFrame(@run) # Update entities entity.update(tick) for entity in @entities # Draw! entity.draw(@ctx) for entity in @entities stop: -> root.cancelAnimationFrame(@frameId) requestAnimationFrame returns a int “id”
  • 30. Entities and Rects: The largest things are built From just these pieces.
  • 31. Simple Game Entity • Respond to update call • Manage position, height, width • Delegate draw calls
  • 32. # Game level object class Entity # Rect is a drawing primitive constructor: (options = {}) -> @rect = options.rect ? null {x, y} = options @position = {x: x, y: y} update: (tick) -> # override and do something interesting here. draw: (ctx) -> # delegate to your drawing primitive! @rect.draw(ctx) module.exports = Entity
  • 33. # Game level object class Entity # Rect is a drawing primitive constructor: (options = {}) -> @rect = options.rect ? null {x, y} = options @position = {x: x, y: y} update: (tick) -> # override and do something interesting here. draw: (ctx) -> # delegate to your drawing primitive! @rect.draw(ctx) module.exports = Entity Game level logic, once per tick
  • 34. # Game level object class Entity # Rect is a drawing primitive constructor: (options = {}) -> @rect = options.rect ? null {x, y} = options @position = {x: x, y: y} update: (tick) -> # override and do something interesting here. draw: (ctx) -> # delegate to your drawing primitive! @rect.draw(ctx) module.exports = Entity DELEGATE!!!!!
  • 35. Simple Draw Primitive • Associated with Entity • Responds to draw() call • Has actual CanvasContext2D drawing calls • You can determine containment here (image hit detection, for instance)
  • 36. # # Rect: Drawing primitive for canvas. class Rect constructor: (options = {}) -> @width = options?.width ? 1 @height = options?.height ? 1 {x, y} = options @position = {x: x, y: y} draw: (canvasCtx) -> throw new Error("Implement draw()") module.exports = Rect
  • 37. # # Rect: Drawing primitive for canvas. class Rect constructor: (options = {}) -> @width = options?.width ? 1 @height = options?.height ? 1 {x, y} = options @position = {x: x, y: y} draw: (canvasCtx) -> throw new Error("Implement draw()") module.exports = Rect Your *simple* interface
  • 38. Rect = require "rect" # Sometimes we just need the simple things. # Make a simple box subclass. class Box extends Rect constructor: (options = {}) -> super(options) draw: (ctx) -> {x, y} = @position.get() ctx.fillRect(x, y, @width, @height) module.exports = Box
  • 39. Rect = require "rect" # Sometimes we just need the simple things. # Make a simple box subclass. class Box extends Rect constructor: (options = {}) -> super(options) draw: (ctx) -> {x, y} = @position.get() ctx.fillRect(x, y, @width, @height) module.exports = Box Implement draw
  • 40. Rect = require "rect" # Sometimes we just need the simple things. # Make a simple box subclass. class Box extends Rect constructor: (options = {}) -> super(options) draw: (ctx) -> {x, y} = @position.get() ctx.fillRect(x, y, @width, @height) module.exports = Box Do work on CanvasContext2D
  • 41. Canvas tests are hard; The one true way is to cheat. Stub, Stub, stub the world.
  • 42. CanvasRenderingContext2D.prototype.__update = function () { var args = Array.prototype.slice.call(arguments); this.__lastUpdateTime = new Date(); this.__updateCount++; } CanvasRenderingContext2D.prototype.__fill = function () { var args = Array.prototype.slice.call(arguments); this.__lastFillTime = new Date(); this.__fillCount++; } // Stub out the real methods. I'm explicitly returning undefined // in cases where the API calls for void return, so as to be clear // about the intent. This is a simple sub-set of the API focused // on operations for images. TODO: implement transforms and state. CanvasRenderingContext2D.prototype.arcTo = function (x1, y1, x2, y2, radius) { this.__openSubpath = true; this.__curX = x2; this.__curY = y2; return undefined; ...etc. };
  • 44. Thanks! dean@massivelyfun.com, @deanero http://massivelyfun.com

Editor's Notes

  1. \n
  2. \n
  3. \n
  4. \n
  5. \n
  6. \n
  7. \n
  8. \n
  9. \n
  10. \n
  11. \n
  12. \n
  13. \n
  14. \n
  15. \n
  16. \n
  17. \n
  18. \n
  19. \n
  20. \n
  21. \n
  22. \n
  23. \n
  24. \n
  25. \n
  26. \n
  27. \n
  28. \n
  29. \n
  30. \n
  31. \n
  32. \n
  33. \n
  34. \n
  35. \n
  36. \n
  37. \n
  38. \n
  39. \n
  40. \n
  41. \n
  42. \n
  43. \n
  44. \n