概要
gosu は Ruby or C++ で 2D ゲーム開発ができるライブラリです
今回は Ruby ライブラリを使って簡単なチュートリアルゲームを作成してみました
環境
- macOS 10.14.6
- Ruby 2.6.2p47
準備
brew install sdl2
bundle init
vim Gemfile
gem "gosu"
bundle install --path vendor
とりあえずウィンドウを表示する
require 'gosu'
class Tutorial < Gosu::Window
def initialize
super 640, 480
self.caption = "Tutorial Game"
end
def update
end
def draw
end
end
Tutorial.new.show
実行すると真っ暗なウィンドウだけが表示されます
背景画像の設定
次にゲームの背景画像を設定します
何でも良いので画像をダウンロードしておきましょう
公式のチュートリアルで使用している画像はここにあります
require 'gosu'
class Tutorial < Gosu::Window
def initialize
super 640, 480
self.caption = "Tutorial Game"
@bg = Gosu::Image.new("bg.png", :tileable => true)
end
def update
end
def draw
@bg.draw(0, 0, 0)
end
end
Tutorial.new.show
Gosu::Image.new
で背景に指定する画像のパスを指定します
また draw
で実際に画像をどの座標に描画するのか指定します
座標は左上が起点になります数字が増えるほど右下に移動します
実行すると以下のようにダウンロードした画像が背景として設定されています
プレイヤーの作成と配置
ゲームに必須の要素になります
キーボードで移動可能なプレイヤーを設置してみます
player.rb
プレイヤーを管理するクラスを別途作成します
少し長いですがあとで解説します
require 'gosu'
class Player
def initialize
@image = Gosu::Image.new("starfighter.bmp")
@x = @y = @vel_x = @vel_y = @angle = 0.0
@score = 0
end
def warp(x, y)
@x = x
@y = y
end
def turn_left
@angle -= 4.5
end
def turn_right
@angle += 4.5
end
def accelerate
@vel_x += Gosu.offset_x(@angle, 0.5)
@vel_y += Gosu.offset_y(@angle, 0.5)
end
def move
@x += @vel_x
@y += @vel_y
@x %= 640
@y %= 480
@vel_x *= 0.95
@vel_y *= 0.95
end
def draw
@image.draw_rot(@x, @y, 1, @angle)
end
end
キーボードの右を押した時に右に回転し左を押した時に左に回転するようにします
キーボードの上を押した際にプレイヤーが前に動くようにします
それぞれ turn_right
, turn_left
, accelerate
メソッドが対応しています
move
メソッドは実際に設定された座標にプレイヤーを移動するメソッドです
@x %= 640
と @y %= 480
はプレイヤーが画面からはみ出さないようにするための設定です
@vel_x *= 0.95
, @vel_y *= 0.95
は摩擦係数のようなものでこれを設定しないと一度 accelerate したプレイヤーが永遠に動き続けてしまいます
少し難しいのは Gosu.offset_x
と Gosu.offset_y
になります
これは現在の位置からどの方向にどれだけ動かすかを指定することができるメソッドです
@angle
で調整した角度に対して 0.5 だけ動かします
つまり上ボタンが押されている間徐々に動くような挙動になります
プレイヤー自体を動かす場合は draw_rot
で動かします
draw
メソッドはプレイヤーが固定されて伸縮するような表現に使います
warp
は初期配置用のメソッドです
メインにプレイヤーを配置する
キーボードの入力のハンドリングはメインとなる app.rb 側の update で処理します
require 'gosu'
require './player.rb'
class Tutorial < Gosu::Window
def initialize
super 640, 480
self.caption = "Tutorial Game"
@bg = Gosu::Image.new("bg.png", :tileable => true)
@player = Player.new
@player.warp(320, 240)
end
def update
if Gosu::button_down?(Gosu::KB_LEFT) || Gosu::button_down?(Gosu::GP_LEFT)
@player.turn_left
end
if Gosu::button_down?(Gosu::KB_RIGHT) || Gosu::button_down?(Gosu::GP_RIGHT)
@player.turn_right
end
if Gosu::button_down?(Gosu::KB_UP) || Gosu::button_down?(Gosu::GP_BUTTON_0)
@player.accelerate
end
@player.move
end
def draw
@bg.draw(0, 0, 0)
@player.draw
end
def button_down(id)
if id == Gosu::KB_ESCAPE
close
else
super
end
end
end
Tutorial.new.show
キーボードの入力は Gosu::button_down?
でハンドリングできます
ハンドリング可能なキーボードは定数で定義されています
update
と draw
メソッドは 60fps でコールされ続けるので内部的にはキーボードに移動と再描画を繰り返すことであたかもプレイヤーが動いているような表現を実現しています
button_down
は他のキーボードの入力をハンドリングするためのメソッドです
close
を呼び出すことでわざわざ左上のバツボタン押さなくてもゲームが終了するようにしています
これで実行してみましょう
マシンのキーボードを使って以下のようにプレイヤーが操縦できるようになります
星を追加する
star.rb
少しゲーム要素を追加します
星を取得するとスコアがカウントアップするようにします
また星は連続したタイル画像を使います (これ)
なので星が回転するような動きになるようなアニメーションを実装します
require 'gosu'
class Star
attr_reader :x, :y
def initialize(animation)
@animation = animation
@color = Gosu::Color::BLACK.dup
@color.red = rand(256 - 40) + 40
@color.green = rand(256 - 40) + 40
@color.blue = rand(256 - 40) + 40
@x = rand * 640
@y = rand * 480
end
def draw
img = @animation[Gosu.milliseconds / 100 % @animation.size]
img.draw(@x - img.width / 2.0, @y - img.height / 2.0, 1, 1, 1, @color, :add)
end
end
@animation
はタイルに上に切り分けられた連続した画像になります
色や場所をランダムで決定します
そして draw
が呼ばれる際に適切なタイルの情報を取得することであたかも星が回転しているようなアニメーションを実現してます
draw
はいろいろと引数を指定していますがそれぞれ「X 座標」「Y 座標」「Z 座標」「X 方向の画像のスケール」「Y 方向の画像のスケール」「色」「追加モード」になります
プレイヤーが星を集められるようにする
だいぶ長くなってきました
player.rb
を修正します
追加しているのは下部にある score
と collect_star
メソッドになります
require 'gosu'
class Player
def initialize
@image = Gosu::Image.new("starfighter.bmp")
@x = @y = @vel_x = @vel_y = @angle = 0.0
@score = 0
end
def warp(x, y)
@x = x
@y = y
end
def turn_left
@angle -= 4.5
end
def turn_right
@angle += 4.5
end
def accelerate
@vel_x += Gosu.offset_x(@angle, 0.5)
@vel_y += Gosu.offset_y(@angle, 0.5)
end
def move
@x += @vel_x
@y += @vel_y
@x %= 640
@y %= 480
@vel_x *= 0.95
@vel_y *= 0.95
end
def draw
@image.draw_rot(@x, @y, 1, @angle)
end
def score
@score
end
def collect_stars(stars)
stars.reject! { |star| Gosu.distance(@x, @y, star.x, star.y) < 35 }
end
end
ポイントは プレイヤーと星の距離に応じて stars 配列から reject!
している Gosu.distance
になります
これで 2 つのオブジェクトの距離を計算することができます
今回は 35 以下であれば集めたと判定して星を管理する配列から星を削除します
メインで星を描画する
あとはメインで星を描画する処理を実装します
星は連続するタイル画像になります
そういった画像を使う場合は Gosu::Image.load_tiles
を使います
これに各タイルのサイズをしていすることで配列として扱うことができます
require 'gosu'
require './player.rb'
require './star.rb'
class Tutorial < Gosu::Window
def initialize
super 640, 480
self.caption = "Tutorial Game"
@bg = Gosu::Image.new("bg.png", :tileable => true)
@player = Player.new
@player.warp(320, 240)
@star_anim = Gosu::Image.load_tiles("star.png", 25, 25)
@stars = Array.new
end
def update
if Gosu::button_down?(Gosu::KB_LEFT) || Gosu::button_down?(Gosu::GP_LEFT)
@player.turn_left
end
if Gosu::button_down?(Gosu::KB_RIGHT) || Gosu::button_down?(Gosu::GP_RIGHT)
@player.turn_right
end
if Gosu::button_down?(Gosu::KB_UP) || Gosu::button_down?(Gosu::GP_BUTTON_0)
@player.accelerate
end
@player.move
@player.collect_stars(@stars)
if rand(100) < 4 && @stars.size < 25
@stars.push(Star.new(@star_anim))
end
end
def draw
@bg.draw(0, 0, 0)
@player.draw
@stars.each do |star|
star.draw
end
end
def button_down(id)
if id == Gosu::KB_ESCAPE
close
else
super
end
end
end
Tutorial.new.show
また先程紹介しましたが星の数は配列で管理しています
集めると reject!
されるので追加していく必要があります
update
されるたびに stars
の状態を確認して減ってきたり 4% の確率で追加するようにします
また星を集めているかどうかも update
で @player.collect_stars(@stars)
を呼び出すことで実現します
各星との距離を毎回計算するので少し重い処理になりそうですが今回はチュートリアル通りに進めます
これで実行すると以下のように星を集められるようになります
だいぶゲームっぽくなってきました
スコアの表示を行う
最後にスコアの表示を行う処理を実装します
@font
を initialize
で初期化して draw
で描画しています
require 'gosu'
require './player.rb'
require './star.rb'
class Tutorial < Gosu::Window
def initialize
super 640, 480
self.caption = "Tutorial Game"
@bg = Gosu::Image.new("bg.png", :tileable => true)
@player = Player.new
@player.warp(320, 240)
@star_anim = Gosu::Image.load_tiles("star.png", 25, 25)
@stars = Array.new
@font = Gosu::Font.new(20)
end
def update
if Gosu::button_down?(Gosu::KB_LEFT) || Gosu::button_down?(Gosu::GP_LEFT)
@player.turn_left
end
if Gosu::button_down?(Gosu::KB_RIGHT) || Gosu::button_down?(Gosu::GP_RIGHT)
@player.turn_right
end
if Gosu::button_down?(Gosu::KB_UP) || Gosu::button_down?(Gosu::GP_BUTTON_0)
@player.accelerate
end
@player.move
@player.collect_stars(@stars)
if rand(100) < 4 && @stars.size < 25
@stars.push(Star.new(@star_anim))
end
end
def draw
@bg.draw(0, 0, 0)
@player.draw
@stars.each do |star|
star.draw
end
@font.draw("Score: #{@player.score}", 10, 10, 3, 1.0, 1.0, Gosu::Color::YELLOW)
end
def button_down(id)
if id == Gosu::KB_ESCAPE
close
else
super
end
end
end
Tutorial.new.show
あとは星を収集した際にスコアのカウントを行うだけです
collect_stars
を少し改修しています
require 'gosu'
class Player
def initialize
@image = Gosu::Image.new("starfighter.bmp")
@x = @y = @vel_x = @vel_y = @angle = 0.0
@score = 0
end
def warp(x, y)
@x = x
@y = y
end
def turn_left
@angle -= 4.5
end
def turn_right
@angle += 4.5
end
def accelerate
@vel_x += Gosu.offset_x(@angle, 0.5)
@vel_y += Gosu.offset_y(@angle, 0.5)
end
def move
@x += @vel_x
@y += @vel_y
@x %= 640
@y %= 480
@vel_x *= 0.95
@vel_y *= 0.95
end
def draw
@image.draw_rot(@x, @y, 1, @angle)
end
def score
@score
end
def collect_stars(stars)
stars.reject! do |star|
if Gosu.distance(@x, @y, star.x, star.y) < 35
@score += 10
else
false
end
end
end
end
これで実行すれば完成です
ちゃんと星を取得するとスコアがカウントアップするのが確認できます
Tips
Gosu は背景が #ff00ff
の色を自動的に透過にしてくれます
最後に
Gosu で Ruby を使って 2D ゲーム開発に入門してみました
キーボードを使ったゲームですが簡単にできました
物理エンジンなどおそらくないので重力加速などが必要な場合は自分で実装する必要がありそうです
当たり判定なども今回自分で実装したので距離などの情報を使って自分で実装するのかなと思います
基本はキーボードが入力になるのでマウスは考慮しなくても良さそうですがマウスの入力もできるっぽいです
詳しくは参考サイトにある Github の Wiki や Rdoc を見てください
Windows と Mac 用にエクスポートできるようなので次回紹介したいと思います
参考サイト