【Godot】ホーミングレーザーの実装サンプル

ホーミングレーザーっぽい挙動の実装サンプルを作成しましたので、プロジェクトファイルを公開しておきます

更新履歴

2022.12.3: 角度差を求める計算が間違っていたので修正しました

以下、簡単な解説を行います。

ホーミングレーザー解説

レーザーのような線の作成方法

レーザーのような線は「Line2D」を使用しています。

線の分割数ですがおおよそ「32」にすると、ほどよい長さで滑らかな動きになります。

線の終端を細くする方法

Line2Dには「Width Curve」という幅の変化を曲線で設定するエディタが用意されています。これを以下のように徐々に小さくするグラフにすることで終端が細くなっていきます。

レーザーの発射開始時の情報

発射開始時にどの情報を持たせるのかは、レーザーの用途によりますが、今回はロックオンレーザーのような挙動を目的としたので以下の情報としました。

  • speed: レーザーの移動速度
  • start_angle: 進行方向(角度)
  • start: 開始座標
  • end: 狙い撃ちする(到達目的となる)座標
# 移動開始処理
# @param speed 移動速度
# @param start_angle 開始角度(-180〜180)
# @param start 開始座標
# @param end 終端座標
func start(speed:float, start_angle:float, start:Vector2, end:Vector2) -> void:
	for i in range(line.points.size()):
		line.points[i] = start
	
	_aim_position = end
	_angle = start_angle
	_speed = speed

ホーミング処理のスクリプト

ホーミング処理を行う “Horming.gd” は以下のとおりです。

extends Node2D

# -------------------------------
# 当たり判定の半径
const HIT_RADIUS = 32

# 速度制限
const SPEED_LIMIT_RATIO = 0.1

# 接触してから消えるまでの時間
const DESTROY_WAIT_TIME = 1.0
# -------------------------------
onready var line = $Line2D

# -------------------------------
# 移動速度
var _speed:float = 500
# 現在の進行方向
var _angle:float = 0
# 旋回速度
var _rot_speed = 0.1

# ターゲット座標
# ・ターゲットが移動する場合はこの座標を更新する
# ・Objectを渡す場合は is_instance_valid() で存在チェックして
#  座標を取得することになるはず……
var _aim_position := Vector2()

var _time_wait = DESTROY_WAIT_TIME # 接触して1秒で消える
# -------------------------------

func _ready() -> void:
	pass

# 移動開始処理
# @param speed 移動速度
# @param start_angle 開始角度(-180〜180)
# @param start 開始座標
# @param end 終端座標
func start(speed:float, start_angle:float, start:Vector2, end:Vector2) -> void:
	for i in range(line.points.size()):
		line.points[i] = start
	
	_aim_position = end
	_angle = start_angle
	_speed = speed
	
func _process(delta: float) -> void:	
	# マウスの場所を狙うテスト
	#_aim_position = get_viewport().get_mouse_position()
	
	var p:Vector2 = line.points[0]

	# 回転方向を求める
	var dir = (_aim_position - p)
	var length = dir.length()
	if length < HIT_RADIUS:
		# 一定距離に近づいたら直接近づける
		# 0.1の重みで線形補間します
		line.points[0] = p.linear_interpolate(_aim_position, 0.1)
		_update_line2d()
		_time_wait -= delta
		if _time_wait < 0:
			# 一定時間で消します
			queue_free()
		return
	
	var rad = atan2(-dir.y, dir.x)
	var deg = rad2deg(rad)
	var d = _diff_angle(_angle, deg)
	
	# 旋回実行
	_angle += d * _rot_speed
	# 旋回速度を上げる
	_rot_speed = lerp(_rot_speed, 1.0, delta * 0.1)
	
	# Line2Dの先頭を移動する
	var next = p
	var spd = _speed * delta
	if spd > length * SPEED_LIMIT_RATIO:
		# 速度制限
		spd = length * SPEED_LIMIT_RATIO
	next.x += spd * cos(deg2rad(_angle))
	next.y += spd * -sin(deg2rad(_angle))
	line.points[0] = next
	
	# line2dを動かす
	_update_line2d()

# Line2Dの座標を更新する
func _update_line2d() -> void:
	for i in range(line.points.size()-1):
		var a = line.points[i]
		var b = line.points[i+1]
		# 0.5の重みで線形補間します
		line.points[i+1] = b.linear_interpolate(a, 0.5)

# 角度差を求める
func _diff_angle(now:float, next:float) -> float:
	# 差を求める
	var d = next - now
	# 0〜360に丸める
	d -= floor(d / 360.0) * 360.0
	# -180〜180にする.
	if d > 180:
		d -= 360
	return d

移動処理は「角度(_angle)」「速さ(_speed)」から計算するようになっています。

図にすると以下の通りで、徐々に目的となる向きに対して旋回していくようにしています。

今回は当たり判定について Godot Engine が用意しているコリジョンシステムを使用せずに 終端座標に近づいたら消滅する…としています。

また狙い撃ちする座標は start() 関数で指定されたものとしています。通常狙い撃ちする対象は移動したり、消滅して消えることもあると思うので、ゲームオブジェクト (Node2Dを継承したもの) のインスタンスを直接渡して、is_instance_valid() で生存チェックしつつ狙い撃ち座標を更新していく…といった作りにするのが良さそうです。

その他の注意点としては、以下のものです。

  • 1. 時間経過で旋回速度を上昇させる
  • 2. 目的の座標に近づいたら減速して一定時間で消滅する

旋回速度はある程度遅いほうが、美しい放物線のレーザーとなります。ですが旋回速度が遅いと、狙い撃ちするターゲットに近づいたときに周りをグルグル回ってしまいなかなか命中しなくなってしまう挙動となる可能性があります。そのため少しずつ旋回速度を上昇させると確実に命中させることができます。

また旋回速度を上げても、発射開始の位置から近い場所にターゲットが存在する場合も、同様に命中しなくなる可能性があるので、移動速度を下げるようしました。

プロジェクトファイル

今回紹介したプロジェクトファイルです。

ブルーム適用バージョン

ホーミングの挙動に変更はないですが、見た目を良くするためにブルームを適用したものも添付しておきます。

ブルームの設定方法はこちらの記事にまとめています。

【Godot】ブルームを適用する方法