diff --git a/doc/classes/AnimatableBody2D.xml b/doc/classes/AnimatableBody2D.xml
index 831f7d73d05..e0479f68288 100644
--- a/doc/classes/AnimatableBody2D.xml
+++ b/doc/classes/AnimatableBody2D.xml
@@ -4,7 +4,7 @@
A 2D physics body that can't be moved by external forces. When moved manually, it affects other bodies in its path.
- An animatable 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform2D].
+ An animatable 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationMixer]s (with [member AnimationMixer.callback_mode_process] set to [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]), and [RemoteTransform2D].
When [AnimatableBody2D] is moved, its linear and angular velocity are estimated and used to affect other physics bodies in its path. This makes it useful for moving platforms, doors, and other moving objects.
diff --git a/doc/classes/AnimatableBody3D.xml b/doc/classes/AnimatableBody3D.xml
index f1b3eb6ad85..f5c62174774 100644
--- a/doc/classes/AnimatableBody3D.xml
+++ b/doc/classes/AnimatableBody3D.xml
@@ -4,7 +4,7 @@
A 3D physics body that can't be moved by external forces. When moved manually, it affects other bodies in its path.
- An animatable 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform3D].
+ An animatable 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationMixer]s (with [member AnimationMixer.callback_mode_process] set to [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]), and [RemoteTransform3D].
When [AnimatableBody3D] is moved, its linear and angular velocity are estimated and used to affect other physics bodies in its path. This makes it useful for moving platforms, doors, and other moving objects.
diff --git a/doc/classes/AnimationMixer.xml b/doc/classes/AnimationMixer.xml
new file mode 100644
index 00000000000..4a489a3ef23
--- /dev/null
+++ b/doc/classes/AnimationMixer.xml
@@ -0,0 +1,342 @@
+
+
+
+ Base class for [AnimationPlayer] and [AnimationTree].
+
+
+ Base class for [AnimationPlayer] and [AnimationTree] to manage animation lists. It also has general properties and methods for playback and blending.
+ After instantiating the playback information data within the extended class, the blending is processed by the [AnimationMixer].
+
+
+
+
+
+
+
+
+
+
+
+
+ A virtual function for processing after key getting during playback.
+
+
+
+
+
+
+
+ Adds [param library] to the animation player, under the key [param name].
+
+
+
+
+
+
+ Manually advance the animations by the specified time (in seconds).
+
+
+
+
+
+ [AnimationMixer] caches animated nodes. It may not notice if a node disappears; [method clear_caches] forces it to update the cache again.
+
+
+
+
+
+
+ Returns the key of [param animation] or an empty [StringName] if not found.
+
+
+
+
+
+
+ Returns the key for the [AnimationLibrary] that contains [param animation] or an empty [StringName] if not found.
+
+
+
+
+
+
+ Returns the [Animation] with the key [param name]. If the animation does not exist, [code]null[/code] is returned and an error is logged.
+
+
+
+
+
+
+ Returns the first [AnimationLibrary] with key [param name] or [code]null[/code] if not found.
+ To get the [AnimationPlayer]'s global animation library, use [code]get_animation_library("")[/code].
+
+
+
+
+
+ Returns the list of stored library keys.
+
+
+
+
+
+ Returns the list of stored animation keys.
+
+
+
+
+
+ Retrieve the motion delta of position with the [member root_motion_track] as a [Vector3] that can be used elsewhere.
+ If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_POSITION_3D], returns [code]Vector3(0, 0, 0)[/code].
+ See also [member root_motion_track] and [RootMotionView].
+ The most basic example is applying position to [CharacterBody3D]:
+ [codeblocks]
+ [gdscript]
+ var current_rotation: Quaternion
+
+ func _process(delta):
+ if Input.is_action_just_pressed("animate"):
+ current_rotation = get_quaternion()
+ state_machine.travel("Animate")
+ var velocity: Vector3 = current_rotation * animation_tree.get_root_motion_position() / delta
+ set_velocity(velocity)
+ move_and_slide()
+ [/gdscript]
+ [/codeblocks]
+ By using this in combination with [method get_root_motion_position_accumulator], you can apply the root motion position more correctly to account for the rotation of the node.
+ [codeblocks]
+ [gdscript]
+ func _process(delta):
+ if Input.is_action_just_pressed("animate"):
+ state_machine.travel("Animate")
+ set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation())
+ var velocity: Vector3 = (animation_tree.get_root_motion_rotation_accumulator().inverse() * get_quaternion()) * animation_tree.get_root_motion_position() / delta
+ set_velocity(velocity)
+ move_and_slide()
+ [/gdscript]
+ [/codeblocks]
+
+
+
+
+
+ Retrieve the blended value of the position tracks with the [member root_motion_track] as a [Vector3] that can be used elsewhere.
+ This is useful in cases where you want to respect the initial key values of the animation.
+ For example, if an animation with only one key [code]Vector3(0, 0, 0)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(1, 0, 1)[/code] is played in the next frame, the difference can be calculated as follows:
+ [codeblocks]
+ [gdscript]
+ var prev_root_motion_position_accumulator: Vector3
+
+ func _process(delta):
+ if Input.is_action_just_pressed("animate"):
+ state_machine.travel("Animate")
+ var current_root_motion_position_accumulator: Vector3 = animation_tree.get_root_motion_position_accumulator()
+ var difference: Vector3 = current_root_motion_position_accumulator - prev_root_motion_position_accumulator
+ prev_root_motion_position_accumulator = current_root_motion_position_accumulator
+ transform.origin += difference
+ [/gdscript]
+ [/codeblocks]
+ However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases.
+
+
+
+
+
+ Retrieve the motion delta of rotation with the [member root_motion_track] as a [Quaternion] that can be used elsewhere.
+ If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_ROTATION_3D], returns [code]Quaternion(0, 0, 0, 1)[/code].
+ See also [member root_motion_track] and [RootMotionView].
+ The most basic example is applying rotation to [CharacterBody3D]:
+ [codeblocks]
+ [gdscript]
+ func _process(delta):
+ if Input.is_action_just_pressed("animate"):
+ state_machine.travel("Animate")
+ set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation())
+ [/gdscript]
+ [/codeblocks]
+
+
+
+
+
+ Retrieve the blended value of the rotation tracks with the [member root_motion_track] as a [Quaternion] that can be used elsewhere.
+ This is necessary to apply the root motion position correctly, taking rotation into account. See also [method get_root_motion_position].
+ Also, this is useful in cases where you want to respect the initial key values of the animation.
+ For example, if an animation with only one key [code]Quaternion(0, 0, 0, 1)[/code] is played in the previous frame and then an animation with only one key [code]Quaternion(0, 0.707, 0, 0.707)[/code] is played in the next frame, the difference can be calculated as follows:
+ [codeblocks]
+ [gdscript]
+ var prev_root_motion_rotation_accumulator: Quaternion
+
+ func _process(delta):
+ if Input.is_action_just_pressed("animate"):
+ state_machine.travel("Animate")
+ var current_root_motion_rotation_accumulator: Quaternion = animation_tree.get_root_motion_Quaternion_accumulator()
+ var difference: Quaternion = prev_root_motion_rotation_accumulator.inverse() * current_root_motion_rotation_accumulator
+ prev_root_motion_rotation_accumulator = current_root_motion_rotation_accumulator
+ transform.basis *= difference
+ [/gdscript]
+ [/codeblocks]
+ However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases.
+
+
+
+
+
+ Retrieve the motion delta of scale with the [member root_motion_track] as a [Vector3] that can be used elsewhere.
+ If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_SCALE_3D], returns [code]Vector3(0, 0, 0)[/code].
+ See also [member root_motion_track] and [RootMotionView].
+ The most basic example is applying scale to [CharacterBody3D]:
+ [codeblocks]
+ [gdscript]
+ var current_scale: Vector3 = Vector3(1, 1, 1)
+ var scale_accum: Vector3 = Vector3(1, 1, 1)
+
+ func _process(delta):
+ if Input.is_action_just_pressed("animate"):
+ current_scale = get_scale()
+ scale_accum = Vector3(1, 1, 1)
+ state_machine.travel("Animate")
+ scale_accum += animation_tree.get_root_motion_scale()
+ set_scale(current_scale * scale_accum)
+ [/gdscript]
+ [/codeblocks]
+
+
+
+
+
+ Retrieve the blended value of the scale tracks with the [member root_motion_track] as a [Vector3] that can be used elsewhere.
+ For example, if an animation with only one key [code]Vector3(1, 1, 1)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(2, 2, 2)[/code] is played in the next frame, the difference can be calculated as follows:
+ [codeblocks]
+ [gdscript]
+ var prev_root_motion_scale_accumulator: Vector3
+
+ func _process(delta):
+ if Input.is_action_just_pressed("animate"):
+ state_machine.travel("Animate")
+ var current_root_motion_scale_accumulator: Vector3 = animation_tree.get_root_motion_scale_accumulator()
+ var difference: Vector3 = current_root_motion_scale_accumulator - prev_root_motion_scale_accumulator
+ prev_root_motion_scale_accumulator = current_root_motion_scale_accumulator
+ transform.basis = transform.basis.scaled(difference)
+ [/gdscript]
+ [/codeblocks]
+ However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases.
+
+
+
+
+
+
+ Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [param name].
+
+
+
+
+
+
+ Returns [code]true[/code] if the [AnimationPlayer] stores an [AnimationLibrary] with key [param name].
+
+
+
+
+
+
+ Removes the [AnimationLibrary] associated with the key [param name].
+
+
+
+
+
+
+
+ Moves the [AnimationLibrary] associated with the key [param name] to the key [param newname].
+
+
+
+
+
+ If [code]true[/code], the [AnimationMixer] will be processing.
+
+
+ The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
+ For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
+
+
+ The call mode to use for Call Method tracks.
+
+
+ The process notification in which to update animations.
+
+
+ If [code]true[/code], the blending uses the deterministic algorithm. The total weight is not normalized and the result is accumulated with an initial value ([code]0[/code] or a [code]"RESET"[/code] animation if present).
+ This means that if the total amount of blending is [code]0.0[/code], the result is equal to the [code]"RESET"[/code] animation.
+ If the number of tracks between the blended animations is different, the animation with the missing track is treated as if it had the initial value.
+ If [code]false[/code], The blend does not use the deterministic algorithm. The total weight is normalized and always [code]1.0[/code]. If the number of tracks between the blended animations is different, nothing is done about the animation that is missing a track.
+ [b]Note:[/b] In [AnimationTree], the blending with [AnimationNodeAdd2], [AnimationNodeAdd3], [AnimationNodeSub2] or the weight greater than [code]1.0[/code] may produce unexpected results.
+ For example, if [AnimationNodeAdd2] blends two nodes with the amount [code]1.0[/code], then total weight is [code]2.0[/code] but it will be normalized to make the total amount [code]1.0[/code] and the result will be equal to [AnimationNodeBlend2] with the amount [code]0.5[/code].
+
+
+ This is used by the editor. If set to [code]true[/code], the scene will be saved with the effects of the reset animation (the animation with the key [code]"RESET"[/code]) applied as if it had been seeked to time 0, with the editor keeping the values that the scene had before saving.
+ This makes it more convenient to preview and edit animations in the editor, as changes to the scene will not be saved as long as they are set in the reset animation.
+
+
+ The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. To specify a track that controls properties or bones, append its name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code].
+ If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D] or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale] and [RootMotionView].
+
+
+ The node from which node path references will travel.
+
+
+
+
+
+
+ Notifies when an animation finished playing.
+ [b]Note:[/b] This signal is not emitted if an animation is looping.
+
+
+
+
+ Notifies when the animation libraries have changed.
+
+
+
+
+ Notifies when an animation list is changed.
+
+
+
+
+
+ Notifies when an animation starts playing.
+
+
+
+
+ Notifies when the caches have been cleared, either automatically, or manually via [method clear_caches].
+
+
+
+
+ Editor only. Notifies when the property have been updated to update dummy [AnimationPlayer] in animation player editor.
+
+
+
+
+
+ Process animation during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]). This is especially useful when animating physics bodies.
+
+
+ Process animation during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]).
+
+
+ Do not process animation. Use [method advance] to process the animation manually.
+
+
+ Batch method calls during the animation process, then do the calls after events are processed. This avoids bugs involving deleting nodes or modifying the AnimationPlayer while playing.
+
+
+ Make method calls immediately when reached in the animation.
+
+
+
diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml
index b7f384d0042..dda0187e8b2 100644
--- a/doc/classes/AnimationPlayer.xml
+++ b/doc/classes/AnimationPlayer.xml
@@ -1,5 +1,5 @@
-
+
A node used for animation playback.
@@ -15,51 +15,19 @@
https://godotengine.org/asset-library/asset/678
-
-
-
-
-
-
-
-
- A virtual function for processing after key getting during playback.
-
-
-
-
-
-
-
- Adds [param library] to the animation player, under the key [param name].
-
-
-
-
-
-
- Shifts position in the animation timeline and immediately updates the animation. [param delta] is the time in seconds to shift. Events between the current frame and [param delta] are handled.
-
-
-
+
- Returns the key of the animation which is queued to play after the [param anim_from] animation.
+ Returns the key of the animation which is queued to play after the [param animation_from] animation.
-
-
+
+
- Triggers the [param anim_to] animation when the [param anim_from] animation completes.
-
-
-
-
-
- [AnimationPlayer] caches animated nodes. It may not notice if a node disappears; [method clear_caches] forces it to update the cache again.
+ Triggers the [param animation_to] animation when the [param animation_from] animation completes.
@@ -68,55 +36,20 @@
Clears all queued, unplayed animations.
-
-
-
-
- Returns the key of [param animation] or an empty [StringName] if not found.
-
-
-
-
-
-
- Returns the key for the [AnimationLibrary] that contains [param animation] or an empty [StringName] if not found.
-
-
-
-
-
-
- Returns the [Animation] with the key [param name]. If the animation does not exist, [code]null[/code] is returned and an error is logged.
-
-
-
-
-
-
- Returns the first [AnimationLibrary] with key [param name] or [code]null[/code] if not found.
- To get the [AnimationPlayer]'s global animation library, use [code]get_animation_library("")[/code].
-
-
-
-
-
- Returns the list of stored library keys.
-
-
-
-
-
- Returns the list of stored animation keys.
-
-
-
-
+
+
Returns the blend time (in seconds) between two animations, referenced by their keys.
+
+
+
+ For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeMethod].
+
+
@@ -124,24 +57,22 @@
Returns a negative value if the current animation is playing backwards.
+
+
+
+ For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess].
+
+
Returns a list of the animation keys that are currently queued to play.
-
-
-
+
+
- Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [param name].
-
-
-
-
-
-
- Returns [code]true[/code] if the [AnimationPlayer] stores an [AnimationLibrary] with key [param name].
+ For backward compatibility. See [member AnimationMixer.root_node].
@@ -187,39 +118,47 @@
[b]Note:[/b] If a looped animation is currently playing, the queued animation will never play unless the looped animation is stopped somehow.
-
-
-
-
- Removes the [AnimationLibrary] associated with the key [param name].
-
-
-
-
-
-
-
- Moves the [AnimationLibrary] associated with the key [param name] to the key [param newname].
-
-
+
Seeks the animation to the [param seconds] point in time (in seconds). If [param update] is [code]true[/code], the animation updates too, otherwise it updates at process time. Events between the current frame and [param seconds] are skipped.
- [b]Note:[/b] Seeking to the end of the animation doesn't emit [signal animation_finished]. If you want to skip animation and emit the signal, use [method advance].
+ If [param update_only] is true, the method / audio / animation playback tracks will not be processed.
+ [b]Note:[/b] Seeking to the end of the animation doesn't emit [signal AnimationMixer.animation_finished]. If you want to skip animation and emit the signal, use [method AnimationMixer.advance].
-
-
+
+
Specifies a blend time (in seconds) between two animations, referenced by their keys.
+
+
+
+
+ For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeMethod].
+
+
+
+
+
+
+ For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess].
+
+
+
+
+
+
+ For backward compatibility. See [member AnimationMixer.root_node].
+
+
@@ -234,10 +173,6 @@
If playing, the current animation's key, otherwise, the animation last played. When set, this changes the animation, but will not play it unless already playing. See also [member current_animation].
-
- The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
- For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
-
The key of the animation to play when the scene loads.
@@ -251,29 +186,13 @@
The position (in seconds) of the currently playing animation.
-
- The call mode to use for Call Method tracks.
-
If [code]true[/code] and the engine is running in Movie Maker mode (see [MovieWriter]), exits the engine with [method SceneTree.quit] as soon as an animation is done playing in this [AnimationPlayer]. A message is printed when the engine quits for this reason.
- [b]Note:[/b] This obeys the same logic as the [signal animation_finished] signal, so it will not quit the engine if the animation is set to be looping.
-
-
- If [code]true[/code], updates animations in response to process-related notifications.
+ [b]Note:[/b] This obeys the same logic as the [signal AnimationMixer.animation_finished] signal, so it will not quit the engine if the animation is set to be looping.
The default time in which to blend animations. Ranges from 0 to 4096 with 0.01 precision.
-
- The process notification in which to update animations.
-
-
- This is used by the editor. If set to [code]true[/code], the scene will be saved with the effects of the reset animation (the animation with the key [code]"RESET"[/code]) applied as if it had been seeked to time 0, with the editor keeping the values that the scene had before saving.
- This makes it more convenient to preview and edit animations in the editor, as changes to the scene will not be saved as long as they are set in the reset animation.
-
-
- The node from which node path references will travel.
-
The speed scaling ratio. For example, if this value is [code]1[/code], then the animation plays at normal speed. If it's [code]0.5[/code], then it plays at half speed. If it's [code]2[/code], then it plays at double speed.
If set to a negative value, the animation is played in reverse. If set to [code]0[/code], the animation will not advance.
@@ -284,54 +203,32 @@
- Emitted when a queued animation plays after the previous animation finished. See [method queue].
- [b]Note:[/b] The signal is not emitted when the animation is changed via [method play] or by an [AnimationTree].
+ Emitted when a queued animation plays after the previous animation finished. See also [method AnimationPlayer.queue].
+ [b]Note:[/b] The signal is not emitted when the animation is changed via [method AnimationPlayer.play] or by an [AnimationTree].
-
-
+
+
- Notifies when an animation finished playing.
- [b]Note:[/b] This signal is not emitted if an animation is looping.
-
-
-
-
- Notifies when the animation libraries have changed.
-
-
-
-
- Notifies when an animation list is changed.
-
-
-
-
-
- Notifies when an animation starts playing.
-
-
-
-
- Notifies when the caches have been cleared, either automatically, or manually via [method clear_caches].
+ Emitted when [member current_animation] changes.
-
- Process animation during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]). This is especially useful when animating physics bodies.
+
+ For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS].
-
- Process animation during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]).
+
+ For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_IDLE].
-
- Do not process animation. Use [method advance] to process the animation manually.
+
+ For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_MANUAL].
-
- Batch method calls during the animation process, then do the calls after events are processed. This avoids bugs involving deleting nodes or modifying the AnimationPlayer while playing.
+
+ For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_METHOD_DEFERRED].
-
- Make method calls immediately when reached in the animation.
+
+ For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_METHOD_IMMEDIATE].
diff --git a/doc/classes/AnimationTree.xml b/doc/classes/AnimationTree.xml
index 88d2d8f5939..79e84192dfe 100644
--- a/doc/classes/AnimationTree.xml
+++ b/doc/classes/AnimationTree.xml
@@ -1,5 +1,5 @@
-
+
A node used for advanced animation transitions in an [AnimationPlayer].
@@ -12,220 +12,48 @@
https://godotengine.org/asset-library/asset/678
-
-
-
-
-
-
-
+
+
- A virtual function for processing after key getting during playback.
+ For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess].
-
+
-
+
- Manually advance the animations by the specified time (in seconds).
-
-
-
-
-
- Retrieve the motion delta of position with the [member root_motion_track] as a [Vector3] that can be used elsewhere.
- If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_POSITION_3D], returns [code]Vector3(0, 0, 0)[/code].
- See also [member root_motion_track] and [RootMotionView].
- The most basic example is applying position to [CharacterBody3D]:
- [codeblocks]
- [gdscript]
- var current_rotation: Quaternion
-
- func _process(delta):
- if Input.is_action_just_pressed("animate"):
- current_rotation = get_quaternion()
- state_machine.travel("Animate")
- var velocity: Vector3 = current_rotation * animation_tree.get_root_motion_position() / delta
- set_velocity(velocity)
- move_and_slide()
- [/gdscript]
- [/codeblocks]
- By using this in combination with [method get_root_motion_position_accumulator], you can apply the root motion position more correctly to account for the rotation of the node.
- [codeblocks]
- [gdscript]
- func _process(delta):
- if Input.is_action_just_pressed("animate"):
- state_machine.travel("Animate")
- set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation())
- var velocity: Vector3 = (animation_tree.get_root_motion_rotation_accumulator().inverse() * get_quaternion()) * animation_tree.get_root_motion_position() / delta
- set_velocity(velocity)
- move_and_slide()
- [/gdscript]
- [/codeblocks]
-
-
-
-
-
- Retrieve the blended value of the position tracks with the [member root_motion_track] as a [Vector3] that can be used elsewhere.
- This is useful in cases where you want to respect the initial key values of the animation.
- For example, if an animation with only one key [code]Vector3(0, 0, 0)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(1, 0, 1)[/code] is played in the next frame, the difference can be calculated as follows:
- [codeblocks]
- [gdscript]
- var prev_root_motion_position_accumulator: Vector3
-
- func _process(delta):
- if Input.is_action_just_pressed("animate"):
- state_machine.travel("Animate")
- var current_root_motion_position_accumulator: Vector3 = animation_tree.get_root_motion_position_accumulator()
- var difference: Vector3 = current_root_motion_position_accumulator - prev_root_motion_position_accumulator
- prev_root_motion_position_accumulator = current_root_motion_position_accumulator
- transform.origin += difference
- [/gdscript]
- [/codeblocks]
- However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases.
-
-
-
-
-
- Retrieve the motion delta of rotation with the [member root_motion_track] as a [Quaternion] that can be used elsewhere.
- If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_ROTATION_3D], returns [code]Quaternion(0, 0, 0, 1)[/code].
- See also [member root_motion_track] and [RootMotionView].
- The most basic example is applying rotation to [CharacterBody3D]:
- [codeblocks]
- [gdscript]
- func _process(delta):
- if Input.is_action_just_pressed("animate"):
- state_machine.travel("Animate")
- set_quaternion(get_quaternion() * animation_tree.get_root_motion_rotation())
- [/gdscript]
- [/codeblocks]
-
-
-
-
-
- Retrieve the blended value of the rotation tracks with the [member root_motion_track] as a [Quaternion] that can be used elsewhere.
- This is necessary to apply the root motion position correctly, taking rotation into account. See also [method get_root_motion_position].
- Also, this is useful in cases where you want to respect the initial key values of the animation.
- For example, if an animation with only one key [code]Quaternion(0, 0, 0, 1)[/code] is played in the previous frame and then an animation with only one key [code]Quaternion(0, 0.707, 0, 0.707)[/code] is played in the next frame, the difference can be calculated as follows:
- [codeblocks]
- [gdscript]
- var prev_root_motion_rotation_accumulator: Quaternion
-
- func _process(delta):
- if Input.is_action_just_pressed("animate"):
- state_machine.travel("Animate")
- var current_root_motion_rotation_accumulator: Quaternion = animation_tree.get_root_motion_Quaternion_accumulator()
- var difference: Quaternion = prev_root_motion_rotation_accumulator.inverse() * current_root_motion_rotation_accumulator
- prev_root_motion_rotation_accumulator = current_root_motion_rotation_accumulator
- transform.basis *= difference
- [/gdscript]
- [/codeblocks]
- However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases.
-
-
-
-
-
- Retrieve the motion delta of scale with the [member root_motion_track] as a [Vector3] that can be used elsewhere.
- If [member root_motion_track] is not a path to a track of type [constant Animation.TYPE_SCALE_3D], returns [code]Vector3(0, 0, 0)[/code].
- See also [member root_motion_track] and [RootMotionView].
- The most basic example is applying scale to [CharacterBody3D]:
- [codeblocks]
- [gdscript]
- var current_scale: Vector3 = Vector3(1, 1, 1)
- var scale_accum: Vector3 = Vector3(1, 1, 1)
-
- func _process(delta):
- if Input.is_action_just_pressed("animate"):
- current_scale = get_scale()
- scale_accum = Vector3(1, 1, 1)
- state_machine.travel("Animate")
- scale_accum += animation_tree.get_root_motion_scale()
- set_scale(current_scale * scale_accum)
- [/gdscript]
- [/codeblocks]
-
-
-
-
-
- Retrieve the blended value of the scale tracks with the [member root_motion_track] as a [Vector3] that can be used elsewhere.
- For example, if an animation with only one key [code]Vector3(1, 1, 1)[/code] is played in the previous frame and then an animation with only one key [code]Vector3(2, 2, 2)[/code] is played in the next frame, the difference can be calculated as follows:
- [codeblocks]
- [gdscript]
- var prev_root_motion_scale_accumulator: Vector3
-
- func _process(delta):
- if Input.is_action_just_pressed("animate"):
- state_machine.travel("Animate")
- var current_root_motion_scale_accumulator: Vector3 = animation_tree.get_root_motion_scale_accumulator()
- var difference: Vector3 = current_root_motion_scale_accumulator - prev_root_motion_scale_accumulator
- prev_root_motion_scale_accumulator = current_root_motion_scale_accumulator
- transform.basis = transform.basis.scaled(difference)
- [/gdscript]
- [/codeblocks]
- However, if the animation loops, an unintended discrete change may occur, so this is only useful for some simple use cases.
+ For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess].
-
- If [code]true[/code], the [AnimationTree] will be processing.
-
- The path to the [Node] used to evaluate the AnimationNode [Expression] if one is not explicitly specified internally.
+ The path to the [Node] used to evaluate the [AnimationNode] [Expression] if one is not explicitly specified internally.
The path to the [AnimationPlayer] used for animating.
-
- The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
- For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
-
-
- The process mode of this [AnimationTree]. See [enum AnimationProcessCallback] for available modes.
-
-
- The path to the Animation track used for root motion. Paths must be valid scene-tree paths to a node, and must be specified starting from the parent node of the node that will reproduce the animation. To specify a track that controls properties or bones, append its name after the path, separated by [code]":"[/code]. For example, [code]"character/skeleton:ankle"[/code] or [code]"character/mesh:transform/local"[/code].
- If the track has type [constant Animation.TYPE_POSITION_3D], [constant Animation.TYPE_ROTATION_3D] or [constant Animation.TYPE_SCALE_3D] the transformation will be canceled visually, and the animation will appear to stay in place. See also [method get_root_motion_position], [method get_root_motion_rotation], [method get_root_motion_scale] and [RootMotionView].
-
-
- The root animation node of this [AnimationTree]. See [AnimationNode].
+
+
+ The root animation node of this [AnimationTree]. See [AnimationRootNode].
-
-
-
- Notifies when an animation finished playing.
- [b]Note:[/b] This signal is not emitted if an animation is looping or aborted. Also be aware of the possibility of unseen playback by sync and xfade.
-
-
Emitted when the [member anim_player] is changed.
-
-
-
- Notifies when an animation starts playing.
- [b]Note:[/b] This signal is not emitted if an animation is looping or playbacked from the middle. Also be aware of the possibility of unseen playback by sync and xfade.
-
-
-
- The animations will progress during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]).
+
+ For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS].
-
- The animations will progress during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]).
+
+ For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_IDLE].
-
- The animations will only progress manually (see [method advance]).
+
+ For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_MANUAL].
diff --git a/doc/classes/RootMotionView.xml b/doc/classes/RootMotionView.xml
index 20485ed08f8..648ea04a7dd 100644
--- a/doc/classes/RootMotionView.xml
+++ b/doc/classes/RootMotionView.xml
@@ -1,10 +1,10 @@
- Editor-only helper for setting up root motion in [AnimationTree].
+ Editor-only helper for setting up root motion in [AnimationMixer].
- [i]Root motion[/i] refers to an animation technique where a mesh's skeleton is used to give impulse to a character. When working with 3D animations, a popular technique is for animators to use the root skeleton bone to give motion to the rest of the skeleton. This allows animating characters in a way where steps actually match the floor below. It also allows precise interaction with objects during cinematics. See also [AnimationTree].
+ [i]Root motion[/i] refers to an animation technique where a mesh's skeleton is used to give impulse to a character. When working with 3D animations, a popular technique is for animators to use the root skeleton bone to give motion to the rest of the skeleton. This allows animating characters in a way where steps actually match the floor below. It also allows precise interaction with objects during cinematics. See also [AnimationMixer].
[b]Note:[/b] [RootMotionView] is only visible in the editor. It will be hidden automatically in the running project.
@@ -12,7 +12,7 @@
- Path to an [AnimationTree] node to use as a basis for root motion.
+ Path to an [AnimationMixer] node to use as a basis for root motion.
The grid's cell size in 3D units.
diff --git a/doc/classes/StaticBody2D.xml b/doc/classes/StaticBody2D.xml
index ac297545081..e732d4d58fa 100644
--- a/doc/classes/StaticBody2D.xml
+++ b/doc/classes/StaticBody2D.xml
@@ -4,7 +4,7 @@
A 2D physics body that can't be moved by external forces. When moved manually, it doesn't affect other bodies in its path.
- A static 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform2D].
+ A static 2D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationMixer]s (with [member AnimationMixer.callback_mode_process] set to [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]), and [RemoteTransform2D].
When [StaticBody2D] is moved, it is teleported to its new position without affecting other physics bodies in its path. If this is not desired, use [AnimatableBody2D] instead.
[StaticBody2D] is useful for completely static objects like floors and walls, as well as moving surfaces like conveyor belts and circular revolving platforms (by using [member constant_linear_velocity] and [member constant_angular_velocity]).
diff --git a/doc/classes/StaticBody3D.xml b/doc/classes/StaticBody3D.xml
index c2fd5e03bdd..81eda8505b8 100644
--- a/doc/classes/StaticBody3D.xml
+++ b/doc/classes/StaticBody3D.xml
@@ -4,7 +4,7 @@
A 3D physics body that can't be moved by external forces. When moved manually, it doesn't affect other bodies in its path.
- A static 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationPlayer]s (with [member AnimationPlayer.playback_process_mode] set to [constant AnimationPlayer.ANIMATION_PROCESS_PHYSICS]), and [RemoteTransform3D].
+ A static 3D physics body. It can't be moved by external forces or contacts, but can be moved manually by other means such as code, [AnimationMixer]s (with [member AnimationMixer.callback_mode_process] set to [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS]), and [RemoteTransform3D].
When [StaticBody3D] is moved, it is teleported to its new position without affecting other physics bodies in its path. If this is not desired, use [AnimatableBody3D] instead.
[StaticBody3D] is useful for completely static objects like floors and walls, as well as moving surfaces like conveyor belts and circular revolving platforms (by using [member constant_linear_velocity] and [member constant_angular_velocity]).
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 4f6e7c4b91d..7105ff280a3 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -2695,7 +2695,7 @@ void AnimationTrackEdit::gui_input(const Ref &p_event) {
AnimationPlayer *ap = ape->get_player();
if (ap) {
NodePath npath = animation->track_get_path(track);
- Node *nd = ap->get_node(ap->get_root())->get_node(NodePath(npath.get_concatenated_names()));
+ Node *nd = ap->get_node(ap->get_root_node())->get_node(NodePath(npath.get_concatenated_names()));
StringName prop = npath.get_concatenated_subnames();
PropertyInfo prop_info;
ClassDB::get_property_info(nd->get_class(), prop, &prop_info);
@@ -4000,13 +4000,15 @@ Ref AnimationTrackEditor::_create_and_get_reset_animation() {
return player->get_animation(SceneStringNames::get_singleton()->RESET);
} else {
Ref al;
- if (!player->has_animation_library("")) {
- al.instantiate();
- player->add_animation_library("", al);
- } else {
- al = player->get_animation_library("");
+ AnimationMixer *mixer = AnimationPlayerEditor::get_singleton()->fetch_mixer_for_library();
+ if (mixer) {
+ if (!mixer->has_animation_library("")) {
+ al.instantiate();
+ mixer->add_animation_library("", al);
+ } else {
+ al = mixer->get_animation_library("");
+ }
}
-
Ref reset_anim;
reset_anim.instantiate();
reset_anim->set_length(ANIM_MIN_LENGTH);
@@ -4293,6 +4295,14 @@ void AnimationTrackEditor::show_select_node_warning(bool p_show) {
info_message->set_visible(p_show);
}
+void AnimationTrackEditor::show_dummy_player_warning(bool p_show) {
+ dummy_player_warning->set_visible(p_show);
+}
+
+void AnimationTrackEditor::show_inactive_player_warning(bool p_show) {
+ inactive_player_warning->set_visible(p_show);
+}
+
bool AnimationTrackEditor::is_key_selected(int p_track, int p_key) const {
SelectedKey sk;
sk.key = p_key;
@@ -4626,6 +4636,8 @@ void AnimationTrackEditor::_notification(int p_what) {
view_group->set_icon(get_editor_theme_icon(view_group->is_pressed() ? SNAME("AnimationTrackList") : SNAME("AnimationTrackGroup")));
selected_filter->set_icon(get_editor_theme_icon(SNAME("AnimationFilter")));
imported_anim_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
+ dummy_player_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
+ inactive_player_warning->set_icon(get_editor_theme_icon(SNAME("NodeWarning")));
main_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
edit->get_popup()->set_item_icon(edit->get_popup()->get_item_index(EDIT_APPLY_RESET), get_editor_theme_icon(SNAME("Reload")));
} break;
@@ -6303,8 +6315,17 @@ float AnimationTrackEditor::snap_time(float p_value, bool p_relative) {
void AnimationTrackEditor::_show_imported_anim_warning() {
// It looks terrible on a single line but the TTR extractor doesn't support line breaks yet.
EditorNode::get_singleton()->show_warning(
- TTR("This animation belongs to an imported scene, so changes to imported tracks will not be saved.\n\nTo modify this animation, navigate to the scene's Advanced Import settings and select the animation.\nSome options, including looping, are available here. To add custom tracks, enable \"Save To File\" and\n\"Keep Custom Tracks\"."),
- TTR("Warning: Editing imported animation"));
+ TTR("This animation belongs to an imported scene, so changes to imported tracks will not be saved.\n\nTo modify this animation, navigate to the scene's Advanced Import settings and select the animation.\nSome options, including looping, are available here. To add custom tracks, enable \"Save To File\" and\n\"Keep Custom Tracks\"."));
+}
+
+void AnimationTrackEditor::_show_dummy_player_warning() {
+ EditorNode::get_singleton()->show_warning(
+ TTR("Some AnimationPlayerEditor's options are disabled since this is the dummy AnimationPlayer for preview.\n\nThe dummy player is forced active, non-deterministic and doesn't have the root motion track. Furthermore, the original node is inactive temporary."));
+}
+
+void AnimationTrackEditor::_show_inactive_player_warning() {
+ EditorNode::get_singleton()->show_warning(
+ TTR("AnimationPlayer is inactive. The playback will not be processed."));
}
void AnimationTrackEditor::_select_all_tracks_for_copy() {
@@ -6489,6 +6510,20 @@ AnimationTrackEditor::AnimationTrackEditor() {
imported_anim_warning->connect("pressed", callable_mp(this, &AnimationTrackEditor::_show_imported_anim_warning));
bottom_hb->add_child(imported_anim_warning);
+ dummy_player_warning = memnew(Button);
+ dummy_player_warning->hide();
+ dummy_player_warning->set_text(TTR("Dummy Player"));
+ dummy_player_warning->set_tooltip_text(TTR("Warning: Editing dummy AnimationPlayer"));
+ dummy_player_warning->connect("pressed", callable_mp(this, &AnimationTrackEditor::_show_dummy_player_warning));
+ bottom_hb->add_child(dummy_player_warning);
+
+ inactive_player_warning = memnew(Button);
+ inactive_player_warning->hide();
+ inactive_player_warning->set_text(TTR("Inactive Player"));
+ inactive_player_warning->set_tooltip_text(TTR("Warning: AnimationPlayer is inactive"));
+ inactive_player_warning->connect("pressed", callable_mp(this, &AnimationTrackEditor::_show_inactive_player_warning));
+ bottom_hb->add_child(inactive_player_warning);
+
bottom_hb->add_spacer();
bezier_edit_icon = memnew(Button);
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index cef726f6c01..53270995172 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -387,6 +387,12 @@ class AnimationTrackEditor : public VBoxContainer {
Button *imported_anim_warning = nullptr;
void _show_imported_anim_warning();
+ Button *dummy_player_warning = nullptr;
+ void _show_dummy_player_warning();
+
+ Button *inactive_player_warning = nullptr;
+ void _show_inactive_player_warning();
+
void _snap_mode_changed(int p_mode);
Vector track_edits;
Vector groups;
@@ -645,6 +651,8 @@ public:
void commit_insert_queue();
void show_select_node_warning(bool p_show);
+ void show_dummy_player_warning(bool p_show);
+ void show_inactive_player_warning(bool p_show);
bool is_key_selected(int p_track, int p_key) const;
bool is_selection_active() const;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index b2763ab1732..e69dcb22781 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -1701,16 +1701,19 @@ int EditorNode::_save_external_resources() {
return saved;
}
-static void _reset_animation_players(Node *p_node, List[> *r_anim_backups) {
+static void _reset_animation_mixers(Node *p_node, List>> *r_anim_backups) {
for (int i = 0; i < p_node->get_child_count(); i++) {
- AnimationPlayer *player = Object::cast_to(p_node->get_child(i));
- if (player && player->is_reset_on_save_enabled() && player->can_apply_reset()) {
- Ref old_values = player->apply_reset();
- if (old_values.is_valid()) {
- r_anim_backups->push_back(old_values);
+ AnimationMixer *mixer = Object::cast_to(p_node->get_child(i));
+ if (mixer && mixer->is_reset_on_save_enabled() && mixer->can_apply_reset()) {
+ Ref backup = mixer->apply_reset();
+ if (backup.is_valid()) {
+ Pair> pair;
+ pair.first = mixer;
+ pair.second = backup;
+ r_anim_backups->push_back(pair);
}
}
- _reset_animation_players(p_node->get_child(i), r_anim_backups);
+ _reset_animation_mixers(p_node->get_child(i), r_anim_backups);
}
}
@@ -1730,8 +1733,8 @@ void EditorNode::_save_scene(String p_file, int idx) {
scene->propagate_notification(NOTIFICATION_EDITOR_PRE_SAVE);
editor_data.apply_changes_in_editors();
- List][> anim_backups;
- _reset_animation_players(scene, &anim_backups);
+ List>> anim_backups;
+ _reset_animation_mixers(scene, &anim_backups);
save_default_environment();
_save_editor_states(p_file, idx);
@@ -1773,8 +1776,8 @@ void EditorNode::_save_scene(String p_file, int idx) {
_save_external_resources();
editor_data.save_editor_external_data();
- for (Ref &E : anim_backups) {
- E->restore();
+ for (Pair> &E : anim_backups) {
+ E.first->restore(E.second);
}
if (err == OK) {
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index fff46904454..26fdb74bd1f 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -115,7 +115,7 @@ void SceneTreeEditor::_cell_button_pressed(Object *p_item, int p_column, int p_i
}
undo_redo->commit_action();
} else if (p_id == BUTTON_PIN) {
- if (n->is_class("AnimationPlayer")) {
+ if (n->is_class("AnimationMixer")) {
AnimationPlayerEditor::get_singleton()->unpin();
_update_tree();
}
@@ -465,8 +465,8 @@ void SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent) {
}
_update_visibility_color(p_node, item);
- } else if (p_node->is_class("AnimationPlayer")) {
- bool is_pinned = AnimationPlayerEditor::get_singleton()->get_player() == p_node && AnimationPlayerEditor::get_singleton()->is_pinned();
+ } else if (p_node->is_class("AnimationMixer")) {
+ bool is_pinned = AnimationPlayerEditor::get_singleton()->get_editing_node() == p_node && AnimationPlayerEditor::get_singleton()->is_pinned();
if (is_pinned) {
item->add_button(0, get_editor_theme_icon(SNAME("Pin")), BUTTON_PIN, false, TTR("AnimationPlayer is pinned.\nClick to unpin."));
diff --git a/editor/icons/AnimationMixer.svg b/editor/icons/AnimationMixer.svg
new file mode 100644
index 00000000000..61f5bf6474f
--- /dev/null
+++ b/editor/icons/AnimationMixer.svg
@@ -0,0 +1 @@
+
diff --git a/editor/import/post_import_plugin_skeleton_renamer.cpp b/editor/import/post_import_plugin_skeleton_renamer.cpp
index 59e07a71171..0c48c8ef76e 100644
--- a/editor/import/post_import_plugin_skeleton_renamer.cpp
+++ b/editor/import/post_import_plugin_skeleton_renamer.cpp
@@ -102,7 +102,7 @@ void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p
continue;
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
- Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
if (node) {
Skeleton3D *track_skeleton = Object::cast_to(node);
if (track_skeleton && track_skeleton == skeleton) {
@@ -219,8 +219,8 @@ void PostImportPluginSkeletonRenamer::internal_process(InternalImportCategory p_
int track_len = anim->get_track_count();
for (int i = 0; i < track_len; i++) {
String track_path = String(anim->track_get_path(i).get_concatenated_names());
- Node *orig_node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
- Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ Node *orig_node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
+ Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
while (node) {
Skeleton3D *track_skeleton = Object::cast_to(node);
if (track_skeleton && track_skeleton == skeleton) {
diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
index bffc100faf1..cd31499b800 100644
--- a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
+++ b/editor/import/post_import_plugin_skeleton_rest_fixer.cpp
@@ -146,7 +146,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
- Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to(node);
@@ -213,7 +213,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
continue;
}
track_path = String(anim->track_get_path(i).get_concatenated_names());
- Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
if (node) {
Skeleton3D *track_skeleton = Object::cast_to(node);
if (track_skeleton && track_skeleton == src_skeleton) {
@@ -388,7 +388,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
- Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to(node);
@@ -448,7 +448,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
- Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to(node);
@@ -544,7 +544,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
}
String track_path = String(anim->track_get_path(i).get_concatenated_names());
- Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node);
Skeleton3D *track_skeleton = Object::cast_to(node);
diff --git a/editor/import/post_import_plugin_skeleton_track_organizer.cpp b/editor/import/post_import_plugin_skeleton_track_organizer.cpp
index 829d97dea7d..e5a8e879fce 100644
--- a/editor/import/post_import_plugin_skeleton_track_organizer.cpp
+++ b/editor/import/post_import_plugin_skeleton_track_organizer.cpp
@@ -78,7 +78,7 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate
Vector remove_indices;
for (int i = 0; i < track_len; i++) {
String track_path = String(anim->track_get_path(i).get_concatenated_names());
- Node *node = (ap->get_node(ap->get_root()))->get_node(NodePath(track_path));
+ Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
if (!node) {
if (remove_except_bone) {
remove_indices.push_back(i);
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index 452a6127537..2f889287f93 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -595,7 +595,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap(p_node);
// Node paths in animation tracks are relative to the following path (this is used to fix node paths below).
- Node *ap_root = ap->get_node(ap->get_root());
+ Node *ap_root = ap->get_node(ap->get_root_node());
NodePath path_prefix = p_root->get_path_to(ap_root);
bool nodes_were_renamed = r_node_renames.size() != 0;
diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp
index 89c21ddb682..184cc5302a7 100644
--- a/editor/plugins/animation_blend_space_1d_editor.cpp
+++ b/editor/plugins/animation_blend_space_1d_editor.cpp
@@ -78,18 +78,12 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Refadd_submenu_item(TTR("Add Animation"), "animations");
- if (tree->has_node(tree->get_animation_player())) {
- AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player()));
+ List names;
+ tree->get_animation_list(&names);
- if (ap) {
- List names;
- ap->get_animation_list(&names);
-
- for (const StringName &E : names) {
- animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E);
- animations_to_add.push_back(E);
- }
- }
+ for (const StringName &E : names) {
+ animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E);
+ animations_to_add.push_back(E);
}
for (const StringName &E : classes) {
diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp
index 3bc5e0c8071..c987d7d6b69 100644
--- a/editor/plugins/animation_blend_space_2d_editor.cpp
+++ b/editor/plugins/animation_blend_space_2d_editor.cpp
@@ -123,16 +123,11 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Refadd_submenu_item(TTR("Add Animation"), "animations");
- if (tree->has_node(tree->get_animation_player())) {
- AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player()));
- if (ap) {
- List names;
- ap->get_animation_list(&names);
- for (const StringName &E : names) {
- animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E);
- animations_to_add.push_back(E);
- }
- }
+ List names;
+ tree->get_animation_list(&names);
+ for (const StringName &E : names) {
+ animations_menu->add_icon_item(get_editor_theme_icon(SNAME("Animation")), E);
+ animations_to_add.push_back(E);
}
for (const StringName &E : classes) {
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
index 157278e4bc0..dc3729393f0 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -238,21 +238,16 @@ void AnimationNodeBlendTreeEditor::update_graph() {
ProgressBar *pb = memnew(ProgressBar);
- if (tree->has_node(tree->get_animation_player())) {
- AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player()));
- if (ap) {
- List anims;
- ap->get_animation_list(&anims);
+ List anims;
+ tree->get_animation_list(&anims);
- for (const StringName &F : anims) {
- mb->get_popup()->add_item(F);
- options.push_back(F);
- }
+ for (const StringName &F : anims) {
+ mb->get_popup()->add_item(F);
+ options.push_back(F);
+ }
- if (ap->has_animation(anim->get_animation())) {
- pb->set_max(ap->get_animation(anim->get_animation())->get_length());
- }
- }
+ if (tree->has_animation(anim->get_animation())) {
+ pb->set_max(tree->get_animation(anim->get_animation())->get_length());
}
pb->set_show_percentage(false);
@@ -625,21 +620,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref &ano
return false;
}
- NodePath player_path = tree->get_animation_player();
-
- if (!tree->has_node(player_path)) {
- EditorNode::get_singleton()->show_warning(TTR("No animation player set, so unable to retrieve track names."));
- return false;
- }
-
- AnimationPlayer *player = Object::cast_to(tree->get_node(player_path));
- if (!player) {
- EditorNode::get_singleton()->show_warning(TTR("Player path set is invalid, so unable to retrieve track names."));
- return false;
- }
-
- Node *base = player->get_node(player->get_root());
-
+ Node *base = tree->get_node(tree->get_root_node());
if (!base) {
EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
return false;
@@ -651,10 +632,10 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref &ano
HashMap> types;
{
List animation_list;
- player->get_animation_list(&animation_list);
+ tree->get_animation_list(&animation_list);
for (const StringName &E : animation_list) {
- Ref anim = player->get_animation(E);
+ Ref anim = tree->get_animation(E);
for (int i = 0; i < anim->get_track_count(); i++) {
String track_path = anim->track_get_path(i);
paths.insert(track_path);
@@ -885,23 +866,16 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) {
graph->set_connection_activity(E.output_node, 0, E.input_node, E.input_index, activity);
}
- AnimationPlayer *player = nullptr;
- if (tree->has_node(tree->get_animation_player())) {
- player = Object::cast_to(tree->get_node(tree->get_animation_player()));
- }
-
- if (player) {
- for (const KeyValue &E : animations) {
- Ref an = blend_tree->get_node(E.key);
- if (an.is_valid()) {
- if (player->has_animation(an->get_animation())) {
- Ref anim = player->get_animation(an->get_animation());
- if (anim.is_valid()) {
- E.value->set_max(anim->get_length());
- //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
- StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time";
- E.value->set_value(tree->get(time_path));
- }
+ for (const KeyValue &E : animations) {
+ Ref an = blend_tree->get_node(E.key);
+ if (an.is_valid()) {
+ if (tree->has_animation(an->get_animation())) {
+ Ref anim = tree->get_animation(an->get_animation());
+ if (anim.is_valid()) {
+ E.value->set_max(anim->get_length());
+ //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node;
+ StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time";
+ E.value->set_value(tree->get(time_path));
}
}
}
diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp
index 6a54bc654fd..c4c0799daad 100644
--- a/editor/plugins/animation_library_editor.cpp
+++ b/editor/plugins/animation_library_editor.cpp
@@ -36,8 +36,8 @@
#include "editor/editor_undo_redo_manager.h"
#include "editor/gui/editor_file_dialog.h"
-void AnimationLibraryEditor::set_animation_player(Object *p_player) {
- player = p_player;
+void AnimationLibraryEditor::set_animation_mixer(Object *p_mixer) {
+ mixer = p_mixer;
}
void AnimationLibraryEditor::_add_library() {
@@ -54,7 +54,7 @@ void AnimationLibraryEditor::_add_library_validate(const String &p_name) {
String error;
if (adding_animation) {
- Ref al = player->call("get_animation_library", adding_animation_to_library);
+ Ref al = mixer->call("get_animation_library", adding_animation_to_library);
ERR_FAIL_COND(al.is_null());
if (p_name == "") {
error = TTR("Animation name can't be empty.");
@@ -64,11 +64,11 @@ void AnimationLibraryEditor::_add_library_validate(const String &p_name) {
error = TTR("Animation with the same name already exists.");
}
} else {
- if (p_name == "" && bool(player->call("has_animation_library", ""))) {
+ if (p_name == "" && bool(mixer->call("has_animation_library", ""))) {
error = TTR("Enter a library name.");
} else if (!AnimationLibrary::is_valid_library_name(p_name)) {
error = TTR("Library name contains invalid characters: '/', ':', ',' or '['.");
- } else if (bool(player->call("has_animation_library", p_name))) {
+ } else if (bool(mixer->call("has_animation_library", p_name))) {
error = TTR("Library with the same name already exists.");
}
}
@@ -97,7 +97,7 @@ void AnimationLibraryEditor::_add_library_confirm() {
String anim_name = add_library_name->get_text();
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
- Ref al = player->call("get_animation_library", adding_animation_to_library);
+ Ref al = mixer->call("get_animation_library", adding_animation_to_library);
ERR_FAIL_COND(!al.is_valid());
Ref anim;
@@ -106,8 +106,8 @@ void AnimationLibraryEditor::_add_library_confirm() {
undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), anim_name));
undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, anim);
undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
} else {
@@ -118,10 +118,10 @@ void AnimationLibraryEditor::_add_library_confirm() {
al.instantiate();
undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), lib_name));
- undo_redo->add_do_method(player, "add_animation_library", lib_name, al);
- undo_redo->add_undo_method(player, "remove_animation_library", lib_name);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(mixer, "add_animation_library", lib_name, al);
+ undo_redo->add_undo_method(mixer, "remove_animation_library", lib_name);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
}
}
@@ -144,7 +144,7 @@ void AnimationLibraryEditor::_load_library() {
}
void AnimationLibraryEditor::_file_popup_selected(int p_id) {
- Ref al = player->call("get_animation_library", file_dialog_library);
+ Ref al = mixer->call("get_animation_library", file_dialog_library);
Ref anim;
if (file_dialog_animation != StringName()) {
anim = al->get_animation(file_dialog_animation);
@@ -214,12 +214,12 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(vformat(TTR("Make Animation Library Unique: %s"), lib_name));
- undo_redo->add_do_method(player, "remove_animation_library", lib_name);
- undo_redo->add_do_method(player, "add_animation_library", lib_name, ald);
- undo_redo->add_undo_method(player, "remove_animation_library", lib_name);
- undo_redo->add_undo_method(player, "add_animation_library", lib_name, al);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(mixer, "remove_animation_library", lib_name);
+ undo_redo->add_do_method(mixer, "add_animation_library", lib_name, ald);
+ undo_redo->add_undo_method(mixer, "remove_animation_library", lib_name);
+ undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
update_tree();
@@ -287,8 +287,8 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) {
undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, animd);
undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name);
undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
update_tree();
@@ -308,12 +308,12 @@ void AnimationLibraryEditor::_load_file(String p_path) {
return;
}
- TypedArray libs = player->call("get_animation_library_list");
+ TypedArray libs = mixer->call("get_animation_library_list");
for (int i = 0; i < libs.size(); i++) {
const StringName K = libs[i];
- Ref al2 = player->call("get_animation_library", K);
+ Ref al2 = mixer->call("get_animation_library", K);
if (al2 == al) {
- error_dialog->set_text(TTR("This library is already added to the player."));
+ error_dialog->set_text(TTR("This library is already added to the mixer."));
error_dialog->popup_centered();
return;
@@ -324,7 +324,7 @@ void AnimationLibraryEditor::_load_file(String p_path) {
int attempt = 1;
- while (bool(player->call("has_animation_library", name))) {
+ while (bool(mixer->call("has_animation_library", name))) {
attempt++;
name = p_path.get_file().get_basename() + " " + itos(attempt);
}
@@ -332,10 +332,10 @@ void AnimationLibraryEditor::_load_file(String p_path) {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), name));
- undo_redo->add_do_method(player, "add_animation_library", name, al);
- undo_redo->add_undo_method(player, "remove_animation_library", name);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(mixer, "add_animation_library", name, al);
+ undo_redo->add_undo_method(mixer, "remove_animation_library", name);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
} break;
case FILE_DIALOG_ACTION_OPEN_ANIMATION: {
@@ -346,7 +346,7 @@ void AnimationLibraryEditor::_load_file(String p_path) {
return;
}
- Ref al = player->call("get_animation_library", adding_animation_to_library);
+ Ref al = mixer->call("get_animation_library", adding_animation_to_library);
List anims;
al->get_animation_list(&anims);
for (const StringName &K : anims) {
@@ -372,13 +372,13 @@ void AnimationLibraryEditor::_load_file(String p_path) {
undo_redo->create_action(vformat(TTR("Load Animation into Library: %s"), name));
undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
} break;
case FILE_DIALOG_ACTION_SAVE_LIBRARY: {
- Ref al = player->call("get_animation_library", file_dialog_library);
+ Ref al = mixer->call("get_animation_library", file_dialog_library);
String prev_path = al->get_path();
EditorNode::get_singleton()->save_resource_in_path(al, p_path);
@@ -388,14 +388,14 @@ void AnimationLibraryEditor::_load_file(String p_path) {
undo_redo->create_action(vformat(TTR("Save Animation library to File: %s"), file_dialog_library));
undo_redo->add_do_method(al.ptr(), "set_path", al->get_path());
undo_redo->add_undo_method(al.ptr(), "set_path", prev_path);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
}
} break;
case FILE_DIALOG_ACTION_SAVE_ANIMATION: {
- Ref al = player->call("get_animation_library", file_dialog_library);
+ Ref al = mixer->call("get_animation_library", file_dialog_library);
Ref anim;
if (file_dialog_animation != StringName()) {
anim = al->get_animation(file_dialog_animation);
@@ -409,8 +409,8 @@ void AnimationLibraryEditor::_load_file(String p_path) {
undo_redo->create_action(vformat(TTR("Save Animation to File: %s"), file_dialog_animation));
undo_redo->add_do_method(anim.ptr(), "set_path", anim->get_path());
undo_redo->add_undo_method(anim.ptr(), "set_path", prev_path);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
}
} break;
@@ -430,14 +430,14 @@ void AnimationLibraryEditor::_item_renamed() {
if (ti->get_parent() == tree->get_root()) {
// Renamed library
- if (player->call("has_animation_library", text)) {
+ if (mixer->call("has_animation_library", text)) {
restore_text = true;
} else {
undo_redo->create_action(vformat(TTR("Rename Animation Library: %s"), text));
- undo_redo->add_do_method(player, "rename_animation_library", old_text, text);
- undo_redo->add_undo_method(player, "rename_animation_library", text, old_text);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(mixer, "rename_animation_library", old_text, text);
+ undo_redo->add_undo_method(mixer, "rename_animation_library", text, old_text);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
updating = true;
undo_redo->commit_action();
updating = false;
@@ -451,7 +451,7 @@ void AnimationLibraryEditor::_item_renamed() {
} else {
// Renamed anim
StringName library = ti->get_parent()->get_metadata(0);
- Ref al = player->call("get_animation_library", library);
+ Ref al = mixer->call("get_animation_library", library);
if (al.is_valid()) {
if (al->has_animation(text)) {
@@ -460,8 +460,8 @@ void AnimationLibraryEditor::_item_renamed() {
undo_redo->create_action(vformat(TTR("Rename Animation: %s"), text));
undo_redo->add_do_method(al.ptr(), "rename_animation", old_text, text);
undo_redo->add_undo_method(al.ptr(), "rename_animation", text, old_text);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
updating = true;
undo_redo->commit_action();
updating = false;
@@ -483,7 +483,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
if (p_item->get_parent() == tree->get_root()) {
// Library
StringName lib_name = p_item->get_metadata(0);
- Ref al = player->call("get_animation_library", lib_name);
+ Ref al = mixer->call("get_animation_library", lib_name);
switch (p_id) {
case LIB_BUTTON_ADD: {
add_library_dialog->set_title(TTR("Animation Name:"));
@@ -541,8 +541,8 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), name));
undo_redo->add_do_method(al.ptr(), "add_animation", name, anim);
undo_redo->add_undo_method(al.ptr(), "remove_animation", name);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
} break;
@@ -564,10 +564,10 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
case LIB_BUTTON_DELETE: {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(vformat(TTR("Remove Animation Library: %s"), lib_name));
- undo_redo->add_do_method(player, "remove_animation_library", lib_name);
- undo_redo->add_undo_method(player, "add_animation_library", lib_name, al);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(mixer, "remove_animation_library", lib_name);
+ undo_redo->add_undo_method(mixer, "add_animation_library", lib_name, al);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
} break;
}
@@ -576,7 +576,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
// Animation
StringName lib_name = p_item->get_parent()->get_metadata(0);
StringName anim_name = p_item->get_metadata(0);
- Ref al = player->call("get_animation_library", lib_name);
+ Ref al = mixer->call("get_animation_library", lib_name);
Ref anim = al->get_animation(anim_name);
ERR_FAIL_COND(!anim.is_valid());
switch (p_id) {
@@ -607,8 +607,8 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int
undo_redo->create_action(vformat(TTR("Remove Animation from Library: %s"), anim_name));
undo_redo->add_do_method(al.ptr(), "remove_animation", anim_name);
undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim);
- undo_redo->add_do_method(this, "_update_editor", player);
- undo_redo->add_undo_method(this, "_update_editor", player);
+ undo_redo->add_do_method(this, "_update_editor", mixer);
+ undo_redo->add_undo_method(this, "_update_editor", mixer);
undo_redo->commit_action();
} break;
}
@@ -621,12 +621,12 @@ void AnimationLibraryEditor::update_tree() {
}
tree->clear();
- ERR_FAIL_NULL(player);
+ ERR_FAIL_NULL(mixer);
Color ss_color = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
TreeItem *root = tree->create_item();
- TypedArray libs = player->call("get_animation_library_list");
+ TypedArray libs = mixer->call("get_animation_library_list");
for (int i = 0; i < libs.size(); i++) {
const StringName K = libs[i];
@@ -638,7 +638,7 @@ void AnimationLibraryEditor::update_tree() {
libitem->set_suffix(0, "");
}
- Ref al = player->call("get_animation_library", K);
+ Ref al = mixer->call("get_animation_library", K);
bool animation_library_is_foreign = false;
String al_path = al->get_path();
if (!al_path.is_resource_file()) {
@@ -727,12 +727,12 @@ void AnimationLibraryEditor::show_dialog() {
popup_centered_ratio(0.5);
}
-void AnimationLibraryEditor::_update_editor(Object *p_player) {
- emit_signal("update_editor", p_player);
+void AnimationLibraryEditor::_update_editor(Object *p_mixer) {
+ emit_signal("update_editor", p_mixer);
}
void AnimationLibraryEditor::_bind_methods() {
- ClassDB::bind_method(D_METHOD("_update_editor", "player"), &AnimationLibraryEditor::_update_editor);
+ ClassDB::bind_method(D_METHOD("_update_editor", "mixer"), &AnimationLibraryEditor::_update_editor);
ADD_SIGNAL(MethodInfo("update_editor"));
}
diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h
index a9b2c72b9dc..49a5c786d32 100644
--- a/editor/plugins/animation_library_editor.h
+++ b/editor/plugins/animation_library_editor.h
@@ -33,7 +33,7 @@
#include "editor/animation_track_editor.h"
#include "editor/editor_plugin.h"
-#include "scene/animation/animation_player.h"
+#include "scene/animation/animation_mixer.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/tree.h"
@@ -90,7 +90,7 @@ class AnimationLibraryEditor : public AcceptDialog {
Tree *tree = nullptr;
- Object *player = nullptr;
+ Object *mixer = nullptr;
void _add_library();
void _add_library_validate(const String &p_name);
@@ -106,11 +106,11 @@ class AnimationLibraryEditor : public AcceptDialog {
bool updating = false;
protected:
- void _update_editor(Object *p_player);
+ void _update_editor(Object *p_mixer);
static void _bind_methods();
public:
- void set_animation_player(Object *p_player);
+ void set_animation_mixer(Object *p_mixer);
void show_dialog();
void update_tree();
AnimationLibraryEditor();
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 51d195e0e1b..f2225fabadf 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -44,6 +44,7 @@
#include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning.
#include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning.
#include "editor/scene_tree_dock.h"
+#include "scene/animation/animation_tree.h"
#include "scene/gui/separator.h"
#include "scene/main/window.h"
#include "scene/resources/animation.h"
@@ -54,7 +55,11 @@
///////////////////////////////////
void AnimationPlayerEditor::_node_removed(Node *p_node) {
- if (player && player == p_node) {
+ if (player && original_node == p_node) {
+ if (is_dummy) {
+ plugin->_clear_dummy_player();
+ }
+
player = nullptr;
set_process(false);
@@ -63,12 +68,20 @@ void AnimationPlayerEditor::_node_removed(Node *p_node) {
track_editor->set_root(nullptr);
track_editor->show_select_node_warning(true);
_update_player();
+
+ _ensure_dummy_player();
}
}
void AnimationPlayerEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_PROCESS: {
+ if (!player || is_dummy) {
+ track_editor->show_inactive_player_warning(false);
+ } else {
+ track_editor->show_inactive_player_warning(!player->is_active());
+ }
+
if (!player) {
return;
}
@@ -103,13 +116,13 @@ void AnimationPlayerEditor::_notification(int p_what) {
} break;
case NOTIFICATION_ENTER_TREE: {
- tool_anim->get_popup()->connect("id_pressed", callable_mp(this, &AnimationPlayerEditor::_animation_tool_menu));
+ tool_anim->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &AnimationPlayerEditor::_animation_tool_menu));
- onion_skinning->get_popup()->connect("id_pressed", callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu));
+ onion_skinning->get_popup()->connect(SNAME("id_pressed"), callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu));
- blend_editor.next->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_blend_editor_next_changed));
+ blend_editor.next->connect(SNAME("item_selected"), callable_mp(this, &AnimationPlayerEditor::_blend_editor_next_changed));
- get_tree()->connect("node_removed", callable_mp(this, &AnimationPlayerEditor::_node_removed));
+ get_tree()->connect(SNAME("node_removed"), callable_mp(this, &AnimationPlayerEditor::_node_removed));
add_theme_style_override("panel", EditorNode::get_singleton()->get_editor_theme()->get_stylebox(SNAME("panel"), SNAME("Panel")));
} break;
@@ -167,6 +180,10 @@ void AnimationPlayerEditor::_notification(int p_what) {
_update_animation_list_icons();
} break;
+
+ case NOTIFICATION_VISIBILITY_CHANGED: {
+ _ensure_dummy_player();
+ } break;
}
}
@@ -303,20 +320,19 @@ void AnimationPlayerEditor::_animation_selected(int p_which) {
bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim);
track_editor->set_animation(anim, animation_library_is_foreign);
- Node *root = player->get_node(player->get_root());
+ Node *root = player->get_node_or_null(player->get_root_node());
if (root) {
track_editor->set_root(root);
}
}
frame->set_max((double)anim->get_length());
-
+ autoplay->set_pressed(current == player->get_autoplay());
} else {
track_editor->set_animation(Ref(), true);
track_editor->set_root(nullptr);
+ autoplay->set_pressed(false);
}
- autoplay->set_pressed(current == player->get_autoplay());
-
AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying();
_animation_key_editor_seek(timeline_position, false);
@@ -506,6 +522,9 @@ void AnimationPlayerEditor::_animation_name_edited() {
undo_redo->add_undo_method(this, "_animation_player_changed", player);
undo_redo->commit_action();
+ if (is_dummy) {
+ plugin->_update_dummy_player(original_node);
+ }
_select_anim_by_name(new_library_prefix + new_name);
} break;
@@ -534,7 +553,7 @@ void AnimationPlayerEditor::_animation_name_edited() {
if (al.is_null()) {
al.instantiate();
lib_added = true;
- undo_redo->add_do_method(player, "add_animation_library", "", al);
+ undo_redo->add_do_method(fetch_mixer_for_library(), "add_animation_library", "", al);
library_name = "";
}
@@ -547,13 +566,17 @@ void AnimationPlayerEditor::_animation_name_edited() {
undo_redo->add_undo_method(this, "_stop_onion_skinning");
}
if (lib_added) {
- undo_redo->add_undo_method(player, "remove_animation_library", "");
+ undo_redo->add_undo_method(fetch_mixer_for_library(), "remove_animation_library", "");
}
undo_redo->commit_action();
if (library_name != "") {
library_name = library_name + "/";
}
+
+ if (is_dummy) {
+ plugin->_update_dummy_player(original_node);
+ }
_select_anim_by_name(library_name + new_name);
} break;
@@ -602,6 +625,10 @@ void AnimationPlayerEditor::_animation_name_edited() {
if (library_name != "") {
library_name = library_name + "/";
}
+
+ if (is_dummy) {
+ plugin->_update_dummy_player(original_node);
+ }
_select_anim_by_name(library_name + new_name);
} break;
}
@@ -714,7 +741,7 @@ void AnimationPlayerEditor::_blend_edited() {
}
void AnimationPlayerEditor::ensure_visibility() {
- if (player && pin->is_pressed()) {
+ if (player) {
return; // another player is pinned, don't reset
}
@@ -724,11 +751,13 @@ void AnimationPlayerEditor::ensure_visibility() {
Dictionary AnimationPlayerEditor::get_state() const {
Dictionary d;
- d["visible"] = is_visible_in_tree();
- if (EditorNode::get_singleton()->get_edited_scene() && is_visible_in_tree() && player) {
- d["player"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(player);
- d["animation"] = player->get_assigned_animation();
- d["track_editor_state"] = track_editor->get_state();
+ if (!is_dummy) {
+ d["visible"] = is_visible_in_tree();
+ if (EditorNode::get_singleton()->get_edited_scene() && is_visible_in_tree() && player) {
+ d["player"] = EditorNode::get_singleton()->get_edited_scene()->get_path_to(player);
+ d["animation"] = player->get_assigned_animation();
+ d["track_editor_state"] = track_editor->get_state();
+ }
}
return d;
@@ -746,14 +775,20 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) {
Node *n = EditorNode::get_singleton()->get_edited_scene()->get_node(p_state["player"]);
if (Object::cast_to(n) && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) {
if (player) {
- if (player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
- player->disconnect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
+ if (player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
+ player->disconnect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
+ }
+ if (player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
+ player->disconnect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed));
}
}
player = Object::cast_to(n);
if (player) {
- if (!player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
- player->connect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
+ if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
+ player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated), CONNECT_DEFERRED);
+ }
+ if (!player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
+ player->connect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed), CONNECT_DEFERRED);
}
}
@@ -794,7 +829,7 @@ void AnimationPlayerEditor::_animation_edit() {
track_editor->set_animation(anim, animation_library_is_foreign);
- Node *root = player->get_node(player->get_root());
+ Node *root = player->get_node_or_null(player->get_root_node());
if (root) {
track_editor->set_root(root);
}
@@ -935,7 +970,7 @@ void AnimationPlayerEditor::_update_player() {
bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim);
track_editor->set_animation(anim, animation_library_is_foreign);
- Node *root = player->get_node(player->get_root());
+ Node *root = player->get_node_or_null(player->get_root_node());
if (root) {
track_editor->set_root(root);
}
@@ -1007,21 +1042,69 @@ void AnimationPlayerEditor::_update_name_dialog_library_dropdown() {
}
}
-void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
+void AnimationPlayerEditor::_ensure_dummy_player() {
+ bool dummy_exists = is_dummy && player && original_node;
+ if (dummy_exists) {
+ if (is_visible()) {
+ player->set_active(true);
+ original_node->set_editing(true);
+ } else {
+ player->set_active(false);
+ original_node->set_editing(false);
+ }
+ }
+
+ // Make some options disabled.
+ tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(TOOL_EDIT_TRANSITIONS), dummy_exists);
+ onion_toggle->set_disabled(dummy_exists);
+ onion_skinning->set_disabled(dummy_exists);
+ int selected = animation->get_selected();
+ autoplay->set_disabled(selected != -1 ? (animation->get_item_text(selected).is_empty() ? true : dummy_exists) : true);
+
+ // Show warning.
+ if (track_editor) {
+ track_editor->show_dummy_player_warning(dummy_exists);
+ }
+}
+
+void AnimationPlayerEditor::edit(AnimationMixer *p_node, AnimationPlayer *p_player, bool p_is_dummy) {
if (player && pin->is_pressed()) {
return; // Ignore, pinned.
}
if (player) {
- if (player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
- player->disconnect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
+ if (player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
+ player->disconnect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
+ }
+ if (player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
+ player->disconnect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed));
}
}
+
+ AnimationTree *tree = Object::cast_to(p_node);
+
+ if (tree) {
+ if (tree->is_connected(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin))) {
+ tree->disconnect(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin));
+ }
+ }
+
+ original_node = p_node;
player = p_player;
+ is_dummy = p_is_dummy;
+
+ if (tree) {
+ if (!tree->is_connected(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin))) {
+ tree->connect(SNAME("animation_player_changed"), callable_mp(this, &AnimationPlayerEditor::unpin));
+ }
+ }
if (player) {
- if (!player->is_connected("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
- player->connect("animation_libraries_updated", callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated));
+ if (!player->is_connected(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated))) {
+ player->connect(SNAME("animation_list_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_libraries_updated), CONNECT_DEFERRED);
+ }
+ if (!player->is_connected(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed))) {
+ player->connect(SNAME("current_animation_changed"), callable_mp(this, &AnimationPlayerEditor::_current_animation_changed));
}
_update_player();
@@ -1042,7 +1125,9 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) {
track_editor->show_select_node_warning(true);
}
- library_editor->set_animation_player(player);
+ library_editor->set_animation_mixer(fetch_mixer_for_library());
+
+ _ensure_dummy_player();
}
void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) {
@@ -1181,12 +1266,12 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool
if (!p_timeline_only) {
if (player->is_valid() && !p_set) {
- float cpos = player->get_current_animation_position();
-
- player->seek_delta(pos, pos - cpos);
+ double delta = pos - player->get_current_animation_position();
+ player->seek(pos, true, true);
+ player->seek(pos + delta, true, true);
} else {
player->stop();
- player->seek(pos, true);
+ player->seek(pos, true, true);
}
}
@@ -1194,14 +1279,12 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool
};
void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) {
- if (player == p_pl && is_visible_in_tree()) {
- _update_player();
- if (blend_editor.dialog->is_visible()) {
- _animation_blend(); // Update.
- }
- if (library_editor->is_visible()) {
- library_editor->update_tree();
- }
+ _update_player();
+ if (blend_editor.dialog->is_visible()) {
+ _animation_blend(); // Update.
+ }
+ if (library_editor->is_visible()) {
+ library_editor->update_tree();
}
}
@@ -1215,6 +1298,25 @@ void AnimationPlayerEditor::_list_changed() {
}
}
+void AnimationPlayerEditor::_current_animation_changed(const String &p_name) {
+ if (is_visible_in_tree()) {
+ if (p_name.is_empty()) {
+ // Means [stop].
+ frame->set_value(0);
+ track_editor->set_anim_pos(0);
+ _update_animation();
+ return;
+ }
+ Ref anim = player->get_animation(p_name);
+ if (anim.is_null()) {
+ return;
+ }
+ bool animation_library_is_foreign = EditorNode::get_singleton()->is_resource_read_only(anim);
+ track_editor->set_animation(anim, animation_library_is_foreign);
+ _update_animation();
+ }
+}
+
void AnimationPlayerEditor::_animation_key_editor_anim_len_changed(float p_len) {
frame->set_max(p_len);
}
@@ -1257,7 +1359,7 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
_animation_new();
} break;
case TOOL_ANIM_LIBRARY: {
- library_editor->set_animation_player(player);
+ library_editor->set_animation_mixer(fetch_mixer_for_library());
library_editor->show_dialog();
} break;
case TOOL_DUPLICATE_ANIM: {
@@ -1267,6 +1369,9 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) {
_animation_rename();
} break;
case TOOL_EDIT_TRANSITIONS: {
+ if (is_dummy) {
+ break;
+ }
_animation_blend();
} break;
case TOOL_REMOVE_ANIM: {
@@ -1506,7 +1611,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
}
// Backup current animation state.
- Ref values_backup = player->backup_animated_values();
+ Ref backup_current = player->make_backup();
float cpos = player->get_current_animation_position();
// Render every past/future step with the capture shader.
@@ -1536,7 +1641,6 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
if (valid) {
player->seek(pos, true);
get_tree()->flush_transform_notifications(); // Needed for transforms of Node3Ds.
- values_backup->update_skeletons(); // Needed for Skeletons (2D & 3D).
RS::get_singleton()->viewport_set_active(onion.captures[cidx], true);
RS::get_singleton()->viewport_set_parent_viewport(root_vp, onion.captures[cidx]);
@@ -1556,7 +1660,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
// (Seeking with update=true wouldn't do the trick because the current value of the properties
// may not match their value for the current point in the animation).
player->seek(cpos, false);
- values_backup->restore();
+ player->restore(backup_current);
// Restore state of main editors.
if (Node3DEditor::get_singleton()->is_visible()) {
@@ -1574,14 +1678,14 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() {
void AnimationPlayerEditor::_start_onion_skinning() {
// FIXME: Using "process_frame" makes onion layers update one frame behind the current.
- if (!get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
- get_tree()->connect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
+ if (!get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
+ get_tree()->connect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
}
}
void AnimationPlayerEditor::_stop_onion_skinning() {
- if (get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
- get_tree()->disconnect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
+ if (get_tree()->is_connected(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) {
+ get_tree()->disconnect(SNAME("process_frame"), callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred));
_free_onion_layers();
@@ -1595,6 +1699,21 @@ void AnimationPlayerEditor::_pin_pressed() {
SceneTreeDock::get_singleton()->get_tree_editor()->update_tree();
}
+AnimationMixer *AnimationPlayerEditor::fetch_mixer_for_library() const {
+ if (!original_node) {
+ return nullptr;
+ }
+ // Does AnimationTree have AnimationPlayer?
+ if (original_node->is_class("AnimationTree")) {
+ AnimationTree *src_tree = Object::cast_to(original_node);
+ Node *src_player = src_tree->get_node_or_null(src_tree->get_animation_player());
+ if (src_player) {
+ return Object::cast_to(src_player);
+ }
+ }
+ return original_node;
+}
+
bool AnimationPlayerEditor::_validate_tracks(const Ref p_anim) {
bool is_valid = true;
if (!p_anim.is_valid()) {
@@ -1668,6 +1787,10 @@ AnimationPlayer *AnimationPlayerEditor::get_player() const {
return player;
}
+AnimationMixer *AnimationPlayerEditor::get_editing_node() const {
+ return original_node;
+}
+
AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin) {
plugin = p_plugin;
singleton = this;
@@ -1724,7 +1847,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
delete_dialog = memnew(ConfirmationDialog);
add_child(delete_dialog);
- delete_dialog->connect("confirmed", callable_mp(this, &AnimationPlayerEditor::_animation_remove_confirmed));
+ delete_dialog->connect(SNAME("confirmed"), callable_mp(this, &AnimationPlayerEditor::_animation_remove_confirmed));
tool_anim = memnew(MenuButton);
tool_anim->set_shortcut_context(this);
@@ -1769,7 +1892,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
onion_toggle->set_theme_type_variation("FlatButton");
onion_toggle->set_toggle_mode(true);
onion_toggle->set_tooltip_text(TTR("Enable Onion Skinning"));
- onion_toggle->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu).bind(ONION_SKINNING_ENABLE));
+ onion_toggle->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_onion_skinning_menu).bind(ONION_SKINNING_ENABLE));
hb->add_child(onion_toggle);
onion_skinning = memnew(MenuButton);
@@ -1808,7 +1931,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
pin->set_toggle_mode(true);
pin->set_tooltip_text(TTR("Pin AnimationPlayer"));
hb->add_child(pin);
- pin->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_pin_pressed));
+ pin->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_pin_pressed));
file = memnew(EditorFileDialog);
add_child(file);
@@ -1838,7 +1961,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
error_dialog->set_title(TTR("Error!"));
add_child(error_dialog);
- name_dialog->connect("confirmed", callable_mp(this, &AnimationPlayerEditor::_animation_name_edited));
+ name_dialog->connect(SNAME("confirmed"), callable_mp(this, &AnimationPlayerEditor::_animation_name_edited));
blend_editor.dialog = memnew(AcceptDialog);
add_child(blend_editor.dialog);
@@ -1855,20 +1978,20 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
blend_editor.dialog->set_title(TTR("Cross-Animation Blend Times"));
updating_blends = false;
- blend_editor.tree->connect("item_edited", callable_mp(this, &AnimationPlayerEditor::_blend_edited));
+ blend_editor.tree->connect(SNAME("item_edited"), callable_mp(this, &AnimationPlayerEditor::_blend_edited));
- autoplay->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_autoplay_pressed));
+ autoplay->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_autoplay_pressed));
autoplay->set_toggle_mode(true);
- play->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_pressed));
- play_from->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_from_pressed));
- play_bw->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_bw_pressed));
- play_bw_from->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_play_bw_from_pressed));
- stop->connect("pressed", callable_mp(this, &AnimationPlayerEditor::_stop_pressed));
+ play->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_play_pressed));
+ play_from->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_play_from_pressed));
+ play_bw->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_play_bw_pressed));
+ play_bw_from->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_play_bw_from_pressed));
+ stop->connect(SNAME("pressed"), callable_mp(this, &AnimationPlayerEditor::_stop_pressed));
- animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected));
+ animation->connect(SNAME("item_selected"), callable_mp(this, &AnimationPlayerEditor::_animation_selected));
- frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(true, false));
- scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed));
+ frame->connect(SNAME("value_changed"), callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(true, false));
+ scale->connect(SNAME("text_submitted"), callable_mp(this, &AnimationPlayerEditor::_scale_changed));
last_active = false;
timeline_position = 0;
@@ -1877,18 +2000,18 @@ AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plug
add_child(track_editor);
track_editor->set_v_size_flags(SIZE_EXPAND_FILL);
- track_editor->connect("timeline_changed", callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_seek));
- track_editor->connect("animation_len_changed", callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_anim_len_changed));
+ track_editor->connect(SNAME("timeline_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_seek));
+ track_editor->connect(SNAME("animation_len_changed"), callable_mp(this, &AnimationPlayerEditor::_animation_key_editor_anim_len_changed));
_update_player();
library_editor = memnew(AnimationLibraryEditor);
add_child(library_editor);
- library_editor->connect("update_editor", callable_mp(this, &AnimationPlayerEditor::_animation_player_changed));
+ library_editor->connect(SNAME("update_editor"), callable_mp(this, &AnimationPlayerEditor::_animation_player_changed));
// Onion skinning.
- track_editor->connect("visibility_changed", callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
+ track_editor->connect(SNAME("visibility_changed"), callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed));
onion.enabled = false;
onion.past = true;
@@ -1943,10 +2066,10 @@ AnimationPlayerEditor::~AnimationPlayerEditor() {
void AnimationPlayerEditorPlugin::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- Node3DEditor::get_singleton()->connect("transform_key_request", callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request));
- InspectorDock::get_inspector_singleton()->connect("property_keyed", callable_mp(this, &AnimationPlayerEditorPlugin::_property_keyed));
- anim_editor->get_track_editor()->connect("keying_changed", callable_mp(this, &AnimationPlayerEditorPlugin::_update_keying));
- InspectorDock::get_inspector_singleton()->connect("edited_object_changed", callable_mp(anim_editor->get_track_editor(), &AnimationTrackEditor::update_keying));
+ Node3DEditor::get_singleton()->connect(SNAME("transform_key_request"), callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request));
+ InspectorDock::get_inspector_singleton()->connect(SNAME("property_keyed"), callable_mp(this, &AnimationPlayerEditorPlugin::_property_keyed));
+ anim_editor->get_track_editor()->connect(SNAME("keying_changed"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_keying));
+ InspectorDock::get_inspector_singleton()->connect(SNAME("edited_object_changed"), callable_mp(anim_editor->get_track_editor(), &AnimationTrackEditor::update_keying));
set_force_draw_over_forwarding_enabled();
} break;
}
@@ -1979,14 +2102,88 @@ void AnimationPlayerEditorPlugin::_update_keying() {
}
void AnimationPlayerEditorPlugin::edit(Object *p_object) {
+ if (player && anim_editor && anim_editor->is_pinned()) {
+ return; // Ignore, pinned.
+ }
+
+ player = nullptr;
if (!p_object) {
return;
}
- anim_editor->edit(Object::cast_to(p_object));
+ last_mixer = p_object->get_instance_id();
+
+ AnimationMixer *src_node = Object::cast_to(p_object);
+ bool is_dummy = false;
+ if (!p_object->is_class("AnimationPlayer")) {
+ // If it needs dummy AnimationPlayer, assign original AnimationMixer to LibraryEditor.
+ _update_dummy_player(src_node);
+
+ is_dummy = true;
+
+ if (!src_node->is_connected(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) {
+ src_node->connect(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player).bind(src_node), CONNECT_DEFERRED);
+ }
+ if (!src_node->is_connected(SNAME("animation_libraries_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) {
+ src_node->connect(SNAME("animation_libraries_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player).bind(src_node), CONNECT_DEFERRED);
+ }
+ } else {
+ _clear_dummy_player();
+ player = Object::cast_to(p_object);
+ }
+ player->set_dummy(is_dummy);
+
+ anim_editor->edit(src_node, player, is_dummy);
+}
+
+void AnimationPlayerEditorPlugin::_clear_dummy_player() {
+ if (!dummy_player) {
+ return;
+ }
+ Node *parent = dummy_player->get_parent();
+ if (parent) {
+ parent->call_deferred("remove_child", dummy_player);
+ }
+ dummy_player->queue_free();
+ dummy_player = nullptr;
+}
+
+void AnimationPlayerEditorPlugin::_update_dummy_player(AnimationMixer *p_mixer) {
+ // Check current editing object.
+ if (p_mixer->get_instance_id() != last_mixer && p_mixer->is_connected(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player))) {
+ p_mixer->disconnect(SNAME("mixer_updated"), callable_mp(this, &AnimationPlayerEditorPlugin::_update_dummy_player));
+ return;
+ }
+
+ // Add dummy player to scene.
+ if (!dummy_player) {
+ Node *parent = p_mixer->get_parent();
+ ERR_FAIL_NULL(parent);
+ dummy_player = memnew(AnimationPlayer);
+ parent->add_child(dummy_player);
+ }
+ player = dummy_player;
+
+ // Convert AnimationTree (AnimationMixer) to AnimationPlayer.
+ AnimationMixer *default_node = memnew(AnimationMixer);
+ List pinfo;
+ default_node->get_property_list(&pinfo);
+ for (const PropertyInfo &E : pinfo) {
+ if (!(E.usage & PROPERTY_USAGE_STORAGE)) {
+ continue;
+ }
+ if (E.name != "script" && E.name != "active" && E.name != "deterministic" && E.name != "root_motion_track") {
+ dummy_player->set(E.name, p_mixer->get(E.name));
+ }
+ }
+ memdelete(default_node);
+
+ if (anim_editor) {
+ anim_editor->_update_player();
+ }
}
bool AnimationPlayerEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("AnimationPlayer");
+ return p_object->is_class("AnimationPlayer") || p_object->is_class("AnimationTree") || p_object->is_class("AnimationMixer");
}
void AnimationPlayerEditorPlugin::make_visible(bool p_visible) {
@@ -2003,6 +2200,12 @@ AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() {
}
AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() {
+ if (dummy_player) {
+ memdelete(dummy_player);
+ }
+ if (player) {
+ memdelete(player);
+ }
}
// AnimationTrackKeyEditEditorPlugin
diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h
index 8c46b5c36e6..4763a008fe2 100644
--- a/editor/plugins/animation_player_editor_plugin.h
+++ b/editor/plugins/animation_player_editor_plugin.h
@@ -47,8 +47,12 @@ class ImageTexture;
class AnimationPlayerEditor : public VBoxContainer {
GDCLASS(AnimationPlayerEditor, VBoxContainer);
+ friend AnimationPlayerEditorPlugin;
+
AnimationPlayerEditorPlugin *plugin = nullptr;
- AnimationPlayer *player = nullptr;
+ AnimationMixer *original_node = nullptr; // For pinned mark in SceneTree.
+ AnimationPlayer *player = nullptr; // For AnimationPlayerEditor, could be dummy.
+ bool is_dummy = false;
enum {
TOOL_NEW_ANIM,
@@ -189,6 +193,7 @@ class AnimationPlayerEditor : public VBoxContainer {
void _blend_editor_next_changed(const int p_idx);
void _list_changed();
+ void _current_animation_changed(const String &p_name);
void _update_animation();
void _update_player();
void _update_animation_list_icons();
@@ -220,6 +225,8 @@ class AnimationPlayerEditor : public VBoxContainer {
void _pin_pressed();
String _get_current() const;
+ void _ensure_dummy_player();
+
~AnimationPlayerEditor();
protected:
@@ -228,19 +235,24 @@ protected:
static void _bind_methods();
public:
+ AnimationMixer *get_editing_node() const;
AnimationPlayer *get_player() const;
+ AnimationMixer *fetch_mixer_for_library() const;
static AnimationPlayerEditor *get_singleton() { return singleton; }
bool is_pinned() const { return pin->is_pressed(); }
- void unpin() { pin->set_pressed(false); }
+ void unpin() {
+ pin->set_pressed(false);
+ _pin_pressed();
+ }
AnimationTrackEditor *get_track_editor() { return track_editor; }
Dictionary get_state() const;
void set_state(const Dictionary &p_state);
void ensure_visibility();
- void edit(AnimationPlayer *p_player);
+ void edit(AnimationMixer *p_node, AnimationPlayer *p_player, bool p_is_dummy);
void forward_force_draw_over_viewport(Control *p_overlay);
AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin);
@@ -249,7 +261,15 @@ public:
class AnimationPlayerEditorPlugin : public EditorPlugin {
GDCLASS(AnimationPlayerEditorPlugin, EditorPlugin);
+ friend AnimationPlayerEditor;
+
AnimationPlayerEditor *anim_editor = nullptr;
+ AnimationPlayer *player = nullptr;
+ AnimationPlayer *dummy_player = nullptr;
+ ObjectID last_mixer;
+
+ void _update_dummy_player(AnimationMixer *p_mixer);
+ void _clear_dummy_player();
protected:
void _notification(int p_what);
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index f25f6ccd04a..746a901a0a1 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -92,7 +92,7 @@ String AnimationNodeStateMachineEditor::_get_root_playback_path(String &r_node_d
if (edited_path.size()) {
while (!is_playable_anodesm_found) {
base_path = String("/").join(edited_path);
- Ref anodesm = !edited_path.size() ? tree->get_tree_root() : tree->get_tree_root()->find_node_by_path(base_path);
+ Ref anodesm = !edited_path.size() ? Ref(tree->get_root_animation_node().ptr()) : tree->get_root_animation_node()->find_node_by_path(base_path);
if (!anodesm.is_valid()) {
break;
} else {
@@ -562,13 +562,7 @@ void AnimationNodeStateMachineEditor::_open_menu(const Vector2 &p_position) {
animations_to_add.clear();
List animation_names;
- if (tree->has_node(tree->get_animation_player())) {
- AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player()));
- if (ap) {
- ap->get_animation_list(&animation_names);
- }
- }
-
+ tree->get_animation_list(&animation_names);
menu->add_submenu_item(TTR("Add Animation"), "animations");
if (animation_names.is_empty()) {
menu->set_item_disabled(menu->get_item_idx_from_text(TTR("Add Animation")), true);
diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp
index 7b5f8aa7f77..1c642f909a7 100644
--- a/editor/plugins/animation_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_tree_editor_plugin.cpp
@@ -50,16 +50,16 @@
#include "scene/scene_string_names.h"
void AnimationTreeEditor::edit(AnimationTree *p_tree) {
- if (p_tree && !p_tree->is_connected("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) {
- p_tree->connect("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed), CONNECT_DEFERRED);
+ if (p_tree && !p_tree->is_connected("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) {
+ p_tree->connect("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed), CONNECT_DEFERRED);
}
if (tree == p_tree) {
return;
}
- if (tree && tree->is_connected("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) {
- tree->disconnect("animation_player_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed));
+ if (tree && tree->is_connected("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed))) {
+ tree->disconnect("animation_list_changed", callable_mp(this, &AnimationTreeEditor::_animation_list_changed));
}
tree = p_tree;
@@ -122,7 +122,7 @@ void AnimationTreeEditor::_update_path() {
void AnimationTreeEditor::edit_path(const Vector &p_path) {
button_path.clear();
- Ref node = tree->get_tree_root();
+ Ref node = tree->get_root_animation_node();
if (node.is_valid()) {
current_root = node->get_instance_id();
@@ -185,8 +185,8 @@ void AnimationTreeEditor::_notification(int p_what) {
} break;
case NOTIFICATION_PROCESS: {
ObjectID root;
- if (tree && tree->get_tree_root().is_valid()) {
- root = tree->get_tree_root()->get_instance_id();
+ if (tree && tree->get_root_animation_node().is_valid()) {
+ root = tree->get_root_animation_node()->get_instance_id();
}
if (root != current_root) {
@@ -247,18 +247,12 @@ Vector AnimationTreeEditor::get_animation_list() {
}
AnimationTree *tree = singleton->tree;
- if (!tree || !tree->has_node(tree->get_animation_player())) {
- return Vector();
- }
-
- AnimationPlayer *ap = Object::cast_to(tree->get_node(tree->get_animation_player()));
-
- if (!ap) {
+ if (!tree) {
return Vector();
}
List anims;
- ap->get_animation_list(&anims);
+ tree->get_animation_list(&anims);
Vector ret;
for (const StringName &E : anims) {
ret.push_back(E);
diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp
index 10b0b214aeb..bc6507155ad 100644
--- a/editor/plugins/root_motion_editor_plugin.cpp
+++ b/editor/plugins/root_motion_editor_plugin.cpp
@@ -29,9 +29,10 @@
/**************************************************************************/
#include "root_motion_editor_plugin.h"
+
#include "editor/editor_node.h"
-#include "scene/animation/animation_player.h"
-#include "scene/animation/animation_tree.h"
+#include "editor/editor_scale.h"
+#include "scene/animation/animation_mixer.h"
#include "scene/gui/button.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/tree.h"
@@ -50,31 +51,26 @@ void EditorPropertyRootMotion::_confirmed() {
}
void EditorPropertyRootMotion::_node_assign() {
- AnimationTree *atree = Object::cast_to(get_edited_object());
- if (!atree->has_node(atree->get_animation_player())) {
- EditorNode::get_singleton()->show_warning(TTR("AnimationTree has no path set to an AnimationPlayer"));
- return;
- }
- AnimationPlayer *player = Object::cast_to(atree->get_node(atree->get_animation_player()));
- if (!player) {
- EditorNode::get_singleton()->show_warning(TTR("Path to AnimationPlayer is invalid"));
+ AnimationMixer *mixer = Object::cast_to(get_edited_object());
+ if (!mixer) {
+ EditorNode::get_singleton()->show_warning(TTR("Path to AnimationMixer is invalid"));
return;
}
- Node *base = player->get_node(player->get_root());
+ Node *base = mixer->get_node(mixer->get_root_node());
if (!base) {
- EditorNode::get_singleton()->show_warning(TTR("Animation player has no valid root node path, so unable to retrieve track names."));
+ EditorNode::get_singleton()->show_warning(TTR("AnimationMixer has no valid root node path, so unable to retrieve track names."));
return;
}
HashSet paths;
{
List animations;
- player->get_animation_list(&animations);
+ mixer->get_animation_list(&animations);
for (const StringName &E : animations) {
- Ref anim = player->get_animation(E);
+ Ref anim = mixer->get_animation(E);
for (int i = 0; i < anim->get_track_count(); i++) {
String pathname = anim->track_get_path(i).get_concatenated_names();
if (!paths.has(pathname)) {
@@ -160,7 +156,7 @@ void EditorPropertyRootMotion::_node_assign() {
}
filters->ensure_cursor_is_visible();
- filter_dialog->popup_centered_ratio();
+ filter_dialog->popup_centered(Size2(500, 500) * EDSCALE);
}
void EditorPropertyRootMotion::_node_clear() {
@@ -232,7 +228,7 @@ bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) {
}
bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const BitField p_usage, const bool p_wide) {
- if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) {
+ if (p_path == "root_motion_track" && p_object->is_class("AnimationMixer") && p_type == Variant::NODE_PATH) {
EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion);
add_property_editor(p_path, editor);
return true;
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index d7c6cb04a49..a83dedad6db 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -1548,7 +1548,7 @@ void SceneTreeDock::_fill_path_renames(Vector base_path, Vector &p_to_delete) const {
AnimationPlayer *ap = Object::cast_to(p_node);
if (ap) {
- Node *root = ap->get_node(ap->get_root());
+ Node *root = ap->get_node(ap->get_root_node());
if (root && !p_to_delete.find(root)) {
List anims;
ap->get_animation_list(&anims);
@@ -1735,7 +1735,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, HashMap
AnimationPlayer *ap = Object::cast_to(p_base);
List anims;
ap->get_animation_list(&anims);
- Node *root = ap->get_node(ap->get_root());
+ Node *root = ap->get_node(ap->get_root_node());
if (root) {
HashMap::Iterator found_root_path = p_renames->find(root);
diff --git a/misc/extension_api_validation/4.1-stable.expected b/misc/extension_api_validation/4.1-stable.expected
index 0fb834bbbf2..a14d0fe935f 100644
--- a/misc/extension_api_validation/4.1-stable.expected
+++ b/misc/extension_api_validation/4.1-stable.expected
@@ -117,6 +117,7 @@ Validate extension JSON: API was removed: classes/GLTFDocumentExtensionTextureWe
Excluded unexposed classes from extension_api.json.
+
GH-79311
--------
@@ -205,3 +206,66 @@ GH-82403
Validate extension JSON: Error: Field 'native_structures/PhysicsServer3DExtensionRayResult': format changed value in new API, from "Vector3 position;Vector3 normal;RID rid;ObjectID collider_id;Object *collider;int shape" to "Vector3 position;Vector3 normal;RID rid;ObjectID collider_id;Object *collider;int shape;int face_index".
Added/moved face_index field (introduced in GH-71233) to end of struct. Should still be compatible with 4.1.
+
+
+GH-80813
+--------
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/_post_process_key_value
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/add_animation_library
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/advance
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/clear_caches
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/find_animation
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/find_animation_library
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_animation
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_animation_library
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_animation_library_list
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_animation_list
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/get_audio_max_polyphony
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/has_animation
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/has_animation_library
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/is_active
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/is_reset_on_save_enabled
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/remove_animation_library
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/rename_animation_library
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/set_active
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/set_audio_max_polyphony
+Validate extension JSON: API was removed: classes/AnimationPlayer/methods/set_reset_on_save_enabled
+Validate extension JSON: API was removed: classes/AnimationPlayer/properties/audio_max_polyphony
+Validate extension JSON: API was removed: classes/AnimationPlayer/properties/method_call_mode
+Validate extension JSON: API was removed: classes/AnimationPlayer/properties/playback_active
+Validate extension JSON: API was removed: classes/AnimationPlayer/properties/playback_process_mode
+Validate extension JSON: API was removed: classes/AnimationPlayer/properties/reset_on_save
+Validate extension JSON: API was removed: classes/AnimationPlayer/properties/root_node
+Validate extension JSON: API was removed: classes/AnimationTree/methods/_post_process_key_value
+Validate extension JSON: API was removed: classes/AnimationTree/methods/advance
+Validate extension JSON: API was removed: classes/AnimationTree/methods/get_audio_max_polyphony
+Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_position
+Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_position_accumulator
+Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_rotation
+Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_rotation_accumulator
+Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_scale
+Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_scale_accumulator
+Validate extension JSON: API was removed: classes/AnimationTree/methods/get_root_motion_track
+Validate extension JSON: API was removed: classes/AnimationTree/methods/is_active
+Validate extension JSON: API was removed: classes/AnimationTree/methods/set_active
+Validate extension JSON: API was removed: classes/AnimationTree/methods/set_audio_max_polyphony
+Validate extension JSON: API was removed: classes/AnimationTree/methods/set_root_motion_track
+Validate extension JSON: API was removed: classes/AnimationTree/properties/active
+Validate extension JSON: API was removed: classes/AnimationTree/properties/audio_max_polyphony
+Validate extension JSON: API was removed: classes/AnimationTree/properties/process_callback
+Validate extension JSON: API was removed: classes/AnimationTree/properties/root_motion_track
+Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_finished
+Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_libraries_updated
+Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_list_changed
+Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_started
+Validate extension JSON: API was removed: classes/AnimationPlayer/signals/caches_cleared
+Validate extension JSON: API was removed: classes/AnimationPlayer/signals/animation_changed
+Validate extension JSON: API was removed: classes/AnimationTree/signals/animation_finished
+Validate extension JSON: API was removed: classes/AnimationTree/signals/animation_started
+Validate extension JSON: Error: Field 'classes/AnimationPlayer/methods/seek/arguments': size changed value in new API, from 2 to 3.
+Validate extension JSON: Error: Field 'classes/AnimationTree/methods/get_tree_root/return_value': type changed value in new API, from "AnimationNode" to "AnimationRootNode".
+Validate extension JSON: Error: Field 'classes/AnimationTree/methods/set_tree_root/arguments/0': type changed value in new API, from "AnimationNode" to "AnimationRootNode".
+
+These definitions have been moved to those base classes, so the APIs are actually available.
+Some properties were renamed for integration, but the old setter/getters are kept.
+Also changed some methods name/argument/signature. Compatibility methods registered.
diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp
index 1e0584e1dbe..981bb88bc4e 100644
--- a/scene/animation/animation_blend_space_1d.cpp
+++ b/scene/animation/animation_blend_space_1d.cpp
@@ -272,14 +272,17 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Refset_backward(na_c->is_backward());
}
//see how much animation remains
- from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only);
+ pi.seeked = false;
+ pi.weight = 0;
+ from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
- max_time_remaining = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
+ pi.time = from;
+ pi.seeked = true;
+ pi.weight = 1.0;
+ max_time_remaining = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
cur_length_internal = from + max_time_remaining;
-
cur_closest = new_closest;
-
} else {
- max_time_remaining = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
+ pi.weight = 1.0;
+ max_time_remaining = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
if (sync) {
+ pi = p_playback_info;
+ pi.weight = 0;
for (int i = 0; i < blend_points_used; i++) {
if (i != cur_closest) {
- blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
+ blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
}
}
}
diff --git a/scene/animation/animation_blend_space_1d.h b/scene/animation/animation_blend_space_1d.h
index db2e0045a61..40679d55ef1 100644
--- a/scene/animation/animation_blend_space_1d.h
+++ b/scene/animation/animation_blend_space_1d.h
@@ -114,7 +114,7 @@ public:
void set_use_sync(bool p_sync);
bool is_using_sync() const;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
String get_caption() const override;
Ref get_child_by_name(const StringName &p_name) const override;
diff --git a/scene/animation/animation_blend_space_2d.cpp b/scene/animation/animation_blend_space_2d.cpp
index ff0be091ac9..37c360b8f8d 100644
--- a/scene/animation/animation_blend_space_2d.cpp
+++ b/scene/animation/animation_blend_space_2d.cpp
@@ -442,7 +442,7 @@ void AnimationNodeBlendSpace2D::_blend_triangle(const Vector2 &p_pos, const Vect
r_weights[2] = w;
}
-double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeBlendSpace2D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
_update_triangles();
Vector2 blend_pos = get_parameter(blend_position);
@@ -450,6 +450,8 @@ double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is
double cur_length_internal = get_parameter(length_internal);
double mind = 0.0; //time of min distance point
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+
if (blend_mode == BLEND_MODE_INTERPOLATED) {
if (triangles.size() == 0) {
return 0;
@@ -512,7 +514,8 @@ double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is
for (int j = 0; j < 3; j++) {
if (i == triangle_points[j]) {
//blend with the given weight
- double t = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, blend_weights[j], FILTER_IGNORE, true, p_test_only);
+ pi.weight = blend_weights[j];
+ double t = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
if (first || t < mind) {
mind = t;
first = false;
@@ -523,7 +526,8 @@ double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is
}
if (sync && !found) {
- blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
+ pi.weight = 0;
+ blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
}
}
} else {
@@ -548,22 +552,28 @@ double AnimationNodeBlendSpace2D::_process(double p_time, bool p_seek, bool p_is
na_n->set_backward(na_c->is_backward());
}
//see how much animation remains
- from = cur_length_internal - blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, false, p_is_external_seeking, 0.0, FILTER_IGNORE, true, p_test_only);
+ pi.seeked = false;
+ pi.weight = 0;
+ from = cur_length_internal - blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
- mind = blend_node(blend_points[new_closest].name, blend_points[new_closest].node, from, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
+ pi.time = from;
+ pi.seeked = true;
+ pi.weight = 1.0;
+ mind = blend_node(blend_points[new_closest].node, blend_points[new_closest].name, pi, FILTER_IGNORE, true, p_test_only);
cur_length_internal = from + mind;
-
cur_closest = new_closest;
-
} else {
- mind = blend_node(blend_points[cur_closest].name, blend_points[cur_closest].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
+ pi.weight = 1.0;
+ mind = blend_node(blend_points[cur_closest].node, blend_points[cur_closest].name, pi, FILTER_IGNORE, true, p_test_only);
}
if (sync) {
+ pi = p_playback_info;
+ pi.weight = 0;
for (int i = 0; i < blend_points_used; i++) {
if (i != cur_closest) {
- blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
+ blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
}
}
}
diff --git a/scene/animation/animation_blend_space_2d.h b/scene/animation/animation_blend_space_2d.h
index a18ae3b2586..33a821d80cd 100644
--- a/scene/animation/animation_blend_space_2d.h
+++ b/scene/animation/animation_blend_space_2d.h
@@ -129,7 +129,7 @@ public:
void set_y_label(const String &p_label);
String get_y_label() const;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual String get_caption() const override;
Vector2 get_closest_point(const Vector2 &p_point);
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index b9cbd3cf94a..6ed61b4f261 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -64,14 +64,11 @@ void AnimationNodeAnimation::_validate_property(PropertyInfo &p_property) const
}
}
-double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
- AnimationPlayer *ap = state->player;
- ERR_FAIL_NULL_V(ap, 0);
-
+double AnimationNodeAnimation::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_time = get_parameter(time);
- if (!ap->has_animation(animation)) {
- AnimationNodeBlendTree *tree = Object::cast_to(parent);
+ if (!process_state->tree->has_animation(animation)) {
+ AnimationNodeBlendTree *tree = Object::cast_to(node_state.parent);
if (tree) {
String node_name = tree->get_node_name(Ref(this));
make_invalid(vformat(RTR("On BlendTree node '%s', animation not found: '%s'"), node_name, animation));
@@ -83,14 +80,18 @@ double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_ex
return 0;
}
- Ref anim = ap->get_animation(animation);
+ Ref anim = process_state->tree->get_animation(animation);
double anim_size = (double)anim->get_length();
double step = 0.0;
double prev_time = cur_time;
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
bool node_backward = play_mode == PLAY_MODE_BACKWARD;
- if (p_seek) {
+ double p_time = p_playback_info.time;
+ bool p_seek = p_playback_info.seeked;
+ bool p_is_external_seeking = p_playback_info.is_external_seeking;
+
+ if (p_playback_info.seeked) {
step = p_time - cur_time;
cur_time = p_time;
} else {
@@ -150,24 +151,30 @@ double AnimationNodeAnimation::_process(double p_time, bool p_seek, bool p_is_ex
// Emit start & finish signal. Internally, the detections are the same for backward.
// We should use call_deferred since the track keys are still being processed.
- if (state->tree && !p_test_only) {
+ if (process_state->tree && !p_test_only) {
// AnimationTree uses seek to 0 "internally" to process the first key of the animation, which is used as the start detection.
if (p_seek && !p_is_external_seeking && cur_time == 0) {
- state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation);
+ process_state->tree->call_deferred(SNAME("emit_signal"), "animation_started", animation);
}
// Finished.
if (prev_time < anim_size && cur_time >= anim_size) {
- state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation);
+ process_state->tree->call_deferred(SNAME("emit_signal"), "animation_finished", animation);
}
}
}
if (!p_test_only) {
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
if (play_mode == PLAY_MODE_FORWARD) {
- blend_animation(animation, cur_time, step, p_seek, p_is_external_seeking, 1.0, looped_flag);
+ pi.time = cur_time;
+ pi.delta = step;
} else {
- blend_animation(animation, anim_size - cur_time, -step, p_seek, p_is_external_seeking, 1.0, looped_flag);
+ pi.time = anim_size - cur_time;
+ pi.delta = -step;
}
+ pi.weight = 1.0;
+ pi.looped_flag = looped_flag;
+ blend_animation(animation, pi);
}
set_parameter(time, cur_time);
@@ -333,7 +340,7 @@ bool AnimationNodeOneShot::has_filter() const {
return true;
}
-double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeOneShot::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
OneShotRequest cur_request = static_cast((int)get_parameter(request));
bool cur_active = get_parameter(active);
bool cur_internal_active = get_parameter(internal_active);
@@ -348,6 +355,10 @@ double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_exte
bool clear_remaining_fade = false;
bool is_fading_out = cur_active == true && cur_internal_active == false;
+ double p_time = p_playback_info.time;
+ bool p_seek = p_playback_info.seeked;
+ bool p_is_external_seeking = p_playback_info.is_external_seeking;
+
if (p_time == 0 && p_seek && !p_is_external_seeking) {
clear_remaining_fade = true; // Reset occurs.
}
@@ -396,7 +407,9 @@ double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_exte
}
if (!is_shooting) {
- return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = 1.0;
+ return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
}
if (do_start) {
@@ -437,13 +450,23 @@ double AnimationNodeOneShot::_process(double p_time, bool p_seek, bool p_is_exte
}
}
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
double main_rem = 0.0;
if (mix == MIX_MODE_ADD) {
- main_rem = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
+ pi.weight = 1.0;
+ main_rem = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
} else {
- main_rem = blend_input(0, p_time, use_blend && p_seek, p_is_external_seeking, 1.0 - blend, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
+ pi.seeked &= use_blend;
+ pi.weight = 1.0 - blend;
+ main_rem = blend_input(0, pi, FILTER_BLEND, sync, p_test_only); // Unlike below, processing this edge is a corner case.
}
- double os_rem = blend_input(1, os_seek ? cur_time : p_time, os_seek, p_is_external_seeking, Math::is_zero_approx(blend) ? CMP_EPSILON : blend, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
+ pi = p_playback_info;
+ if (os_seek) {
+ pi.time = cur_time;
+ }
+ pi.seeked = os_seek;
+ pi.weight = Math::is_zero_approx(blend) ? CMP_EPSILON : blend;
+ double os_rem = blend_input(1, pi, FILTER_PASS, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
if (do_start) {
cur_remaining = os_rem;
@@ -542,10 +565,14 @@ bool AnimationNodeAdd2::has_filter() const {
return true;
}
-double AnimationNodeAdd2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeAdd2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(add_amount);
- double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
- blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync, p_test_only);
+
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = 1.0;
+ double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
+ pi.weight = amount;
+ blend_input(1, pi, FILTER_PASS, sync, p_test_only);
return rem0;
}
@@ -576,11 +603,16 @@ bool AnimationNodeAdd3::has_filter() const {
return true;
}
-double AnimationNodeAdd3::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeAdd3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(add_amount);
- blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_PASS, sync, p_test_only);
- double rem0 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
- blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_PASS, sync, p_test_only);
+
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = MAX(0, -amount);
+ blend_input(0, pi, FILTER_PASS, sync, p_test_only);
+ pi.weight = 1.0;
+ double rem0 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
+ pi.weight = MAX(0, amount);
+ blend_input(2, pi, FILTER_PASS, sync, p_test_only);
return rem0;
}
@@ -608,11 +640,14 @@ String AnimationNodeBlend2::get_caption() const {
return "Blend2";
}
-double AnimationNodeBlend2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeBlend2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(blend_amount);
- double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0 - amount, FILTER_BLEND, sync, p_test_only);
- double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, amount, FILTER_PASS, sync, p_test_only);
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = 1.0 - amount;
+ double rem0 = blend_input(0, pi, FILTER_BLEND, sync, p_test_only);
+ pi.weight = amount;
+ double rem1 = blend_input(1, pi, FILTER_PASS, sync, p_test_only);
return amount > 0.5 ? rem1 : rem0; // Hacky but good enough.
}
@@ -643,11 +678,16 @@ String AnimationNodeBlend3::get_caption() const {
return "Blend3";
}
-double AnimationNodeBlend3::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeBlend3::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(blend_amount);
- double rem0 = blend_input(0, p_time, p_seek, p_is_external_seeking, MAX(0, -amount), FILTER_IGNORE, sync, p_test_only);
- double rem1 = blend_input(1, p_time, p_seek, p_is_external_seeking, 1.0 - ABS(amount), FILTER_IGNORE, sync, p_test_only);
- double rem2 = blend_input(2, p_time, p_seek, p_is_external_seeking, MAX(0, amount), FILTER_IGNORE, sync, p_test_only);
+
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = MAX(0, -amount);
+ double rem0 = blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
+ pi.weight = 1.0 - ABS(amount);
+ double rem1 = blend_input(1, pi, FILTER_IGNORE, sync, p_test_only);
+ pi.weight = MAX(0, amount);
+ double rem2 = blend_input(2, pi, FILTER_IGNORE, sync, p_test_only);
return amount > 0.5 ? rem2 : (amount < -0.5 ? rem0 : rem1); // Hacky but good enough.
}
@@ -679,11 +719,16 @@ bool AnimationNodeSub2::has_filter() const {
return true;
}
-double AnimationNodeSub2::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeSub2::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double amount = get_parameter(sub_amount);
+
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
// Out = Sub.Transform3D^(-1) * In.Transform3D
- blend_input(1, p_time, p_seek, p_is_external_seeking, -amount, FILTER_PASS, sync, p_test_only);
- return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, sync, p_test_only);
+ pi.weight = -amount;
+ blend_input(1, pi, FILTER_PASS, sync, p_test_only);
+ pi.weight = 1.0;
+
+ return blend_input(0, pi, FILTER_IGNORE, sync, p_test_only);
}
void AnimationNodeSub2::_bind_methods() {
@@ -708,13 +753,16 @@ String AnimationNodeTimeScale::get_caption() const {
return "TimeScale";
}
-double AnimationNodeTimeScale::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeTimeScale::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_scale = get_parameter(scale);
- if (p_seek) {
- return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
- } else {
- return blend_input(0, p_time * cur_scale, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
+
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = 1.0;
+ if (!pi.seeked) {
+ pi.time *= cur_scale;
}
+
+ return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
}
void AnimationNodeTimeScale::_bind_methods() {
@@ -738,17 +786,19 @@ String AnimationNodeTimeSeek::get_caption() const {
return "TimeSeek";
}
-double AnimationNodeTimeSeek::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeTimeSeek::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double cur_seek_pos = get_parameter(seek_pos_request);
- if (p_seek) {
- return blend_input(0, p_time, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
- } else if (cur_seek_pos >= 0) {
- double ret = blend_input(0, cur_seek_pos, true, true, 1.0, FILTER_IGNORE, true, p_test_only);
+
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = 1.0;
+ if (cur_seek_pos >= 0) {
+ pi.time = cur_seek_pos;
+ pi.seeked = true;
+ pi.is_external_seeking = true;
set_parameter(seek_pos_request, -1.0); // Reset.
- return ret;
- } else {
- return blend_input(0, p_time, false, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
}
+
+ return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
}
void AnimationNodeTimeSeek::_bind_methods() {
@@ -931,7 +981,7 @@ bool AnimationNodeTransition::is_allow_transition_to_self() const {
return allow_transition_to_self;
}
-double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeTransition::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
String cur_transition_request = get_parameter(transition_request);
int cur_current_index = get_parameter(current_index);
int cur_prev_index = get_parameter(prev_index);
@@ -959,6 +1009,10 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e
pending_update = false;
}
+ double p_time = p_playback_info.time;
+ bool p_seek = p_playback_info.seeked;
+ bool p_is_external_seeking = p_playback_info.is_external_seeking;
+
if (p_time == 0 && p_seek && !p_is_external_seeking) {
clear_remaining_fade = true; // Reset occurs.
}
@@ -994,10 +1048,15 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e
set_parameter(prev_index, -1);
}
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+
// Special case for restart.
if (restart) {
set_parameter(time, 0);
- return blend_input(cur_current_index, 0, true, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
+ pi.time = 0;
+ pi.seeked = true;
+ pi.weight = 1.0;
+ return blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
}
if (switched) {
@@ -1013,16 +1072,18 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e
double abs_time = Math::abs(p_time);
if (sync) {
+ pi.weight = 0;
for (int i = 0; i < get_input_count(); i++) {
if (i != cur_current_index && i != cur_prev_index) {
- blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true, p_test_only);
+ blend_input(i, pi, FILTER_IGNORE, true, p_test_only);
}
}
}
if (cur_prev_index < 0) { // Process current animation, check for transition.
- rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
+ pi.weight = 1.0;
+ rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
if (p_seek) {
cur_time = abs_time;
@@ -1051,13 +1112,17 @@ double AnimationNodeTransition::_process(double p_time, bool p_seek, bool p_is_e
}
// Blend values must be more than CMP_EPSILON to process discrete keys in edge.
+ pi.weight = blend_inv;
if (input_data[cur_current_index].reset && !p_seek && switched) { // Just switched, seek to start of current.
- rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, blend_inv, FILTER_IGNORE, true, p_test_only);
- } else {
- rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, blend_inv, FILTER_IGNORE, true, p_test_only);
+ pi.time = 0;
+ pi.seeked = true;
}
+ rem = blend_input(cur_current_index, pi, FILTER_IGNORE, true, p_test_only);
- blend_input(cur_prev_index, p_time, use_blend && p_seek, p_is_external_seeking, blend, FILTER_IGNORE, true, p_test_only);
+ pi = p_playback_info;
+ pi.seeked &= use_blend;
+ pi.weight = blend;
+ blend_input(cur_prev_index, pi, FILTER_IGNORE, true, p_test_only);
if (p_seek) {
cur_time = abs_time;
} else {
@@ -1116,8 +1181,10 @@ String AnimationNodeOutput::get_caption() const {
return "Output";
}
-double AnimationNodeOutput::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
- return blend_input(0, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
+double AnimationNodeOutput::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = 1.0;
+ return blend_input(0, pi, FILTER_IGNORE, true, p_test_only);
}
AnimationNodeOutput::AnimationNodeOutput() {
@@ -1140,9 +1207,9 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Refconnect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed), CONNECT_REFERENCE_COUNTED);
- p_node->connect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
- p_node->connect("animation_node_removed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
+ p_node->connect(SNAME("tree_changed"), callable_mp(this, &AnimationNodeBlendTree::_tree_changed), CONNECT_REFERENCE_COUNTED);
+ p_node->connect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed), CONNECT_REFERENCE_COUNTED);
+ p_node->connect(SNAME("animation_node_removed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed), CONNECT_REFERENCE_COUNTED);
p_node->connect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed).bind(p_name), CONNECT_REFERENCE_COUNTED);
}
@@ -1202,9 +1269,9 @@ void AnimationNodeBlendTree::remove_node(const StringName &p_name) {
{
Ref node = nodes[p_name].node;
- node->disconnect("tree_changed", callable_mp(this, &AnimationNodeBlendTree::_tree_changed));
- node->disconnect("animation_node_renamed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed));
- node->disconnect("animation_node_removed", callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed));
+ node->disconnect(SNAME("tree_changed"), callable_mp(this, &AnimationNodeBlendTree::_tree_changed));
+ node->disconnect(SNAME("animation_node_renamed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_renamed));
+ node->disconnect(SNAME("animation_node_removed"), callable_mp(this, &AnimationNodeBlendTree::_animation_node_removed));
node->disconnect_changed(callable_mp(this, &AnimationNodeBlendTree::_node_changed));
}
@@ -1333,9 +1400,14 @@ String AnimationNodeBlendTree::get_caption() const {
return "BlendTree";
}
-double AnimationNodeBlendTree::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
+double AnimationNodeBlendTree::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
Ref output = nodes[SceneStringNames::get_singleton()->output].node;
- return _blend_node("output", nodes[SceneStringNames::get_singleton()->output].connections, this, output, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, nullptr, p_test_only);
+ node_state.connections = nodes[SceneStringNames::get_singleton()->output].connections;
+
+ AnimationMixer::PlaybackInfo pi = p_playback_info;
+ pi.weight = 1.0;
+
+ return _blend_node(output, "output", this, pi, FILTER_IGNORE, true, p_test_only, nullptr);
}
void AnimationNodeBlendTree::get_node_list(List *r_list) {
@@ -1496,7 +1568,7 @@ void AnimationNodeBlendTree::_bind_methods() {
BIND_CONSTANT(CONNECTION_ERROR_SAME_NODE);
BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_EXISTS);
- ADD_SIGNAL(MethodInfo("node_changed", PropertyInfo(Variant::STRING_NAME, "node_name")));
+ ADD_SIGNAL(MethodInfo(SNAME("node_changed"), PropertyInfo(Variant::STRING_NAME, "node_name")));
}
void AnimationNodeBlendTree::_initialize_node_tree() {
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h
index bf95c211f6d..f38d7ebde8c 100644
--- a/scene/animation/animation_blend_tree.h
+++ b/scene/animation/animation_blend_tree.h
@@ -53,7 +53,7 @@ public:
static Vector (*get_editable_animation_list)();
virtual String get_caption() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
void set_animation(const StringName &p_name);
StringName get_animation() const;
@@ -161,7 +161,7 @@ public:
MixMode get_mix_mode() const;
virtual bool has_filter() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeOneShot();
};
@@ -184,7 +184,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeAdd2();
};
@@ -204,7 +204,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeAdd3();
};
@@ -222,7 +222,7 @@ public:
virtual Variant get_parameter_default_value(const StringName &p_parameter) const override;
virtual String get_caption() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
virtual bool has_filter() const override;
AnimationNodeBlend2();
@@ -242,7 +242,7 @@ public:
virtual String get_caption() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeBlend3();
};
@@ -261,7 +261,7 @@ public:
virtual String get_caption() const override;
virtual bool has_filter() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeSub2();
};
@@ -280,7 +280,7 @@ public:
virtual String get_caption() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTimeScale();
};
@@ -299,7 +299,7 @@ public:
virtual String get_caption() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTimeSeek();
};
@@ -363,7 +363,7 @@ public:
void set_allow_transition_to_self(bool p_enable);
bool is_allow_transition_to_self() const;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeTransition();
};
@@ -373,7 +373,7 @@ class AnimationNodeOutput : public AnimationNode {
public:
virtual String get_caption() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
AnimationNodeOutput();
};
@@ -445,7 +445,7 @@ public:
void get_node_connections(List *r_connections) const;
virtual String get_caption() const override;
- virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false) override;
+ virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false) override;
void get_node_list(List *r_list);
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
new file mode 100644
index 00000000000..dafee025445
--- /dev/null
+++ b/scene/animation/animation_mixer.cpp
@@ -0,0 +1,2115 @@
+/**************************************************************************/
+/* animation_mixer.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "animation_mixer.h"
+
+#include "core/config/engine.h"
+#include "scene/animation/animation_player.h"
+#include "scene/resources/animation.h"
+#include "scene/scene_string_names.h"
+#include "servers/audio/audio_stream.h"
+
+#ifdef TOOLS_ENABLED
+#include "editor/editor_node.h"
+#include "editor/editor_undo_redo_manager.h"
+#endif // TOOLS_ENABLED
+
+bool AnimationMixer::_set(const StringName &p_name, const Variant &p_value) {
+ String name = p_name;
+
+#ifndef DISABLE_DEPRECATED
+ if (name.begins_with("anims/")) {
+ // Backwards compatibility with 3.x, add them to "default" library.
+ String which = name.get_slicec('/', 1);
+
+ Ref anim = p_value;
+ Ref al;
+ if (!has_animation_library(StringName())) {
+ al.instantiate();
+ add_animation_library(StringName(), al);
+ } else {
+ al = get_animation_library(StringName());
+ }
+ al->add_animation(which, anim);
+ } else if (name.begins_with("libraries")) {
+#else
+ if (name.begins_with("libraries")) {
+#endif // DISABLE_DEPRECATED
+ Dictionary d = p_value;
+ while (animation_libraries.size()) {
+ remove_animation_library(animation_libraries[0].name);
+ }
+ List keys;
+ d.get_key_list(&keys);
+ for (const Variant &K : keys) {
+ StringName lib_name = K;
+ Ref lib = d[lib_name];
+ add_animation_library(lib_name, lib);
+ }
+ emit_signal(SNAME("animation_libraries_updated"));
+
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool AnimationMixer::_get(const StringName &p_name, Variant &r_ret) const {
+ String name = p_name;
+
+ if (name.begins_with("libraries")) {
+ Dictionary d;
+ for (const AnimationLibraryData &lib : animation_libraries) {
+ d[lib.name] = lib.library;
+ }
+ r_ret = d;
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+void AnimationMixer::_get_property_list(List *p_list) const {
+ List anim_names;
+ anim_names.push_back(PropertyInfo(Variant::DICTIONARY, PNAME("libraries")));
+ for (const PropertyInfo &E : anim_names) {
+ p_list->push_back(E);
+ }
+
+ for (PropertyInfo &E : *p_list) {
+ _validate_property(E);
+ }
+}
+
+void AnimationMixer::_validate_property(PropertyInfo &p_property) const {
+#ifdef TOOLS_ENABLED
+ if (editing && (p_property.name == "active" || p_property.name == "deterministic" || p_property.name == "root_motion_track")) {
+ p_property.usage |= PROPERTY_USAGE_READ_ONLY;
+ }
+#endif // TOOLS_ENABLED
+}
+
+/* -------------------------------------------- */
+/* -- Data lists ------------------------------ */
+/* -------------------------------------------- */
+
+void AnimationMixer::_animation_set_cache_update() {
+ // Relatively fast function to update all animations.
+ animation_set_update_pass++;
+ bool clear_cache_needed = false;
+
+ // Update changed and add otherwise.
+ for (const AnimationLibraryData &lib : animation_libraries) {
+ for (const KeyValue> &K : lib.library->animations) {
+ StringName key = lib.name == StringName() ? K.key : StringName(String(lib.name) + "/" + String(K.key));
+ if (!animation_set.has(key)) {
+ AnimationData ad;
+ ad.animation = K.value;
+ ad.animation_library = lib.name;
+ ad.name = key;
+ ad.last_update = animation_set_update_pass;
+ animation_set.insert(ad.name, ad);
+ } else {
+ AnimationData &ad = animation_set[key];
+ if (ad.last_update != animation_set_update_pass) {
+ // Was not updated, update. If the animation is duplicated, the second one will be ignored.
+ if (ad.animation != K.value || ad.animation_library != lib.name) {
+ // Animation changed, update and clear caches.
+ clear_cache_needed = true;
+ ad.animation = K.value;
+ ad.animation_library = lib.name;
+ }
+
+ ad.last_update = animation_set_update_pass;
+ }
+ }
+ }
+ }
+
+ // Check removed.
+ List to_erase;
+ for (const KeyValue &E : animation_set) {
+ if (E.value.last_update != animation_set_update_pass) {
+ // Was not updated, must be erased.
+ to_erase.push_back(E.key);
+ clear_cache_needed = true;
+ }
+ }
+
+ while (to_erase.size()) {
+ animation_set.erase(to_erase.front()->get());
+ to_erase.pop_front();
+ }
+
+ if (clear_cache_needed) {
+ // If something was modified or removed, caches need to be cleared.
+ _clear_caches();
+ }
+
+ emit_signal(SNAME("animation_list_changed"));
+}
+
+void AnimationMixer::_animation_added(const StringName &p_name, const StringName &p_library) {
+ _animation_set_cache_update();
+}
+
+void AnimationMixer::_animation_removed(const StringName &p_name, const StringName &p_library) {
+ StringName name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name));
+
+ if (!animation_set.has(name)) {
+ return; // No need to update because not the one from the library being used.
+ }
+
+ _animation_set_cache_update();
+
+ _remove_animation(name);
+}
+
+void AnimationMixer::_animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library) {
+ StringName from_name = p_library == StringName() ? p_name : StringName(String(p_library) + "/" + String(p_name));
+ StringName to_name = p_library == StringName() ? p_to_name : StringName(String(p_library) + "/" + String(p_to_name));
+
+ if (!animation_set.has(from_name)) {
+ return; // No need to update because not the one from the library being used.
+ }
+ _animation_set_cache_update();
+
+ _rename_animation(from_name, to_name);
+}
+
+void AnimationMixer::_animation_changed(const StringName &p_name) {
+ _clear_caches();
+}
+
+void AnimationMixer::_set_active(bool p_active) {
+ //
+}
+
+void AnimationMixer::_remove_animation(const StringName &p_name) {
+ //
+}
+
+void AnimationMixer::_rename_animation(const StringName &p_from_name, const StringName &p_to_name) {
+ //
+}
+
+TypedArray AnimationMixer::_get_animation_library_list() const {
+ TypedArray ret;
+ for (const AnimationLibraryData &lib : animation_libraries) {
+ ret.push_back(lib.name);
+ }
+ return ret;
+}
+
+void AnimationMixer::get_animation_library_list(List *p_libraries) const {
+ for (const AnimationLibraryData &lib : animation_libraries) {
+ p_libraries->push_back(lib.name);
+ }
+}
+
+Ref AnimationMixer::get_animation_library(const StringName &p_name) const {
+ for (const AnimationLibraryData &lib : animation_libraries) {
+ if (lib.name == p_name) {
+ return lib.library;
+ }
+ }
+ ERR_FAIL_V(Ref());
+}
+
+bool AnimationMixer::has_animation_library(const StringName &p_name) const {
+ for (const AnimationLibraryData &lib : animation_libraries) {
+ if (lib.name == p_name) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+StringName AnimationMixer::find_animation_library(const Ref &p_animation) const {
+ for (const KeyValue &E : animation_set) {
+ if (E.value.animation == p_animation) {
+ return E.value.animation_library;
+ }
+ }
+ return StringName();
+}
+
+Error AnimationMixer::add_animation_library(const StringName &p_name, const Ref &p_animation_library) {
+ ERR_FAIL_COND_V(p_animation_library.is_null(), ERR_INVALID_PARAMETER);
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + ".");
+#endif
+
+ int insert_pos = 0;
+
+ for (const AnimationLibraryData &lib : animation_libraries) {
+ ERR_FAIL_COND_V_MSG(lib.name == p_name, ERR_ALREADY_EXISTS, "Can't add animation library twice with name: " + String(p_name));
+ ERR_FAIL_COND_V_MSG(lib.library == p_animation_library, ERR_ALREADY_EXISTS, "Can't add animation library twice (adding as '" + p_name.operator String() + "', exists as '" + lib.name.operator String() + "'.");
+
+ if (lib.name.operator String() >= p_name.operator String()) {
+ break;
+ }
+
+ insert_pos++;
+ }
+
+ AnimationLibraryData ald;
+ ald.name = p_name;
+ ald.library = p_animation_library;
+
+ animation_libraries.insert(insert_pos, ald);
+
+ ald.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added).bind(p_name));
+ ald.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed).bind(p_name));
+ ald.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed).bind(p_name));
+ ald.library->connect(SNAME("animation_changed"), callable_mp(this, &AnimationMixer::_animation_changed));
+
+ _animation_set_cache_update();
+
+ notify_property_list_changed();
+
+ return OK;
+}
+
+void AnimationMixer::remove_animation_library(const StringName &p_name) {
+ int at_pos = -1;
+
+ for (uint32_t i = 0; i < animation_libraries.size(); i++) {
+ if (animation_libraries[i].name == p_name) {
+ at_pos = i;
+ break;
+ }
+ }
+
+ ERR_FAIL_COND(at_pos == -1);
+
+ animation_libraries[at_pos].library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added));
+ animation_libraries[at_pos].library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed));
+ animation_libraries[at_pos].library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed));
+ animation_libraries[at_pos].library->disconnect(SNAME("animation_changed"), callable_mp(this, &AnimationMixer::_animation_changed));
+
+ animation_libraries.remove_at(at_pos);
+ _animation_set_cache_update();
+
+ notify_property_list_changed();
+}
+
+void AnimationMixer::rename_animation_library(const StringName &p_name, const StringName &p_new_name) {
+ if (p_name == p_new_name) {
+ return;
+ }
+#ifdef DEBUG_ENABLED
+ ERR_FAIL_COND_MSG(String(p_new_name).contains("/") || String(p_new_name).contains(":") || String(p_new_name).contains(",") || String(p_new_name).contains("["), "Invalid animation library name: " + String(p_new_name) + ".");
+#endif
+
+ bool found = false;
+ for (AnimationLibraryData &lib : animation_libraries) {
+ ERR_FAIL_COND_MSG(lib.name == p_new_name, "Can't rename animation library to another existing name: " + String(p_new_name) + ".");
+ if (lib.name == p_name) {
+ found = true;
+ lib.name = p_new_name;
+ // rename connections
+ lib.library->disconnect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added));
+ lib.library->disconnect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed));
+ lib.library->disconnect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed));
+
+ lib.library->connect(SNAME("animation_added"), callable_mp(this, &AnimationMixer::_animation_added).bind(p_new_name));
+ lib.library->connect(SNAME("animation_removed"), callable_mp(this, &AnimationMixer::_animation_removed).bind(p_new_name));
+ lib.library->connect(SNAME("animation_renamed"), callable_mp(this, &AnimationMixer::_animation_renamed).bind(p_new_name));
+
+ for (const KeyValue> &K : lib.library->animations) {
+ StringName old_name = p_name == StringName() ? K.key : StringName(String(p_name) + "/" + String(K.key));
+ StringName new_name = p_new_name == StringName() ? K.key : StringName(String(p_new_name) + "/" + String(K.key));
+ _rename_animation(old_name, new_name);
+ }
+ }
+ }
+
+ ERR_FAIL_COND(!found);
+
+ animation_libraries.sort(); // Must keep alphabetical order.
+
+ _animation_set_cache_update(); // Update cache.
+
+ notify_property_list_changed();
+}
+
+void AnimationMixer::get_animation_list(List *p_animations) const {
+ List anims;
+ for (const KeyValue &E : animation_set) {
+ anims.push_back(E.key);
+ }
+ anims.sort();
+ for (const String &E : anims) {
+ p_animations->push_back(E);
+ }
+}
+
+Ref AnimationMixer::get_animation(const StringName &p_name) const {
+ ERR_FAIL_COND_V_MSG(!animation_set.has(p_name), Ref(), vformat("Animation not found: \"%s\".", p_name));
+ const AnimationData &anim_data = animation_set[p_name];
+ return anim_data.animation;
+}
+
+bool AnimationMixer::has_animation(const StringName &p_name) const {
+ return animation_set.has(p_name);
+}
+
+StringName AnimationMixer::find_animation(const Ref &p_animation) const {
+ for (const KeyValue &E : animation_set) {
+ if (E.value.animation == p_animation) {
+ return E.key;
+ }
+ }
+ return StringName();
+}
+
+/* -------------------------------------------- */
+/* -- General settings for animation ---------- */
+/* -------------------------------------------- */
+
+void AnimationMixer::_set_process(bool p_process, bool p_force) {
+ if (processing == p_process && !p_force) {
+ return;
+ }
+
+ switch (callback_mode_process) {
+ case ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS:
+#ifdef TOOLS_ENABLED
+ set_physics_process_internal(p_process && active && !editing);
+#else
+ set_physics_process_internal(p_process && active);
+#endif // TOOLS_ENABLED
+ break;
+ case ANIMATION_CALLBACK_MODE_PROCESS_IDLE:
+#ifdef TOOLS_ENABLED
+ set_process_internal(p_process && active && !editing);
+#else
+ set_process_internal(p_process && active);
+#endif // TOOLS_ENABLED
+ break;
+ case ANIMATION_CALLBACK_MODE_PROCESS_MANUAL:
+ break;
+ }
+
+ processing = p_process;
+}
+
+void AnimationMixer::set_active(bool p_active) {
+ if (active == p_active) {
+ return;
+ }
+
+ active = p_active;
+ _set_active(active);
+ _set_process(processing, true);
+
+ if (!active && is_inside_tree()) {
+ _clear_caches();
+ }
+}
+
+bool AnimationMixer::is_active() const {
+ return active;
+}
+
+void AnimationMixer::set_root_node(const NodePath &p_path) {
+ root_node = p_path;
+ clear_caches();
+}
+
+NodePath AnimationMixer::get_root_node() const {
+ return root_node;
+}
+
+void AnimationMixer::set_deterministic(bool p_deterministic) {
+ deterministic = p_deterministic;
+ clear_caches();
+}
+
+bool AnimationMixer::is_deterministic() const {
+ return deterministic;
+}
+
+void AnimationMixer::set_callback_mode_process(AnimationCallbackModeProcess p_mode) {
+ if (callback_mode_process == p_mode) {
+ return;
+ }
+
+ bool was_active = is_active();
+ if (was_active) {
+ set_active(false);
+ }
+
+ callback_mode_process = p_mode;
+
+ if (was_active) {
+ set_active(true);
+ }
+
+#ifdef TOOLS_ENABLED
+ emit_signal(SNAME("mixer_updated"));
+#endif // TOOLS_ENABLED
+}
+
+AnimationMixer::AnimationCallbackModeProcess AnimationMixer::get_callback_mode_process() const {
+ return callback_mode_process;
+}
+
+void AnimationMixer::set_callback_mode_method(AnimationCallbackModeMethod p_mode) {
+ callback_mode_method = p_mode;
+#ifdef TOOLS_ENABLED
+ emit_signal(SNAME("mixer_updated"));
+#endif // TOOLS_ENABLED
+}
+
+AnimationMixer::AnimationCallbackModeMethod AnimationMixer::get_callback_mode_method() const {
+ return callback_mode_method;
+}
+
+void AnimationMixer::set_audio_max_polyphony(int p_audio_max_polyphony) {
+ ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
+ audio_max_polyphony = p_audio_max_polyphony;
+}
+
+int AnimationMixer::get_audio_max_polyphony() const {
+ return audio_max_polyphony;
+}
+
+#ifdef TOOLS_ENABLED
+void AnimationMixer::set_editing(bool p_editing) {
+ if (editing == p_editing) {
+ return;
+ }
+
+ editing = p_editing;
+ _set_process(processing, true);
+
+ if (editing && is_inside_tree()) {
+ _clear_caches();
+ }
+
+ notify_property_list_changed(); // To make active readonly.
+}
+
+bool AnimationMixer::is_editing() const {
+ return editing;
+}
+
+void AnimationMixer::set_dummy(bool p_dummy) {
+ dummy = p_dummy;
+}
+
+bool AnimationMixer::is_dummy() const {
+ return dummy;
+}
+#endif // TOOLS_ENABLED
+
+/* -------------------------------------------- */
+/* -- Caches for blending --------------------- */
+/* -------------------------------------------- */
+
+void AnimationMixer::_clear_caches() {
+ _init_root_motion_cache();
+ _clear_audio_streams();
+ _clear_playing_caches();
+ for (KeyValue &K : track_cache) {
+ memdelete(K.value);
+ }
+ track_cache.clear();
+ cache_valid = false;
+
+ emit_signal(SNAME("caches_cleared"));
+}
+
+void AnimationMixer::_clear_audio_streams() {
+ for (int i = 0; i < playing_audio_stream_players.size(); i++) {
+ playing_audio_stream_players[i]->call(SNAME("stop"));
+ playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref());
+ }
+ playing_audio_stream_players.clear();
+}
+
+void AnimationMixer::_clear_playing_caches() {
+ for (const TrackCache *E : playing_caches) {
+ if (ObjectDB::get_instance(E->object_id)) {
+ E->object->call(SNAME("stop"));
+ }
+ }
+ playing_caches.clear();
+}
+
+void AnimationMixer::_init_root_motion_cache() {
+ root_motion_cache.loc = Vector3(0, 0, 0);
+ root_motion_cache.rot = Quaternion(0, 0, 0, 1);
+ root_motion_cache.scale = Vector3(1, 1, 1);
+ root_motion_position = Vector3(0, 0, 0);
+ root_motion_rotation = Quaternion(0, 0, 0, 1);
+ root_motion_scale = Vector3(0, 0, 0);
+ root_motion_position_accumulator = Vector3(0, 0, 0);
+ root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1);
+ root_motion_scale_accumulator = Vector3(1, 1, 1);
+}
+
+bool AnimationMixer::_update_caches() {
+ setup_pass++;
+
+ root_motion_cache.loc = Vector3(0, 0, 0);
+ root_motion_cache.rot = Quaternion(0, 0, 0, 1);
+ root_motion_cache.scale = Vector3(1, 1, 1);
+
+ List sname;
+ get_animation_list(&sname);
+
+ Node *parent = get_node_or_null(root_node);
+ if (!parent) {
+ cache_valid = false;
+ return false;
+ }
+
+ Ref reset_anim;
+ bool has_reset_anim = has_animation(SceneStringNames::get_singleton()->RESET);
+ if (has_reset_anim) {
+ reset_anim = get_animation(SceneStringNames::get_singleton()->RESET);
+ }
+ for (const StringName &E : sname) {
+ Ref anim = get_animation(E);
+ for (int i = 0; i < anim->get_track_count(); i++) {
+ NodePath path = anim->track_get_path(i);
+ Animation::TrackType track_type = anim->track_get_type(i);
+
+ Animation::TrackType track_cache_type = track_type;
+ if (track_cache_type == Animation::TYPE_POSITION_3D || track_cache_type == Animation::TYPE_ROTATION_3D || track_cache_type == Animation::TYPE_SCALE_3D) {
+ track_cache_type = Animation::TYPE_POSITION_3D; // Reference them as position3D tracks, even if they modify rotation or scale.
+ }
+
+ TrackCache *track = nullptr;
+ if (track_cache.has(path)) {
+ track = track_cache.get(path);
+ }
+
+ // If not valid, delete track.
+ if (track && (track->type != track_cache_type || ObjectDB::get_instance(track->object_id) == nullptr)) {
+ playing_caches.erase(track);
+ memdelete(track);
+ track_cache.erase(path);
+ track = nullptr;
+ }
+
+ if (!track) {
+ Ref resource;
+ Vector leftover_path;
+ Node *child = parent->get_node_and_resource(path, resource, leftover_path);
+
+ if (!child) {
+ ERR_PRINT("AnimationMixer: '" + String(E) + "', couldn't resolve track: '" + String(path) + "'.");
+ continue;
+ }
+
+ switch (track_type) {
+ case Animation::TYPE_VALUE: {
+ TrackCacheValue *track_value = memnew(TrackCacheValue);
+
+ if (resource.is_valid()) {
+ track_value->object = resource.ptr();
+ } else {
+ track_value->object = child;
+ }
+
+ track_value->is_continuous = anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE;
+ track_value->is_using_angle = anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
+
+ track_value->subpath = leftover_path;
+ track_value->object_id = track_value->object->get_instance_id();
+
+ track = track_value;
+
+ // If a value track without a key is cached first, the initial value cannot be determined.
+ // It is a corner case, but which may cause problems with blending.
+ ERR_CONTINUE_MSG(anim->track_get_key_count(i) == 0, "AnimationMixer: '" + String(E) + "', Value Track: '" + String(path) + "' must have at least one key to cache for blending.");
+ track_value->init_value = anim->track_get_key_value(i, 0);
+ track_value->init_value.zero();
+
+ // If there is a Reset Animation, it takes precedence by overwriting.
+ if (has_reset_anim) {
+ int rt = reset_anim->find_track(path, track_type);
+ if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) {
+ track_value->init_value = reset_anim->track_get_key_value(rt, 0);
+ }
+ }
+ } break;
+ case Animation::TYPE_POSITION_3D:
+ case Animation::TYPE_ROTATION_3D:
+ case Animation::TYPE_SCALE_3D: {
+#ifndef _3D_DISABLED
+ Node3D *node_3d = Object::cast_to(child);
+
+ if (!node_3d) {
+ ERR_PRINT("AnimationMixer: '" + String(E) + "', transform track does not point to Node3D: '" + String(path) + "'.");
+ continue;
+ }
+
+ TrackCacheTransform *track_xform = memnew(TrackCacheTransform);
+ track_xform->type = Animation::TYPE_POSITION_3D;
+
+ track_xform->node_3d = node_3d;
+ track_xform->skeleton = nullptr;
+ track_xform->bone_idx = -1;
+
+ bool has_rest = false;
+ if (path.get_subname_count() == 1 && Object::cast_to(node_3d)) {
+ Skeleton3D *sk = Object::cast_to(node_3d);
+ track_xform->skeleton = sk;
+ int bone_idx = sk->find_bone(path.get_subname(0));
+ if (bone_idx != -1) {
+ has_rest = true;
+ track_xform->bone_idx = bone_idx;
+ Transform3D rest = sk->get_bone_rest(bone_idx);
+ track_xform->init_loc = rest.origin;
+ track_xform->init_rot = rest.basis.get_rotation_quaternion();
+ track_xform->init_scale = rest.basis.get_scale();
+ }
+ }
+
+ track_xform->object = node_3d;
+ track_xform->object_id = track_xform->object->get_instance_id();
+
+ track = track_xform;
+
+ switch (track_type) {
+ case Animation::TYPE_POSITION_3D: {
+ track_xform->loc_used = true;
+ } break;
+ case Animation::TYPE_ROTATION_3D: {
+ track_xform->rot_used = true;
+ } break;
+ case Animation::TYPE_SCALE_3D: {
+ track_xform->scale_used = true;
+ } break;
+ default: {
+ }
+ }
+
+ // For non Skeleton3D bone animation.
+ if (has_reset_anim && !has_rest) {
+ int rt = reset_anim->find_track(path, track_type);
+ if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) {
+ switch (track_type) {
+ case Animation::TYPE_POSITION_3D: {
+ track_xform->init_loc = reset_anim->track_get_key_value(rt, 0);
+ } break;
+ case Animation::TYPE_ROTATION_3D: {
+ track_xform->init_rot = reset_anim->track_get_key_value(rt, 0);
+ } break;
+ case Animation::TYPE_SCALE_3D: {
+ track_xform->init_scale = reset_anim->track_get_key_value(rt, 0);
+ } break;
+ default: {
+ }
+ }
+ }
+ }
+#endif // _3D_DISABLED
+ } break;
+ case Animation::TYPE_BLEND_SHAPE: {
+#ifndef _3D_DISABLED
+ if (path.get_subname_count() != 1) {
+ ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not contain a blend shape subname: '" + String(path) + "'.");
+ continue;
+ }
+ MeshInstance3D *mesh_3d = Object::cast_to(child);
+
+ if (!mesh_3d) {
+ ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track does not point to MeshInstance3D: '" + String(path) + "'.");
+ continue;
+ }
+
+ StringName blend_shape_name = path.get_subname(0);
+ int blend_shape_idx = mesh_3d->find_blend_shape_by_name(blend_shape_name);
+ if (blend_shape_idx == -1) {
+ ERR_PRINT("AnimationMixer: '" + String(E) + "', blend shape track points to a non-existing name: '" + String(blend_shape_name) + "'.");
+ continue;
+ }
+
+ TrackCacheBlendShape *track_bshape = memnew(TrackCacheBlendShape);
+
+ track_bshape->mesh_3d = mesh_3d;
+ track_bshape->shape_index = blend_shape_idx;
+
+ track_bshape->object = mesh_3d;
+ track_bshape->object_id = mesh_3d->get_instance_id();
+ track = track_bshape;
+
+ if (has_reset_anim) {
+ int rt = reset_anim->find_track(path, track_type);
+ if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) {
+ track_bshape->init_value = reset_anim->track_get_key_value(rt, 0);
+ }
+ }
+#endif
+ } break;
+ case Animation::TYPE_METHOD: {
+ TrackCacheMethod *track_method = memnew(TrackCacheMethod);
+
+ if (resource.is_valid()) {
+ track_method->object = resource.ptr();
+ } else {
+ track_method->object = child;
+ }
+
+ track_method->object_id = track_method->object->get_instance_id();
+
+ track = track_method;
+
+ } break;
+ case Animation::TYPE_BEZIER: {
+ TrackCacheBezier *track_bezier = memnew(TrackCacheBezier);
+
+ if (resource.is_valid()) {
+ track_bezier->object = resource.ptr();
+ } else {
+ track_bezier->object = child;
+ }
+
+ track_bezier->subpath = leftover_path;
+ track_bezier->object_id = track_bezier->object->get_instance_id();
+
+ track = track_bezier;
+
+ if (has_reset_anim) {
+ int rt = reset_anim->find_track(path, track_type);
+ if (rt >= 0 && reset_anim->track_get_key_count(rt) > 0) {
+ track_bezier->init_value = (reset_anim->track_get_key_value(rt, 0).operator Array())[0];
+ }
+ }
+ } break;
+ case Animation::TYPE_AUDIO: {
+ TrackCacheAudio *track_audio = memnew(TrackCacheAudio);
+
+ track_audio->object = child;
+ track_audio->object_id = track_audio->object->get_instance_id();
+ track_audio->audio_stream.instantiate();
+ track_audio->audio_stream->set_polyphony(audio_max_polyphony);
+
+ track = track_audio;
+
+ } break;
+ case Animation::TYPE_ANIMATION: {
+ TrackCacheAnimation *track_animation = memnew(TrackCacheAnimation);
+
+ track_animation->object = child;
+ track_animation->object_id = track_animation->object->get_instance_id();
+
+ track = track_animation;
+
+ } break;
+ default: {
+ ERR_PRINT("Animation corrupted (invalid track type).");
+ continue;
+ }
+ }
+
+ track_cache[path] = track;
+ } else if (track_cache_type == Animation::TYPE_POSITION_3D) {
+ TrackCacheTransform *track_xform = static_cast(track);
+ if (track->setup_pass != setup_pass) {
+ track_xform->loc_used = false;
+ track_xform->rot_used = false;
+ track_xform->scale_used = false;
+ }
+ switch (track_type) {
+ case Animation::TYPE_POSITION_3D: {
+ track_xform->loc_used = true;
+ } break;
+ case Animation::TYPE_ROTATION_3D: {
+ track_xform->rot_used = true;
+ } break;
+ case Animation::TYPE_SCALE_3D: {
+ track_xform->scale_used = true;
+ } break;
+ default: {
+ }
+ }
+ } else if (track_cache_type == Animation::TYPE_VALUE) {
+ // If it has at least one angle interpolation, it also uses angle interpolation for blending.
+ TrackCacheValue *track_value = static_cast(track);
+ bool was_continuous = track_value->is_continuous;
+ bool was_using_angle = track_value->is_using_angle;
+ track_value->is_continuous |= anim->value_track_get_update_mode(i) != Animation::UPDATE_DISCRETE;
+ track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
+
+ if (was_continuous != track_value->is_continuous) {
+ WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different update modes between some animations may be blended. Blending prioritizes UpdateMode.UPDATE_CONTINUOUS, so the process treat UpdateMode.UPDATE_DISCRETE as UpdateMode.UPDATE_CONTINUOUS with InterpolationType.INTERPOLATION_NEAREST.");
+ }
+ if (was_using_angle != track_value->is_using_angle) {
+ WARN_PRINT_ONCE_ED("Value Track: " + String(path) + " has different interpolation types for rotation between some animations may be blended. Blending prioritizes angle interpolation, so the blending result uses the shortest path referenced to the initial (RESET animation) value.");
+ }
+ }
+
+ track->setup_pass = setup_pass;
+ }
+ }
+
+ List to_delete;
+
+ for (const KeyValue &K : track_cache) {
+ TrackCache *tc = track_cache[K.key];
+ if (tc->setup_pass != setup_pass) {
+ to_delete.push_back(K.key);
+ }
+ }
+
+ while (to_delete.front()) {
+ NodePath np = to_delete.front()->get();
+ memdelete(track_cache[np]);
+ track_cache.erase(np);
+ to_delete.pop_front();
+ }
+
+ track_map.clear();
+
+ int idx = 0;
+ for (const KeyValue &K : track_cache) {
+ track_map[K.key] = idx;
+ idx++;
+ }
+
+ track_count = idx;
+
+ cache_valid = true;
+
+ return true;
+}
+
+/* -------------------------------------------- */
+/* -- Blending processor ---------------------- */
+/* -------------------------------------------- */
+
+void AnimationMixer::_process_animation(double p_delta, bool p_update_only) {
+ _blend_init();
+ if (_blend_pre_process(p_delta, track_count, track_map)) {
+ if (!deterministic) {
+ _blend_calc_total_weight();
+ }
+ _blend_process(p_delta, p_update_only);
+ _blend_apply();
+ _blend_post_process();
+ };
+ clear_animation_instances();
+}
+
+Variant AnimationMixer::post_process_key_value(const Ref &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx) {
+ Variant res;
+ if (GDVIRTUAL_CALL(_post_process_key_value, p_anim, p_track, p_value, const_cast]