キーコードを使用する場面がこれから増えて来るのでその場合は下記リンクを 参照する事。
前週の内容を加味して話を進めて行きます。 ここからは、いよいよゲームの制作に入って行きます。 不明な点が出てきた場合は過去の話に戻って、 良く理解してから進めて下さい。
キャラクタの操作を行うために、 キーボード入力の受け付けを行なう必要が出てきます。
DXRuby では、キーボード、マウス、 ゲームパッドのそれぞれのアクション (要するにボタンを押したりする行為) を受け付けています。 その為、ユーザーからの入力は簡単に扱えます。 そして、この機能を使ってキャラクタの移動の機能を実装します。
今回はキャラクタの移動については、
キーボードの入力を受け付ける形にします。
キーボード入力の受け付けは、
Input.keyDown?
というメソッドを使用する事で出来ます。
メソッドの () 内で指定したキーの入力
(押し続けを含む) を判定し、指定したキーを押していれば true
という、"真" をあらわす値が返されます。
指定したキーが押されていなければ false
という "偽" をあらわす値が返されます。
このメソッドだけでは押しているのかどうかが分かるだけで、 処理がゲームに反映されない (特定のボタンを押した時にキャラクタが移動しない) ため、 条件分岐を使います。
キーボードの特定のキーを押すと キャラクタが移動するという機能が欲しい場合、 キーを押していると移動する、 何も押していない場合は動かない、といった処理にする必要があります。
下記の様な記述を行なう事で、入力に対して動作を割りあてる事が出来ます。
Window.loop do
# K_L と言うのは、キーボードの L キーという意味
# つまりキーボードの L のキーを押して (押し続けて) いたら true (真) である
# true が返ってくると、if `条件式` ~ end の間に書いた処理が実行される
# この場合は sample_sprite.x += 1 が実行される
# 因みに、移動速度を変更したい場合は数値を変更すると良い
if Input.keyDown?(K_L)
sample_sprite.x += 1
end
# Sprite.draw メソッドを使うと指定した Sprite データを画面に表示 (描画) することが出来る
Sprite.draw(sample_sprite)
end
使いたいキーのコードを調べたい場合、 DXRuby キーコードリファレンス を参照して下さい。
矢印キーを使って、 上下左右に移動出来る様にプログラムを作成して下さい。
衝突判定とは、 オブジェクトとオブジェクトが衝突を起こしていることを判定する事です。 衝突判定に穴があると、 キャラクタが重なったり、壁のすり抜け等のバグに繋ります。
本来、衝突判定は非常に面倒くさい処理を必要としますが (数学で、行列やベクトル等が必須)、 簡単に判定できる仕組みがありますのでそちらを使用します。
まず、当たり前ですが、衝突には二つ以上のオブジェクトが必要ですので、 Sprite データとして 2 つの画像 (オブジェクト) を用意します。 簡単に判定を行うには Sprite データとして扱わないと行けないので、 必ず Sprite データとして画像を用意して下さい。
衝突判定を行なうには、===
という比較を使用します。
==
と間違えない様に注意して下さい。
===
を挟んだ、
左辺のオブジェクトと右辺のオブジェクトが
重なっている場合に true を返します。
重なっていない場合は false を返します。
サンプルコードを実行して衝突する事と確認して下さい。
image_chara = Image.load_tiles("../character.png", 4, 4)
chara = Sprite.new(200, 200, image_chara[0])
image_box = Image.load_tiles("../image/colorbox.png", 6, 1)
box = Sprite.new(400, 200, image_box[0])
Window.loop do
if Input.keyDown?(K_L)
chara.x += 2
# === を使って左辺と右辺を比較する事で衝突の判定をとることが出来る
# この時両方のデータは Sprite データを持った変数か、配列であること
# それ以外ではエラーとなる
# 衝突が行われた場合は true が返ってくるため、この場合、
# 直前にキャラクタの座標を x+2 しているため、その時に衝突した場合は
# x+2 の処理を取りやめている
# 結果的に、キャラクタは移動せずに壁にぶつかって止まっているように見える
if chara === box
chara.x -= 2
end
end
# 画面の更新を行っている
# この処理が実行されるまでは、オブジェクトにどれだけ変化が起こっていても
# 見た目には反映されない
Sprite.draw(chara)
Sprite.draw(box)
end
上記プログラムでは衝突の判定は box に対して右にのみ存在しています。 その為、 キャラクタが箱に当たった時に移動しないという操作は右からのみ有効です。 これを、全ての方向から判定を行ない、すり抜けが発生しない様にして下さい。
配列は下記プログラムの様に、 最初からデータを入れた状態で宣言する事も出来ます。
array = [1, "test"]
print(array) #=> [1, "test"]
print(array[0]) #=> 1
print(array[1]) #=> test
この様な記述の方法を使う事で、 Sprite データを画面に表示する時のプログラムを纏める事が出来ます。 これを利用し、 問題 9. で作成したプログラムに存在している Sprite.draw の部分を 1 行に纏めて下さい。
Map という名前がついてる為、キャラクタとは異なるモノを連想しがちですが、 基本的に Map も画像を並べているだけ (障害物や人等の意味合いをもたせるのはあくまでもプログラムの仕事) で、 自分が表現したいように画像を並べる方法を覚えると、 簡単に Map を生成することが出来ます。
Map を作成するには、既に習った配列と繰り返しを使用する事で出来ます。 手作業で Map を作成する場合、 下記の様なプログラムを作成する事になります。
require 'dxruby'
image = Image.load_tiles("../image/colorbox.png", 6, 1)
gray1 = Sprite.new(0,0,image[5])
gray2 = Sprite.new(20,0,image[5])
gray3 = Sprite.new(40,0,image[5])
gray4 = Sprite.new(60,0,image[5])
gray5 = Sprite.new(80,0,image[5])
gray6 = Sprite.new(100,0,image[5])
gray7 = Sprite.new(120,0,image[5])
gray8 = Sprite.new(140,0,image[5])
gray9 = Sprite.new(160,0,image[5])
gray10 = Sprite.new(180,0,image[5])
Window.loop do
Sprite.draw(gray1)
Sprite.draw(gray2)
Sprite.draw(gray3)
Sprite.draw(gray4)
Sprite.draw(gray5)
Sprite.draw(gray6)
Sprite.draw(gray7)
Sprite.draw(gray8)
Sprite.draw(gray9)
Sprite.draw(gray10)
end
上記プログラムを実行してみると、 少しばかりのブロックが生成されている事が分かります。
これでは本格的なゲームだけでなく、 簡単なゲームを作るだけでもかなりの労力を伴います。 その為、繰り返しを使う訳です。
上記のプログラムを繰り返しを使用して書き直すと下記の様になります。
require 'dxruby'
block_x = 0
block_y = 0
count = 0
sprites = []
images = Image.load_tiles("../image/colorbox.png", 6, 1)
loop do
sprites[count] = Sprite.new(block_x, block_y, images[5])
if 180 < block_x
break
end
block_x = block_x + 20
count = count + 1
end
Window.loop do
Sprite.draw(sprites)
end
block_x の値を 20 ずつ増やしているのは block が 1 つ辺り 20x20 px のサイズだからです。 この様な方法で敷き詰めると、Map の形式になります。
少しタイミング的に遅くなりましたが、 Window のサイズの変更に関して説明します。
DXRuby において、WIndow のサイズを変更するのは簡単です。
Window.width
や Window.height
という変数の数値を変更する事で
サイズが変わります。
Window.width = 360
Window.height = 480
Window.loop do
end
上記プログラムで Window のサイズが変更出来る事が確認出来ます。 各自確認して下さい。 因みに、入れる数値 (サイズ)は px (ピクセル) を単位としています。
Window のサイズを 300x300 px に設定し、 画面の上下左右端を全て block で埋める様にプログラムを作成して下さい。 この時、キャラクタは作成する必要はありません。
問題 11 で作ったプログラムにキャラクタを追加して下さい。 そのキャラクタは block に衝突した時、すり抜けない様にして下さい。
衝突判定に使用する ===
は配列を扱う事も出来ます。
なので、Sprite データのみを入れた配列同士、
もしくは変数と配列の組み合せであればまとめて判定を行なう事が出来ます。
上手く利用して下さい。
これから、単純なアクションゲームを作成して行きたいと思います。 まず、先程出て来た Map を作成するプログラムを利用して足場を作成したい と思います。
Window.width = 360
Window.height = 480
images = Image.load_tiles("../image/colorbox.png", 6, 1)
block_x = 0
block_y = 460
blocks = []
count = 0
loop do
blocks[count] = Sprite.new(block_x, block_y, images[5])
if 340 < block_x
break
end
count = count + 1
block_x = block_x + 20
end
Window.loop do
Sprite.draw(blocks)
end
このプログラムによって足場を作成出来たのが確認出来ると思います。 後はキャラクタを作成し、世界に重力を与えるとゲームの様相を見せてきます。 そこで、問題です。
先程のプログラムにキャラクタを登場させて下さい。 その時、キャラクタはブロックより上の位置に登場し (ブロックより上で、画面内であればどこでも可)、 ブロックに衝突するまで下に移動し続ける事を条件とします。
また、問題 13 で作成したプログラムに、 矢印キーで左右に移動出来る機能を付けて下さい。 この時、キャラクタが画面の外に出る事について、 行動を制限する機能を入れる必要はありません。
ここまででは、入れ物と登場キャラクタが存在するだけで 肝心のゲームとして遊ぶ機能が足りません。 なので、その要素を追加したいと思います。
アクションゲームと言っているので 様々な要素が考えられると思いますが、 まずは物が落ちて来る要素を入れたいと思います。
物が落ちて来るというのは、 今迄の学習から考えると、 画像が画面の上部から降りてくる事だと分かります。
画像には、block の灰色以外の物を使いたいと思います。
item_x = x
item_y = 0
item = Sprite.new(item_x, item_y, image[0])
Window.loop do
item.y += 1
Sprite.draw(item)
end
上記プログラムの image 変数は、 既に読み込んでいる (筈の) block の画像を想定しています。 なので、各自 block 画像を保持している変数を指定して下さい。
また、ブロックを状況によって消したいという事もあるので、 消し方についても解説します。
Ruby では、変数を消すには nil を変数に代入します。
下記の用に書きます。
item = nil
ブロックを消す時も同様の書き方が出来ますが、
少し気を付ける必要があります。
基本的に、nil とは変数が値を持た無いという意味を持つ為、
エラーをおこす事があります。
そして Sprite.draw
は nil を持つ変数を与えると
エラーになります。
その為、Sprite.draw
は変数が nil を持つ際に避ける条件をつけます。
if item.nil? == false
Sprite.draw(item)
end
if の書き方に新しい書き方が出て来ました。
.nil?
というのは変数の中身が nil かどうかを調査する書き方です。
nil の時は true
を返し、それ以外のデータが入っている時は false
を返します。
現状では、item が落ちてくるだけで何も起らないので キャラクタが item に触った時に消える用にして下さい。
ブロックが消えた後、 最初にブロックが出てきた位置から再度落ちて来る様にして下さい。 再出現は、ブロックに触って消える限り無限に繰り返して下さい。
ブロックの出現と消去、再出現までを作成しましたが、 延々と同じ場所から出現し続けるのではゲームになりません。
なので、出現する位置を出る度に変更したいと思います。 大抵のプログラミング言語にはランダムに数値を作成する機能があります。 Ruby のランダムメソッドはこの様に使用します。
x = rand(340)
rand メソッドの () には数値を入れる事が出来ます。 () に入れた数値未満の値を得る事が出来ます。 但し、0 を与えた場合のみ小数点を返します。
今回はこのメソッドを使って、 ブロック出現時の X 座標に関する位置をランダムにします。
x = nil
Window.loop do
if item.nil?
x = rand(340)
item = Sprite.new(x, y, image[0])
end
Sprite.draw(item)
end
このプログラムには、ブロックを消す要素は入れていません。 その為、ブロックを消す要素が必要な場合は別途入れて下さい。
ブロックが存在するかどうかをチェックした後、 存在しない場合は変数 x の値を rand メソッドによって ランダムな値に変更します。 この時、340 を指定しているのは、 ブロックのサイズは 20x20 で、画面サイズは 360 を想定している為、 360 - 20 で 340 となります。 何故数値の差を求める必要があるかと言うと、 画像を表示する時に基準として指定した値を原点 0 として ゲームの画面上に描画する為、 表示したい画像のサイズ分だけ画面の端から空けておく必要があります。 そうしないと、画面の外に画像がはみでる事になります。
また、逆側 (マイナス座標に対して) は、 指定した座標を原点に捉える為、 画像の座標がマイナスにならなければ、はみ出る事はありません。
問題 16 で作成したプログラムを ブロックがランダムに出現する様に変更して下さい。
ここまで来ると、大分ゲームらしくなってきます。 キャラクタは自由に移動し (左右のみですが)、 ランダムにブロックを落し、 それを取得する様になったので動きが多いですよね。
ただひたすら落ちてくるブロックを取得し続けるゲームも それはそれで良いのですが、 どうせならやる気が出る様な内容にしたいとも思います。
そこで、ゲームにスコアの機能を付けたいと思います。
スコアとは成績の事です。 ゲーム結果に成績を付けるとなると、 テキスト (要するに文字の事) を表示する必要があります。
ゲームの画面に文字を表示するには下記の様に記述します。
# coding: cp932
require 'dxruby'
x = 100
y = 100
font = Font.new(32)
Window.loop do
Window.draw_font(x, y, "ふぉんと", font)
end
実際に新しくプログラムを作成して確認してみて下さい。 "ふぉんと" という文字が表示されていると思います。 フォントを表示する際に注意する点として、 font 変数に渡しているデータの作成があります。
Font.new
で行なっている作業は、
ゲーム内で使用するフォントのサイズの指定です。
後に Window.draw_font というメソッドで作成した
font 変数を使用しています。
このメソッドを呼び出した時点で描画されるので注意して下さい。
成績の評価方法は、 キャラクタが落下ブロックに触った際に 1 点ずつ加算する方式で、 スコア機能を実装して下さい。
スコアを表示する為には数値を文字列に変換する必要があります。
test = 0
test.to_s
このプログラムの様に .to_s
は変換したい数値の後ろに付ける事で
文字列に変換できます。
ここまで来ると本当にゲームとしての体裁が整っています。 後 1 つ残念な点を挙げるとすれば、 それはゲームの明確な終わりをプレイヤーに示せない点でしょうか。
ゲームが唐突に始まる事よりも、 ゲームの終りとしての区切りが無い方が不便と思われます。
プログラムの定義にも正しく終了出来る事がある事ですし、 ここはゲーム終了させるタイミングを作成したいと思います。
まず、現在このゲームではブロックを取れない場合、 画面の外に出て返ってこない仕様です。 その為、次のブロックが出て来る事を永遠に待つ事になります。 これが問題です。 なので、この問題点を解消し、 落下ブロックが下のブロックに触れた段階で終了にします。
終了する為の条件式は、 今迄やって来た中で理解出来ると思いますが、 終了する為の具体的な方法が現状では分かりません。
DXRuby では、
ゲームを終了するには Window.loop
を break
すると終了出来ます。
但し、唐突に終了します。
何もメッセージを出さずウィンドウが消える為
プレイヤーには何が起ったのか理解出来ない可能性があります。
そこで、アイテムを落とした際に、 Game Over の様なメッセージと、 スコアがどれ位であったかを示し、 何かボタンを押した時に終了する様にして貰います。 ゲームが終了した瞬間の画面は表示したままにして下さい。 条件分岐を上手く使用する事で解決出来ます。
以下ヒント。 画面に画像や文字を表示するのに使用しているメソッドは、 毎回のループの中で画面を更新しています。 その為、描画する宣言をしていない部分に関しては、 他の画像やフォントを描画する事で消えます。 逆に言えば、 そのまま画面を残したい場合は画面が更新出来なくなれば良いという事です。
この様に、表示したいパーツだけを宣言するテクニックも ゲームの中では重要になります。