yonekawaです。
この記事ではJoyboxというライブラリを使用しRubyMotionでゲームを作る方法を紹介します。
JoyboxはiOSでメジャーなゲームエンジンであるCocos2DとBox2Dという物理エンジンを内包し、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
- joybox.io
- rubymotion/joybox
- Create an Asteroids Game for iOS in 15 Minutes… with Joybox 1.1.0 and RubyMotion!