落ち物ゲームの作成

falling-objects-gameの作成

簡単なゲーム作成で、DXRubyの基本的な使い方は理解できました。それでは、もう少し難しい落ち物ゲームの作成に挑戦していきます。

新しく出てきた内容(クラスやメソッドなどの仕様)は、Rubyのリファレンスマニュアルや、DXRubyのAPIリファレンスを見て確認しながら進めると、理解が深まります。

ゲーム用フォルダの作成

落ち物ゲーム専用のフォルダを作成します。フォルダ名は、falling-objects-gameとします。

作成したら、cdコマンドで移動します。

Create Folder

画像・音源の取得

落ち物ゲームで使用する画像と音源は、yhara氏によって開発されたDXOpalのサンプルプログラムに含まれるものを使います1

以下のボタンを押して、画像と音源をダウンロードしてください2

画像と音源のダウンロード

ダウンロードしたdxopal-sample.zipファイルを展開します。

samplesフォルダ内のapple_catcherフォルダにあるimagesフォルダとsoundsフォルダを、falling-objects-gameフォルダ内に移動させてください。

Image Sound Folders
Image Sound Folders

main.rb作成

最初に、main.rbファイルを作成し、基本となるウィンドウ表示用のプログラムを書いて実行してみます。

main.rbファイルに、以下のように書いてください。

require 'dxruby'

Window.loop do
end

コマンドプロンプトに以下を入力して、プログラムを実行します。

ruby main.rb
main.rb

ウィンドウが表示されることを確認します。

main.rb

背景の作成

背景が全面黒では寂しいですね。背景に地面と空を描きます。

DXRubyのImageクラスは、既存の画像を扱うだけでなく、線、円、三角、四角を描画するメソッドが用意されています。

ここでは、背景を空色に塗り、茶色と緑色を使った地面を配置します。

main.rbファイルに、以下のように書き加えます。

require 'dxruby'

Window.bgcolor = [255, 128, 255, 255] # 追加
ground_img = Image.new(640, 80, [255, 116, 80, 48]) # 追加
ground_img.box_fill(0, 0, 640, 10, [255, 0, 128, 0]) # 追加

Window.loop do
  Window.draw(0, 400, ground_img) # 追加
end

プログラムを実行します。ウィンドウに背景が表示されます。

Background

【プログラムの説明】

  • Window.bgcolorで、ウィンドウのバックグラウンドカラーを指定します。色はARGB配色に従って指定します。
  • Image.newメソッドで、Imageクラスのインスタンス(地面)を生成します。引数にザイズ、色(茶色)を指定しています。
  • box_fillメソッドは、上記Imageインスタンスの画像に色指定(緑色)の四角を描画(上書き)しています。
  • Window.drawでは、ウィンドウ上の(0, 400)の位置に地面を描画しています。

プレイヤーの作成

プレイヤーを作成します。簡単なゲーム作成では、4方向に移動できるようにしましたが、落ち物ゲームでは、左右にのみ動けるようにします。

player.rbファイルを新規作成し、main.rbファイルに書き加えます。

player.rb

class Player < Sprite
  def initialize()
    image = Image.load("images/noschar1.png")
    x = (640 - image.width) / 2
    y = 400 - image.height
    super(x, y, image)
  end

  def update
    self.x += Input.x
  end
end

main.rb

require 'dxruby'

require_relative 'player' # 追加

Window.bgcolor = [255, 128, 255, 255] # 追加
ground_img = Image.new(640, 80, [255, 116, 80, 48]) # 追加
ground_img.box_fill(0, 0, 640, 10, [255, 0, 128, 0]) # 追加

player = Player.new() # 追加

Window.loop do
  Window.draw(0, 400, ground_img) # 追加

  player.update # 追加
  player.draw # 追加
end

プログラムを実行します。左右の矢印キーで、プレイヤーが動くか確認してください。

Player

【プログラムの説明】

  • main.rbでは、プレイヤーのインスタンスを生成(new)し、ループ内で、位置の更新(update)と描画(draw)をしています。
  • player.rbでは、initializeメソッド内で、画像や初期位置を設定し、super文を使って、スーパークラス(ここではSpriteクラス)のメソッド(ここではinitializeメソッド)を呼んでいます。
  • プレイヤーの初期位置は、widthメソッドや、heightメソッドを使い、画像のサイズを取得して、描画位置を決めています。つまり、画像が変更になっても、ちょうど良い位置に描画されるように考慮してあります。

プレイヤーの制限

矢印キーで動かしていて気がついたかもしれませんが、ウィンドウの端で消えてしまいます。それ以上移動できないように制限をします。

player.rbファイルを、以下のように書き換えます。

player.rb

class Player < Sprite
  def initialize()
    image = Image.load("images/noschar1.png")
    x = (640 - image.width) / 2
    y = 400 - image.height
    super(x, y, image)
  end

  def update # 修正
    dx = Input.x
    if (dx == -1 && self.x > 0) || (dx == 1 && self.x < (640 - image.width)) 
      self.x += dx
    end
  end
end

プログラムを実行します。左右の矢印キーで、プレイヤーが端で止まるか確認してください。

Player

【プログラムの説明】

  • Input.xは、矢印右キーが押されると1を返し、矢印左キーが押されると-1を返し、それ以外は0を返します。ここでは、一旦dx変数に値を格納し、if文を使って、条件が合った(真)場合のみ、sefl.xの値に反省させるようにしています。
  • if文の条件式には、論理演算子を用いています。右端の場合は、プレイヤー画像の幅も考慮しています。

りんごの作成

落下するりんごを作成します。りんごの落下スピードは、ランダムになるように設定します。

apple.rbファイルを新規作成して、、以下のように書きます。

apple.rb

class Apple < Sprite
  def initialize()
    image = Image.load("images/apple.png")
    x = rand(0..(640 - image.width))
    y = image.height * -1
    @speed = rand(1..5)
    super(x, y, image)
  end

  def update
    self.y += @speed
    if self.y > 400 - image.height
      self.vanish
    end
  end
end

main.rb

require 'dxruby'

require_relative 'apple' # 追加
require_relative 'player'

Window.bgcolor = [255, 128, 255, 255]
ground_img = Image.new(640, 80, [255, 116, 80, 48])
ground_img.box_fill(0, 0, 640, 10, [255, 0, 128, 0])

apple = Apple.new() # 追加
player = Player.new()


Window.loop do
  Window.draw(0, 400, ground_img)

  apple.update # 追加
  player.update

  apple.draw # 追加
  player.draw
end

プログラムを数回実行してください。りんごの落下スピードが違っているか、確認してください。

Apple

【プログラムの説明】

  • Appleクラスの@speed変数にランダムな整数を入れています。これを落下の時の増分にすることで、早く落ちるりんごやゆっくり落ちるりんごを実現しています。
  • Appleクラスのupdateメソッドは、りんごが落下して、地面に接触した時、vanishメソッドを呼んで、りんごを非表示にしています。

りんごの複数化

落下するりんごの数を増やします。地面に落下して消えたりんご分の数を新たに作成して、落下させます。

main.rbファイルを、以下のように書き換えます。

main.rb

require 'dxruby'

require_relative 'apple'
require_relative 'player'

Window.bgcolor = [255, 128, 255, 255]
ground_img = Image.new(640, 80, [255, 116, 80, 48])
ground_img.box_fill(0, 0, 640, 10, [255, 0, 128, 0])

apples = []
apple_n = 5 # 追加
apple_n.times do # 変更
  apples << Apple.new()
end
player = Player.new()

Window.loop do
  Window.draw(0, 400, ground_img)

  Sprite.update(apples) # 変更
  player.update

  Sprite.clean(apples) # 追加
  (apple_n - apples.size).times do # 追加
    apples << Apple.new()
  end

  Sprite.draw(apples)
  player.draw
end

実行すると、次々とりんごが落下して、常に5つのりんごがウィンドウに表示されます。

Apple

【プログラムの説明】

  • 配列applesを作成し、複数の*Apple**クラスのインスタンスを入れます。
  • apple_nを作成して、りんごの表示数5を代入しています。こうすることで、数の変更が一箇所で行えます。
  • Sprite.cleanメソッドは、vanishメソッドが呼ばれたインスタンスを削除します。
  • ループの中で、常に削除されたりんごのインスタンスを補填しています。

得点の表示

りんごをキャッチすると、音が鳴って得点が入るようにします。また、得点をウィンドウに表示させます。

プログラムを、以下のように書き換えます。

player.rb

class Player < Sprite
  attr_accessor :score

  def initialize()
    image = Image.load("images/noschar1.png")
    x = (640 - image.width) / 2
    y = 400 - image.height
    @sound = Sound.new("sounds/get.wav") # 追加
    @score = 0 # 追加
    super(x, y, image)
  end

  def update
    dx = Input.x
    if (dx == -1 && self.x > 0) || (dx == 1 && self.x < (640 - image.width)) 
      self.x += dx
    end
  end

  def shot # 追加
    @sound.play
    @score += 1
  end
end

apple.rb

class Apple < Sprite
  def initialize()
    image = Image.load("images/apple.png")
    x = rand(0..(640 - image.width))
    y = 0
    @speed = rand(1..5)
    super(x, y, image)
  end

  def update
    self.y += @speed
    if self.y > 400 - image.height
      self.vanish
    end
  end

  def hit # 追加
    self.vanish
  end
end

main.rb

require 'dxruby'

require_relative 'apple'
require_relative 'player'

Window.bgcolor = [255, 128, 255, 255]
ground_img = Image.new(640, 80, [255, 116, 80, 48])
ground_img.box_fill(0, 0, 640, 10, [255, 0, 128, 0])
font = Font.new(32) # 追加

apples = []
apple_n = 5
apple_n.times do
  apples << Apple.new()
end
player = Player.new()

Window.loop do
  Window.draw(0, 400, ground_img)

  Sprite.update(apples)
  player.update

  Sprite.check(player, apples) # 追加
  Sprite.clean(apples)
  (apple_n - apples.size).times do
    apples << Apple.new()
  end

  Sprite.draw(apples)
  player.draw

  Window.draw_font(10, 10, "スコア:#{player.score}", font) # 追加
end

実行すると、次々とりんごが落下するので、プレイヤーを動かして、りんごをキャッチしてください。得点が加算されるのを確認します。

Apple

【プログラムの説明】

  • Playerクラスに、音用の変数@sound, 得点用の変数@scoreを作り、initializeメソッド内で初期値を設定しています。
  • Playerクラスに、衝突時に呼ばれるshotメソッドを作り、音を鳴らし、得点に加点をしています。
  • Appleクラスに、衝突時に呼ばれるhitメソッドを作り、vanishメソッドで表示を消しています。
  • main.rbのループ内で、Sprite.checkを使って衝突判定を行っています。
  • main.rbのループ内で、得点を表示しています。

爆弾の作成

このままでは、ゲームは無限に終わりません。爆弾を落下させて、プレイヤーが当たるとゲームが終了するようにします。

プログラムを、以下のように書き換えます。

player.rb

class Player < Sprite
  attr_accessor :score, :active

  def initialize()
    image = Image.load("images/noschar1.png")
    x = (640 - image.width) / 2
    y = 400 - image.height
    @sound = Sound.new("sounds/get.wav")
    @score = 0
    @active = true
    super(x, y, image)
  end

  def update
    dx = Input.x
    if (dx == -1 && self.x > 0) || (dx == 1 && self.x < (640 - image.width)) 
      self.x += dx
    end
  end

  def shot
    @sound.play
    @score += 1
  end

  def hit
    @active = false
  end
end

bomb.rb

class Bomb < Sprite
  def initialize()
    image = Image.load("images/bomb.png")
    x = rand(0..(640 - image.width))
    y = 0
    @sound = Sound.new("sounds/explosion.wav")
    @speed = rand(1..5)
    super(x, y, image)
  end

  def update
    self.y += @speed
    if self.y > 400 - image.height
      self.vanish
    end
  end

  def shot
    @sound.play
  end
end

main.rb

require 'dxruby'

require_relative 'apple'
require_relative 'bomb'
require_relative 'player'

Window.bgcolor = [255, 128, 255, 255]
ground_img = Image.new(640, 80, [255, 116, 80, 48])
ground_img.box_fill(0, 0, 640, 10, [255, 0, 128, 0])
font = Font.new(32) # 追加

apples = []
apple_n = 5
apple_n.times do
  apples << Apple.new()
end

bombs = []
bomb_n = 5
bomb_n.times do
  bombs << Bomb.new()
end

player = Player.new()

Window.loop do
  Window.draw(0, 400, ground_img)

  if player.active # 追加
    Sprite.update(apples)
    Sprite.update(bombs)
    player.update

    Sprite.check(player, apples)
    Sprite.clean(apples)
    (apple_n - apples.size).times do
      apples << Apple.new()
    end

    Sprite.check(bombs, player) # 追加
    Sprite.clean(bombs) # 追加
    (bomb_n - bombs.size).times do
      bombs << Bomb.new()
    end
  end

  Sprite.draw(apples)
  Sprite.draw(bombs) # 追加
  player.draw

  Window.draw_font(10, 10, "スコア:#{player.score}", font)
end

実行すると、りんごと爆弾が落下します。プレイヤーを動かして、りんごをキャッチしながら、爆弾をかわしてください。爆弾に接触するとゲームが停止します。

Apple

【プログラムの説明】

  • Bombクラスは、ほとんどAppleクラスと同じです。違いは、プレイヤーとの衝突時、shotメソッドで音を鳴らします。
  • Playerクラスには、プレイヤーの状態を保持する@active変数を追加しています。初期値はtrueで、爆弾と接触すると、hitメソッドでfalseに変更します。
  • main.rbでは、りんごと同様に、爆弾の初期化やループ内の処理を追加しています。
  • プレイヤーが爆弾と接触後、動きを停止させるために、ループ内のupdatecheckcleanの処理を、プレイヤーがアクティブの場合のみに限定しています。

ゲーム開始、終了

このゲームは、プログラムを起動したらすぐに始まり、ゲームが終了したら動きが止まります。 もう一度ゲームをするには、一度閉じてプログラムを起動しなければなりません。

そこで、スペースキーを押してゲームを開始し、終了したら、再度ゲームを開始するかプログラムを終了するか選択できるようにします。

プログラムを、以下のように書き換えます。

player.rb

class Player < Sprite
  attr_accessor :score, :active, :game_end

  def initialize()
    image = Image.load("images/noschar1.png")
    x = (640 - image.width) / 2
    y = 400 - image.height
    @sound = Sound.new("sounds/get.wav")
    @score = 0
    @active = false # 変更
    @game_end = false # 追加
    super(x, y, image)
  end

  def update
    dx = Input.x
    if (dx == -1 && self.x > 0) || (dx == 1 && self.x < (640 - image.width)) 
      self.x += dx
    end
  end

  def shot
    @sound.play
    @score += 1
  end

  def hit
    @active = false
    @game_end = true # 追加
  end

  def restart # 追加
    self.x = (640 - image.width) / 2
    self.y = 400 - image.height
    @score = 0
    @active = true
    @game_end = false
  end
end

main.rb

require 'dxruby'

require_relative 'apple'
require_relative 'bomb'
require_relative 'player'

Window.bgcolor = [255, 128, 255, 255]
ground_img = Image.new(640, 80, [255, 116, 80, 48])
ground_img.box_fill(0, 0, 640, 10, [255, 0, 128, 0])
font = Font.new(32)

apples = []
apple_n = 5
apple_n.times do
  apples << Apple.new()
end

bombs = []
bomb_n = 5
bomb_n.times do
  bombs << Bomb.new()
end

player = Player.new()

Window.loop do
  Window.draw(0, 400, ground_img)

  if player.active
    Sprite.update(apples)
    Sprite.update(bombs)
    player.update

    Sprite.check(player, apples)
    Sprite.clean(apples)
    (apple_n - apples.size).times do
      apples << Apple.new()
    end

    Sprite.check(bombs, player)
    Sprite.clean(bombs)
    (bomb_n - bombs.size).times do
      bombs << Bomb.new()
    end
  end

  Sprite.draw(apples)
  Sprite.draw(bombs)
  player.draw

  Window.draw_font(10, 10, "落ち物ゲーム スコア:#{player.score}", font, {color: C_BLUE})

  if !player.active # 以下追加
    if player.game_end
      Window.draw_font(210, 190, "ゲームオーバー", font, {color: C_BLUE})
    end
    Window.draw_font(120, 282, "スペースキー:ゲームスタート", font, {color: C_BLUE})
    Window.draw_font(181, 314, "ESCキー:ゲーム終了", font, {color: C_BLUE})
    if Input.key_push?(K_SPACE)
      if player.game_end
        apples.map {|apple| apple.vanish}
        Sprite.clean(apples)
        bombs.map {|bomb| bomb.vanish}
        Sprite.clean(bombs)
      end
      player.restart
    elsif Input.key_push?(K_ESCAPE)
      break
    end
  end
end

実行すると、スタートか終了のキー入力が表示されます。スペースキーを押すと、ゲームがスタート、または再スタートします。エスケープキーを押すと、ウィンドウが閉じてゲーム終了になります。

Apple
Apple

【プログラムの説明】

  • Playerクラスの@active変数の初期値をfalseにして、プログラム開始時にゲームスタートしないようにしています。また@game_end変数を作り、プレイヤーが爆弾に触れたかどうかを判断するのに使用します。
  • Playerクラスに、restartメソットを追加しました。このメソッドを呼ぶと、位置や変数を初期化します。
  • main.rbのループ内に、プレイヤーの状態によって表示内容や、キー入力などの機能を制御するコードを記入しています。
  • 特定のキーが押されているかどうかの判断は、Input.key_push?メソッドを使います。キーの種類はこちらから確認できます。
  • 配列のメソッドmapは、配列の各要素への処理を行う場合に使用します。ここでは、すべてのりんごや爆弾にvanishメソッドを呼び、次のSprite.cleanで、削除しています。
  • Windows.draw_fontメソッドに、文字色(青)を設定しています。

カスタマイズ

一通りゲームを作ってきました。

本格的なゲームとなると、改善したいところや追加したい機能がいっぱい考えられます。

みなさん、考えてみてください。

  1. DXRubyに似たゲーム開発用フレームワーク。Rubyでブラウザゲームを作るためのライブラリ。MITライセンス。 

  2. リポジトリをフォークしてimages、soundsフォルダのみ残したリポジトリのzipダウンロードリンク 

更新日: