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で書き換えたものです。

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
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側に書いています。

メソッド名はスネークケースに、isXXXXXX?に変換しています。
(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.cppRubyEngine.cpp で実装しています。

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

エディタ

LuaやJSの場合、Cocos Code IDEを使った効率の良い開発を行えますが、mruby bindingは当然対応していません。
しかしそれだと不便なので、mruby binding用のSublime Textプラグインを作りました。
CocosRubyEditor

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

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

TODO

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

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

  • rubycompileコマンドの実装(バイトコード化と暗号化)

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

  • ドキュメント生成する

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


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

以上。