【AGMaker 深入】プラグイン公開:特定の Node2D を移動方向に向けて回転させる
著作権ライセンス
このプラグインは、Action Game Maker 内で、無料または商用のプロジェクトを制作する目的であれば、無料で自由に使用・改変することができます。
ただし、再配布は行わないでください。クレジット表示は必須ではありませんが、歓迎します。
プラグインの効果
このプラグインアクションの名前は JatkRotateToMovementDemo です。
VS エディター上でカスタムアクションとして機能し、指定された Node2D を毎フレーム回転させ、オブジェクトが実際に移動した方向に向ける役割を果たします。
以前は GameObject にのみ適用され velocity に依存する実装でしたが、このバージョンはフレーム間の位置差から方向を計算するため、GameObject と Area2DGameObject の両方で同じロジックを使用できます。
要点
JatkRotateToMovementDemo.gdをプロジェクト内の任意の場所に配置するだけで OK- どの VS エディターでも「アクションを実行」を追加しようとすると、新しい
JusAgmToolkitタブにこの追加されたアクションが表示されます - このアクションは、オーナー自身を回転させることも、オーナー配下の特定の
Node2Dを回転させることも可能 rotate_targetを入力しない場合、デフォルトではオーナー自身を回転させますInstantとSmoothの 2 つの回転モードを使用可能rotation_speedはSmoothモードでのみ有効min_displacement_squaredを使用して、ごく小さな揺れ(ジッター)による移動をフィルタリング可能- このアクションは、オーナーが
GameObjectまたはArea2DGameObjectのステート内で使用するのが適しています
核心
操作手順
- 以下の
JatkRotateToMovementDemo.gdスクリプト全体をコピーし、プロジェクト内の任意の場所に保存してください。 - AGMaker/Godot がスクリプトクラスをリフレッシュするのを待ちます。
- 任意の VS エディターを開きます。
- 「アクションを実行」を追加してみてください。
- 新しく表示された
JusAgmToolkitタブから、JatkRotateToMovementDemoという名前のアクションを見つけます。 - 適切なステートに追加し、必要に応じて
rotate_target、rotation_mode、rotation_speed、min_displacement_squaredを設定します。 - オーナー自身を回転させたい場合は
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)