许可协议
您可以自由地在 Action Game Maker 中出于任何目的使用或修改此插件,无论是创建免费项目还是商业项目。但是,请勿重新分发它。
署名并非必需,但我们会非常感激。
插件效果
此插件动作的名称为 JatkRotateToMovementDemo。
其功能是在 VS 编辑器中作为自定义动作运行,每一帧旋转特定的 Node2D,使其指向实际移动位移的方向。
与之前仅适配 GameObject 且依赖 velocity 的方法不同,此版本通过计算帧与帧之间的位置差来确定方向,因此 GameObject 和 Area2DGameObject 都可以使用相同的逻辑。
关键点
- 您只需将
JatkRotateToMovementDemo.gd放置在项目中的任何位置即可。 - 在任何 VS 编辑器中,尝试添加一个
Execute Action,您应该能在新的JusAgmToolkit选项卡下找到此新动作。 - 此动作可以旋转所有者本身,也可以旋转所有者下的特定
Node2D。 - 如果
rotate_target留空,则默认旋转所有者本身。 - 它支持两种旋转模式:
Instant(即时)和Smooth(平滑)。 rotation_speed仅在Smooth模式下使用。min_displacement_squared可用于忽略极微小的位移抖动。- 此动作旨在用于所有者为
GameObject或Area2DGameObject的状态。
核心
操作步骤
- 复制下方的
JatkRotateToMovementDemo.gd脚本,并将其放置在项目中的任何位置,然后保存。 - 等待 AGMaker/Godot 刷新脚本类。
- 打开任意 VS 编辑器。
- 尝试添加一个
Execute Action。 - 在新的
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
最小平方位移阈值。仅当帧与帧之间的位移平方大于此值时才会发生旋转。使用平方值还可以避免不必要的平方根计算。
脚本
@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] 所有者必须是 Node2D、GameObject 或 Area2DGameObject。")
return
_target_node_2d = _resolve_rotate_target(_owner_node_2d)
if _target_node_2d == null:
push_error("[JatkRotateToMovementDemo] 未找到有效的 Node2D 旋转目标。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)