CreateJS のクラスを CoffeeScript 上で継承するには

2014.05.07

CoffeeScript には簡単にクラスの継承を実現できる機能が備わっていますが、こと CreateJS のクラスを継承する際には従来とは少し異なる手法で実装するがあるので、注意が必要です。

知ってしまえば大したこと無いのですが、ちょっとクセのあるテクニックなのでサンプルを交えてその方法を紹介します。

例) createjs の Bitmap クラスを継承してみる

従来の継承

通常、CoffeeSctript でクラスの継承をするには以下のように記述します。

Animal クラス
class Animal
  constructor: (@name) ->

  alive: ->
    false
Dog クラス
class Parrot extends Animal
  constructor: ->
    super("Parrot")

  dead: ->
    not @alive()

Animal クラスを継承した Dog クラスです。Dog クラスの constructor メソッド内で super() と実行すると、継承元である Animal クラスの constructor メソッドが実行され、Animal クラスが持つメソッドを Dog クラスのメソッドとして使用できるようになります。

CreateJS のクラスを継承する場合

通常はこの super()メソッドだけで事足りるのですが、CreateJS のクラスを継承する際は super()メソッドではなくinitialize() メソッドを呼び出すことで実現します

Bitmap クラスを継承した ImageButton クラス
class ImageButton extends createjs.Bitmap
  constructor: (args)->
    @initialize(args.img)
    
    @x = args.x
    @y = args.y
    @regX = args.img.width / 2
    @regY = args.img.height / 2
    @scaleX = 1
    @scaleY = 1
    @addEventListener 'click', args.clickHandler

super() は呼び出しません。ちなみに僕が参考にした下記のエントリでは先にinitializeを呼んでから、super()すると書かれています。試しに下記のエントリと同じく createjs.Event を継承したサンプルで試してみましたが、initialize() を呼び出しただけで動作しました *1。仕様が変わったんでしょうか?

詳しくは解説しませんが、Bitmap という関数は中で initialize() というメソッドを呼び出す処理のため、これを直接呼び出してあげることで Bitmap インスタンスが生成されるという訳です(たぶん)。

Bitmap.js (※ 一部抜粋)
/**
 * A Bitmap represents an Image, Canvas, or Video in the display list. A Bitmap can be instantiated using an existing
 * ( 中略 )
 * @class Bitmap
 * @extends DisplayObject
 * @constructor
 * @param {Image | HTMLCanvasElement | HTMLVideoElement | String} imageOrUri The source object or URI to an image to
 * display. This can be either an Image, Canvas, or Video object, or a string URI to an image file to load and use.
 * If it is a URI, a new Image object will be constructed and assigned to the .image property.
 **/
var Bitmap = function(imageOrUri) {
  this.initialize(imageOrUri);
};

全てのコードをご覧になりたい方はこちらからどうぞ。

以上、ちょっとした備忘録でした。最後にサンプルコードとデモを紹介しておきます。

Bitmap クラスを継承したデモ

ImageButton.coffee
class window.ImageButton extends createjs.Bitmap
  baseScale: 1
  sound: null

  constructor: (args)->
    @initialize(args.img)
    
    @x = args.x
    @y = args.y
    @regX = args.img.width / 2
    @regY = args.img.height / 2
    @scaleX = 1
    @scaleY = 1
    @addEventListener 'click', args.clickHandler

    @baseScale = @scaleX

    @sound = createjs.Sound.createInstance args.soundInstanceName

  ring = ->
    @sound.play()

  expand: ->
    expandedScale = @baseScale * 1.2
    createjs.Tween.get(@, {override: true}).to({scaleX: expandedScale, scaleY: expandedScale}, 50).to({scaleX: @baseScale, scaleY: @baseScale}, 50);
    ring.call @
application.coffee
//=require 'ImageButton'

stage = null
queue = null
imageButton = null

createMainCanvasStage = ->
  canvasId = 'main-canvas'
  s = new createjs.Stage canvasId
  canvas = s.canvas
  h = window.innerHeight
  w = window.innerWidth
  canvas.height = h
  canvas.width = w
  canvas.style.height = "#{h}px"
  canvas.style.width = "#{w}px"
  s

loadAssets = ->
  manifest = [
    { id: 'thumbnail', src: './assets/images/myLogo_@128.jpg' }
    { id: 'sound', src: './assets/audio/click.m4a' }
  ]
  queue = new createjs.LoadQueue true
  queue.installPlugin createjs.Sound
  createjs.Sound.alternateExtensions = ['mp3']
  queue.addEventListener 'complete', handleInitialComplete
  queue.loadManifest manifest, true
  @

initThumbnail = ->
  imageButton = new ImageButton
    img: queue.getResult 'thumbnail'
    x: stage.canvas.width / 2
    y: stage.canvas.height / 2
    clickHandler: handleThumbnailClick
    soundInstanceName: 'sound'

  stage.addChild imageButton
  @

handleInitialComplete = =>
  initThumbnail()
  @

handleThumbnailClick = (event)->
  imageButton.expand()
  
initLoad = ->
  stage = createMainCanvasStage()

  loadAssets()

  createjs.Ticker.setFPS 24
  createjs.Ticker.addEventListener 'tick', =>
    stage.update()


window.addEventListener 'load', (event)->
  event.target.removeEventListener event.type, arguments.callee
  initLoad()

脚注

  1. super() に渡している引数 type, bubble, cancelable をinitialize() に渡して呼び出しました。