Copyright Agreement
You are free to use or modify this plugin in Action Game Maker for any purpose, including creating both free and commercial projects.
However, please do not redistribute it. Attribution is not required, but appreciated.
Plugin Functionality
The name of this plugin node is JatkRotateToMovement.
Its function is to rotate a Node2D each physics frame so that it points in the direction of the actual displacement of a source Node2D.
This is the node-based version of the previous JatkRotateToMovementDemo action concept. The action version is suitable for embedding behavior within a VS state, controlled by the state to start and stop. The node version is suitable for using the rotation behavior as a persistent component in the scene tree, just like a standard Godot node.
Since it calculates direction based on the difference in position between frames, it does not rely on velocity. As long as global_position changes over time, it can be used with GameObject, Area2DGameObject, or even standard Node2D.
Key Points
- Simply place
JatkRotateToMovement.gdanywhere in your project. - Add a standard
Nodeto the scene, attach this script, and configure it in the inspector. movement_sourceis theNode2Dwhose movement direction is being observed.node_to_rotateis theNode2Dthat actually needs to be rotated.- If
movement_sourceis left empty, the parentNode2Dis used by default as the movement source. - If
node_to_rotateis left empty, the parentNode2Dis prioritized for rotation. - Therefore, you can place the component under a visual model node, point
movement_sourceat the moving character, and leavenode_to_rotateempty to rotate the model itself. - Two rotation modes are available:
InstantandSmooth. rotation_speedonly takes effect inSmoothmode.- You can use
min_displacement_squaredto filter out very small jittery displacements.
Core
Steps
- Copy the entire
JatkRotateToMovement.gdscript below and save it anywhere in your project. - Wait for AGMaker/Godot to refresh the script class.
- Select the object that is moving, or select the visual
Node2Dyou want to rotate. - Press
Ctrl+Ato add a standardNode, and attachJatkRotateToMovement.gdto this node. - Configure
movement_source,node_to_rotate,rotation_mode,rotation_speed, andmin_displacement_squaredas needed. - If this component is attached directly under the same
Node2Dresponsible for movement and rotation, you can leave bothmovement_sourceandnode_to_rotateempty. - If this component is attached under a visual model but needs to follow the movement direction of the character object, point
movement_sourceat the character and leavenode_to_rotateempty.
Parameter Description
movement_source
TheNode2Dused to calculate the global position difference between two frames. Defaults to the parentNode2Dif left empty.node_to_rotate
TheNode2Dthat needs to be rotated. Prioritizes the parentNode2Dif left empty.rotation_mode
Instantaligns immediately to the current displacement direction.Smoothrotates smoothly towards it.rotation_speed
Used only inSmoothmode. Higher values result in faster tracking of the displacement direction.min_displacement_squared
Minimum displacement squared threshold. Rotation only occurs if the squared displacement between two frames exceeds this value. Using the squared value avoids unnecessary square root calculations.
When to Use the Node Version
Use JatkRotateToMovement if this rotation behavior belongs to the scene object itself and should run continuously regardless of the current VS state.
Use the custom action JatkRotateToMovementDemo if this rotation behavior should be started and stopped by a specific VS state.
The core concept of both versions is the same: derive orientation from actual displacement rather than relying on 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)