Part 2 の今回は、ボールのオブジェクトを作って、パドルからボールを発射するところまで進めていく。
それでは前回に引き続きブロック崩しを開発していこう。
Memo:
過去のシリーズをまだご覧になっていない方は、そちらを先にご覧いただくことをおすすめします。
Godot で作るブロック崩し
ボールを作る
前回の Part 1 でプレイヤーが操作するパドルを作ったので、今度はボールを作る。
ルートノード「Game」の子ノードとして「RigidBody2D」ノードを追加する。
名前は「Ball」とする。
次に「Ball」ノードに必要な子ノードを追加していく。これは「Paddle」ノードの時と同様の作業だ。まずは「Sprite」ノードを追加しよう。
続けて「CollisionShape2D」ノードを追加しよう。
「Ball」ノードに2つの子ノードが追加された。
それぞれのプロパティを設定していこう。
まずは「Sprite」ノードから。シーンドックで「Sprite」を選択した状態にして、右側のインスペクタドックを見ると「Texture」プロパティが [空] になっている。
その「空」めがけて、ファイルシステムドックから「Ball.png」をドラッグ&ドロップする。
「Texture」プロパティにボールの画像が反映したのを確認しておこう。
次にシーンドックで「CollisionShape2D」ノードを選択し、インスペクタを見ると「Shape」プロパティが [空] になっている。
その[空]をクリックして「新規 CircleShape2D」を選択する。
「Shape」プロパティが設定されました。2D ワークスペースを見てみよう。
半透明の緑色の円が「CollisionShape2D」ノードの形だ。サイズが「Sprite」ノードの Texture よりやや大きいので、赤丸のポイントをドラッグしながら大きさを「Sprite」ノードの Texture に合わせよう。
これくらいにする。
それではここからインスペクタドックで「Ball」ノードの変更が必要なプロパティを設定していこう。シーンドックで「Ball」ノードを選択してインスペクタを見てほしい。
上から順番にいこう。まずはの「Mode」プロパティを変更する。「Character」にする。これによりボールが回転するのを防ぐことができる。
次に「Physics Material」プロパティの [空] をクリックして「新規 PhysicsMaterial」を選択する。
設定した「PhysicsMaterial」をクリックするとさらにプロパティが表示される。「Friction」プロパティを 0 にして摩擦の影響でボールのスピードが落ちないようにする。また「Bounce」プロパティを 1 に変更して、パドルに当たったらきっちり跳ね返るようにする。
続いて「Gravity Scale」プロパティを設定する。この値は 0 にしないと、ボールが重力の影響を受けて、常に画面の下方向に落ちる力がかかってしまう。
「Linear」>「Damp」プロパティも0にする。これもボールの勢いがなくならないようにするためだ。
では 2D ワークスペースで「Ball」ノードを移動させる前に、「Ball」ノードの子を選択不可にしておこう。
シーンドックでも選択不可になっていることが確認しておく。
ドラッグして「Ball」ノードを移動させる際に、ツールバーでいくつか操作する。
まず、位置をパドルに合わせやすいように、ツールバーから「グリッドスナップ」を有効にする。
そして「移動モード」アイコンをクリックして、ドラッグで移動できるようにする。ショートカットキーを覚えておくと便利だ( Windows: W / macOS: W )。
2D ワークスペースのディスプレイ表示範囲の左上角(0, 0)から、パドルのちょうど真上にドラッグして「Ball」ノードを動かする。
「Ball」ノードの「Transform」>「Position」プロパティを確認しておこう。x座標が 256 で「Paddle」ノードと同じなので、今ボールがパドルのちょうど真上に位置していることがわかる。もしズレてしまった場合は、プロパティの値を直接触って調整すると効率的だ。
ここで一度、シーンを実行してみよう。
なんだかパドルに対してボールが大きすぎる印象だ。サイズを変更しよう。
シーンドックで「Paddle」ノードの子ノード「Sprite」を選択した状態で、インスペクタの Node2D クラスの「Transform」の「Scale」プロパティで x, y の値をそれぞれ 0.5 とする。サイズを半分にするという意味だ。
次に同じく「Paddle」ノードの子ノード「CollisionShape2D」ノードを選択して、2D ワークスペース内でポイントをドラッグして、サイズを「Sprite」ノードに合わせよう。
Memo:
「Paddle」ノードの「Scale」プロパティを直接変更すれば1回の作業で済むのではないか、とお思いになる方もいらっしゃるかもしれません。しかし残念ながら、子ノードの「CollisionShape2D」ノードのサイズが変わってなければ、物理エンジンによって「Paddle」ノードの「Scale」プロパティの値が上書きされてしまいます。
ボールがパドルから離れてしまったので、「Ball」ノードを選択した状態にして、2D ワークスペース上でドラッグして移動させよう。この時、ツールバーでピクセルスナップを有効にして、グリッドスナップは無効にしておくと移動しやすいと思う。
さっきより良いバランスになった。
念のため、インスペクタで「Ball」ノードの「Position」プロパティを確認しておこう。以下のスクリーンショットのようになっていれば問題ないだろう。
ボールを発射する
それではここからスクリプトを作ってボールを動かしていく。シーンドックで「Ball」ノードを選択した状態で、スクリプトをアタッチする。
先に作っておいた「scripts」フォルダに保存したいので、フォルダアイコンをクリックしよう。
もしパスが「res://scene」になっていたら、一つ上の階層に戻ってから「scripts」フォルダを選択しよう。パスが「res://scripts」になったら、ファイル名を「Ball.gd」として「開く」をクリックする。
パスが更新された。「作成」をクリックする。
スクリプトワークスペースが開いた。
今回、スクリプトに記述する全体のコードは以下の通りだ。コーディングはちょっと苦手、という方はひとまずこのコードでスクリプトのコードを置き換えてほしい。
extends RigidBody2D
export (float) var ball_speed = 150.0
func _ready():
var direction = Vector2(1, -1).normalized()
var velocity = direction * ball_speed
apply_impulse(Vector2.ZERO, velocity)
スクリプト解説
あとで使う_ready()
関数以外の部分を削除しよう。一旦、以下のようになる。
extends RigidBody2D
func _ready():
pass # Replace with function body.
ボールの速さをball_speed
という名前のプロパティで定義する。
# ボールの速さ:1秒間に移動する距離(pixel)
export (float) var ball_speed = 150.0
インスペクタで簡単に編集できるようにexport
キーワードを頭につける。(float)
はこのプロパティの値が float というデータ型であることを示している。int 型でも大きな問題はないが、今後ボールがだんだん速くなる仕組みを作る時に、より滑らかにスピードアップできた方が良いので float 型にしている。と、散々説明しておいてなんだが、値を150.0
と浮動小数で記述していることから、GDScript が自動的に float 型と判断するため、(float)
の記述は無くても構わない。
次に_ready()
関数を編集していく。まずはpass
から始まる行を削除して、別のコードに書き換えていく。
pass # Replace with function body.
Memo:
func
キーワードで宣言したメソッドのブロック(インデントで区分されたコードのかたまり)には何かコードを記述しないとエラーになります。まだ記述する内容が決まっていない場合に、一旦pass
と入れておくことで、そのエラーを回避することができます。pass
にはそれ以上の意味は特にありません。
_ready()
関数のブロックに以下のコードを記述する。インデントを忘れないように注意が必要だ。
# ボールが発射される向き
var direction = Vector2(1, -1).normalized()
_ready()
関数内でプロパティを2つ定義している。
一つ目のプロパティはdirection
だ。これはボールが飛んでいく方向を定義している。Vector2(1, -1)
で右上に飛んでいくのはなんとなくおわかりいただけるかと思うが、.normalized()
とはなんだろう。
まず、座標上 (0, 0) から (1, -1) までの長さが 1 ではないということを理解するところから始めよう。(1, 0) と (0, -1) はどちらも (0, 0) からの距離が 1 だ。その両方の点と接する円があったとすると、その円周に位置する点は全て (0, 0) からの距離が 1 ということになる。しかし、(1, -1) は、その円より外側にあるので、(0, 0) からの距離は 1 より大きい(厳密には √2 )ということになる。
プロパティdirection
は方向のみを表すプロパティにしたいので、長さを 1 にする必要がある。そこで (1, -1) の方向はそのままで、長さだけちょうど 1 にしてくれる便利な関数がnormalized()
なのだ。これは Vector2 クラスでもともと用意されているメソッドなので最初から使える。
次にボールの速度を表すプロパティvelocity
だ。単純に先に定義した方向の情報を持たない速さを示すプロパティball_speed
と方向を示すdirection
の掛け算で定義している。
# ボールの速度(方向の情報をもったスピード:方向 x 速さ)
var velocity = direction * ball_speed
最後に RigidBody2D のメソッドapply_impulse()
を使って、ボールに衝撃を与える。
# 一回だけオブジェクト(ボール)に衝撃を与える
apply_impulse(Vector2.ZERO, velocity)
これを_ready()
関数内に記述したのは、シーンを実行した時にゲームの準備処理段階で1回限りの衝撃をボールに加えてゲーム開始と同時にボールを飛ばすためだ。第一引数の offset はVector2.ZERO
で (0, 0) を指定、第二引数 impulse はボールの速度を衝撃として与えるため、先に定義しておいたvelocity
を指定している。apply_impulse()
メソッドについて詳しくは Godot 公式ドキュメンテーション
参照のこと。
では改めてプロジェクトを実行してみよう。
斜め45°の方向へ真っ直ぐ飛んでいった。
おわりに
Part 2 はここまでで終了だ。次回 Part 3 では、ボールが画面外に飛んで行かないように壁を作っていく。