3Dにおけるベクトル描画
この記事は Godot 3から Godot 4 へ内容の書き換え中です。 Godot4では存在しない変数、関数が含まれている場合があります。もしその場合はリポジトリのIssuesまでご報告ください。
課題
3Dゲームで視覚的なデバッグ情報が欲しい。例えば、速度や位置などを表すベクトルを可視化する方法があれば嬉しい。
解決策
2Dでの描画デバッグは非常に便利です。CanvasItemは_draw()コールバック内で使用できる多彩なプリミティブ描画メソッドを提供します。3Dの場合、状況はそれほど単純ではありません。一つの解決策としてImmediateGeometryを使って手動でメッシュを作成する方法がありますが、これは非常に手間がかかり、迅速なデバッグには不便です。
適切な解決策としては、引き続きCanvasItemの描画メソッドを使用することです。そのためにはまず、3D空間内の位置を2Dビューポートに投影する必要があります。幸い、Cameraオブジェクトは、そのunproject_position()メソッドを使ってこれを処理してくれます。
セットアップ方法
表示レイヤーについては、3DシーンにCanvasLayerコンポーネントを追加し、その中にControlを配置してください。さらに、このControlにスクリプトを割り当てる必要があります。
例:描画制御ノードがプレイヤーノードを参照しており、そのvelocity(速度)ベクトルを描画したいとします。また、Cameraも参照しています。これらの参照方法については後ほど詳しく説明します。
var player
var camera
func _draw():
var color = Color(0, 1, 0)
var start = camera.unproject_position(player.global_transform.origin)
var end = camera.unproject_position(player.global_transform.origin + player.velocity)
node.draw_line(start, end, color, width)
node.draw_triangle(end, start.direction_to(end), width*2, color)
func draw_triangle(pos, dir, size, color):
var a = pos + dir * size
var b = pos + dir.rotated(2*PI/3) * size
var c = pos + dir.rotated(4*PI/3) * size
var points = PoolVector2Array([a, b, c])
draw_polygon(points, PoolColorArray([color]))
ベクトルの始点と終点を求めるためにunproject_position()を使用します。draw_triangle()は、見栄えの良い尖った矢印形状を表示するために用意されています。
ゲームオブジェクトからの簡単なアクセス方法
さて、これをより実用的な機能に改良します。ゲームにはデバッグ用ベクトルを描画したいオブジェクトが多数存在するはずです。敵の向き、加速度ベクトル、目的地など、様々な要素が考えられます。どんなオブジェクトでも簡単にデバッグ描画レイヤーに追加できる仕組みが必要となります。
以下の手順でDebugOverlayを自動読み込みとして追加し、シングルトンとして設定してください。これにより、どのノードからでもアクセス可能になります。このスクリプトに以下の内容を追加してください。
extends CanvasLayer
@onready var draw = $DebugDraw3D
func _ready():
if not InputMap.has_action("toggle_debug"):
InputMap.add_action("toggle_debug")
var ev = InputEventKey.new()
ev.scancode = KEY_BACKSLASH
InputMap.action_add_event("toggle_debug", ev)
func _input(event):
if event.is_action_pressed("toggle_debug"):
for n in get_children():
n.visible = not n.visible
視認性を切り替える入力アクションを追加するコードを含めておきました。これにより、任意のプロジェクトに簡単に組み込むことができ、インプットマップを変更する必要がありません。これで、DebugOverlay.draw を使って描画レイヤーを参照できるようになりました。
ここには他のデバッグレイヤーも追加できます。例えば、プロパティをテキスト形式で表示するようなものです。
まず、表示したいデバッグ値に関する情報をすべて保持するためのカスタムオブジェクトを定義します。
extends Control
class Vector:
var object # The node to follow
var property # The property to draw
var scale # Scale factor
var width # Line width
var color # Draw color
func _init(_object, _property, _scale, _width, _color):
object = _object
value = _property
scale = _scale
width = _width
color = _color
func draw(node, camera):
var start = camera.unproject_position(object.global_transform.origin)
var end = camera.unproject_position(object.global_transform.origin + object.get(property) * scale)
node.draw_line(start, end, color, width)
node.draw_triangle(end, start.direction_to(end), width*2, color)
var vectors = [] # Array to hold all registered values.
このオブジェクトには、表示したい各ベクトルの機能がすべてカプセル化されており、前述した描画コードも含まれています。_process() メソッド内では、これらのベクトルを描画することができ、その際には必ず現在アクティブなカメラを取得する必要があります。
func _process(delta):
if not visible:
return
update()
func _draw():
var camera = get_viewport().get_camera()
for vector in vectors:
vector.draw(self, camera)
最後に、新しいフォロー対象ベクトルを登録する関数を追加できます。
func add_vector(object, property, scale, width, color):
vectors.append(Vector.new(object, property, scale, width, color))
これでゲーム内の任意のオブジェクトから、以下の方法でデバッグ用ベクトルを追加できるようになりました。
DebugOverlay.draw.add_vector(self, "velocity", 1, 4, Color(0,1,0, 0.5))
以下に、レイキャストと操舵方向を表示するAI制御車両の実例をご紹介します。