版权协议
你可以免费地在Action Game Maker中以任何目的使用或修改该插件,用于制作任何免费或是商用的项目。
但是请不要再分发它。署名不是必须的,但会受到感谢。
插件效果
这个插件节点的名字为JatkRotateToMovement。
它的作用是每个物理帧旋转一个Node2D,使其指向某个Node2D来源对象真实发生位移的方向。
这是之前JatkRotateToMovementDemo动作思路的节点版本。动作版本适合把行为放进某个VS状态里,由状态控制开始和结束。节点版本则适合把旋转行为作为场景树里的常驻组件使用,就像普通Godot节点一样。
因为它根据前后帧的位置差计算方向,所以不依赖velocity。只要global_position会随时间变化,它就可以用于GameObject、Area2DGameObject,也可以用于普通Node2D。
要点
- 只需要把
JatkRotateToMovement.gd放在项目中的任何位置 - 在场景中添加一个普通
Node,把这个脚本挂上去,然后在检查器里配置即可 movement_source是被观察运动方向的Node2Dnode_to_rotate是真正需要被旋转的Node2D- 如果不填写
movement_source,默认使用父级Node2D作为运动来源 - 如果不填写
node_to_rotate,会优先旋转父级Node2D - 因此你可以把组件放在视觉模型节点下面,把
movement_source指向正在移动的角色,然后让node_to_rotate留空,用它来旋转模型本身 - 可以使用
Instant和Smooth两种旋转模式 rotation_speed只在Smooth模式下生效- 可以使用
min_displacement_squared来过滤掉非常小的抖动位移
核心
操作步骤
- 把下方的
JatkRotateToMovement.gd脚本整个复制出来,放到你项目中的任何位置,保存。 - 等待AGMaker/Godot刷新脚本类。
- 选中正在移动的对象,或者选中你想旋转的视觉
Node2D。 - 按
Ctrl+A添加一个普通Node,然后把JatkRotateToMovement.gd挂到这个节点上。 - 按需配置
movement_source、node_to_rotate、rotation_mode、rotation_speed和min_displacement_squared。 - 如果这个组件就是挂在同一个负责移动且需要旋转的
Node2D下面,可以让movement_source和node_to_rotate都留空。 - 如果这个组件挂在视觉模型下面,但要跟随角色对象的移动方向,就把
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 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)