Part 9 の今回は、ブロック崩しの一部の要素にアニメーションを追加する。全てのオブジェクトをアニメーションさせると作業量が膨大になるので、今回はパドルとブロックに対象を絞ってに簡単なアニメーションを追加していく。

なお、アニメーションについては Godot 公式ドキュメントの「ステップ・バイ・ステップ」のチュートリアル にもある程度わかりやすく掲載しているので、そちらも参考にしていただきたい。

それでは前回に引き続きブロック崩しを開発していこう。


Memo:
過去のシリーズをまだご覧になっていない方は、そちらを先にご覧いただくことをおすすめします。
Godot で作るブロック崩し


パドルにアニメーションを追加する

今回、パドルに追加するアニメーションは2種類。一つは、パドルの顔に「まばたき」のアニメーションをさせる。もう一つは、ボールがパドルに当たった時に、パドルの色と顔をアニメーションさせてる。それではやっていこう。


パドルのアニメーションを作る

アニメーションを実装するには、アニメーションをさせたいノードの子として「AnimationPlayer」ノードを追加する必要がある。これを追加しないとアニメーションの編集ができない。


「AnimationPlayer」ノードを追加する

では「Paddle」ノードに「AnimationPlayer」ノードを追加しよう。
PaddleにAnimationPlayerを追加

さらに、あとで使うことになる「Timer」ノードも追加しておこう。名前は「AnimationTimer」に変更しておく。
PaddleにTimerを追加

「AnimationTimer」のプロパティも、「Wait Time」を「4」に、「Autostart」を「オン」にしておく。
AnimationTimerのプロパティ変更

新規アニメーションを作成する

「AnimationPlayer」ノードを追加したので、Godot エディタ下部のアニメーションエディタ・パネルでアニメーションが編集可能になる。
アニメーションエディタ

それではいよいよアニメーションを作っていく。まずは以下の手順で新規アニメーションを用意しよう。

  1. アニメーションエディタの上部にある「アニメーション」ボタンをクリックする。
    アニメーションをクリック
  2. 「新規」を選択する。
    新規を選択
  3. 新規アニメーション名を入力する。ここでは「blink」(まばたきの意味)としておこう。
    新規アニメーションの名前を入力
  4. これで新しいアニメーションを作る準備ができた。
    新規アニメーション準備完了
  5. 同様に 1 ~ 3 の手順で「hit」という名前のアニメーションも用意しておこう。なお、編集する対象のアニメーションを切り替えるには、アニメーションエディタの上部中央のアニメーション名の右の矢印をクリックすれば選択可能だ。
    編集するアニメーションの切り替え

アニメーション「blink」を編集する

アニメーション「blink」の方から編集していこう。

まず、アニメーションエディタ右上のアニメーションの長さはデフォルトの「1」秒のままにしておく。アニメーションループも不要なので、アイコンをクリックせず無効にしておく。
アニメーションの長さとリピートの有効化

アニメーションエディタ右下の「スナップ」の数値はエディタ上でキーの挿入箇所を選択するときに、何秒間隔でスナップさせるかを設定できる。ひとまず今はデフォルトの「0.1」のままにしておく。また、タイムラインのメモリ幅が小さい(または大きい)と感じたら、アニメーションエディタ右下にあるスライダーで拡大率を調整しよう。
拡大率のスライダー

タイムラインのゲージ上で「0.0」秒のところをクリックしよう。すると青い縦線がそこにスナップして移動する。このバーの位置がこれからアニメーションのキーを挿入する箇所になる。
ライムラインをクリック

アニメーションエディタを開いた状態で、シーンドックで「Paddle」の子ノード「Sprite」を選択し、インスペクタドックの「Texture」プロパティ右横の「キー」アイコンをクリックしてみよう。この操作で、そのプロパティをアニメーションのキーとして追加できるのだ。このように Godot ではキーを挿入するだけであらゆるプロパティをアニメーションさせることができる。
Textureプロパティのキーアイコン

次のようなダイアログが表示されるので、「作成」をクリックして進めよう。
プロパティのキー追加のダイアログ

すると、タイムライン上の青いバーがある「0.0」秒のところに、「Sprite」ノードの「Texture」プロパティのキーが挿入された。
ライムライン確認

そのままアニメーションエディタ上でたった今挿入したキーを選択した状態にすると、インスペクタに「AnimationTrackKeyEdit」というプロパティが表示される。この状態で、ファイルシステムドックから「res://sprites/Paddle2.png」の画像を「Value」プロパティめがけてドラッグ&ドロップしよう。すると、アニメーション開始してすぐ(0.0秒)で今追加した目を閉じた顔のパドルに変わるアニメーションが出来上がる。
Valueの編集

同様にして、以下の秒数のところでキーを挿入し、それぞれの「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」ができた。
アニメーションblink再生


アニメーション「hit」を編集する

それでは「blink」のアニメーションを作成したのと同様に、今度はボールがパドルに当たった時に再生する「Paddle」ノードのアニメーション「hit」を作っていく。

  1. アニメーションの長さを「0.2」秒にして、アニメーションループは無しにする。
  2. インスペクタで「Sprite」ノードの「Texture」プロパティのキーを挿入する。
  3. 以下の秒数のところでキーを挿入し、それぞれの「Value」に画像を設定しよう。
  • 0秒:「res://sprites/Paddle3.png」の画像
    Textureを変更
  • 0.2秒:「res://sprites/Paddle1.png」の画像
  1. 今度は「Sprite」ノードの「Modulate」プロパティ右横のキーアイコンをクリックして、キーを挿入する。
    Modulateのキーを挿入
  2. 0.0 秒のところと、先ほど「Texture」を変更した秒数のところで、キーを挿入して色を変更していこう。
  • 0.0秒:78ffc8
  • 0.3秒:ffffff

これで「Paddle」ノードの「Texture」プロパティと「Modulate」プロパティの2つをアニメーションのキーが追加できた。それでは再生して確認してみよう。
アニメーションhitを再生


パドルのアニメーションを実行させる

ここまでで作った 2 つのアニメーションが適切なタイミングで実行されるようにスクリプトを編集していこう。

アニメーション「blink」をスクリプトで制御する

まずはアニメーション「blink」の方から。編集するのは「Paddle.gd」スクリプトだ。まず先に「AnimationTimer」のシグナルをこのスクリプトに接続しておこう。接続先の受信メソッドの名前はデフォルトのままでOKだ。
timeoutシグナルの接続

接続されたら以下のメソッドが「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秒おきにまばたきのアニメーションが繰り返し再生されることになる。

では、プロジェクトを実行してアニメーションを確認しておこう。
プロジェクトを実行してblink動作確認


アニメーション「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) # コメントアウトしておく
# 以下省略

プロジェクトを実行してhit動作確認
ボールがパドルに衝突したらパドルの顔がアニメーションすることが確認できた。これでパドルへのアニメーション追加作業は完了だ。



ブロックのアニメーションを追加する

パドルのアニメーションを作って、大体の要領を得たことと思う。今度はブロックにアニメーションを追加していこう。ブロックのアニメーションはボールがブロックに当たった時に再生されるようにする。


ブロックのアニメーションを作る

まずは必要なノードを追加しよう。シーンドックを「Brick.tscn」に切り替えて、ルートノード「Brick」に「AnimationPlayer」ノードを追加しよう。
BrickノードにAnimationPlayerノード追加

アニメーションエディタを表示して、アニメーションを作っていく。まずは、パドルのアニメーション作成作業を思い出しながら、以下の手順でアニメーションの枠組みから用意しよう。

  1. 「collided」という名前でアニメーションを新規作成する。
  2. アニメーションの長さは「0.4」秒にする
  3. ループはしない設定のまま。

collidedアニメーションを新規作成

次に「Brick」の子「Sprite」ノードのアニメーションさせたいプロパティのトラックを作成し、いくつかキーとして挿入して値を変化させよう。今回は細かいアニメーションのため、作業しやすいようにスナップの間隔を「0.05」秒にして、スライダーで少し拡大しておこう。
スナップ0.05秒にしてスライダーで拡大

ここからは 3 つのプロパティのトラックを追加して、それぞれを編集していく。ぜひ編集しつつ適宜再生してチェックしながら作業を進めてほしい。

ではまず「Transform」>「Position」プロパティの新規トラックを作成する。インスペクタのキーアイコンをクリックして作成しよう。
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 はノータッチだ。
Scaleプロパティ

  • 0秒 - Value: (1, 1)
  • 0.4秒: - Value: (0.8, 0.8)

これで0.4秒かけて振動しながら少し縮小するアニメーションになった。

最後に「Visibility」>「Modulate」プロパティの新規トラックを追加しよう。こちらは Easing も少し変更する。
Modulateプロパティ

  • 0.0秒 - Value: ffffff / Easing: 3.00
  • 0.4秒 - Value: 00ffffff / Easing: 1.00

ここまでの作業で、震えながら若干縮小しつつ透明になって消えるアニメーションができた。
アニメーショントラック編集完了

ちなみに、再生後、プロパティの値がアニメーションの最後の値になってしまうため、アニメーションエディタで0秒の位置に青いバーを戻すか「巻き戻し」アイコンをクリックして、適宜それぞれの値を元に戻しておこう。
collidedアニメーション再生


ブロックのアニメーションを実行させる

それでは完成したブロックのアニメーションをスクリプトで制御していこう。このアニメーションはボールがブロックに当たった時に再生されるようにするため、基本的に編集すべきスクリプトは「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 は完了だ。今回はパドルとブロックを対象にアニメーションを追加して、それをスクリプトで制御した。新しい作業だったので少し時間がかかったかもしれない。アニメーションエディタも慣れるまでとっつきにくかったのではないだろうか。余力があれば、是非アニメーションの内容を変更したり、他のオブジェクトにアニメーションを追加してみてほしい。

次回はパワーアップアイテムを追加していく。