[深入 AGMaker] 插件节点:旋转特定 Node2D 使其指向移动方向

许可协议

您可以自由地在 Action Game Maker 中使用或修改此插件,用于任何目的,无论是创建免费项目还是商业项目。但是,请勿重新分发该插件。
无需注明出处,但我们会很感激。

插件效果

此插件节点的名称为 JatkRotateToMovement
其功能是在每个物理帧旋转一个 Node2D,使其指向所选 Node2D 源的实际移动位移方向。

这是早期 JatkRotateToMovementDemo 动作概念的节点版本。当您希望该行为存在于 VS 状态内部时,动作版本非常方便。当您希望旋转行为像普通 Godot 节点一样作为可重用组件保留在场景树中时,此节点版本非常方便。

因为它根据帧之间的位置差计算方向,所以不依赖于 velocity。它可以与 GameObjectArea2DGameObject 或任何随时间改变 global_position 的普通 Node2D 配合使用。

关键点

  • 您只需将 JatkRotateToMovement.gd 放置在项目中的任何位置。
  • 在您的场景中添加一个普通 Node,将此脚本附加到它上面,并在检查器中进行配置。
  • movement_source 是要观察其移动的 Node2D
  • node_to_rotate 是要旋转的 Node2D
  • 如果 movement_source 留空,该节点将使用其父级 Node2D 作为移动源。
  • 如果 node_to_rotate 留空,该节点将首先尝试旋转其父级 Node2D
  • 这意味着您可以将组件放在视觉模型下方,将 movement_source 设置为移动的角色,并留空 node_to_rotate 以旋转模型本身。
  • 它支持两种旋转模式:Instant(即时)和 Smooth(平滑)。
  • rotation_speed 仅在 Smooth 模式下使用。
  • min_displacement_squared 可用于忽略非常微小的位移抖动。

核心

操作步骤

  1. 复制下方的 JatkRotateToMovement.gd 脚本,将其放置在项目中的任何位置,然后保存。
  2. 等待 AGMaker/Godot 刷新脚本类。
  3. 选择要旋转的移动对象或视觉 Node2D
  4. Ctrl+A 添加一个普通 Node,然后将 JatkRotateToMovement.gd 附加到它上面。
  5. 根据需要配置 movement_sourcenode_to_rotaterotation_moderotation_speedmin_displacement_squared
  6. 如果该组件是同一个 Node2D 的子节点,而该 Node2D 既要提供移动又要被旋转,您可以将 movement_sourcenode_to_rotate 都留空。
  7. 如果该组件位于视觉模型下方,但应跟随角色对象的移动,请将 movement_source 设置为角色,并留空 node_to_rotate

参数说明

  • movement_source
    用于作为移动方向的 Node2D,其帧到帧的全局位置差将被使用。如果留空,则使用父级 Node2D
  • node_to_rotate
    要旋转的 Node2D。如果留空,则首先使用父级 Node2D
  • rotation_mode
    Instant 会立即对齐到当前的位移方向。Smooth 会逐渐向该方向旋转。
  • rotation_speed
    仅在 Smooth 模式下使用。较大的值会使旋转更快地追上目标方向。
  • min_displacement_squared
    最小平方位移阈值。仅当帧到帧的平方位移大于此值时才进行旋转。使用平方值还可以避免不必要的平方根计算。

何时使用节点版本

当旋转行为属于场景对象本身,并且无论哪个 VS 状态处于活动状态都应持续运行时,请使用 JatkRotateToMovement

当旋转行为应由特定的 VS 状态作为自定义动作启动和停止时,请使用 JatkRotateToMovementDemo

两个版本都基于相同的核心思想:从实际位移而非 velocity 推导朝向方向。

脚本

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 未设置且父级不是 Node2D")
		return

	if _resolved_node_to_rotate == null:
		printerr("[JatkRotateToMovement] node_to_rotate 未设置且没有可用的备用 Node2D")
		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)

1 个赞