RubyMotion + Joyboxでゲームを作る

[yonekawa](https://twitter.com/yonekawa)です。
この記事では[Joybox](http://joybox.io/)というライブラリを使用しRubyMotionでゲームを作る方法を紹介します。
JoyboxはiOSでメジャーなゲームエンジンである[Cocos2D](http://www.cocos2d-iphone.org/)と[Box2D](http://box2d.org/)という物理エンジンを内包し、RubyMotionで扱えるようにAPIをラップしたゲーム開発ライブラリです。

## Joyboxのインストール/プロジェクトの作成

JoyboxはRubyGems経由でインストールします。
 
    % gem install joybox

Joyboxは[RubyMotion Advent Calendarの6日目の記事](http://qiita.com/meganemura/items/d99fe6ad407d68f8844b)で紹介された、テンプレートに対応しています。
今回はiOS用のテンプレートを使ってプロジェクトを作成します。

    % motion create --template=joybox-ios amazing-game
      Create amaizing-game
      Create amaizing-game/.gitignore
      Create amaizing-game/app/app_delegate.rb
      Create amaizing-game/Rakefile
      Create amaizing-game/resources/Default-568h@2x.png
      Create amaizing-game/resources/fps_images-hd.png
      Create amaizing-game/resources/fps_images-ipadhd.png
      Create amaizing-game/resources/fps_images.png
      Create amaizing-game/spec/main_spec.rb

## シーンを作る
まずはSceneに表示するLayerを作成します。`Joybox::Core::Layer`の子クラスです。
Spriteを作ってキャラクターの画像を表示させています。

``` ruby
class MainLayer < Joybox::Core::Layer
  def on_enter
    character = Sprite.new(
      file_name: 'sprites/character.png',
      position: Screen.center
    )
    self << character
  end
end
```

`MainLayer`をSceneに追加します。Sceneは`Joybox::Core::Scene`の子クラスとして作成します。

``` ruby
class MainScene < Joybox::Core::Scene
  def on_enter
    main_layer = MainLayer.new
    self << main_layer
  end
end
```

作成した`Scene`を画面に表示します。
`app/app_delegate.rb`の最後のtrueを返す前に記述します。

```ruby
  @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
  @window.setRootViewController(@navigation_controller)
  @window.makeKeyAndVisible

  # Here
  @director << MainScene.new

  true
end
```

`rake`で実行すれば、Layerに追加したキャラクター画像が画面中央に表示されるはずです。Joyboxではこのようにdirectorに作成したSceneをpushしていくことでゲーム画面を作成していきます。

## アニメーション
クールなゲームではアニメーションの存在が欠かせません。
JoyboxではCocos2D由来の豊富なアニメーションAPIが用意されています。
先ほどのキャラクターをアニメーションさせてみましょう。

### Move
キャラクターが今いる位置から2秒かけて右へ300移動します

```ruby
move_by_action = Move.by(position: [300, 0], duration: 2.0)
ease_exponential_in_action = Ease.exponential_in(action: move_by_action)
character.run_action(ease_exponential_in_action)
```

### Rotate
キャラクターが3秒かけてかけて360度回転します

```ruby
rotate_by_action = Rotate.by(angle: 360.0, duration: 3.0)
character.run_action(rotate_by_action)
```

### Blink
キャラクターが点滅します

``` ruby
blink_action = Blink.with(times: 10, duration: 10.0)
character.run_action(blink_action)
```

### 複数のアニメーション
複数のアニメーションをまとめて実行することもできます。
例えば順番に実行したい場合は`Sequence`を使います。

```ruby
sequence_action = Sequence.with(actions:[ease_exponential_in_action, rotate_by_action])
character.run_action(sequence_action)
```

同時に実行したい場合には`Spawn`を使います。

```ruby
spawn_action = Spawn.with(actions:[ease_exponential_in_action, rotate_by_action])
character.run_action(spawn_action)
```

## 衝突判定

例えばシューティングゲームなどで、弾が自機や敵機に当たったかどうかを判定したいことがあります。いくつか方法はありますが、今回は`CGRectIntersectsRect`を使った方法を紹介します。

アニメーション等で常に動いているSprite同士の衝突判定をするには、ゲーム内で更新が発生する度にSpriteのboxが重なっていることを判定するのが簡単です。
そのためにJoyboxには`shedule_update`というメソッドが用意されています。

```ruby
class MainLayer < Joybox::Core::Layer
  def on_enter
    @character = Sprite.new(
      file_name: 'sprites/character.png',
      position: Screen.center,
      alive: true
    )

    move_by_action = Move.by(position: [300, 0], duration: 2.0)
    ease_exponential_in_action = Ease.exponential_in(action: move_by_action)
    character.run_action(ease_exponential_in_action)

    self << @character

    @enemy = Sprite.new(
      file_name: 'sprites/enemy.png',
      position: [Screen.width - 100, Screen.half_height].to_point
    )

    self << @enemy

    schedule_update do |dt|
      check_for_collisions if @character[:alive]
    end
  end

  def check_for_collisions
    if CGRectIntersectsRect(@character.bounding_box, @enemy.bounding_box)
      @character.stop_all_actions
      blink_action = Blink.with(times: 10, duration: 10.0)
      @character.run_action(blink_action)
      @character[:alive] = false
    end
  end
```

schedule_updateに渡されたブロックが、Cocos2Dのゲームループ毎に毎回呼び出されます。
その度に`CGRectIntersectsRect`でキャラクターと敵のbouding_boxを判定することで、衝突判定を実現できます。
あとは今回のようにキャラクターのすべてのアクションを止めて点滅させる、みたいなことをすれば敵に当たって死んだ、みたいな表現を実現できます。

## 物理エンジン
本来はRubyMotionではC++のライブラリが使えないため、Box2Dは使えないのですが、JoyboxではBox2DをObjective-Cでラップして使えるようにしています。

```ruby
class PhysicsLayer < Joybox::Core::Layer
  scene

  def on_enter
    init_world
    create_character
    
    schedule_update do |delta|
      @world.step(delta: delta)
    end
  end

  def init_world
    @world = World.new(gravity: [0, -10])
    body = @world.new_body(position: [0, 0]) do
      edge_fixture start_point: [0, 0],
      end_point:   [Screen.width, 0]
      edge_fixture start_point: [0, Screen.height],
      end_point:   [Screen.width, Screen.height]
      edge_fixture start_point: [0, Screen.height],
      end_point:   [0, 0]
      edge_fixture start_point: [Screen.width, Screen.height],
      end_point:   [Screen.width, 0]
    end
  end
  
  def create_character
    @character_body = @world.new_body(
      position: Screen.center,
      type: KDynamicBodyType
    ) do
      polygon_fixture(
        box: [47, 48.5],
        friction: 0.7,
        density: 1.0
      )
    end

    @character = PhysicsSprite.new(file_name: 'character.png', body: @character_body)
    self << @character
  end
end
```

まず`init_world`で、Box2Dの物理計算のためのWorldを作ります。
そしてcreate_characterで先ほど同様キャラクターを追加するのですが、この時にworldを経由してbodyを作り、PhyisicsSpriteを作ります。これによりSpriteを物理演算の対象にできます。
そしてゲームループで、worldのstepを進めることで、物理演算が実行されるようになります。

例えばこういうコードをon_enterに入れると、画面をタッチするとキャラクターが吹っ飛びます。`apply_force`で物理的に力が加えられるからです。

```ruby
on_touches_ended do |touches, event|
  @character.body.apply_force(force: [1000, 1000])
end
```

## SpriteKit
ここまでJoyboxの紹介をしてきましたが、iOSには[SpriteKit](https://developer.apple.com/Library/ios/documentation/SpriteKit/Reference/SpriteKitFramework_Ref/_index.html)というフレームワークがあります。
SpriteKitはiOS7からSDKに追加された、Apple製の2Dゲームフレームワークです。物理エンジンも搭載しており、Cocos2Dとほぼ同じことが実現できると言われています。
Cocos2Dの作者もSpriteKitに[言及](http://www.learn-cocos2d.com/2013/06/apple-create-spritekit/)しています。

RubyMotionならSpriteKitもすぐ使えるということで、比較のために試しに書いてみました。
簡単なサンプルですが[こちら](https://github.com/yonekawa/rubymotion-advent-calendar-2013-8/tree/master/spritekit-rubymotion)にあげてあります。

```ruby
class MainScene < SKScene
  def initWithSize(size)
    super(size)
    self.physicsBody = SKPhysicsBody.bodyWithEdgeLoopFromRect(CGRectMake(0, 0, self.size.width, self.size.height))
    self
  end

  def createCharacter(location)
    character = SKSpriteNode.alloc.initWithImageNamed('character')
    character.name = "character"
    character.physicsBody = SKPhysicsBody.bodyWithRectangleOfSize(character.size)
    character.physicsBody.dynamic = true
    character.position = [location.x, location.y]
    character
  end

  def touchesBegan(touches, withEvent: event)
    touch = touches.anyObject
    location = touch.locationInView(touch.view)

    character = createCharacter(location)
    self.addChild(character)
  end
end
```

感想としては、UIKitとの親和性が高くシームレスに連携されるので通常のアプリ開発と同じような流れでゲームUIが作れるのはいいなと思いました。
Joybox(Cocos2D)のようにゲーム開発フレームワークという感じではなくSprite操作を楽にできるようにする機能群という印象を受けました。
ゲーム開発の取っ掛かりとしてAPIもシンプルだしよさそうです。

## Joybox vs SpriteKit
RubyMotionでゲーム開発をする場合、JoyboxとSpriteKitのどちらを選ぶべきでしょうか。これは難しい問題です。

SpriteKitに関してはやはりApple純正というのは強く、今後もXcodeなどデフォルトのツールなどのサポートが充実していくことが期待できます。パフォーマンスなども最適化されていると予想されます。
これから勉強していくならSpriteKitは有力だと思います。

Cocos2Dには今まで多くのゲームで採用されてきた実績があり、情報も多く枯れたゲームエンジンと言えます。Tile Mapなど機能も充実してますし、ゲーム開発をする上で何かが足りなくて困ることは少ないです。
そしてJoyboxにはRuby likeなAPIでコードがスッキリ書けたりテストが書きやすかったりとRubyMotionならではのメリットがあります。

オープンソースなので何か困ったら開発者に直してもらうこともできますし、自分でソースを見て直すこともできます。実際僕も自分で使うのに必要な機能を自分で追加してPull Requestしたところ、すぐ取り込んでもらえました。
この辺りをメリットとして感じるのであればJoyboxはとてもいいライブラリだと思っています。

もちろんiOS6への対応が求められるのであればJoybox一択ですね。

## RubyMotion + games = happiness
Joyboxの[Juan Jose Karam](https://twitter.com/juankaram)も言っていますが、RubyMotionでゲームを開発するのはすごく楽しい体験です。使い慣れた道具でカジュアルにゲームを作れる環境はなかなかありません。
Joyboxを選ぶにせよSpriteKitにせよ、ぜひ一度体験してみて楽しいゲームを生み出してほしいと思います。

Joyboxは引き続き開発が進められているので、何か問題があればIssueやPull Requestを送ってみるといいのではないでしょうか。

## Links

### Joybox
- [joybox.io](http://joybox.io/)
- [rubymotion/joybox](https://github.com/rubymotion/Joybox)
- [Create an Asteroids Game for iOS in 15 Minutes… with Joybox 1.1.0 and RubyMotion!](http://blog.rubymotion.com/post/57465814533/create-an-asteroids-game-for-ios-in-15-minutes-with)

### SpriteKit
- [SpriteKit Framework Reference](https://developer.apple.com/Library/ios/documentation/SpriteKit/Reference/SpriteKitFramework_Ref/_index.html)
- [SpriteKit Tutorial for Beginners](http://www.raywenderlich.com/42699/spritekit-tutorial-for-beginners)

### Sample Code
- [yonekawa/rubymotion-advent-calendar-2013-8](https://github.com/yonekawa/rubymotion-advent-calendar-2013-8)