Godot4 ビッグカツブロック崩し26 Curveから各位置の反射角度を取得

※この連載の全ての記事は、タグ「ビッグカツ」の検索一覧から探すことができます。
※この連載で作ったゲームは「BigBreakOut(ゲームの作り方の記事付き) | フリーゲーム投稿サイト GodotPlayer」でプレイできます。

昔から人気の駄菓子「ビッグカツ」フリー素材画像が公開されたので、無料・軽快な 2D / 3D 用のゲームエンジン Godot Engine 4 を使って、ビッグカツ画像を使ったブロック崩しを作成します。

「ビッグカツブロック崩し」作成の第26回では、前回紹介した、ボールパドル衝突した位置によるボールの反射角度を設定したパドルの左側用・右側用の各 Curve リソースを用いて、以前に計算して行っていた反射角度の取得をより直感的に行うスクリプト例テストの結果を紹介します。

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

前回の記事

前回は、Curve リソースで各位置の反射角度をグラフとして設定する手順を紹介しました。

前回作成したカーブ

下図のように反射角度を得られるグラフを作りました。
中心の 0.5 の部分は、50 %の確率で -120 度か -60 度にするため、その部分だけ、縦線になって同じ X 座標に二つの Y の値の点が存在します。

Godot4 ビッグカツブロック崩し パドルに衝突する位置によって反射角度を変えます

そこで、パドルの左側と右側の2つの Curve リソースを以下のように作成しました。

カーブリソースによって反射角度を取得するスクリプト例

以下は、パドルに割り当ている paddle.gd スクリプトのボールがぶつかった位置に応じて反射角度を得る get_bounce 関数と、それに必要な Curve リソース2つのプロパティです。
最後にかかれている test_get_bounce テスト関数は、等分にしたパドルの各位置の反射角度を出力する他に、パドルの中心位置の反射角度も出力するようにしました。
※反射角度を出力するために get_bounce 関数内の print 文を有効にしています。
※詳細はスクリプト内のコメントを参照してください。

以前のスクリプトの

  • right_end_angle
  • right_start_angle
  • left_start_angle
  • left_end_angle

4つのプロパティの定義部分get_bounce 関数と test_get_bounce 関数を以下のスクリプトで置き換えます。

以前の paddle.gd スクリプトについては以下の記事を参照してください。

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

以前との大きな違いは、前回は左端・中心(左寄り)・中心(右寄り)・右端の各位置の反射角度を設定することで、その途中の場所も計算式で反射角度を得ていた処理を、左側・右側用の2つの Curve リソースを用いることで、より簡単にその位置の反射角度を得られるようにしたことです。

## パドルの左端から中心までの位置ごとの反射角度を設定した Curve リソースを割り当てます。
## x は、左端が 0 で中心が 1.0 になります。
@export var curve_reflection_angle_left: Curve = null
## パドルの右端から中心までの位置ごとの反射角度を設定した Curve リソースを割り当てます。
## x は、右端が 0 で中心が 1.0 になります。
@export var curve_reflection_angle_right: Curve = null

## 衝突した位置によって設定された反射角度の単位ベクトルを返します。
## 入射角度は無視され、衝突した位置がパドルのどの位置かによって反射角度を決定します。
##
## 反射角度は、左側のカーブ curve_reflection_angle_left と
## 右側のカーブ curve_reflection_angle_right の衝突位置の x 座標を 0.0 ~ 1.0 で正規化
## した値に対応するカーブの y の値に設定されています。
## 左端    中心    右端
## ┏─────────────────────┓ TODO:この図をカーブの 0~1 の対応図に変える。D
## ┃        Paddle       ┃
## ┗─────────────────────┛
## 左側のカーブ    右側のカーブ
## 0.0 -> 1.0    1.0 -> 0.0 (x 座標の値)
## 完全に中心に衝突した場合は 50 % の確率で左側または右側のカーブの x = 1.0 (パドル中心)の
## y の値(カーブの例では -120 度または -60 度)が選ばれます。
#func get_bounce(velocity: Vector2, collision: KinematicCollision2D) -> Vector2:
func get_bounce(velocity: Vector2, position_hit: Vector2) -> Vector2:
	# 戻り値の単位ベクトルのもととなる反射角度
	var reflection_angle: float = 0
	
	# 衝突した位置とパドルの位置です。どちらもグローバル座標です。
	var pos_hit: Vector2 = position_hit #collision.get_position()
	var pos_paddle: Vector2 = global_position # center?
	
	# パドルの横幅と中心の座標を取得します。
	var shape: Shape2D = $CollisionShape2D.shape
	var shape_rect: Rect2 = shape.get_rect()
	var paddle_center: Vector2 = shape_rect.get_center() + pos_paddle
	var width_paddle_half = shape_rect.size.x / 2
	#print("paddle_center.x = ", str(paddle_center.x))
	
	# 衝突した位置が、パドル内の左右どちらかの端からどの程度離れているかを 0.0 ~ 1.0 の範囲で取得します。
	var distance_from_end: float = width_paddle_half - abs(pos_hit.x - paddle_center.x)
	var normalized_distance: float = clamp(distance_from_end / width_paddle_half, 0.0, 1.0)
	
	# 当たった場所がパドルの右側の場合
	if paddle_center.x < pos_hit.x:
		reflection_angle = curve_reflection_angle_right.sample(normalized_distance)
	# 当たった場所がパドルの左側の場合
	elif paddle_center.x > pos_hit.x:
		reflection_angle = curve_reflection_angle_left.sample(normalized_distance)
	# 当たった場所がパドルの中心の場合
	else:
		# 50 % の確率で、右寄りまたは左寄りの中心の反射角度を設定します。
		if randf() < 0.5:
			reflection_angle = curve_reflection_angle_right.sample(1.0)
		else:
			reflection_angle = curve_reflection_angle_left.sample(1.0)

	# 得られた角度を単位ベクトルに変換します。
	var radian: float = deg_to_rad(reflection_angle)
	var reflection_vector: Vector2 = Vector2(cos(radian), sin(radian))
	print("reflection_angle = ", str(reflection_angle), ", radian = ", str(radian))
	# 単位ベクトルに速度をかけたベクトルを返します。
	return reflection_vector * velocity.length()

## get_bound メンバ関数のテスト用の関数です。
## パドルの横幅を 10 等分した位置の反射角度の単位ベクトルを得た結果を出力します。
## あわせて get_bound メンバ関数でも、内部で得られた reflection_angle (反射角度)を print 出力して確認しました。
func test_get_bound():
	var vel: Vector2 = Vector2(50, 50)
	var paddle_width_half: float = $CollisionShape2D.shape.get_rect().size.x / 2
	for x in range(-paddle_width_half, paddle_width_half, paddle_width_half / 5):
		var pos_hit = Vector2(position.x + x, position.y)
		var ret: Vector2 = get_bounce(vel, pos_hit)
		print("☆ X = ", str(x), ", ret = ", str(ret))
	
	# 中心の場合のテスト。50%の確率で左側の最大角度(-120)または右側の最小角度(-60)が出力されます。
	# 数回行って確認します。
	var pos_hit = Vector2(position.x, position.y)
	var ret: Vector2 = get_bounce(vel, pos_hit)
	print("☆ X = ", "center", ", ret = ", str(ret))

念のため、この記事の最後に現段階の paddle.gd 全行を記載します。

2つのカーブを Paddle クラスのプロパティに設定

paddle.gd の get_bounce 関数と test_get_bounce 関数を変更すると、 Padlle ノードを選択した際のインスペクタードックに、先ほど追加したふたつの Curve リソースのプロパティが表示されます。

そこに前回作成した以下の Curve リソースのファイルを設定します。

ファイルシステムドックで左側と右側用のそれぞれの Curve リソースを、インスペクタードックのそれぞれのプロパティにドラッグ&ドロップで設定します。

Godot4 ビッグカツブロック崩し Curveリソースをスクリプトのプロパティに設定

テスト

2D ワークスペースステージのシーンボールの位置パドルの水平方向5等分(目安)した位置に配置してから、そのつど、F6 キーで開いているステージのシーンを実行します。
※ボールの初期角度は真下にいくように以前の記事の調整しています。

ボールが衝突するパドルの各位置によって、Curve リソースの各 x 座標(0.0 ~ 1.0 に正規化済み)に対応する反射角度( y の値)が得られ、そのカーブに設定した角度に応じてボールが反射しました。

以上のテストでは、正確に真ん中にヒットしなかったので、先ほどのスクリプトに記載した test_get_bounce テスト関数に付け加えたパドルの中心位置の反射角度を得て print 文で出力しました。
数回やると左側用と右側用の2つのカーブx = 1.0 (中心側)の y の値のどちらか(例では -120, -60 )が出ることを確認できました。

Godot4 パドル左右に対応する二つの Curve リソースを使って反射角度を設定する処理のテストSS6

テスト用のボールの初期角度を元に戻す

テストを簡単に行うために ball.gd スクリプトのボールの初期角度を 90 度(真下)に一時的に変更していました。
テストが済んだら、 PI / 2 から PI / 4 (45度、右下)に初期角度の値を戻しましょう。

## ボールの初期角度です。
@export var INITIAL_ANGLE: float = PI / 4  # 45度
#@export var INITIAL_ANGLE: float = PI / 2  # 90度 # ←この行を削除

現段階の paddle.gd

_ready イベント関数の test_get_bound() のテスト用の出力や各場所の print 文は不要ならば、適宜コメントアウトしてください。

extends CharacterBody2D
class_name Paddle
## 操作するパドル(バー)のスクリプトです。

# パドル(バー)が横に移動する際の速度です。
const SPEED = 300.0

func _ready():
	test_get_bound()
	# 外部から設定してもらうプロパティの確認
	if curve_reflection_angle_left == null:
		printerr("curve_reflection_angle_left を設定してください。")
	if curve_reflection_angle_right == null:
		printerr("curve_reflection_angle_right を設定してください。")
	
	return

# _physics_process は、物理的な相互作用を安定させるために、
# 可能な限り固定時間間隔(デフォルトでは毎秒60回)で呼び出されます。
# この間隔は、プロジェクト設定の Physics -> Common-> Physics Fps で変更できます。
# [param delta] は前回の _physics_process 関数が呼び出されてからの経過時間(ミリ秒)です。
func _physics_process(_delta):
	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	# ← →キーなど左右に移動する入力を取得します。
	var direction = Input.get_axis("ui_left", "ui_right")
	# 左右いずれかへの入力がある場合は、その方向に向けて SPEED の値だけ加速します。
	if direction:
		velocity.x = direction * SPEED
	# 左右どちらへの入力もない場合は、加速度を 0 にして止まります。
	# move_toward を使うことで、 veliocity.x の値を SPEED を最大値として 0 に近づけます。
	# これにより、第3引数を小さくすればするほど、少し滑ったような感じで少しずつ減速します。
	# 現状は、移動に使う加速度の SPEED と同じ値なので、入力がなければすぐに 0 になり、止まります。
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)
	
	# 衝突したら止まるように物理演算を行いながら、設定された velocity ベクトルの方向と量で移動します。
	move_and_slide()
	return


### パドルの右端にぶつかった場合の反射角度です。右側が0度、上方向はマイナスになります。
### right_end_angle >= right_start_angle >= left_start_angle >= left_end_angle の関係になるように設定してください。
#@export var right_end_angle: float = -30.0
### パドルの中心からごくわずかに右寄りの場所にぶつかった場合の反射角度です。
### right_end_angle >= right_start_angle >= left_start_angle >= left_end_angle の関係になるように設定してください。
#@export var right_start_angle: float = -60.0
### パドルの中心からごくわずかに左寄りの場所にぶつかった場合の反射角度です。
### right_end_angle >= right_start_angle >= left_start_angle >= left_end_angle の関係になるように設定してください。
#@export var left_start_angle: float = -120.0
### パドルの左端にぶつかった場合の反射角度です。
### right_end_angle >= right_start_angle >= left_start_angle >= left_end_angle の関係になるように設定してください。
#@export var left_end_angle: float = -150

## パドルの左端から中心までの位置ごとの反射角度を設定した Curve リソースを割り当てます。
## x は、左端が 0 で中心が 1.0 になります。
@export var curve_reflection_angle_left: Curve = null
## パドルの右端から中心までの位置ごとの反射角度を設定した Curve リソースを割り当てます。
## x は、右端が 0 で中心が 1.0 になります。
@export var curve_reflection_angle_right: Curve = null

## 衝突した位置によって設定された反射角度の単位ベクトルを返します。
## 入射角度は無視され、衝突した位置がパドルのどの位置かによって反射角度を決定します。
##
## 反射角度は、左側のカーブ curve_reflection_angle_left と
## 右側のカーブ curve_reflection_angle_right の衝突位置の x 座標を 0.0 ~ 1.0 で正規化
## した値に対応するカーブの y の値に設定されています。
## 左端    中心    右端
## ┏─────────────────────┓ TODO:この図をカーブの 0~1 の対応図に変える。D
## ┃        Paddle       ┃
## ┗─────────────────────┛
## 左側のカーブ    右側のカーブ
## 0.0 -> 1.0    1.0 <- 0.0 (x 座標の値)
## 完全に中心に衝突した場合は 50 % の確率で左側または右側のカーブの x = 1.0 (パドル中心)の
## y の値(カーブの例では -120 度または -60 度)が選ばれます。
#func get_bounce(velocity: Vector2, collision: KinematicCollision2D) -> Vector2:
func get_bounce(velocity: Vector2, position_hit: Vector2) -> Vector2:
	# 戻り値の単位ベクトルのもととなる反射角度
	var reflection_angle: float = 0
	
	# 衝突した位置とパドルの位置です。どちらもグローバル座標です。
	var pos_hit: Vector2 = position_hit #collision.get_position()
	var pos_paddle: Vector2 = global_position # center?
	
	# パドルの横幅と中心の座標を取得します。
	var shape: Shape2D = $CollisionShape2D.shape
	var shape_rect: Rect2 = shape.get_rect()
	var paddle_center: Vector2 = shape_rect.get_center() + pos_paddle
	var width_paddle_half = shape_rect.size.x / 2
	#print("paddle_center.x = ", str(paddle_center.x))
	
	# 衝突した位置が、パドル内の左右どちらかの端からどの程度離れているかを 0.0 ~ 1.0 の範囲で取得します。
	var distance_from_end: float = width_paddle_half - abs(pos_hit.x - paddle_center.x)
	var normalized_distance: float = clamp(distance_from_end / width_paddle_half, 0.0, 1.0)
	
	# 当たった場所がパドルの右側の場合
	if paddle_center.x < pos_hit.x:
		reflection_angle = curve_reflection_angle_right.sample(normalized_distance)
	# 当たった場所がパドルの左側の場合
	elif paddle_center.x > pos_hit.x:
		reflection_angle = curve_reflection_angle_left.sample(normalized_distance)
	# 当たった場所がパドルの中心の場合
	else:
		# 50 % の確率で、右寄りまたは左寄りの中心の反射角度を設定します。
		if randf() < 0.5:
			reflection_angle = curve_reflection_angle_right.sample(1.0)
		else:
			reflection_angle = curve_reflection_angle_left.sample(1.0)

	# 得られた角度を単位ベクトルに変換します。
	var radian: float = deg_to_rad(reflection_angle)
	var reflection_vector: Vector2 = Vector2(cos(radian), sin(radian))
	print("reflection_angle = ", str(reflection_angle), ", radian = ", str(radian))
	# 単位ベクトルに速度をかけたベクトルを返します。
	return reflection_vector * velocity.length()

## get_bound メンバ関数のテスト用の関数です。
## パドルの横幅を 10 等分した位置の反射角度の単位ベクトルを得た結果を出力します。
## あわせて get_bound メンバ関数でも、内部で得られた reflection_angle (反射角度)を print 出力して確認しました。
func test_get_bound():
	var vel: Vector2 = Vector2(50, 50)
	var paddle_width_half: float = $CollisionShape2D.shape.get_rect().size.x / 2
	for x in range(-paddle_width_half, paddle_width_half, paddle_width_half / 5):
		var pos_hit = Vector2(position.x + x, position.y)
		var ret: Vector2 = get_bounce(vel, pos_hit)
		print("☆ X = ", str(x), ", ret = ", str(ret))
	
	# 中心の場合のテスト。50%の確率で左側の最大角度(-120)または右側の最小角度(-60)が出力されます。
	# 数回行って確認します。
	var pos_hit = Vector2(position.x, position.y)
	var ret: Vector2 = get_bounce(vel, pos_hit)
	print("☆ X = ", "center", ", ret = ", str(ret))

まとめ

「ビッグカツブロック崩し」作成の第26回では、前回紹介した、ボールパドル衝突した位置によるボールの反射角度を設定したパドルの左側用・右側用の各 Curve リソースを用いて、以前に計算して行っていた反射角度の取得をより直感的に行うスクリプト例テストの結果を紹介しました。

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