簡単なゲームの作成

simple-gameの作成

簡単なゲーム作成を通して、DXRubyのプログラミングに慣れていきます。

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

ゲーム用フォルダの作成

最初に、プログラムファイルを置くフォルダを作成します。どこのフォルダでプログラムを作成してもよいですが、 ここでは複数のファイルを扱うため、専用のフォルダを作った方が管理しやすいためです。

コマンドプロンプトで、今いる場所がC:¥ruby_lecture¥codeであることを確認します。 もし違っていたら、cdコマンドで移動します。

コマンドプロンプトに以下を入力します。

mkdir simple-game

simple-gameフォルダが作成されます。

mkdir command

cdコマンドで、作成したsimple-gameフォルダに移動します。

コマンドプロンプトに以下を入力します。

cd simple-game
cd command

プログラムファイルの作成

プログラム用のファイルを作成します。どんなファイル名でもよいですが、ここではmain.rbとします。

VSCodeの左側のツリーから、simple-gameフォルダ上にマウスカーソルを移動させて、右クリックします。 メニューが表示されますので、新しいファイルをクリックします。

Create File

ファイル名の入力フィールドに、main.rbと入力します。

Create File

main.rbファイルが作成され、右側に表示されます。

Create File

ウィンドウの表示

それでは、プログラムを書いていきます。最初にゲーム画面を表示させるプログラムを書きます。

main.rbに以下のコードを書きます。

require 'dxruby'

Window.loop do
end

Windowの最初のWは大文字です。

保存して、実行します。

コマンドプロンプトに以下を入力します。

ruby main.rb
Program

黒いウィンドウが表示されます。

Program

プログラムを終了させる場合は、ウィンドウ右上のバツをクリックするか、コマンドプロンプトでCtrl+C(コントロールキーを押しながらC)を押します。

少しプログラムを説明をします。

require 'dxruby'は、DXRubyライブラリを使いますという宣言です。

Window.loop do〜endは、繰り返し処理(ループ)です。DXRubyライブラリの中で定義してあり、ウィンドウを作成して、表示する仕様になっています。 また、このループは、1秒間に60回繰り返されます。

DXRubyを使ったプログラムの基本的な書き方は、以下のようになります。

require 'dxruby'

# ここに、初期設定(変数の代入、インスタンスの生成など)を書く

Window.loop do
# ここに、データの更新や描画処理などを書く
end

ウィンドウのサイズは、デフォルトで640ピクセルx480ピクセルです。

文字の描画

画面に文字を表示してみます。

main.rbに、追加の行を追加します。

require 'dxruby'

font = Font.new(32) # 追加

Window.loop do
  Window.draw_font(100, 100, "テスト", font) # 追加
end

プログラムを実行します。

Font

ウィンドウにテストが表示されました。

少しプログラムを説明をします。

最初に、Fontクラスのインスタンスをfont変数に代入しています。これは、文字のフォントの条件(種類やサイズなど)を設定するもので、 ここでは、フォントサイズを32ピクセルに設定しています。

ループの中では、Window.draw_fontメソッドを使って、テストを表示させています。 ウィンドウや文字、画像などは、xとyを使って位置を表します。向かって左上が原点(0, 0)で、右下に行くほど数字が増えていきます。 このコードでは、ウィンドウの(100, 100)の位置に、テスト文字の(0, 0)(左上)がくるように、表示させています。

次に、ループの繰り返しごとに数字を1ずつ増やして表示するプログラムに書き換えます。

main.rbを、以下のように書き換えてください。

require 'dxruby'

font = Font.new(32)
count = 1 # 追加

Window.loop do
  Window.draw_font(100, 100, "ループ回数:#{count}", font) # 書き換え
  count = count + 1 # 追加
end

プログラムを実行します。

Font

ウィンドウにループ回数が表示されました。目まぐるしく増えていきますね。

画像の描画

画面に画像を表示してみます。

画像ファイルは、こちらからダウンロードしてください。

ダウンロードしたimage.zipファイルを展開して、imageフォルダ内にあるimageフォルダを、simple-gameフォルダ内に移動させてください。

main.rbを、以下のように書き換えてください(文字の部分は削除します)。

require 'dxruby'

player_img = Image.load("image/player.png")

Window.loop do
  Window.draw(100, 100, player_img)
end

プログラムを実行します。

Image

文字の場合と同様、ウィンドウの(100, 100)の位置に画像が表示されました(粗末な画像ですみません)。

プログラムの書き方は、文字の場合と若干異なりますが、同じような処理を行っています。

次に、プログラムを、画面右方向に1ループで1ピクセル移動するように書き換えます。

main.rbを、以下のように書き換えてください。

require 'dxruby'

player_img = Image.load("image/player.png")
x = 100 # 追加

Window.loop do
  Window.draw(x, 100, player_img) # 書き換え
  x = x + 1 # 追加
end

プログラムを実行します。

Image

画像が右方向に動くのが分かります。つまり、ループ内の処理で描画したものを、次のループでは一旦消して、再度描画していることが分かります。

キーボード入力

キーボードからの入力を反映させるようにします。

main.rbを、以下のように書き換えてください。

require 'dxruby'

player_img = Image.load("image/player.png")
x = 100
y = 100 # 追加

Window.loop do
  x = x + Input.x # 追加
  y = y + Input.y # 追加
  Window.draw(x, y, player_img) # 書き換え
end

プログラムを実行します。

キーボードの矢印キーを押してみてください。

Image

画像が上下左右に動くようになりました。プログラム中のInput.xは、右矢印キーが押されると、整数1を返し、左矢印キーが押されると整数-1を返します。 Input.yについては、みなさん想像できるでしょう。

Spriteクラス

DXRubyを使ったゲームを作成する上で、重要になるのがSpriteクラスです。

ゲームでは、プレイヤー、敵、弾やビームなどの武器、障害物などが出てきます。そして、それらが当たったかどうかで、ゲームの流れが変わってきます。 当たったかどうかの判定は、基本的に画像の位置関係から判断します。しかし、その処理をプログラミングしようとしたら、結構手間がかかります。

Spriteクラスには、衝突判定の処理があらかじめ用意されているため、それを使うことで、比較的簡単にプログラムを書くことができます。

まず最初に、Spriteクラスに画像を設定します。

main.rbを、以下のように書き換えてください。

require 'dxruby'

player_img = Image.load("image/player.png")
x = 100
y = 100
player = Sprite.new(x, y, player_img)

Window.loop do
  player.x += Input.x
  player.y += Input.y
  player.draw
end

プログラムを実行します。

キーボードの矢印キーを押してみてください。前のプログラムと同じように、プレイヤーを動かすことができます。

プログラムの説明をします。

  • Spriteクラスは、newメソッドを使って、インスタンスを生成します。引数には、位置と画像を渡しています。
  • Spriteクラスには、x、yが外部から参照・変更できるようになっているので、ループの中で位置を更新しています。
  • Spriteクラスには、描画メソッドdrawが定義されています。
  • player.x += Input.xの書き方は初めて見ると思います。a = a + nの短縮形がa += nです。

上記では、直接Spriteクラスを使用しました。ただ、独自の処理を書きたい場合、このままではできません。 そこで、Rubyの継承を使います。継承を使うと、既存のクラス定義(変数やメソッドなど)を引き継いだ、新しいクラスを自分で定義することができます。

継承の書き方は、以下のようになります。

class 新しいクラス名 < 既存クラス名
  def メソッド名
    処理
  end
  ・・・
end 

上記の、既存クラススーパークラス(親クラス)新しいクラスサブクラス(子クラス)と言います。

新しいクラスでは、既存クラスのメソッドを使うことができます。また、新しいクラス定義内で、独自のメソッドを定義することができます。さらに、既存クラスのメソッドと同じ名前のメソッド定義を書くと、既存クラスのメソッドを上書き(オーバーライド)することもできます。

それでは、Spriteクラスを継承した、プレイヤー用のクラスPlayerを作ります。

main.rbを、以下のように書き換えてください。

require 'dxruby'

class Player < Sprite
  def update
    self.x += Input.x
    self.y += Input.y
  end
end

player_img = Image.load("image/player.png")
x = 100
y = 100
player = Player.new(x, y, player_img)

Window.loop do
  player.update
  player.draw
end

プログラムを実行します。

キーボードの矢印キーを押してみてください。前のプログラムと同じように、プレイヤーを動かすことができます。

Playerクラスの定義で、プレイヤーの位置を更新するためのupdateメソッドを追加しています。これにより、ループ内のコードが簡潔になります。

次に、衝突判定の機能を追加します。

Spriteクラスには、衝突判定用のメソッドSprite.checkが定義されています。 このメソッドは、衝突しているか判定し、もし衝突していたら、hitメソッド、shotメソッドを呼びます。

require 'dxruby'

class Player < Sprite
  def update
    self.x += Input.x
    self.y += Input.y
  end
end

class Enemy < Sprite
  def hit
    self.vanish
  end
end

player_img = Image.load("image/player.png")
x = 100
y = 100
player = Player.new(x, y, player_img)

enemy_img = Image.load("image/enemy.png")
x = 300
y = 300
enemy = Enemy.new(x, y, enemy_img)

Window.loop do
  player.update
  player.draw

  enemy.draw

  Sprite.check(player, enemy)
end

プログラムを実行します。

プレイヤー(善)、と敵(悪)が表示されます。キーボードの矢印キーを押してプレイヤーを移動させて、敵に体当たりしてみてください。敵が消えます。

Sprite

プログラムの説明をします。

  • Playerクラスと同様にSpriteクラスを継承したEnemyクラスを定義しています。
  • Enemyクラスのhitメソッドは、衝突した時に呼ばれるメソッドです。
  • vanishメソッドは、Spriteのインスタンスを消します(描画できなくします)。この場合、自分自身(敵)を消します。
  • ループの中では、敵の描画と衝突判定のコードを追加しています。

ファイル分割

プログラムコードが長くなってきました。 一つのプログラムファイルにまとめて書いてもよいですが、長くなればなるほど、可読性が損なわれていきます。

そこで、ファイルを分割してみます。通常、クラスごとにファイルを分け、ファイル名にはクラス名を(小文字で)使います。

以下のように、ファイルを分割します。

player.rb

class Player < Sprite
  def update
    self.x += Input.x
    self.y += Input.y
  end
end

enemy.rb

class Enemy < Sprite
  def hit
    self.vanish
  end
end

main.rb

require 'dxruby'

require_relative 'player'
require_relative 'enemy'

player_img = Image.load("image/player.png")
x = 100
y = 100
player = Player.new(x, y, player_img)

enemy_img = Image.load("image/enemy.png")
x = 300
y = 300
enemy = Enemy.new(x, y, enemy_img)

Window.loop do
  player.update
  player.draw

  enemy.draw

  Sprite.check(player, enemy)
end
Split Files

main.rbには、他のプログラムファイルをrequire_relative構文を使って読み込みます(拡張子.rbは不要です)。

プログラムの実行は、これまでと同じruby main.rbです。

キーボードの矢印キーを押してみてください。前のプログラムと同じ動きをします。

配列を使う

敵が1体ではおもしろくないので、増やしてみます。

main.rbを、以下のように書き換えてください。

main.rb

require 'dxruby'

require_relative 'player'
require_relative 'enemy'

player_img = Image.load("image/player.png")
enemy_img = Image.load("image/enemy.png")

player = Player.new(100, 100, player_img) # 簡潔にする
enemies = [] # 書き換え、以下追加
10.times do
  enemies << Enemy.new(rand(0..(640 - 32 - 1)), rand((480 - 32 - 1)), enemy_img)
end

Window.loop do
  player.update
  player.draw

  Sprite.draw(enemies)

  Sprite.check(player, enemies) # 書き換え
end

プログラムを実行します。

敵(悪)が10体表示されます。同様にキーボードの矢印キーを押してプレイヤーを移動させて、すべての敵に体当たりしてみてください。

Split Files

プログラムの説明をします。

  • enemies変数に空の配列を作って、Emenyクラスのインスタンスを挿入しています。
  • 整数.times do〜endtimesは、整数クラス(Integer)に定義されているメソッドで、整数回ループを回します。
  • Enemyクラスのインスタンス生成時、敵画像がウィンドウをはみ出さないxとyをrandメソッドを使ってランダムに設定しています。
  • Sprite.checkメソッドは、引数に配列を渡すことができます。

インスタンス変数

体当たりした敵の数を表示するようにします。

player.rbmain.rbを、以下のように書き換えてください。

player.rb

class Player < Sprite
  attr_accessor :score # 追加

  def initialize(x, y, image) # 追加
    @score = 0
    super
  end

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

  def shot # 追加
    @score += 1
  end
end

main.rb

require 'dxruby'

require_relative 'player'
require_relative 'enemy'

font = Font.new(32) # 追加
player_img = Image.load("image/player.png")
enemy_img = Image.load("image/enemy.png")

player = Player.new(100, 100, player_img)
enemies = []
10.times do
  enemies << Enemy.new(rand(0..(640 - 32 - 1)), rand((480 - 32 - 1)), enemy_img)
end

Window.loop do
  player.update
  player.draw

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

  Sprite.check(player, enemies)
end

プログラムを実行します。

スコアが表示されます。キーボードの矢印キーを押してプレイヤーを移動させて、敵に体当たりすると、スコアが加算されます。

Split Files

プログラムの説明をします。

  • Playerクラスに、スコアを格納する@score変数を追加しました。
  • Playerクラスに、shotメソッドを追加しました。衝突判定で、衝突した時にスコアをカウントアップします。
  • main.rbのループの中に、スコアを表示する機能を追加しました。

衝突判定で、衝突した時にshotメソットとhitが呼ばれます。 Spriteクラスのcheckメソッドで、第1引数の方には、shotメソッド、第2引数の方には、hitメソッドが呼ばれる仕様です。

整数の演算

ゲーム性を持たせるために、残り時間を表示し、ゼロになったらプレイヤーの動きを止めます。

main.rbを、以下のように書き換えてください。

main.rb

require 'dxruby'

require_relative 'player'
require_relative 'enemy'

font = Font.new(32)
player_img = Image.load("image/player.png")
enemy_img = Image.load("image/enemy.png")

player = Player.new(100, 100, player_img)
enemies = []
10.times do
  enemies << Enemy.new(rand(0..(640 - 32 - 1)), rand((480 - 32 - 1)), enemy_img)
end

timer = 600 + 60 # 追加

Window.loop do
  if timer >= 60 # 追加
    timer -= 1 # 追加
    player.update
  end

  player.draw

  Sprite.draw(enemies)
  Window.draw_font(10, 0, "スコア:#{player.score}", font)
  Window.draw_font(10, 32, "残り時間:#{timer / 60}秒", font) # 追加

  Sprite.check(player, enemies)
end

プログラムを実行します。

残り時間がカウントダウンしていき、ゼロになったらプレイヤーが動かなくなります。

Split Files

プログラムの説明をします。

Window.loopは、1秒間に60回ループします。この特性を利用して、残り時間を算出します。

Rubyでは、整数どうしの計算結果は整数というルールがあります。ちなみに、実数どうしの計算、実数と整数の計算は実数になります。 つまり、600 / 60の結果は10ですが、599 / 60の結果は9.98333ではなく、切り捨てられて9となります。 ここでは、そのRubyの仕様を利用して、残り時間を秒で表示しています。

カスタマイズ

単純ですが、ゲームらしくなってきました。

もっと楽しめるゲームにするには、どんな機能があったらよいでしょうか。考えてみてください。

更新日: