Godot4 awaitで一定時間ごとに円を拡大して描画するプログラム

2D / 3D ゲームを作成できる無料・オープンソースの軽快なゲームエンジン「Godot Engine 4」で、前回行った円の描画処理を使って、時間経過とともに半径を大きくして波紋のようなアニメーションを作成します。

※この記事の内容は、アプリ タップ The 宝箱 の開発でも使用しています。
※ GodotEngine のバージョンは 4.2.1 です。 .NET 版ではありません

※記事で紹介するスクリプト / プログラム / コードは自己責任で使用してください。

前回の記事

前回は、ゲームエンジン「Godot Engine 4」で、線分や円弧を描く関数を用いて円を描画する処理について紹介しました。

await について

await キーワードを使用すると、待機するコルーチンを作成して処理を一時停止します。
待機しているシグナルが発行されると処理を再開します。

今回ならば、1秒間経過した後のタイムアウトのシグナルを待ってから処理を再開して、円の半径を大きくしてから描画命令を呼び出します。

The await keyword can be used to create coroutines which wait until a signal is emitted before continuing execution. Using the await keyword with a signal or a call to a function that is also a coroutine will immediately return the control to the caller. When the signal is emitted (or the called coroutine finishes), it will resume execution from the point on where it stopped.

await キーワードを使用すると、実行を続行する前にシグナルが発行されるまで待機するコルーチンを作成できます。シグナルまたはコルーチンでもある関数の呼び出しで await キーワードを使用すると、制御が呼び出し元にすぐに返されます。シグナルが発行されると (または呼び出されたコルーチンが終了すると)、停止した時点から実行が再開されます。

GDScript reference — Godot Engine (stable) documentation in English と Google 翻訳

await で指定した時間が経過したあとに別の描画を行うための関数

_draw イベント関数で描画した内容は保存され画面に表示され続けますが、そのノードの _draw イベント関数は1度しか呼ばれません
再び別の描画を行うには CanvasItem クラスの queue_redraw() 関数で redraw を要求して、GodotEngine から、再び _draw イベント関数を呼び出してもらいます

CanvasItem は Node2D の継承元クラスなので、Node2D 派生クラスに紐づくスクリプトならば、そのスクリプトの画像の更新をしたい処理の部分で queue_redraw() と書いて継承元クラス CanvasItem の関数を呼び出すだけです。

If re-drawing is required because a state or something else changed, call CanvasItem.queue_redraw() in that same node and a new _draw() call will happen.

状態またはその他のものが変更されたために再描画が必要な場合は、同じノードで CanvasItem.queue_redraw() を呼び出すと、新しい _draw() 呼び出しが発生します。

2Dカスタム描画 — Godot Engine (4.x)の日本語のドキュメント#描画の更新

うまく描画されなかった際のスクリプトと結果

原因は不明ですが、 _draw イベント内で以下のように await で一定時間待ってから新たな描画を行い更新のリクエストをしても、ログは出力されましたが1度目の await の後の描画命令は反映されませんでした。

以下はそのときの _draw イベント関数の処理です。
※sc_draw_circle 関数は、後述するうまくいった際のスクリプトの _draw 関数よりあとの部分に記述してあるので、それをコピーして追加してください。

# _draw() 関数は一度だけ呼び出され、その後は描画コマンドがキャッシュされて記憶されるため、それ以上の呼び出しは不要です。
# 再描画が必要な場合は CanvasItem.queue_redraw() を呼び出してください。
func _draw():
	# 色の変数に白色を設定します。
	var white : Color = Color.WHITE
	# 中心の座標を設定します。
	var center = Vector2(320, 240 + 10)
	# 円を指定した座標を中心に、指定した半径と指定した色で描画します。
	sc_draw_circle(center, 24, white)
	queue_redraw()	# Node2D の継承元の CanvasItem クラスのメンバ関数を呼び出します。
	await get_tree().create_timer(1.0).timeout # 作成したタイマーが1秒後にタイムアウトするシグナルを待ちます。
	sc_draw_circle(center, 120, white)
	queue_redraw()	# Node2D の継承元の CanvasItem クラスのメンバ関数を呼び出します。
	await get_tree().create_timer(1.0).timeout # 作成したタイマーが1秒後にタイムアウトするシグナルを待ちます。
	sc_draw_circle(center, 240, white)
	queue_redraw()	# Node2D の継承元の CanvasItem クラスのメンバ関数を呼び出します。
	return

実行結果

_draw イベント関数の await を呼び出す前の円の描画処理は画面に反映されますが、その後の円の描画処理は反映されません。
queue_redraw() で更新のリクエストをすると再び _draw 関数が呼ばれますが同様です。
※ queue_redraw() を使わない場合も最初の円の描画しか画面に反映されませんでした。

しかし、描画関数自体は await の後も呼び出されていて、print 文のログは1秒おきに追加されています。

うまくいったスクリプト

以下のスクリプトは、await を使う部分を _draw イベント関数から自作関数に移動しました。
_draw イベント関数ではメンバ変数 radius の値の半径の円を描画するだけにしました。
追加した animation_ripple 自作関数で await を使い1秒経過ごとに radius の値を変えて再描画させることで、波紋のようなアニメーションを行うようにしました。

extends Node2D

# 描画する円の半径です。時間経過で変化します。
var radius = 3

# Called when the node enters the scene tree for the first time.
func _ready():
	animation_ripple()
	return

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass

func animation_ripple():
	radius = 3
	queue_redraw()	# Node2D の継承元の CanvasItem クラスのメンバ関数を呼び出します。
	await get_tree().create_timer(1.0).timeout
	radius = 24
	queue_redraw()	# Node2D の継承元の CanvasItem クラスのメンバ関数を呼び出します。
	await get_tree().create_timer(1.0).timeout
	radius = 240
	queue_redraw()	# Node2D の継承元の CanvasItem クラスのメンバ関数を呼び出します。
	return 
	

# _draw() 関数は一度だけ呼び出され、その後は描画コマンドがキャッシュされて記憶されるため、それ以上の呼び出しは不要です。
# 再描画が必要な場合は CanvasItem.queue_redraw() を呼び出してください。
func _draw():
	# 色の変数に白色を設定します。
	var white : Color = Color.WHITE
	# 中心の座標を設定します。
	var center = Vector2(320, 240 + 10)
	# 円を指定した座標を中心に、指定した半径と指定した色で描画します。
	sc_draw_circle(center, radius, white)
	return

func draw_circle_arc(center, radius, angle_from, angle_to, color):
	var nb_points = 32
	var points_arc = PackedVector2Array()

	for i in range(nb_points + 1):
		var angle_point = deg_to_rad(angle_from + i * (angle_to-angle_from) / nb_points - 90)
		points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)

	for index_point in range(nb_points):
		draw_line(points_arc[index_point], points_arc[index_point + 1], color)

# https://docs.godotengine.org/ja/4.x/tutorials/2d/custom_drawing_in_2d.html#arc-function
func sc_draw_circle_arc(center, radius, angle_from, angle_to, color, numof_points):
	var nb_points = numof_points # オリジナルは 32 固定でしたが引数にしました。
	var points_arc = PackedVector2Array()

	for i in range(nb_points + 1):
		var angle_point = deg_to_rad(angle_from + i * (angle_to-angle_from) / nb_points - 90)
		points_arc.push_back(center + Vector2(cos(angle_point), sin(angle_point)) * radius)

	for index_point in range(nb_points):
		draw_line(points_arc[index_point], points_arc[index_point + 1], color)
	
	# numof_points の値によっては、頂点を分割する角度が 360 度で割り切れず、最後の線分が 360 度に達しない場合があるので
	# 最後と最初の点を念のため線分で描画します。
	draw_line(points_arc[nb_points], points_arc[0], color)
	
	return

# 指定した座標 center を中心に、半径 radius、色 color で塗りつぶさない円を描画します。
# _draw イベント関数で用います。再描画が必要な場合は CanvasItem.queue_redraw() を呼び出してください。
func sc_draw_circle(center, radius, color):
	# 円周(2*PI+r)の距離に比例した個数の頂点を結ぶ線分で円を描画します。
	# 試した結果、円周÷12 の頂点数だとあまり角が目立たないと思いました。
	var numof_points = ceili(2 * PI * radius / 12)
	# 円周が短く、頂点数が 12 個より小さい場合は角が目立つので 12 に補正します。
	if numof_points < 12:
		numof_points = 12
	print("radius = ", radius, ", numof_points = ", numof_points)
	# 頂点数を指定して 0 ~ 360 度まで円弧(円)を描画します。
	sc_draw_circle_arc(center, radius, 0, 360, color, numof_points)
	return

まとめ

今回は、2D / 3D ゲームを作成できる無料・オープンソースの軽快なゲームエンジン「Godot Engine 4」で、円を描画する処理と、指定した時間待ってから処理を再開できる await を使って、波紋のようなアニメーションを描画する手順を紹介しました。
_draw イベント関数内で await を使うとその後の描画命令が画面に反映されない現象についても紹介しました。

参照サイト Thank You!

記事一覧 → Compota-Soft-Press

コメント

Ads Blocker Image Powered by Code Help Pro

お願い - Ads Blocker Detected

このサイトは広告を掲載して運営しています。

ポップアップを閉じて閲覧できますが、よろしければ

このサイト内の広告を非表示にする拡張機能をオフにしていただけませんか?

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

タイトルとURLをコピーしました