2D / 3D ゲームを作成できる無料・オープンソースの軽快なゲームエンジン「Godot Engine 4」で、円を描画する処理について紹介します。
線分を細かくつないで円を描画する方法と、円弧を描画する関数を用いた方法を紹介します。
※この記事の実装は、アプリ TapTheTakarabako の開発でも使用しています。
※ GodotEngine のバージョンは 4.2.1 です。 .NET 版ではありません。
※記事で紹介するスクリプト / プログラム / コードは自己責任で使用してください。
前回の記事
前回は 2D / 3D ゲームを作成できる無料・オープンソースの軽快なゲームエンジン「Godot Engine 4」で、クリック / タップしたのイベントを検知する設定と、その座標をログ出力するスクリプトを紹介しました。
作成する Ripple (波紋) シーンについて
作成する Ripple シーンでは、円が少しずつ大きくなる波紋のアニメーションを行います。
Ripple シーンは、前回作成した GameManager がクリック / タップを感知した際に、GameManager シーン内に Ripple ノードとして作成・配置されます。
Ripple ノードは、作成された場所で一定時間波紋の描画処理を行った後に消滅します。
Ripple シーンの作成
編集中のシーンがあれば保存した後、メニュー「シーン」→「新規シーン」を選択します。
今回は円の描画を行うために 2D シーン ( 基底クラス Node2D ) を選択して新規シーンのルートノードを作成します。
参照:「Custom drawing in 2D — Godot Engine (stable) documentation in English」
Node2D ルートノードを作成したら、それの右クリックメニュー「名前の変更」で Ripple にノード名を変更します。
※ノードを選択してから F2 キーや 2 回クリックでもリネームできます。
Ripple ノードにスクリプトを作成して割り当てます。
シーンドックで Ripple ノードを選択した状態で、右上の+のついたボタンをクリックします。
「ノードにスクリプトをアタッチする」ダイアログでファイル名が Ripple.gd であることを確認して「作成」ボタンを押します。
エディタの画面上部が Script ビューに切り替わります。
スクリプトの編集をする前に、メニュー「シーン」→「シーンを保存」などで ripple.tscn というファイル名でシーンを保存します。
※ Godot ではファイル名は snake_case が推奨されています。
現在の位置を中心に円を描画する
「Custom drawing in 2D — Godot Engine (stable) documentation in English#Drawing circles」のサンプルコードを参考に _draw イベント関数内で draw_circle 関数を使って円を描きます。
以下のコードを作成した Ripple.gd の最後に追加します。
詳細についてはコメントをお読みください。
# _draw() 関数は一度だけ呼び出され、その後は描画コマンドがキャッシュされて記憶されるため、それ以上の呼び出しは不要です。
# 再描画が必要な場合は CanvasItem.queue_redraw() を呼び出してください。
func _draw():
# 色の変数に白色を設定します。
var white : Color = Color.WHITE
# 中心の座標、半径、色を指定して円を描画する関数を呼び出します。
draw_circle(Vector2(42.479, 65.4825), 9.3905, white)
しかし、 draw_circle では塗りつぶしてしまい、波紋のようには見えません。
背景色の少し小さな円で塗りつぶすこともできますが、他の波紋があった場合に重なった部分が不自然に欠けてしまいます。
公式の塗りつぶさない円のサンプルコード
「2Dカスタム描画 — Godot Engine (4.x)の日本語のドキュメント」の円弧を描くサンプルコードを Ripple.gd に追加します。
# 2Dカスタム描画 — Godot Engine (4.x)の日本語のドキュメント - https://docs.godotengine.org/ja/4.x/tutorials/2d/custom_drawing_in_2d.html#arc-function
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)
このサンプルコードの関数を _draw イベント関数で以下のように呼び出します。
# _draw() 関数は一度だけ呼び出され、その後は描画コマンドがキャッシュされて記憶されるため、それ以上の呼び出しは不要です。
# 再描画が必要な場合は CanvasItem.queue_redraw() を呼び出してください。
func _draw():
# 色の変数に白色を設定します。
var white : Color = Color.WHITE
# 中心の座標を設定します。
var center = Vector2(320, 240 + 10)
draw_circle_arc(center, 3, 0, 360, white)
draw_circle_arc(center, 24, 0, 360, white)
draw_circle_arc(center, 120, 0, 360, white)
draw_circle_arc(center, 240, 0, 360, white)
F6 キーで実行してみると、頂点数が 32 で固定されているため円が大きくなると角が目立ってきます。
サンプルコードの頂点数のおおまかな調整
サンプルコードの頂点数の部分を外部から引数で指定できるように変更しました。
また、頂点数によっては、0 ~ 360 の円弧を指定しても 0 ~ 359 のように一部欠けた円になってしまったので、最後の頂点と最初の頂点を線分で結ぶ処理を最後に追加しました。
# 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
円周に応じて頂点数を決めて呼び出すための関数を作成しました。
円周が短すぎる場合は、頂点数が 3 以下で三角形などになってしまうので、最小の頂点数は 12 になるように補正します。
# 指定した座標 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
呼び出す関数が変わったので _draw イベント関数の処理も変更します。
# _draw() 関数は一度だけ呼び出され、その後は描画コマンドがキャッシュされて記憶されるため、それ以上の呼び出しは不要です。
# 再描画が必要な場合は CanvasItem.queue_redraw() を呼び出してください。
func _draw():
# 色の変数に白色を設定します。
var white : Color = Color.WHITE
# 中心の座標を設定します。
var center = Vector2(320, 240 + 10)
# 円を指定した座標を中心に、指定した半径と指定した色で描画します。
sc_draw_circle(center, 3, white)
sc_draw_circle(center, 24, white)
sc_draw_circle(center, 120, white)
sc_draw_circle(center, 240, white)
return
テスト
保存して F6 キーまたは右上の「現在のシーンを実行」ボタンで描画プログラムの結果を確認しましょう。
前述の _draw イベント関数の 4 つの関数呼び出しの通り、半径が 3, 24, 120, 240 ピクセルの円を描画した結果、どの円も角が目立たないようにできました。
円が大きくなると頂点数が少なくても角が目立たない傾向にあるようなので、さらに無駄のない頂点数を設定する余地はあると思いますが、そこまでの高速性を求めていないので、今回はこれで完成とします。
draw_arc 関数の頂点数だけを自動計算
塗りつぶさない円を描く関数はないと思っていましたが、ここまで記事を書いてから draw_arc 関数を知りました。
draw_arc 関数は円弧を描きますが、開始と終了の角度を 0.0 ~ 360.0 にすれば正円を描けます。
しかも線の太さやアンチエイリアスの有無も指定できます。
しかし、頂点数を指定しなければいけなかったので、その部分だけを自動計算する関数を作成しました。
# 円弧を描画します。 draw_arc 関数の point_count (頂点数) を自動計算して呼び出します。
# center : 中心の座標
# radius : 半径
# start_angle, end_angle : 円弧の開始と終了の角度
# color : 色
# width : 線の太さ
# antiariased : アンチエイリアスの有無. 詳しくは CanvasItem — Godot Engine (4.x)の日本語のドキュメント - https://docs.godotengine.org/ja/4.x/classes/class_canvasitem.html#class-canvasitem-method-draw-arc
func sc_draw_arc(center, radius, start_angle, end_angle, color, width, antialiased):
# 円周(2*PI+r)の距離に比例した個数の頂点を結ぶ線分で円を描画します。
# 試した結果、円周÷6 の頂点数だとあまり角が目立たないと思いました。
var numof_points = ceili(2 * PI * radius / 6)
# 円周が短く、頂点数が 12 個より小さい場合は角が目立つので 12 に補正します。
if numof_points < 12:
numof_points = 12
print("radius = ", radius, ", numof_points = ", numof_points)
draw_arc(center, radius, 0.0, 360.0, numof_points, color, width, true)
return
以下のコードを _draw 関数に書いて実行します。
# _draw() 関数は一度だけ呼び出され、その後は描画コマンドがキャッシュされて記憶されるため、それ以上の呼び出しは不要です。
# 再描画が必要な場合は CanvasItem.queue_redraw() を呼び出してください。
func _draw():
# 色の変数に白色を設定します。
var white : Color = Color.WHITE
# 中心の座標を設定します。
var center = Vector2(320, 240 + 10)
# 円を指定した座標を中心に、指定した半径と指定した色で描画します。
sc_draw_arc(center, radius, 0.0, 360.0, white, 3, true)
return
半径によっては角が少し目立つ(例では2番目の半径24)ので、 sc_draw_arc 関数のマジックナンバー 6 を小さくすれば頂点数が増えてより滑らかになります。
まとめ
今回は、2D / 3D ゲームを作成できる無料・オープンソースの軽快なゲームエンジン「Godot Engine 4」で、円を描画する処理について紹介しました。
draw_circle 関数は塗りつぶした円を描画する関数であること、描画処理を記述する _draw イベント関数があること、線分をつないで疑似的に円を描く処理がサンプルや、頂点数を円周に応じて増減するスクリプトについても紹介しました。
参照サイト Thank You!
- Godot Engine – Free and open source 2D and 3D game engine
- Custom drawing in 2D — Godot Engine (stable) documentation in English
- 2Dカスタム描画 — Godot Engine (4.x)の日本語のドキュメント
- Custom drawing in 2D — Godot Engine (stable) documentation in English#Drawing circles
記事一覧 → Compota-Soft-Press
コメント