19
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RubyMotionAdvent Calendar 2013

Day 8

RubyMotion + Joyboxでゲームを作る

Last updated at Posted at 2013-12-07

yonekawaです。
この記事ではJoyboxというライブラリを使用しRubyMotionでゲームを作る方法を紹介します。
JoyboxはiOSでメジャーなゲームエンジンであるCocos2DBox2Dという物理エンジンを内包し、RubyMotionで扱えるようにAPIをラップしたゲーム開発ライブラリです。

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

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

% gem install joybox

JoyboxはRubyMotion Advent Calendarの6日目の記事で紹介された、テンプレートに対応しています。
今回は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を作ってキャラクターの画像を表示させています。

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の子クラスとして作成します。

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

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

  @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移動します

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度回転します

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

Blink

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

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

複数のアニメーション

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

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

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

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

衝突判定

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

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

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でラップして使えるようにしています。

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で物理的に力が加えられるからです。

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

SpriteKit

ここまでJoyboxの紹介をしてきましたが、iOSにはSpriteKitというフレームワークがあります。
SpriteKitはiOS7からSDKに追加された、Apple製の2Dゲームフレームワークです。物理エンジンも搭載しており、Cocos2Dとほぼ同じことが実現できると言われています。
Cocos2Dの作者もSpriteKitに言及しています。

RubyMotionならSpriteKitもすぐ使えるということで、比較のために試しに書いてみました。
簡単なサンプルですがこちらにあげてあります。

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も言っていますが、RubyMotionでゲームを開発するのはすごく楽しい体験です。使い慣れた道具でカジュアルにゲームを作れる環境はなかなかありません。
Joyboxを選ぶにせよSpriteKitにせよ、ぜひ一度体験してみて楽しいゲームを生み出してほしいと思います。

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

Links

Joybox

SpriteKit

Sample Code

19
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?