【AGMaker 深入解説】プラグイン公開:Node2D の特定ノードを移動方向に向ける

【AGMaker 深入】プラグイン公開:特定の Node2D を移動方向に向けて回転させる

著作権ライセンス

このプラグインは、Action Game Maker 内で、無料または商用のプロジェクトを制作する目的であれば、無料で自由に使用・改変することができます。
ただし、再配布は行わないでください。クレジット表示は必須ではありませんが、歓迎します。

プラグインの効果

このプラグインアクションの名前は JatkRotateToMovementDemo です。
VS エディター上でカスタムアクションとして機能し、指定された Node2D を毎フレーム回転させ、オブジェクトが実際に移動した方向に向ける役割を果たします。

以前は GameObject にのみ適用され velocity に依存する実装でしたが、このバージョンはフレーム間の位置差から方向を計算するため、GameObjectArea2DGameObject の両方で同じロジックを使用できます。

要点

  • JatkRotateToMovementDemo.gd をプロジェクト内の任意の場所に配置するだけで OK
  • どの VS エディターでも「アクションを実行」を追加しようとすると、新しい JusAgmToolkit タブにこの追加されたアクションが表示されます
  • このアクションは、オーナー自身を回転させることも、オーナー配下の特定の Node2D を回転させることも可能
  • rotate_target を入力しない場合、デフォルトではオーナー自身を回転させます
  • InstantSmooth の 2 つの回転モードを使用可能
  • rotation_speedSmooth モードでのみ有効
  • min_displacement_squared を使用して、ごく小さな揺れ(ジッター)による移動をフィルタリング可能
  • このアクションは、オーナーが GameObject または Area2DGameObject のステート内で使用するのが適しています

核心

操作手順

  1. 以下の JatkRotateToMovementDemo.gd スクリプト全体をコピーし、プロジェクト内の任意の場所に保存してください。
  2. AGMaker/Godot がスクリプトクラスをリフレッシュするのを待ちます。
  3. 任意の VS エディターを開きます。
  4. 「アクションを実行」を追加してみてください。
  5. 新しく表示された JusAgmToolkit タブから、JatkRotateToMovementDemo という名前のアクションを見つけます。
  6. 適切なステートに追加し、必要に応じて rotate_targetrotation_moderotation_speedmin_displacement_squared を設定します。
  7. オーナー自身を回転させたい場合は rotate_target を入力しなくて構いません。特定のサブノードを回転させたい場合は、rotate_target をその Node2D へ指し示します。

パラメータ説明

  • rotate_target
    回転させる Node2D のパス。空欄の場合はデフォルトでオーナー自身を回転させます。
  • rotation_mode
    Instant は現在の移動方向に即座に合わせます。Smooth は滑らかに回転させます。
  • rotation_speed
    Smooth モードでのみ使用されます。数値が大きいほど、移動方向への追従速度が速くなります。
  • min_displacement_squared
    最小移動距離の二乗閾値。2 フレーム間の移動距離の二乗がこの値より大きい場合のみ回転が発生します。二乗値を使用することで、不要な平方根計算を回避できます。

スクリプト

@tool
extends AGMPluginAction
class_name JatkRotateToMovementDemo

enum RotationMode {
	INSTANT,
	SMOOTH,
}

@export_group("Rotation Target")
@export_node_path("Node2D") var rotate_target: NodePath

@export_group("Rotation Settings")
@export_enum("Instant", "Smooth") var rotation_mode: int = RotationMode.SMOOTH:
	set(value):
		rotation_mode = value
		notify_property_list_changed()
@export_range(0.0, 100000.0, 0.1, "or_greater") var rotation_speed: float = 5.0
@export_range(0.0, 100000.0, 0.0001, "or_greater") var min_displacement_squared: float = 0.0001

var _owner_node_2d: Node2D
var _target_node_2d: Node2D
var _previous_global_position: Vector2 = Vector2.ZERO
var _is_active: bool = false


func get_plugin_tab_name() -> String:
	return "JusAgmToolkit"


func get_group_name() -> String:
	return "Demo"


func get_description() -> String:
	var locale: String = _get_editor_locale()
	if locale.begins_with("zh"):
		return "教育用サンプル:GameObject または Area2DGameObject の移動量の向きからノードを回転し、velocity には依存しません。"
	if locale.begins_with("ja"):
		return "教育用サンプル:GameObject または Area2DGameObject の移動量の向きからノードを回転し、velocity には依存しません。"
	return "Teaching demo: rotates a node from the displacement direction of a GameObject or Area2DGameObject without relying on velocity."


func on_state_enter(p_owner: Object) -> void:
	if Engine.is_editor_hint():
		return

	_reset_runtime_state()
	_owner_node_2d = p_owner as Node2D
	if _owner_node_2d == null:
		push_error("[JatkRotateToMovementDemo] Owner must be a Node2D, GameObject, or Area2DGameObject.")
		return

	_target_node_2d = _resolve_rotate_target(_owner_node_2d)
	if _target_node_2d == null:
		push_error("[JatkRotateToMovementDemo] No valid Node2D rotate target was found. owner=%s configured_path=%s" % [
			_owner_node_2d.name,
			str(rotate_target),
		])
		_reset_runtime_state()
		return

	# 中文:进入状态时先记录当前位置,后续用「位置差」而不是 velocity 来推导朝向。
	# English: Snapshot the starting position so later updates can derive facing from displacement instead of velocity.
	# 日本語: state 開始時に現在位置を記録し、以後は velocity ではなく位置差から向きを求めます。
	_previous_global_position = _owner_node_2d.global_position
	_is_active = true


func on_state_update(_p_owner: Object, p_delta: float) -> void:
	if Engine.is_editor_hint() or not _is_active:
		return

	if _owner_node_2d == null or not is_instance_valid(_owner_node_2d):
		_reset_runtime_state()
		return

	if _target_node_2d == null or not is_instance_valid(_target_node_2d):
		_reset_runtime_state()
		return

	var current_global_position: Vector2 = _owner_node_2d.global_position
	var displacement: Vector2 = current_global_position - _previous_global_position
	_previous_global_position = current_global_position

	# 中文:这里只关心两帧之间真实发生了多少位移,因此 GameObject 和 Area2DGameObject 都能共用同一套逻辑。
	# English: We only care about the actual frame-to-frame displacement, so GameObject and Area2DGameObject share the same logic.
	# 日本語: ここではフレーム間の実際の移動量だけを見るため、GameObject と Area2DGameObject で同じロジックを共有できます。
	if displacement.length_squared() < min_displacement_squared:
		return

	_apply_rotation_from_displacement(displacement, p_delta)


func on_state_exit(_p_owner: Object) -> void:
	if Engine.is_editor_hint():
		return

	_reset_runtime_state()


func _validate_property(property: Dictionary) -> void:
	if property.name == "rotation_speed" and rotation_mode != RotationMode.SMOOTH:
		property.usage = PROPERTY_USAGE_NO_EDITOR


func _apply_rotation_from_displacement(displacement: Vector2, p_delta: float) -> void:
	var target_angle: float = displacement.angle()

	match rotation_mode:
		RotationMode.INSTANT:
			_target_node_2d.global_rotation = target_angle
		RotationMode.SMOOTH:
			# 中文:平滑模式沿用旧 RotateToVelocity 的角度插值思路,只是输入方向改成位移方向。
			# English: Smooth mode keeps the old RotateToVelocity angular interpolation, but now feeds it with displacement direction.
			# 日本語: スムーズ回転は旧 RotateToVelocity の角度補間を流用しつつ、入力だけを移動量の向きへ置き換えています。
			var current_angle: float = _target_node_2d.global_rotation
			var angle_diff: float = wrapf(target_angle - current_angle, -PI, PI)
			_target_node_2d.global_rotation = current_angle + angle_diff * minf(1.0, maxf(rotation_speed, 0.0) * maxf(p_delta, 0.0))


func _resolve_rotate_target(owner_node_2d: Node2D) -> Node2D:
	if owner_node_2d == null:
		return null

	if rotate_target == NodePath():
		return owner_node_2d

	var target_node: Node = owner_node_2d.get_node_or_null(rotate_target)
	if target_node == null and owner_node_2d.owner != null:
		target_node = owner_node_2d.owner.get_node_or_null(rotate_target)

	return target_node as Node2D


func _reset_runtime_state() -> void:
	_owner_node_2d = null
	_target_node_2d = null
	_previous_global_position = Vector2.ZERO
	_is_active = false


func _get_editor_locale() -> String:
	if Engine.is_editor_hint() and ClassDB.class_exists("EditorInterface"):
		var editor_settings: EditorSettings = EditorInterface.get_editor_settings()
		if editor_settings:
			var editor_language: Variant = editor_settings.get_setting("interface/editor/editor_language")
			if editor_language is String and not editor_language.is_empty():
				return editor_language

	return TranslationServer.get_locale()

JatkRotateToMovementDemo.gd (5.5 KB)

「いいね!」 1