Cocos2d-x mruby binding

Cocos2d-xのmruby bindngを開発しています。
iOS、Androidである程度動くようになったので公開しました。
https://github.com/tkyaji/cocos2d-ruby

#使い方
上記のgithubリポジトリからcloneしてください。

```
$ git clone https://github.com/tkyaji/cocos2d-ruby
```

あとは通常のCocos2d-xの使い方と同じです。
`setup.py`スクリプトを実行し、環境変数をロードします。

```
$ cd cocos2d-ruby
$ python setup.py
$ source ~/.bash_profile
```

`cocos new`コマンドでプロジェクトを作ります。
`-l`オプションで`ruby`を指定すると、mruby bindingのプロジェクトができます。

```
$ cocos new MyGame -l ruby
```

なお、Cocos2d-x本家のリポジトリからforkしているので、C++やLuaも使用できます。

#HelloWorld
生成したプロジェクトで、そのまま`cocos run`を実行すると、HelloWorldのサンプルが起動します。

```
$ cocos run -p ios
$ cocos run -p android
```

中身はC++版のHelloWorldを、rubyで書き換えたものです。

```ruby:main.rb
require "HelloWorld"

def main
  # size define
  resources = {
    small: {
      size: {w: 480, h: 320},
      directory: "res/iphone"
    },
    medium: {
      size: {w: 1024, h: 768},
      directory: "res/ipad"
    },
    large: {
      size: {w: 2048, h: 1536},
      directory: "res/ipadhd"
    }
  }
  design_resolution_size = resources[:small][:size]

  director = CC::Director.get_instance
  glview = director.get_open_glview
  if ! glview
    glview = CC::GLViewImpl.create("Ruby Empty Test")
    director.set_open_gl_view(glview)
  end

  glview.set_design_resolution_size(design_resolution_size[:w], design_resolution_size[:h], ResolutionPolicy.NO_BORDER)

  frame_size = glview.get_frame_size
  ssize = resources[:small][:size]
  msize = resources[:medium][:size]

  target_res = resources[:small]
  if frame_size.height > msize[:h]
    target_res = resources[:large]
  elsif frame_size.height > ssize[:h]
    target_res = resources[:medium]
  end
  target_size = target_res[:size]
  director.set_content_scale_factor([target_size[:h] / design_resolution_size[:h], target_size[:w] / design_resolution_size[:w]].min)

  futils = CC::FileUtils.get_instance
  futils.add_search_path(target_res[:directory])

  director.set_display_stats(true)
  director.set_animation_interval(1.0 / 60)

  scene = HelloWorld.scene
  director.run_with_scene(scene)
end

main
```

```ruby:HelloWorld.rb
class HelloWorld < CC::Layer
  include CC

  TITLE_FONT_SIZE = 20;

  def self.scene
    # 'scene' is an autorelease object
    scene = Scene.create

    # 'layer' is an autorelease object
    layer = HelloWorld.create

    #add layer as a child to scene
    scene.add_child(layer)

    # return the scene
    return scene
  end

  def initialize
    init
  end

  def init

    director = Director.get_instance
    visible_size = director.get_visible_size
    visible_vec2 = Vec2.new(visible_size.width, visible_size.height)
    origin = director.get_visible_origin

    # 1. add a menu item with "X" image, which is clicked to quit the program
    #    you may modify it.

    # add a "close" icon to exit the progress. it's an autorelease object
    close_item = MenuItemImage.create("CloseNormal.png", "CloseSelected.png", Proc.new {|sender|
      p "Exit button clicked"
    })
    content_size = close_item.get_content_size
    content_vec2 = Vec2.new(content_size.width, content_size.height)
    close_item.set_position(origin + visible_vec2 - content_vec2 / 2)

    # create menu, it's an autorelease object
    menu = Menu.create(close_item)
    menu.set_position(Vec2.ZERO)
    self.add_child(menu, 1)

    # 2. add your codes below...

    # add a label shows "Hello World"
    # create and initialize a label

    label = Label::create_with_system_font("Hello World", "Arial", TITLE_FONT_SIZE)
    # position the label on the center of the screen
    label.set_position(origin.x + visible_size.width / 2, origin.y + visible_size.height - label.get_content_size.height)

    # add the label as a child to this layer
    self.add_child(label, 1)

    # add "HelloWorld" splash screen"
    sprite = Sprite.create("HelloWorld.png")

    # position the sprite on the center of the screen

    tmp_size = visible_size / 2
    sprite.set_position(Vec2.new(tmp_size.width, tmp_size.height) + origin)

    # add the sprite as a child to this layer
    self.add_child(sprite)
  end
end
```

main.rbに書いてある処理は、実際はAppDelegate.cppに書いた方が良いと思いますが、
サンプルなのであえてRuby側に書いています。

メソッド名はスネークケースに、`isXXX`は`XXX?`に変換しています。
(binding-generator実行時の設定で、この変換は無効化して生成できます)

コールバックは`Proc`で実装します。

#メモリ管理
Lua Bindingでは、C++と同様、状況によってはretain、releaseでリファレンスカウンタを操作する必要があります。
mruby Bindingでは、retain、releaseは不要で、Ruby側で全て管理してくれるように実装しています。

具体的には、retain、releaseが呼び出された際に、mrubyでラップしたインスタンスだった場合、
C++のリファレンスカウンタ（RC）は増やさずに、代わりにRuby側に作ったRCをインクリメントしているイメージです。
Ruby側のRCが0になったタイミングで、C++側のRCをデクリメント（つまりC++の元のrelease処理）を実行し、
インスタンスがdeleteされるようにしています。
この辺の処理は、[Ref.cpp](https://github.com/tkyaji/cocos2d-ruby/blob/v3/cocos/base/CCRef.cpp)、[RubyEngine.cpp](https://github.com/tkyaji/cocos2d-ruby/blob/v3/cocos/scripting/ruby-bindings/manual/RubyEngine.cpp) で実装しています。

正直、今の実装で問題ないかまだ少し心配なので、この場合にダメそうとか、ツッコミをもらえると嬉しいです


#エディタ
LuaやJSの場合、Cocos Code IDEを使った効率の良い開発を行えますが、mruby bindingは当然対応していません。
しかしそれだと不便なので、mruby binding用のSublime Textプラグインを作りました。
[CocosRubyEditor](https://github.com/tkyaji/CocosRubyEditor)

これを使うと以下のように自動補完が行えます。

![CocosRubyEditor.gif](https://qiita-image-store.s3.amazonaws.com/0/19872/1966a68a-87db-d8bb-f81a-ef1922b9c8fc.gif)


Package Controlに登録したため、"CocosRubyEditor"で検索すると出て来るはずです。
定数等、現状では補完されないものがありますが、そのうち対応する予定です。


#TODO

* 未対応のクラスに対応する

* `cocos new`でプロジェクトを作る時に、mrubyのgemを追加できるようにする

* rubycompileコマンドの実装（バイトコード化と暗号化）

* ruby-testプロジェクトの実装

* ドキュメント生成する

* ios / Android / Mac 以外のプラットフォームに対応


---
使ってみてもらえると嬉しいです。

以上。