Godot4 Shape2Dで指定した領域とタイルマップのタイルとの接触判定

無料・軽快な 2D / 3D 用のゲームエンジン Godot Engine 4 で、操作キャラクターの当たり判定などに用いる Shape2D 派生リソースを用いて、タイルマップのどのタイル群と操作キャラクターが重なっているのかを判定するスクリプトの例と使用結果について紹介します。

※ GodotEngine 4.3 を使用しています。.NET 版ではありません。
※スクリプトは自己責任でご使用ください。

前回の記事

前回は、TileMapLayer の各タイルと、キャラクターが接触しているタイル群を判定するために、タイルサイズで割って得られるセルの座標の端数切り上げと端数切り下げの結果が必要になり、自作した関数群を紹介しました。

取得するタイルの範囲

はじめに、キャラクターの当たり判定領域を含む外接矩形(領域を全て含められる最小の四角形)に重なっているセルの座標の範囲(下図の例では、 2×2のセル)を算出します。
その範囲内のセルにタイル(下図の例ではハシゴや木や地面など)が配置されていれば、それを取得します。

Godot4 キャラクターが重なっている TileMapLayer の全てのタイルを取得したい場合、端数の切り下げ切り上げが必要

当たり判定の図形に応じた接触判定に便利な関数

外接矩形(四角形)の範囲は Shape2D.get_rect 関数で取得できます。

さらに、Shape2D から派生した、カプセル型や円などのさまざまな図形の当たり判定領域と重なっているかを判定するには、Shape2Dcollide メンバ関数を使うと便利です。

Godot4 Shape2D 派生クラス ( doxygen で生成したドキュメントより)

bool collide(local_xform: Transform2D, with_shape: Shape2D, shape_xform: Transform2D

Returns true if this shape is colliding with another.

This method needs the transformation matrix for this shape (local_xform), the shape to check collisions with (with_shape), and the transformation matrix of that shape (shape_xform).

このシェイプが別のシェイプと衝突している場合は true を返します。

このメソッドには、この形状の変換行列 (local_xform)、衝突をチェックする形状 (with_shape)、およびその形状の変換行列 (shape_xform) が必要です。
Shape2D — Godot Engine (4.x)の日本語のドキュメント と Google 翻訳

Shape2D リソースの取得の手順や、 collide 関数の説明については以下の記事を参照してください。

キャラクターが接触しているタイルの取得スクリプトの例

前述した手順でキャラクターが接触しているタイルの配列を得るスクリプトは以下です。

前回作成したグローバル座標をタイルマップのセルの座標に切り下げ・切り捨てして変換する関数や、Shape2D.collide 関数を用いて、キャラクターの当たり判定領域に接触しているタイル群を取得するスクリプトは以下です。
※詳細はコメント文を参照してください。
ハイライトの行は後述のデバッグ用の枠線の情報を設定する処理です。

# body の当たり判定領域と重なっている tilemap_layer のタイル群を取得します。
func get_entered_tiles(tilemap_layer: TileMapLayer, body: CollisionObject2D) -> Array[TileData]:
	# デバッグ用。判定領域のタイルを描画する情報の配列を初期化します。
	debug_entered_tile_draw_info_array.clear()

	# 戻り値の変数。body の当たり判定領域と接触したタイルデータの配列です。
	var entered_tiles: Array[TileData]

	# キャラクターの位置を取得
	var body_pos = body.global_position

	# キャラクターの当たり判定領域 (Shape2D 派生リソース) の配列を取得
	var shape_array = get_shape2d_all(body)
	if shape_array.size() < 1:
		return entered_tiles	# 0 個の場合は判定できないので空の配列を返します。

	# 取得した当たり判定領域 (Shape2D 派生リソース)1つずつ、その外接矩形内のタイルと重なりを判定します。
	for shape in shape_array:
		# 当たり判定領域の外接矩形を取得して、body の scale に合わせて拡大縮小します。
		var rect = shape.get_rect()
		rect.position *= body.scale
		rect.size *= body.scale
		
		# デバッグ用に16の倍数に設定
		#rect.position = Vector2(-8, -8)
		#rect.size = Vector2(16, 16)
		#print("Collision shape rect: ", rect) # Debug

		# タイルの当たり判定用の RectangleShape2D (四角形の Shape2D 派生リソース)をループ前に用意。
		var tile_rect_shape = RectangleShape2D.new()
		tile_rect_shape.size = tilemap_layer.tile_set.tile_size	# タイルのサイズを設定します。
		# 当たり判定の位置(オフセット)を origin メンバ変数で指定する Transform2D 変数をループ前に用意。
		# タイルの当たり判定領域が (0, 0) ~ (tile_size) なので、指定する位置は当たり判定の左上の座標になります。
		var tile_transform2d = Transform2D()

		# グローバル座標の body の当たり判定領域の外接矩形の範囲を、タイルマップのセルの単位に変換します。
		var cell_start_pos = to_cell_pos_floor(rect.position + body_pos, tilemap_layer)
		var cell_end_pos = to_cell_pos_ceil(rect.position + rect.size + body_pos, tilemap_layer)
		#print("Cell start position: ", cell_start_pos) # Debug
		#print("Cell end position: ", cell_end_pos) # Debug

		# body の当たり判定領域の位置(オフセット)を設定します。
		var shape_transform = Transform2D()
		shape_transform.origin = body_pos + rect.position

		# 当たり判定領域の外接矩形内のタイルマップのセルを1つずつ確認します。
		for x in range(cell_start_pos.x, cell_end_pos.x):
			for y in range(cell_start_pos.y, cell_end_pos.y):
				var tile_pos = Vector2(x, y)
				#print("Checking tile position: ", tile_pos) # Debug

				# 調べるタイルの位置(オフセット)を設定します。
				# セルの位置にタイルサイズをかけて、tilemap_layer のオフセットを加味してグローバル座標に変換しています。
				tile_transform2d.origin = tilemap_layer.to_global(tile_pos * tile_rect_shape.size)
				#print("tile_pos = ", tile_pos, ", tile_transform2d.origin = ", tile_transform2d.origin) # Debug

				# タイルの範囲が、 body の当たり判定領域(円や楕円など)に重なっているかを判定します。
				if shape.collide(shape_transform, tile_rect_shape, tile_transform2d):
					# 当たり判定領域に重なっている場所にタイルが配置されていれば、それを戻り値の配列に追加します。
					var tile_data: TileData = tilemap_layer.get_cell_tile_data(tile_pos)
					if tile_data:
						entered_tiles.append(tile_data)
						# デバッグ用。配置されているタイルの範囲を表す矩形情報と、色を指定します。
						debug_entered_tile_draw_info_array.append(
						{"rect2": Rect2(tile_transform2d.origin, tile_rect_shape.size),
						 "color": Color.CRIMSON})
					else:
						# デバッグ用。タイルはないけれど確認したセルの範囲を表す矩形情報と、色を指定します。
						debug_entered_tile_draw_info_array.append(
						{"rect2": Rect2(tile_transform2d.origin, tile_rect_shape.size),
						 "color": Color.AQUA})

	queue_redraw() # _draw イベント関数による再描画を要求します。
	return entered_tiles

セル座標の小数の切り下げ・切り上げを行う独自の関数 to_cell_pos_floor, to_cell_pos_ceil については前回の記事を参照してください。

接触しているタイルを枠線で表すテスト用の描画処理の説明

前述したスクリプトの debug_entered_tile_draw_info_array 変数を使っている部分は、接触していると判定したタイルに太い枠線を表示するテスト用の処理です。
※このテストをしたおかげで、わずかなずれが発生しているバグをみつけることができました。

以下は、そのデバッグ用の変数の定義と、枠線を描画する _draw イベント関数の実装例です。
はしごや背景の画像などのタイルが配置されている枠は赤色 (CRIMSON) 、何もタイルが配置されていないセル水色 (AQUA) のを描画します。

_draw イベント関数では、前述の get_entered_tiles 関数内で、デバッグ用の変数に追加したタイルの枠線の情報1つずつ取り出して、 draw_rect で枠を描画しています。

# get_entered_tiles 関数でキャラクターと重なっているタイルのグローバル座標での矩形の配列。 _draw 関数で使用します。
var debug_entered_tile_draw_info_array: Array[Dictionary]

# 再描画したい場合は queue_redraw 関数を呼び出してください。
func _draw():
	# デバッグ用。キャラクターと接触しているセルについて枠を描画します。赤はタイルのあるセルで、青はないセルです。
	for draw_info in debug_entered_tile_draw_info_array:
		draw_rect(draw_info.rect2, draw_info.color, false, 3, false)

draw_rect など _draw イベント関数による描画処理については以下の記事を参照してください。

https://compota-soft.work/wp1/wp-admin/post.php?post=39559&action=edit

デバッグ用の変数は、辞書型変数の配列で、辞書型変数には、枠の範囲を表す Rect2 型の rect2 データと、枠の色を表す color データが設定されています。
前述の get_entered_tiles 関数のハイライトしている行が、デバッグ用変数に描画する情報を設定している行です。

テスト

F5 / F6 キーなどでタイルマップのシーンを実行してキャラクターを操作すると、キャラクターが接触している範囲が枠で描画されました。

Godot タイルとの接触判定(青は判定範囲、赤はタイルが配置されているマス)

はしごなどのタイルが取得できたセルは赤色、タイルが配置されていないセルは水色で表示されて、タイルが正しく取得できていることを確認できました。

Godot4 操作キャラと TileMapLayer のセルの接触判定の例

まとめ

今回は、無料・軽快な 2D / 3D 用のゲームエンジン Godot Engine 4 で、操作キャラクターの当たり判定などに用いる Shape2D 派生リソースを用いて、タイルマップのどのタイル群と操作キャラクターが重なっているのかを判定するスクリプトの例と使用結果について紹介しました。

参照サイト 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をコピーしました