License Agreement
You may freely use or modify this plugin in Action Game Maker for any purpose, whether for creating free or commercial projects. However, please do not redistribute it.
Credit is not necessary, but will be appreciated.
Plugin Effects
The name of this plugin node is JatkRotateToMovement.
Its function is to rotate a Node2D every physics frame so that it points in the direction of the actual movement displacement of a selected Node2D source.
This is the node version of the earlier JatkRotateToMovementDemo action idea. The action version is convenient when you want the behavior to live inside a VS state. This node version is convenient when you want the rotation behavior to stay in the scene tree as a reusable component, just like a normal Godot node.
Because it calculates direction from the position difference between frames, it does not depend on velocity. It can work with GameObject, Area2DGameObject, or any ordinary Node2D whose global_position changes over time.
Key Points
- You only need to place
JatkRotateToMovement.gdanywhere you like inside your project. - Add a normal
Nodein your scene, attach this script to it, and configure it in the Inspector. movement_sourceis theNode2Dwhose movement should be observed.node_to_rotateis theNode2Dthat should be rotated.- If
movement_sourceis left empty, the node uses its parentNode2Das the movement source. - If
node_to_rotateis left empty, the node first tries to rotate its parentNode2D. - This means you can put the component under a visual model, set
movement_sourceto the moving character, and leavenode_to_rotateempty to rotate the model itself. - It supports two rotation modes:
InstantandSmooth. rotation_speedis only used inSmoothmode.min_displacement_squaredcan be used to ignore very tiny displacement jitter.
Core
Operation Steps
- Copy the
JatkRotateToMovement.gdscript below and place it anywhere you like inside your project, then save it. - Wait for AGMaker/Godot to refresh the script class.
- Select the moving object or the visual
Node2Dyou want to rotate. - Press
Ctrl+Ato add a normalNode, then attachJatkRotateToMovement.gdto it. - Configure
movement_source,node_to_rotate,rotation_mode,rotation_speed, andmin_displacement_squaredas needed. - If the component is a child of the same
Node2Dthat should provide movement and be rotated, you can leave bothmovement_sourceandnode_to_rotateempty. - If the component is under a visual model but should follow the movement of a character object, set
movement_sourceto the character and leavenode_to_rotateempty.
Parameter Explanation
movement_source
TheNode2Dwhose frame-to-frame global position difference is used as the movement direction. If left empty, the parentNode2Dis used.node_to_rotate
TheNode2Dthat should be rotated. If left empty, the parentNode2Dis used first.rotation_mode
Instantaligns immediately to the current displacement direction.Smoothrotates toward it gradually.rotation_speed
Only used inSmoothmode. Larger values make the rotation catch up faster.min_displacement_squared
The minimum squared displacement threshold. Rotation only happens when the frame-to-frame displacement squared is larger than this value. Using a squared value also avoids unnecessary square root work.
When to Use the Node Version
Use JatkRotateToMovement when the rotation behavior belongs to the scene object itself and should keep running regardless of which VS state is active.
Use JatkRotateToMovementDemo when the rotation behavior should be started and stopped by a specific VS state as a custom action.
Both versions are based on the same core idea: derive facing direction from actual displacement, not from velocity.
Script
class_name JatkRotateToMovement extends Node
enum RotationMode {
INSTANT,
SMOOTH,
}
@export var node_to_rotate: Node2D
@export var movement_source: Node2D
@export var rotation_mode: RotationMode = RotationMode.SMOOTH
@export var rotation_speed: float = 5.0
@export var min_displacement_squared: float = 0.0001
var _resolved_node_to_rotate: Node2D
var _resolved_movement_source: Node2D
var _previous_global_position: Vector2 = Vector2.ZERO
var _has_previous_global_position: bool = false
func _ready() -> void:
process_physics_priority = 100
_resolve_runtime_nodes()
func _physics_process(delta: float) -> void:
if _resolved_movement_source == null or not is_instance_valid(_resolved_movement_source):
_resolve_runtime_nodes()
return
if _resolved_node_to_rotate == null or not is_instance_valid(_resolved_node_to_rotate):
_resolved_node_to_rotate = _resolve_node_to_rotate()
if _resolved_node_to_rotate == null:
return
var current_global_position: Vector2 = _resolved_movement_source.global_position
if not _has_previous_global_position:
_previous_global_position = current_global_position
_has_previous_global_position = true
return
var displacement: Vector2 = current_global_position - _previous_global_position
_previous_global_position = current_global_position
if displacement.length_squared() < min_displacement_squared:
return
_apply_rotation_from_displacement(displacement, delta)
func _resolve_runtime_nodes() -> void:
_resolved_movement_source = _resolve_movement_source()
_resolved_node_to_rotate = _resolve_node_to_rotate()
_has_previous_global_position = false
if _resolved_movement_source == null:
printerr("[JatkRotateToMovement] movement_source is not set and parent is not a Node2D")
return
if _resolved_node_to_rotate == null:
printerr("[JatkRotateToMovement] node_to_rotate is not set and no fallback Node2D is available")
return
_previous_global_position = _resolved_movement_source.global_position
_has_previous_global_position = true
func _resolve_movement_source() -> Node2D:
if movement_source != null:
return movement_source
return get_parent() as Node2D
func _resolve_node_to_rotate() -> Node2D:
if node_to_rotate != null:
return node_to_rotate
var parent_node_2d: Node2D = get_parent() as Node2D
if parent_node_2d != null:
return parent_node_2d
if _resolved_movement_source != null:
return _resolved_movement_source
return _resolve_movement_source()
func _apply_rotation_from_displacement(displacement: Vector2, delta: float) -> void:
var target_angle: float = displacement.angle()
match rotation_mode:
RotationMode.INSTANT:
_resolved_node_to_rotate.global_rotation = target_angle
RotationMode.SMOOTH:
var current_angle: float = _resolved_node_to_rotate.global_rotation
var angle_diff: float = wrapf(target_angle - current_angle, -PI, PI)
var rotation_weight: float = minf(1.0, maxf(rotation_speed, 0.0) * maxf(delta, 0.0))
_resolved_node_to_rotate.global_rotation = current_angle + angle_diff * rotation_weight
JatkRotateToMovement.gd (3.0 KB)