Part 9 の今回は、ブロック崩しの一部の要素にアニメーションを追加する。全てのオブジェクトをアニメーションさせると作業量が膨大になるので、今回はパドルとブロックに対象を絞ってに簡単なアニメーションを追加していく。
なお、アニメーションについては Godot 公式ドキュメントの「ステップ・バイ・ステップ」のチュートリアル にもある程度わかりやすく掲載しているので、そちらも参考にしていただきたい。
それでは前回に引き続きブロック崩しを開発していこう。
Memo:
過去のシリーズをまだご覧になっていない方は、そちらを先にご覧いただくことをおすすめします。
Godot で作るブロック崩し
パドルにアニメーションを追加する
今回、パドルに追加するアニメーションは2種類。一つは、パドルの顔に「まばたき」のアニメーションをさせる。もう一つは、ボールがパドルに当たった時に、パドルの色と顔をアニメーションさせてる。それではやっていこう。
パドルのアニメーションを作る
アニメーションを実装するには、アニメーションをさせたいノードの子として「AnimationPlayer」ノードを追加する必要がある。これを追加しないとアニメーションの編集ができない。
「AnimationPlayer」ノードを追加する
では「Paddle」ノードに「AnimationPlayer」ノードを追加しよう。
さらに、あとで使うことになる「Timer」ノードも追加しておこう。名前は「AnimationTimer」に変更しておく。
「AnimationTimer」のプロパティも、「Wait Time」を「4」に、「Autostart」を「オン」にしておく。
新規アニメーションを作成する
「AnimationPlayer」ノードを追加したので、Godot エディタ下部のアニメーションエディタ・パネルでアニメーションが編集可能になる。
それではいよいよアニメーションを作っていく。まずは以下の手順で新規アニメーションを用意しよう。
- アニメーションエディタの上部にある「アニメーション」ボタンをクリックする。
- 「新規」を選択する。
- 新規アニメーション名を入力する。ここでは「blink」(まばたきの意味)としておこう。
- これで新しいアニメーションを作る準備ができた。
- 同様に 1 ~ 3 の手順で「hit」という名前のアニメーションも用意しておこう。なお、編集する対象のアニメーションを切り替えるには、アニメーションエディタの上部中央のアニメーション名の右の矢印をクリックすれば選択可能だ。
アニメーション「blink」を編集する
アニメーション「blink」の方から編集していこう。
まず、アニメーションエディタ右上のアニメーションの長さはデフォルトの「1」秒のままにしておく。アニメーションループも不要なので、アイコンをクリックせず無効にしておく。
アニメーションエディタ右下の「スナップ」の数値はエディタ上でキーの挿入箇所を選択するときに、何秒間隔でスナップさせるかを設定できる。ひとまず今はデフォルトの「0.1」のままにしておく。また、タイムラインのメモリ幅が小さい(または大きい)と感じたら、アニメーションエディタ右下にあるスライダーで拡大率を調整しよう。
タイムラインのゲージ上で「0.0」秒のところをクリックしよう。すると青い縦線がそこにスナップして移動する。このバーの位置がこれからアニメーションのキーを挿入する箇所になる。
アニメーションエディタを開いた状態で、シーンドックで「Paddle」の子ノード「Sprite」を選択し、インスペクタドックの「Texture」プロパティ右横の「キー」アイコンをクリックしてみよう。この操作で、そのプロパティをアニメーションのキーとして追加できるのだ。このように Godot ではキーを挿入するだけであらゆるプロパティをアニメーションさせることができる。
次のようなダイアログが表示されるので、「作成」をクリックして進めよう。
すると、タイムライン上の青いバーがある「0.0」秒のところに、「Sprite」ノードの「Texture」プロパティのキーが挿入された。
そのままアニメーションエディタ上でたった今挿入したキーを選択した状態にすると、インスペクタに「AnimationTrackKeyEdit」というプロパティが表示される。この状態で、ファイルシステムドックから「res://sprites/Paddle2.png」の画像を「Value」プロパティめがけてドラッグ&ドロップしよう。すると、アニメーション開始してすぐ(0.0秒)で今追加した目を閉じた顔のパドルに変わるアニメーションが出来上がる。
同様にして、以下の秒数のところでキーを挿入し、それぞれの「Value」に画像を設定しよう。
- 0.1秒:「res://sprites/Paddle1.png」の画像
- 0.7秒:「res://sprites/Paddle2.png」の画像
- 0.8秒:「res://sprites/Paddle1.png」の画像
- 0.9秒:「res://sprites/Paddle2.png」の画像
- 1.0秒:「res://sprites/Paddle1.png」の画像
アニメーションエディタ上はこのような状態になったはずだ。
では、アニメーションを再生してみよう。
- 一番右のアイコンはタイムライン上の青いラインから再生
- 右から二番目のアイコンは初めから再生
まばたきのアニメーション「Blink」ができた。
アニメーション「hit」を編集する
それでは「blink」のアニメーションを作成したのと同様に、今度はボールがパドルに当たった時に再生する「Paddle」ノードのアニメーション「hit」を作っていく。
- アニメーションの長さを「0.2」秒にして、アニメーションループは無しにする。
- インスペクタで「Sprite」ノードの「Texture」プロパティのキーを挿入する。
- 以下の秒数のところでキーを挿入し、それぞれの「Value」に画像を設定しよう。
- 0秒:「res://sprites/Paddle3.png」の画像
- 0.2秒:「res://sprites/Paddle1.png」の画像
- 今度は「Sprite」ノードの「Modulate」プロパティ右横のキーアイコンをクリックして、キーを挿入する。
- 0.0 秒のところと、先ほど「Texture」を変更した秒数のところで、キーを挿入して色を変更していこう。
- 0.0秒:78ffc8
- 0.3秒:ffffff
これで「Paddle」ノードの「Texture」プロパティと「Modulate」プロパティの2つをアニメーションのキーが追加できた。それでは再生して確認してみよう。
パドルのアニメーションを実行させる
ここまでで作った 2 つのアニメーションが適切なタイミングで実行されるようにスクリプトを編集していこう。
アニメーション「blink」をスクリプトで制御する
まずはアニメーション「blink」の方から。編集するのは「Paddle.gd」スクリプトだ。まず先に「AnimationTimer」のシグナルをこのスクリプトに接続しておこう。接続先の受信メソッドの名前はデフォルトのままでOKだ。
接続されたら以下のメソッドが「paddle.gd」に追加されたはずだ。
func _on_AnimationTimer_timeout():
pass
では今回編集したあとの「Paddle.gd」スクリプトがどうなるのか確認しておこう。
extends KinematicBody2D
export (int) var speed = 200
onready var animation = $AnimationPlayer # 追加
func _physics_process(delta):
var direction = Vector2.ZERO
if Input.is_action_pressed("move_right"):
direction.x = 1
if Input.is_action_pressed("move_left"):
direction.x = -1
move_and_slide(speed * direction)
func _on_AnimationTimer_timeout():
animation.play("blink") # 追加
スクリプトの更新箇所は行の右端に「# 追加」のコメントをつけている。
まずonready
キーワード付きで、変数animation
に「AnimationPlayer」ノードを代入して定義している。
そして、先ほど接続した「timeout」シグナルが発信されたら実行される_on_AnimationTimer_timeout
メソッドの中身として、animation.play("blink")
を記述した。play
は「AnimationPlayer」に用意されたメソッドで、実行すると引数で指定したアニメーションを再生してくれる。
先ほど「AnimationTimer」のプロパティで 4 秒にタイマーをセットしたので、4秒おきにまばたきのアニメーションが繰り返し再生されることになる。
では、プロジェクトを実行してアニメーションを確認しておこう。
アニメーション「hit」をスクリプトで制御する
では次にもう一つ用意したアニメーション「hit」の方をスクリプトで制御していく。こちらはボールがパドルに当たったらアニメーションさせることになるので、「Ball」ノードにアタッチされた「Ball.gd」スクリプトを編集していく。
「Ball.gd」の編集箇所は行の右端に「# 追加」と記載している。
extends RigidBody2D
# 中略
func _on_Ball_body_entered(body):
ball_speed += speed_up
#print("ball_speed: "+str(ball_speed))
direction = linear_velocity.normalized()
velocity = direction * min(ball_speed, MAX_SPEED)
if body.is_in_group("Bricks"):
body.queue_free()
if body.get_name() == "Paddle":
var animation = body.get_node("AnimationPlayer") # 追加
if animation.is_playing(): # 追加
animation.stop() # 追加
animation.play("hit") # 追加
direction = (position - body.position).normalized()
velocity = direction * min(ball_speed, MAX_SPEED)
linear_velocity = velocity
# 以下省略
今回追加したのは_on_Ball_body_entered
メソッドブロックの中のif body.get_name() == "Paddle":
ブロック内の3行だ。ちなみに、この_on_Ball_body_entered
メソッドは、ボールが何らかのオブジェクトに衝突したら発信されるシグナルをトリガーにして実行される。
そして、このif
ブロックでは、body
が「Paddle」を指している。つまり、ボールがパドルに当たったら、という条件だ。
そのif
ブロックの中で、まず変数animation
を「AnimationPlayer」ノードとして定義している。
そのあと、is_playing
メソッドで、今アニメーションを再生中かどうか確認している。再生中ならtrue
、停止中ならfalse
が返される。つまり、アニメーションが再生中だったら、stop
メソッドでそれを止める、という内容になっている。これは「blink」アニメーションがたまたま再生中だったら、そちらを先に停止するようにしているのだ。
そして次のanimation.play("hit")
のコードが実行され、play
メソッドが引数で指定しているhit
のアニメーションを再生する。
では、パドルにボールが当たったらアニメーションが再生されるかチェックしてみよう。おそらく今、デバッグのためにゲーム開始時にブロックが一つになるようにしているので、「Game.gd」の_ready
メソッド内のleave_one_brick
メソッドをコメントアウトして全てのブロックが表示されるようにしてからプロジェクトを実行しよう。
func _ready():
# For debug
#leave_one_brick(43) # コメントアウトしておく
# 以下省略
ボールがパドルに衝突したらパドルの顔がアニメーションすることが確認できた。これでパドルへのアニメーション追加作業は完了だ。
ブロックのアニメーションを追加する
パドルのアニメーションを作って、大体の要領を得たことと思う。今度はブロックにアニメーションを追加していこう。ブロックのアニメーションはボールがブロックに当たった時に再生されるようにする。
ブロックのアニメーションを作る
まずは必要なノードを追加しよう。シーンドックを「Brick.tscn」に切り替えて、ルートノード「Brick」に「AnimationPlayer」ノードを追加しよう。
アニメーションエディタを表示して、アニメーションを作っていく。まずは、パドルのアニメーション作成作業を思い出しながら、以下の手順でアニメーションの枠組みから用意しよう。
- 「collided」という名前でアニメーションを新規作成する。
- アニメーションの長さは「0.4」秒にする
- ループはしない設定のまま。
次に「Brick」の子「Sprite」ノードのアニメーションさせたいプロパティのトラックを作成し、いくつかキーとして挿入して値を変化させよう。今回は細かいアニメーションのため、作業しやすいようにスナップの間隔を「0.05」秒にして、スライダーで少し拡大しておこう。
ここからは 3 つのプロパティのトラックを追加して、それぞれを編集していく。ぜひ編集しつつ適宜再生してチェックしながら作業を進めてほしい。
ではまず「Transform」>「Position」プロパティの新規トラックを作成する。インスペクタのキーアイコンをクリックして作成しよう。
「Position」プロパティのキー挿入時間と値は以下の通りだ。なお Easing はノータッチだ。
- 0.0秒 - Value: (0.5, 0.5)
- 0.05秒 - Value: (-0.5, -0.5)
- 0.1秒 - Value: (0.5, -0.5)
- 0.15秒 - Value: (-0.5, 0.5)
- 0.2秒 - Value: (0.5, 0.5)
- 0.25秒 - Value: (-0.5, -0.5)
- 0.3秒 - Value: (0.5, -0.5)
- 0.35秒 - Value: (-0.5, 0.5)
- 0.4秒 - Value: (0, 0)
これで細かく振動するようなアニメーションができたはずだ。
次に「Transform」>「Scale」プロパティの新規トラックを作成する。こちらも Easing はノータッチだ。
- 0秒 - Value: (1, 1)
- 0.4秒: - Value: (0.8, 0.8)
これで0.4秒かけて振動しながら少し縮小するアニメーションになった。
最後に「Visibility」>「Modulate」プロパティの新規トラックを追加しよう。こちらは Easing も少し変更する。
- 0.0秒 - Value: ffffff / Easing: 3.00
- 0.4秒 - Value: 00ffffff / Easing: 1.00
ここまでの作業で、震えながら若干縮小しつつ透明になって消えるアニメーションができた。
ちなみに、再生後、プロパティの値がアニメーションの最後の値になってしまうため、アニメーションエディタで0秒の位置に青いバーを戻すか「巻き戻し」アイコンをクリックして、適宜それぞれの値を元に戻しておこう。
ブロックのアニメーションを実行させる
それでは完成したブロックのアニメーションをスクリプトで制御していこう。このアニメーションはボールがブロックに当たった時に再生されるようにするため、基本的に編集すべきスクリプトは「Ball.gd」だ。
それでは編集後の「Ball.gd」スクリプトを見てみよう。「# 追加」とコメントしている行が編集箇所だ。
# ここまで省略
func _on_Ball_body_entered(body):
ball_speed += speed_up
direction = linear_velocity.normalized()
velocity = direction * min(ball_speed, MAX_SPEED)
if body.is_in_group("Bricks"):
var animation = body.get_node("AnimationPlayer") # 追加
animation.play("collided") # 追加
yield(animation, "animation_finished") # 追加
body.queue_free()
if body.get_name() == "Paddle":
var animation = body.get_node("AnimationPlayer")
if animation.is_playing():
animation.stop()
animation.play("hit")
direction = (position - body.position).normalized()
velocity = direction * min(ball_speed, MAX_SPEED)
linear_velocity = velocity
# 以下省略
スクリプトの中で編集したのは_on_Ball_body_entered
メソッドの中のif body.is_in_group("Bricks"):
のブロック中にコードを 3 行追加した。このif
構文は、ボールが衝突したオブジェクトが「Bricks」グループに属していたら、という条件だが、これはボールがブロックに衝突したら、という意味だ。
メソッドの引数body
がこのif
構文の中では「Brick」ノードを指している。そこでまず、「Brick」ノードの子「AnimationPlayer」を変数animation
で定義している。パドルのアニメーションで追加したスクリプトと同様だ。
変数animation
を定義したら、次の行で早速アニメーション「collided」をメソッドplay
で再生している。この次の行にすぐqueue_free
メソッドがくると、アニメーションが目視できないレベルの速さですぐに「Brick」ノードが消えてしまう。しっかりアニメーションを最後まで再生してからqueue_free
を実行させたい。
そこでyield
を用いている。yield
の第一引数にanimesion
変数、つまり「AnimationPlayer」ノードを、第二引数にそのシグナルanimation_finished
を渡している。これにより、「AnimationPlayer」がアニメーションを終えたら次のコードが読み込まれる形になり、アニメーション後にノードが消えるようになる。
あとは、毎回ゲーム再開時にアニメーションで変更されたプロパティが元に戻るように「Brick.gd」スクリプトの方も以下のようの編集した。
# ここまで省略
func _ready():
set_color(brick_color)
scale = Vector2(1, 1) # 追加
modulate = Color(1, 1, 1, 1) # 追加
# 以下省略
編集箇所は_ready
メソッドの中だけだ。「Scale」プロパティと「Modulate」プロパティの値をそれぞれ初期値に戻している。ちなみに「Position」プロパティもアニメーションさせているが、アニメーションの最後の値が初期値と同じなので、スクリプトには含めていない。
では、最後にプロジェクトを実行して、アニメーションを確認しておこう。
おわりに
以上で Part 9 は完了だ。今回はパドルとブロックを対象にアニメーションを追加して、それをスクリプトで制御した。新しい作業だったので少し時間がかかったかもしれない。アニメーションエディタも慣れるまでとっつきにくかったのではないだろうか。余力があれば、是非アニメーションの内容を変更したり、他のオブジェクトにアニメーションを追加してみてほしい。
次回はパワーアップアイテムを追加していく。