Implement AnimationManager the base class of AnimationPlayer/Tree

This commit is contained in:
Silc Lizard (Tokage) Renew 2023-07-21 00:34:06 +09:00
parent 545d1c0adb
commit 1b95827d3e
49 changed files with 4746 additions and 4943 deletions

View file

@ -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.
</brief_description>
<description>
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.
</description>
<tutorials>

View file

@ -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.
</brief_description>
<description>
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.
</description>
<tutorials>

View file

@ -0,0 +1,342 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AnimationMixer" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Base class for [AnimationPlayer] and [AnimationTree].
</brief_description>
<description>
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].
</description>
<tutorials>
</tutorials>
<methods>
<method name="_post_process_key_value" qualifiers="virtual const">
<return type="Variant" />
<param index="0" name="animation" type="Animation" />
<param index="1" name="track" type="int" />
<param index="2" name="value" type="Variant" />
<param index="3" name="object" type="Object" />
<param index="4" name="object_idx" type="int" />
<description>
A virtual function for processing after key getting during playback.
</description>
</method>
<method name="add_animation_library">
<return type="int" enum="Error" />
<param index="0" name="name" type="StringName" />
<param index="1" name="library" type="AnimationLibrary" />
<description>
Adds [param library] to the animation player, under the key [param name].
</description>
</method>
<method name="advance">
<return type="void" />
<param index="0" name="delta" type="float" />
<description>
Manually advance the animations by the specified time (in seconds).
</description>
</method>
<method name="clear_caches">
<return type="void" />
<description>
[AnimationMixer] caches animated nodes. It may not notice if a node disappears; [method clear_caches] forces it to update the cache again.
</description>
</method>
<method name="find_animation" qualifiers="const">
<return type="StringName" />
<param index="0" name="animation" type="Animation" />
<description>
Returns the key of [param animation] or an empty [StringName] if not found.
</description>
</method>
<method name="find_animation_library" qualifiers="const">
<return type="StringName" />
<param index="0" name="animation" type="Animation" />
<description>
Returns the key for the [AnimationLibrary] that contains [param animation] or an empty [StringName] if not found.
</description>
</method>
<method name="get_animation" qualifiers="const">
<return type="Animation" />
<param index="0" name="name" type="StringName" />
<description>
Returns the [Animation] with the key [param name]. If the animation does not exist, [code]null[/code] is returned and an error is logged.
</description>
</method>
<method name="get_animation_library" qualifiers="const">
<return type="AnimationLibrary" />
<param index="0" name="name" type="StringName" />
<description>
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].
</description>
</method>
<method name="get_animation_library_list" qualifiers="const">
<return type="StringName[]" />
<description>
Returns the list of stored library keys.
</description>
</method>
<method name="get_animation_list" qualifiers="const">
<return type="PackedStringArray" />
<description>
Returns the list of stored animation keys.
</description>
</method>
<method name="get_root_motion_position" qualifiers="const">
<return type="Vector3" />
<description>
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]
</description>
</method>
<method name="get_root_motion_position_accumulator" qualifiers="const">
<return type="Vector3" />
<description>
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.
</description>
</method>
<method name="get_root_motion_rotation" qualifiers="const">
<return type="Quaternion" />
<description>
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]
</description>
</method>
<method name="get_root_motion_rotation_accumulator" qualifiers="const">
<return type="Quaternion" />
<description>
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.
</description>
</method>
<method name="get_root_motion_scale" qualifiers="const">
<return type="Vector3" />
<description>
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]
</description>
</method>
<method name="get_root_motion_scale_accumulator" qualifiers="const">
<return type="Vector3" />
<description>
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.
</description>
</method>
<method name="has_animation" qualifiers="const">
<return type="bool" />
<param index="0" name="name" type="StringName" />
<description>
Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [param name].
</description>
</method>
<method name="has_animation_library" qualifiers="const">
<return type="bool" />
<param index="0" name="name" type="StringName" />
<description>
Returns [code]true[/code] if the [AnimationPlayer] stores an [AnimationLibrary] with key [param name].
</description>
</method>
<method name="remove_animation_library">
<return type="void" />
<param index="0" name="name" type="StringName" />
<description>
Removes the [AnimationLibrary] associated with the key [param name].
</description>
</method>
<method name="rename_animation_library">
<return type="void" />
<param index="0" name="name" type="StringName" />
<param index="1" name="newname" type="StringName" />
<description>
Moves the [AnimationLibrary] associated with the key [param name] to the key [param newname].
</description>
</method>
</methods>
<members>
<member name="active" type="bool" setter="set_active" getter="is_active" default="true">
If [code]true[/code], the [AnimationMixer] will be processing.
</member>
<member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
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.
</member>
<member name="callback_mode_method" type="int" setter="set_callback_mode_method" getter="get_callback_mode_method" enum="AnimationMixer.AnimationCallbackModeMethod" default="0">
The call mode to use for Call Method tracks.
</member>
<member name="callback_mode_process" type="int" setter="set_callback_mode_process" getter="get_callback_mode_process" enum="AnimationMixer.AnimationCallbackModeProcess" default="1">
The process notification in which to update animations.
</member>
<member name="deterministic" type="bool" setter="set_deterministic" getter="is_deterministic" default="false">
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].
</member>
<member name="reset_on_save" type="bool" setter="set_reset_on_save_enabled" getter="is_reset_on_save_enabled" default="true">
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.
</member>
<member name="root_motion_track" type="NodePath" setter="set_root_motion_track" getter="get_root_motion_track" default="NodePath(&quot;&quot;)">
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].
</member>
<member name="root_node" type="NodePath" setter="set_root_node" getter="get_root_node" default="NodePath(&quot;..&quot;)">
The node from which node path references will travel.
</member>
</members>
<signals>
<signal name="animation_finished">
<param index="0" name="anim_name" type="StringName" />
<description>
Notifies when an animation finished playing.
[b]Note:[/b] This signal is not emitted if an animation is looping.
</description>
</signal>
<signal name="animation_libraries_updated">
<description>
Notifies when the animation libraries have changed.
</description>
</signal>
<signal name="animation_list_changed">
<description>
Notifies when an animation list is changed.
</description>
</signal>
<signal name="animation_started">
<param index="0" name="anim_name" type="StringName" />
<description>
Notifies when an animation starts playing.
</description>
</signal>
<signal name="caches_cleared">
<description>
Notifies when the caches have been cleared, either automatically, or manually via [method clear_caches].
</description>
</signal>
<signal name="mixer_updated">
<description>
Editor only. Notifies when the property have been updated to update dummy [AnimationPlayer] in animation player editor.
</description>
</signal>
</signals>
<constants>
<constant name="ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS" value="0" enum="AnimationCallbackModeProcess">
Process animation during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]). This is especially useful when animating physics bodies.
</constant>
<constant name="ANIMATION_CALLBACK_MODE_PROCESS_IDLE" value="1" enum="AnimationCallbackModeProcess">
Process animation during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]).
</constant>
<constant name="ANIMATION_CALLBACK_MODE_PROCESS_MANUAL" value="2" enum="AnimationCallbackModeProcess">
Do not process animation. Use [method advance] to process the animation manually.
</constant>
<constant name="ANIMATION_CALLBACK_MODE_METHOD_DEFERRED" value="0" enum="AnimationCallbackModeMethod">
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.
</constant>
<constant name="ANIMATION_CALLBACK_MODE_METHOD_IMMEDIATE" value="1" enum="AnimationCallbackModeMethod">
Make method calls immediately when reached in the animation.
</constant>
</constants>
</class>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AnimationPlayer" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="AnimationPlayer" inherits="AnimationMixer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A node used for animation playback.
</brief_description>
@ -15,51 +15,19 @@
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
</tutorials>
<methods>
<method name="_post_process_key_value" qualifiers="virtual const">
<return type="Variant" />
<param index="0" name="animation" type="Animation" />
<param index="1" name="track" type="int" />
<param index="2" name="value" type="Variant" />
<param index="3" name="object" type="Object" />
<param index="4" name="object_idx" type="int" />
<description>
A virtual function for processing after key getting during playback.
</description>
</method>
<method name="add_animation_library">
<return type="int" enum="Error" />
<param index="0" name="name" type="StringName" />
<param index="1" name="library" type="AnimationLibrary" />
<description>
Adds [param library] to the animation player, under the key [param name].
</description>
</method>
<method name="advance">
<return type="void" />
<param index="0" name="delta" type="float" />
<description>
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.
</description>
</method>
<method name="animation_get_next" qualifiers="const">
<return type="StringName" />
<param index="0" name="anim_from" type="StringName" />
<param index="0" name="animation_from" type="StringName" />
<description>
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.
</description>
</method>
<method name="animation_set_next">
<return type="void" />
<param index="0" name="anim_from" type="StringName" />
<param index="1" name="anim_to" type="StringName" />
<param index="0" name="animation_from" type="StringName" />
<param index="1" name="animation_to" type="StringName" />
<description>
Triggers the [param anim_to] animation when the [param anim_from] animation completes.
</description>
</method>
<method name="clear_caches">
<return type="void" />
<description>
[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.
</description>
</method>
<method name="clear_queue">
@ -68,55 +36,20 @@
Clears all queued, unplayed animations.
</description>
</method>
<method name="find_animation" qualifiers="const">
<return type="StringName" />
<param index="0" name="animation" type="Animation" />
<description>
Returns the key of [param animation] or an empty [StringName] if not found.
</description>
</method>
<method name="find_animation_library" qualifiers="const">
<return type="StringName" />
<param index="0" name="animation" type="Animation" />
<description>
Returns the key for the [AnimationLibrary] that contains [param animation] or an empty [StringName] if not found.
</description>
</method>
<method name="get_animation" qualifiers="const">
<return type="Animation" />
<param index="0" name="name" type="StringName" />
<description>
Returns the [Animation] with the key [param name]. If the animation does not exist, [code]null[/code] is returned and an error is logged.
</description>
</method>
<method name="get_animation_library" qualifiers="const">
<return type="AnimationLibrary" />
<param index="0" name="name" type="StringName" />
<description>
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].
</description>
</method>
<method name="get_animation_library_list" qualifiers="const">
<return type="StringName[]" />
<description>
Returns the list of stored library keys.
</description>
</method>
<method name="get_animation_list" qualifiers="const">
<return type="PackedStringArray" />
<description>
Returns the list of stored animation keys.
</description>
</method>
<method name="get_blend_time" qualifiers="const">
<return type="float" />
<param index="0" name="anim_from" type="StringName" />
<param index="1" name="anim_to" type="StringName" />
<param index="0" name="animation_from" type="StringName" />
<param index="1" name="animation_to" type="StringName" />
<description>
Returns the blend time (in seconds) between two animations, referenced by their keys.
</description>
</method>
<method name="get_method_call_mode" qualifiers="const" is_deprecated="true">
<return type="int" enum="AnimationPlayer.AnimationMethodCallMode" />
<description>
For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeMethod].
</description>
</method>
<method name="get_playing_speed" qualifiers="const">
<return type="float" />
<description>
@ -124,24 +57,22 @@
Returns a negative value if the current animation is playing backwards.
</description>
</method>
<method name="get_process_callback" qualifiers="const" is_deprecated="true">
<return type="int" enum="AnimationPlayer.AnimationProcessCallback" />
<description>
For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess].
</description>
</method>
<method name="get_queue">
<return type="PackedStringArray" />
<description>
Returns a list of the animation keys that are currently queued to play.
</description>
</method>
<method name="has_animation" qualifiers="const">
<return type="bool" />
<param index="0" name="name" type="StringName" />
<method name="get_root" qualifiers="const" is_deprecated="true">
<return type="NodePath" />
<description>
Returns [code]true[/code] if the [AnimationPlayer] stores an [Animation] with key [param name].
</description>
</method>
<method name="has_animation_library" qualifiers="const">
<return type="bool" />
<param index="0" name="name" type="StringName" />
<description>
Returns [code]true[/code] if the [AnimationPlayer] stores an [AnimationLibrary] with key [param name].
For backward compatibility. See [member AnimationMixer.root_node].
</description>
</method>
<method name="is_playing" qualifiers="const">
@ -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.
</description>
</method>
<method name="remove_animation_library">
<return type="void" />
<param index="0" name="name" type="StringName" />
<description>
Removes the [AnimationLibrary] associated with the key [param name].
</description>
</method>
<method name="rename_animation_library">
<return type="void" />
<param index="0" name="name" type="StringName" />
<param index="1" name="newname" type="StringName" />
<description>
Moves the [AnimationLibrary] associated with the key [param name] to the key [param newname].
</description>
</method>
<method name="seek">
<return type="void" />
<param index="0" name="seconds" type="float" />
<param index="1" name="update" type="bool" default="false" />
<param index="2" name="update_only" type="bool" default="false" />
<description>
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].
</description>
</method>
<method name="set_blend_time">
<return type="void" />
<param index="0" name="anim_from" type="StringName" />
<param index="1" name="anim_to" type="StringName" />
<param index="0" name="animation_from" type="StringName" />
<param index="1" name="animation_to" type="StringName" />
<param index="2" name="sec" type="float" />
<description>
Specifies a blend time (in seconds) between two animations, referenced by their keys.
</description>
</method>
<method name="set_method_call_mode" is_deprecated="true">
<return type="void" />
<param index="0" name="mode" type="int" enum="AnimationPlayer.AnimationMethodCallMode" />
<description>
For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeMethod].
</description>
</method>
<method name="set_process_callback" is_deprecated="true">
<return type="void" />
<param index="0" name="mode" type="int" enum="AnimationPlayer.AnimationProcessCallback" />
<description>
For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess].
</description>
</method>
<method name="set_root" is_deprecated="true">
<return type="void" />
<param index="0" name="path" type="NodePath" />
<description>
For backward compatibility. See [member AnimationMixer.root_node].
</description>
</method>
<method name="stop">
<return type="void" />
<param index="0" name="keep_state" type="bool" default="false" />
@ -234,10 +173,6 @@
<member name="assigned_animation" type="String" setter="set_assigned_animation" getter="get_assigned_animation">
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].
</member>
<member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
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.
</member>
<member name="autoplay" type="String" setter="set_autoplay" getter="get_autoplay" default="&quot;&quot;">
The key of the animation to play when the scene loads.
</member>
@ -251,29 +186,13 @@
<member name="current_animation_position" type="float" setter="" getter="get_current_animation_position">
The position (in seconds) of the currently playing animation.
</member>
<member name="method_call_mode" type="int" setter="set_method_call_mode" getter="get_method_call_mode" enum="AnimationPlayer.AnimationMethodCallMode" default="0">
The call mode to use for Call Method tracks.
</member>
<member name="movie_quit_on_finish" type="bool" setter="set_movie_quit_on_finish_enabled" getter="is_movie_quit_on_finish_enabled" default="false">
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.
</member>
<member name="playback_active" type="bool" setter="set_active" getter="is_active">
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.
</member>
<member name="playback_default_blend_time" type="float" setter="set_default_blend_time" getter="get_default_blend_time" default="0.0">
The default time in which to blend animations. Ranges from 0 to 4096 with 0.01 precision.
</member>
<member name="playback_process_mode" type="int" setter="set_process_callback" getter="get_process_callback" enum="AnimationPlayer.AnimationProcessCallback" default="1">
The process notification in which to update animations.
</member>
<member name="reset_on_save" type="bool" setter="set_reset_on_save_enabled" getter="is_reset_on_save_enabled" default="true">
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.
</member>
<member name="root_node" type="NodePath" setter="set_root" getter="get_root" default="NodePath(&quot;..&quot;)">
The node from which node path references will travel.
</member>
<member name="speed_scale" type="float" setter="set_speed_scale" getter="get_speed_scale" default="1.0">
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 @@
<param index="0" name="old_name" type="StringName" />
<param index="1" name="new_name" type="StringName" />
<description>
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].
</description>
</signal>
<signal name="animation_finished">
<param index="0" name="anim_name" type="StringName" />
<signal name="current_animation_changed">
<param index="0" name="name" type="String" />
<description>
Notifies when an animation finished playing.
[b]Note:[/b] This signal is not emitted if an animation is looping.
</description>
</signal>
<signal name="animation_libraries_updated">
<description>
Notifies when the animation libraries have changed.
</description>
</signal>
<signal name="animation_list_changed">
<description>
Notifies when an animation list is changed.
</description>
</signal>
<signal name="animation_started">
<param index="0" name="anim_name" type="StringName" />
<description>
Notifies when an animation starts playing.
</description>
</signal>
<signal name="caches_cleared">
<description>
Notifies when the caches have been cleared, either automatically, or manually via [method clear_caches].
Emitted when [member current_animation] changes.
</description>
</signal>
</signals>
<constants>
<constant name="ANIMATION_PROCESS_PHYSICS" value="0" enum="AnimationProcessCallback">
Process animation during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]). This is especially useful when animating physics bodies.
<constant name="ANIMATION_PROCESS_PHYSICS" value="0" enum="AnimationProcessCallback" is_deprecated="true">
For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS].
</constant>
<constant name="ANIMATION_PROCESS_IDLE" value="1" enum="AnimationProcessCallback">
Process animation during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]).
<constant name="ANIMATION_PROCESS_IDLE" value="1" enum="AnimationProcessCallback" is_deprecated="true">
For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_IDLE].
</constant>
<constant name="ANIMATION_PROCESS_MANUAL" value="2" enum="AnimationProcessCallback">
Do not process animation. Use [method advance] to process the animation manually.
<constant name="ANIMATION_PROCESS_MANUAL" value="2" enum="AnimationProcessCallback" is_deprecated="true">
For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_MANUAL].
</constant>
<constant name="ANIMATION_METHOD_CALL_DEFERRED" value="0" enum="AnimationMethodCallMode">
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.
<constant name="ANIMATION_METHOD_CALL_DEFERRED" value="0" enum="AnimationMethodCallMode" is_deprecated="true">
For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_METHOD_DEFERRED].
</constant>
<constant name="ANIMATION_METHOD_CALL_IMMEDIATE" value="1" enum="AnimationMethodCallMode">
Make method calls immediately when reached in the animation.
<constant name="ANIMATION_METHOD_CALL_IMMEDIATE" value="1" enum="AnimationMethodCallMode" is_deprecated="true">
For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_METHOD_IMMEDIATE].
</constant>
</constants>
</class>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="AnimationTree" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="AnimationTree" inherits="AnimationMixer" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A node used for advanced animation transitions in an [AnimationPlayer].
</brief_description>
@ -12,220 +12,48 @@
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
</tutorials>
<methods>
<method name="_post_process_key_value" qualifiers="virtual const">
<return type="Variant" />
<param index="0" name="animation" type="Animation" />
<param index="1" name="track" type="int" />
<param index="2" name="value" type="Variant" />
<param index="3" name="object" type="Object" />
<param index="4" name="object_idx" type="int" />
<method name="get_process_callback" qualifiers="const" is_deprecated="true">
<return type="int" enum="AnimationTree.AnimationProcessCallback" />
<description>
A virtual function for processing after key getting during playback.
For backward compatibility. See [enum AnimationMixer.AnimationCallbackModeProcess].
</description>
</method>
<method name="advance">
<method name="set_process_callback" is_deprecated="true">
<return type="void" />
<param index="0" name="delta" type="float" />
<param index="0" name="mode" type="int" enum="AnimationTree.AnimationProcessCallback" />
<description>
Manually advance the animations by the specified time (in seconds).
</description>
</method>
<method name="get_root_motion_position" qualifiers="const">
<return type="Vector3" />
<description>
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]
</description>
</method>
<method name="get_root_motion_position_accumulator" qualifiers="const">
<return type="Vector3" />
<description>
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.
</description>
</method>
<method name="get_root_motion_rotation" qualifiers="const">
<return type="Quaternion" />
<description>
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]
</description>
</method>
<method name="get_root_motion_rotation_accumulator" qualifiers="const">
<return type="Quaternion" />
<description>
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.
</description>
</method>
<method name="get_root_motion_scale" qualifiers="const">
<return type="Vector3" />
<description>
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]
</description>
</method>
<method name="get_root_motion_scale_accumulator" qualifiers="const">
<return type="Vector3" />
<description>
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].
</description>
</method>
</methods>
<members>
<member name="active" type="bool" setter="set_active" getter="is_active" default="false">
If [code]true[/code], the [AnimationTree] will be processing.
</member>
<member name="advance_expression_base_node" type="NodePath" setter="set_advance_expression_base_node" getter="get_advance_expression_base_node" default="NodePath(&quot;.&quot;)">
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.
</member>
<member name="anim_player" type="NodePath" setter="set_animation_player" getter="get_animation_player" default="NodePath(&quot;&quot;)">
The path to the [AnimationPlayer] used for animating.
</member>
<member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
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.
</member>
<member name="process_callback" type="int" setter="set_process_callback" getter="get_process_callback" enum="AnimationTree.AnimationProcessCallback" default="1">
The process mode of this [AnimationTree]. See [enum AnimationProcessCallback] for available modes.
</member>
<member name="root_motion_track" type="NodePath" setter="set_root_motion_track" getter="get_root_motion_track" default="NodePath(&quot;&quot;)">
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].
</member>
<member name="tree_root" type="AnimationNode" setter="set_tree_root" getter="get_tree_root">
The root animation node of this [AnimationTree]. See [AnimationNode].
<member name="deterministic" type="bool" setter="set_deterministic" getter="is_deterministic" overrides="AnimationMixer" default="true" />
<member name="tree_root" type="AnimationRootNode" setter="set_tree_root" getter="get_tree_root">
The root animation node of this [AnimationTree]. See [AnimationRootNode].
</member>
</members>
<signals>
<signal name="animation_finished">
<param index="0" name="anim_name" type="StringName" />
<description>
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.
</description>
</signal>
<signal name="animation_player_changed">
<description>
Emitted when the [member anim_player] is changed.
</description>
</signal>
<signal name="animation_started">
<param index="0" name="anim_name" type="StringName" />
<description>
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.
</description>
</signal>
</signals>
<constants>
<constant name="ANIMATION_PROCESS_PHYSICS" value="0" enum="AnimationProcessCallback">
The animations will progress during physics frames (see [constant Node.NOTIFICATION_INTERNAL_PHYSICS_PROCESS]).
<constant name="ANIMATION_PROCESS_PHYSICS" value="0" enum="AnimationProcessCallback" is_deprecated="true">
For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS].
</constant>
<constant name="ANIMATION_PROCESS_IDLE" value="1" enum="AnimationProcessCallback">
The animations will progress during process frames (see [constant Node.NOTIFICATION_INTERNAL_PROCESS]).
<constant name="ANIMATION_PROCESS_IDLE" value="1" enum="AnimationProcessCallback" is_deprecated="true">
For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_IDLE].
</constant>
<constant name="ANIMATION_PROCESS_MANUAL" value="2" enum="AnimationProcessCallback">
The animations will only progress manually (see [method advance]).
<constant name="ANIMATION_PROCESS_MANUAL" value="2" enum="AnimationProcessCallback" is_deprecated="true">
For backward compatibility. See [constant AnimationMixer.ANIMATION_CALLBACK_MODE_PROCESS_MANUAL].
</constant>
</constants>
</class>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="RootMotionView" inherits="VisualInstance3D" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Editor-only helper for setting up root motion in [AnimationTree].
Editor-only helper for setting up root motion in [AnimationMixer].
</brief_description>
<description>
[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.
</description>
<tutorials>
@ -12,7 +12,7 @@
</tutorials>
<members>
<member name="animation_path" type="NodePath" setter="set_animation_path" getter="get_animation_path" default="NodePath(&quot;&quot;)">
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.
</member>
<member name="cell_size" type="float" setter="set_cell_size" getter="get_cell_size" default="1.0">
The grid's cell size in 3D units.

View file

@ -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.
</brief_description>
<description>
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]).
</description>

View file

@ -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.
</brief_description>
<description>
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]).
</description>

View file

@ -2695,7 +2695,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &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<Animation> AnimationTrackEditor::_create_and_get_reset_animation() {
return player->get_animation(SceneStringNames::get_singleton()->RESET);
} else {
Ref<AnimationLibrary> 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<Animation> 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);

View file

@ -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<AnimationTrackEdit *> track_edits;
Vector<AnimationTrackEditGroup *> 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;

View file

@ -1701,16 +1701,19 @@ int EditorNode::_save_external_resources() {
return saved;
}
static void _reset_animation_players(Node *p_node, List<Ref<AnimatedValuesBackup>> *r_anim_backups) {
static void _reset_animation_mixers(Node *p_node, List<Pair<AnimationMixer *, Ref<AnimatedValuesBackup>>> *r_anim_backups) {
for (int i = 0; i < p_node->get_child_count(); i++) {
AnimationPlayer *player = Object::cast_to<AnimationPlayer>(p_node->get_child(i));
if (player && player->is_reset_on_save_enabled() && player->can_apply_reset()) {
Ref<AnimatedValuesBackup> old_values = player->apply_reset();
if (old_values.is_valid()) {
r_anim_backups->push_back(old_values);
AnimationMixer *mixer = Object::cast_to<AnimationMixer>(p_node->get_child(i));
if (mixer && mixer->is_reset_on_save_enabled() && mixer->can_apply_reset()) {
Ref<AnimatedValuesBackup> backup = mixer->apply_reset();
if (backup.is_valid()) {
Pair<AnimationMixer *, Ref<AnimatedValuesBackup>> 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<Ref<AnimatedValuesBackup>> anim_backups;
_reset_animation_players(scene, &anim_backups);
List<Pair<AnimationMixer *, Ref<AnimatedValuesBackup>>> 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<AnimatedValuesBackup> &E : anim_backups) {
E->restore();
for (Pair<AnimationMixer *, Ref<AnimatedValuesBackup>> &E : anim_backups) {
E.first->restore(E.second);
}
if (err == OK) {

View file

@ -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."));

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M1 1v14h1v-2h2v2h8v-2h2v2h1V1h-1v2h-2V1H4v2H2V1zm1 4h2v2H2zm10 0h2v2h-2zM2 9h2v2H2zm10 0h2v2h-2z" fill="#919191"/></svg>

After

Width:  |  Height:  |  Size: 213 B

View file

@ -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<Skeleton3D>(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<Skeleton3D>(node);
if (track_skeleton && track_skeleton == skeleton) {

View file

@ -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<Skeleton3D>(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<Skeleton3D>(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<Skeleton3D>(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<Skeleton3D>(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<Skeleton3D>(node);

View file

@ -78,7 +78,7 @@ void PostImportPluginSkeletonTrackOrganizer::internal_process(InternalImportCate
Vector<int> 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);

View file

@ -595,7 +595,7 @@ Node *ResourceImporterScene::_pre_fix_node(Node *p_node, Node *p_root, HashMap<R
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(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;

View file

@ -78,18 +78,12 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven
menu->add_submenu_item(TTR("Add Animation"), "animations");
if (tree->has_node(tree->get_animation_player())) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player()));
List<StringName> names;
tree->get_animation_list(&names);
if (ap) {
List<StringName> 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) {

View file

@ -123,16 +123,11 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven
ClassDB::get_inheriters_from_class("AnimationRootNode", &classes);
menu->add_submenu_item(TTR("Add Animation"), "animations");
if (tree->has_node(tree->get_animation_player())) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player()));
if (ap) {
List<StringName> 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<StringName> 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) {

View file

@ -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<AnimationPlayer>(tree->get_node(tree->get_animation_player()));
if (ap) {
List<StringName> anims;
ap->get_animation_list(&anims);
List<StringName> 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<AnimationNode> &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<AnimationPlayer>(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<AnimationNode> &ano
HashMap<String, RBSet<String>> types;
{
List<StringName> animation_list;
player->get_animation_list(&animation_list);
tree->get_animation_list(&animation_list);
for (const StringName &E : animation_list) {
Ref<Animation> anim = player->get_animation(E);
Ref<Animation> 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<AnimationPlayer>(tree->get_node(tree->get_animation_player()));
}
if (player) {
for (const KeyValue<StringName, ProgressBar *> &E : animations) {
Ref<AnimationNodeAnimation> an = blend_tree->get_node(E.key);
if (an.is_valid()) {
if (player->has_animation(an->get_animation())) {
Ref<Animation> 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<StringName, ProgressBar *> &E : animations) {
Ref<AnimationNodeAnimation> an = blend_tree->get_node(E.key);
if (an.is_valid()) {
if (tree->has_animation(an->get_animation())) {
Ref<Animation> 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));
}
}
}

View file

@ -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<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library);
Ref<AnimationLibrary> 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<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library);
Ref<AnimationLibrary> al = mixer->call("get_animation_library", adding_animation_to_library);
ERR_FAIL_COND(!al.is_valid());
Ref<Animation> 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<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library);
Ref<AnimationLibrary> al = mixer->call("get_animation_library", file_dialog_library);
Ref<Animation> 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<StringName> libs = player->call("get_animation_library_list");
TypedArray<StringName> libs = mixer->call("get_animation_library_list");
for (int i = 0; i < libs.size(); i++) {
const StringName K = libs[i];
Ref<AnimationLibrary> al2 = player->call("get_animation_library", K);
Ref<AnimationLibrary> 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<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library);
Ref<AnimationLibrary> al = mixer->call("get_animation_library", adding_animation_to_library);
List<StringName> 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<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library);
Ref<AnimationLibrary> 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<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library);
Ref<AnimationLibrary> al = mixer->call("get_animation_library", file_dialog_library);
Ref<Animation> 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<AnimationLibrary> al = player->call("get_animation_library", library);
Ref<AnimationLibrary> 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<AnimationLibrary> al = player->call("get_animation_library", lib_name);
Ref<AnimationLibrary> 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<AnimationLibrary> al = player->call("get_animation_library", lib_name);
Ref<AnimationLibrary> al = mixer->call("get_animation_library", lib_name);
Ref<Animation> 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<StringName> libs = player->call("get_animation_library_list");
TypedArray<StringName> 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<AnimationLibrary> al = player->call("get_animation_library", K);
Ref<AnimationLibrary> 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"));
}

View file

@ -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();

View file

@ -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<Animation>(), 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<AnimationPlayer>(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<AnimationPlayer>(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<AnimationTree>(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<Animation> 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<AnimatedValuesBackup> values_backup = player->backup_animated_values();
Ref<AnimatedValuesBackup> 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<AnimationTree>(original_node);
Node *src_player = src_tree->get_node_or_null(src_tree->get_animation_player());
if (src_player) {
return Object::cast_to<AnimationMixer>(src_player);
}
}
return original_node;
}
bool AnimationPlayerEditor::_validate_tracks(const Ref<Animation> 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<AnimationPlayer>(p_object));
last_mixer = p_object->get_instance_id();
AnimationMixer *src_node = Object::cast_to<AnimationMixer>(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<AnimationPlayer>(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<PropertyInfo> 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

View file

@ -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);

View file

@ -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<AnimationNodeStateMachine> anodesm = !edited_path.size() ? tree->get_tree_root() : tree->get_tree_root()->find_node_by_path(base_path);
Ref<AnimationNodeStateMachine> anodesm = !edited_path.size() ? Ref<AnimationNode>(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<StringName> animation_names;
if (tree->has_node(tree->get_animation_player())) {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(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);

View file

@ -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<String> &p_path) {
button_path.clear();
Ref<AnimationNode> node = tree->get_tree_root();
Ref<AnimationNode> 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<String> AnimationTreeEditor::get_animation_list() {
}
AnimationTree *tree = singleton->tree;
if (!tree || !tree->has_node(tree->get_animation_player())) {
return Vector<String>();
}
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(tree->get_node(tree->get_animation_player()));
if (!ap) {
if (!tree) {
return Vector<String>();
}
List<StringName> anims;
ap->get_animation_list(&anims);
tree->get_animation_list(&anims);
Vector<String> ret;
for (const StringName &E : anims) {
ret.push_back(E);

View file

@ -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<AnimationTree>(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<AnimationPlayer>(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<AnimationMixer>(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<String> paths;
{
List<StringName> animations;
player->get_animation_list(&animations);
mixer->get_animation_list(&animations);
for (const StringName &E : animations) {
Ref<Animation> anim = player->get_animation(E);
Ref<Animation> 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<PropertyUsageFlags> 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;

View file

@ -1548,7 +1548,7 @@ void SceneTreeDock::_fill_path_renames(Vector<StringName> base_path, Vector<Stri
bool SceneTreeDock::_has_tracks_to_delete(Node *p_node, List<Node *> &p_to_delete) const {
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(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<StringName> anims;
ap->get_animation_list(&anims);
@ -1735,7 +1735,7 @@ void SceneTreeDock::perform_node_renames(Node *p_base, HashMap<Node *, NodePath>
AnimationPlayer *ap = Object::cast_to<AnimationPlayer>(p_base);
List<StringName> 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<Node *, NodePath>::Iterator found_root_path = p_renames->find(root);

View file

@ -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.

View file

@ -272,14 +272,17 @@ void AnimationNodeBlendSpace1D::_add_blend_point(int p_index, const Ref<Animatio
}
}
double AnimationNodeBlendSpace1D::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
double AnimationNodeBlendSpace1D::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
if (blend_points_used == 0) {
return 0.0;
}
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (blend_points_used == 1) {
// only one point available, just play that animation
return blend_node(blend_points[0].name, blend_points[0].node, p_time, p_seek, p_is_external_seeking, 1.0, FILTER_IGNORE, true, p_test_only);
pi.weight = 1.0;
return blend_node(blend_points[0].node, blend_points[0].name, pi, FILTER_IGNORE, true, p_test_only);
}
double blend_pos = get_parameter(blend_position);
@ -341,10 +344,12 @@ double AnimationNodeBlendSpace1D::_process(double p_time, bool p_seek, bool p_is
for (int i = 0; i < blend_points_used; i++) {
if (i == point_lower || i == point_higher) {
double remaining = blend_node(blend_points[i].name, blend_points[i].node, p_time, p_seek, p_is_external_seeking, weights[i], FILTER_IGNORE, true, p_test_only);
pi.weight = weights[i];
double remaining = blend_node(blend_points[i].node, blend_points[i].name, pi, FILTER_IGNORE, true, p_test_only);
max_time_remaining = MAX(max_time_remaining, remaining);
} else if (sync) {
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 {
@ -369,22 +374,28 @@ double AnimationNodeBlendSpace1D::_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);
}
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);
}
}
}

View file

@ -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<AnimationNode> get_child_by_name(const StringName &p_name) const override;

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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<AnimationNodeBlendTree>(parent);
if (!process_state->tree->has_animation(animation)) {
AnimationNodeBlendTree *tree = Object::cast_to<AnimationNodeBlendTree>(node_state.parent);
if (tree) {
String node_name = tree->get_node_name(Ref<AnimationNodeAnimation>(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<Animation> anim = ap->get_animation(animation);
Ref<Animation> 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<OneShotRequest>((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, Ref<AnimationNod
emit_changed();
emit_signal(SNAME("tree_changed"));
p_node->connect("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<AnimationNode> 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<AnimationNodeOutput> 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<StringName> *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() {

View file

@ -53,7 +53,7 @@ public:
static Vector<String> (*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<NodeConnection> *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<StringName> *r_list);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,384 @@
/**************************************************************************/
/* animation_mixer.h */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef ANIMATION_MIXER_H
#define ANIMATION_MIXER_H
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
#include "scene/resources/animation_library.h"
#include "scene/resources/audio_stream_polyphonic.h"
#ifdef TOOLS_ENABLED
class AnimatedValuesBackup;
#endif // TOOLS_ENABLED
class AnimationMixer : public Node {
GDCLASS(AnimationMixer, Node);
#ifdef TOOLS_ENABLED
friend AnimatedValuesBackup;
bool reset_on_save = true;
bool editing = false;
bool dummy = false;
#endif // TOOLS_ENABLED
public:
enum AnimationCallbackModeProcess {
ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS,
ANIMATION_CALLBACK_MODE_PROCESS_IDLE,
ANIMATION_CALLBACK_MODE_PROCESS_MANUAL,
};
enum AnimationCallbackModeMethod {
ANIMATION_CALLBACK_MODE_METHOD_DEFERRED,
ANIMATION_CALLBACK_MODE_METHOD_IMMEDIATE,
};
/* ---- Data ---- */
struct AnimationLibraryData {
StringName name;
Ref<AnimationLibrary> library;
bool operator<(const AnimationLibraryData &p_data) const { return name.operator String() < p_data.name.operator String(); }
};
struct AnimationData {
String name;
Ref<Animation> animation;
StringName animation_library;
uint64_t last_update = 0;
};
struct PlaybackInfo {
double time = 0.0;
double delta = 0.0;
bool seeked = false;
bool is_external_seeking = false;
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
real_t weight = 0.0;
Vector<real_t> track_weights;
};
struct AnimationInstance {
AnimationData animation_data;
PlaybackInfo playback_info;
};
protected:
/* ---- Data lists ---- */
LocalVector<AnimationLibraryData> animation_libraries;
HashMap<StringName, AnimationData> animation_set; // HashMap<Library name + Animation name, AnimationData>
TypedArray<StringName> _get_animation_library_list() const;
Vector<String> _get_animation_list() const {
List<StringName> animations;
get_animation_list(&animations);
Vector<String> ret;
while (animations.size()) {
ret.push_back(animations.front()->get());
animations.pop_front();
}
return ret;
}
// For caches.
uint64_t animation_set_update_pass = 1;
void _animation_set_cache_update();
// Signals.
virtual void _animation_added(const StringName &p_name, const StringName &p_library);
virtual void _animation_removed(const StringName &p_name, const StringName &p_library);
virtual void _animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library);
virtual void _animation_changed(const StringName &p_name);
/* ---- General settings for animation ---- */
AnimationCallbackModeProcess callback_mode_process = ANIMATION_CALLBACK_MODE_PROCESS_IDLE;
AnimationCallbackModeMethod callback_mode_method = ANIMATION_CALLBACK_MODE_METHOD_DEFERRED;
int audio_max_polyphony = 32;
NodePath root_node;
bool processing = false;
bool active = true;
void _set_process(bool p_process, bool p_force = false);
/* ---- Caches for blending ---- */
bool cache_valid = false;
uint64_t setup_pass = 1;
uint64_t process_pass = 1;
struct TrackCache {
bool root_motion = false;
uint64_t setup_pass = 0;
Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION;
Object *object = nullptr;
ObjectID object_id;
real_t total_weight = 0.0;
};
struct TrackCacheTransform : public TrackCache {
#ifndef _3D_DISABLED
Node3D *node_3d = nullptr;
Skeleton3D *skeleton = nullptr;
#endif // _3D_DISABLED
int bone_idx = -1;
bool loc_used = false;
bool rot_used = false;
bool scale_used = false;
Vector3 init_loc = Vector3(0, 0, 0);
Quaternion init_rot = Quaternion(0, 0, 0, 1);
Vector3 init_scale = Vector3(1, 1, 1);
Vector3 loc;
Quaternion rot;
Vector3 scale;
TrackCacheTransform() {
type = Animation::TYPE_POSITION_3D;
}
};
struct RootMotionCache {
Vector3 loc = Vector3(0, 0, 0);
Quaternion rot = Quaternion(0, 0, 0, 1);
Vector3 scale = Vector3(1, 1, 1);
};
struct TrackCacheBlendShape : public TrackCache {
MeshInstance3D *mesh_3d = nullptr;
float init_value = 0;
float value = 0;
int shape_index = -1;
TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; }
};
struct TrackCacheValue : public TrackCache {
Variant init_value;
Variant value;
Vector<StringName> subpath;
bool is_continuous = false;
bool is_using_angle = false;
TrackCacheValue() { type = Animation::TYPE_VALUE; }
};
struct TrackCacheMethod : public TrackCache {
TrackCacheMethod() { type = Animation::TYPE_METHOD; }
};
struct TrackCacheBezier : public TrackCache {
real_t init_value = 0.0;
real_t value = 0.0;
Vector<StringName> subpath;
TrackCacheBezier() {
type = Animation::TYPE_BEZIER;
}
};
// Audio stream information for each audio stream placed on the track.
struct PlayingAudioStreamInfo {
AudioStreamPlaybackPolyphonic::ID index = -1; // ID retrieved from AudioStreamPlaybackPolyphonic.
double start = 0.0;
double len = 0.0;
};
// Audio track information for mixng and ending.
struct PlayingAudioTrackInfo {
HashMap<int, PlayingAudioStreamInfo> stream_info;
double length = 0.0;
double time = 0.0;
real_t volume = 0.0;
bool loop = false;
bool backward = false;
bool use_blend = false;
};
struct TrackCacheAudio : public TrackCache {
Ref<AudioStreamPolyphonic> audio_stream;
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Key is Animation resource ObjectID.
TrackCacheAudio() {
type = Animation::TYPE_AUDIO;
}
};
struct TrackCacheAnimation : public TrackCache {
bool playing = false;
TrackCacheAnimation() {
type = Animation::TYPE_ANIMATION;
}
};
RootMotionCache root_motion_cache;
HashMap<NodePath, TrackCache *> track_cache;
HashSet<TrackCache *> playing_caches;
Vector<Node *> playing_audio_stream_players;
// Helpers.
void _clear_caches();
void _clear_audio_streams();
void _clear_playing_caches();
void _init_root_motion_cache();
bool _update_caches();
/* ---- Blending processor ---- */
LocalVector<AnimationInstance> animation_instances;
HashMap<NodePath, int> track_map;
int track_count = 0;
bool deterministic = false;
/* ---- Root motion accumulator for Skeleton3D ---- */
NodePath root_motion_track;
Vector3 root_motion_position = Vector3(0, 0, 0);
Quaternion root_motion_rotation = Quaternion(0, 0, 0, 1);
Vector3 root_motion_scale = Vector3(0, 0, 0);
Vector3 root_motion_position_accumulator = Vector3(0, 0, 0);
Quaternion root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1);
Vector3 root_motion_scale_accumulator = Vector3(1, 1, 1);
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _notification(int p_what);
virtual void _validate_property(PropertyInfo &p_property) const;
static void _bind_methods();
void _node_removed(Node *p_node);
// Helper for extended class.
virtual void _set_active(bool p_active);
virtual void _remove_animation(const StringName &p_name);
virtual void _rename_animation(const StringName &p_from_name, const StringName &p_to_name);
/* ---- Blending processor ---- */
virtual void _process_animation(double p_delta, bool p_update_only = false);
virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1);
Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1);
GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, Object *, int);
void _blend_init();
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map);
void _blend_calc_total_weight(); // For undeterministic blending.
void _blend_process(double p_delta, bool p_update_only = false);
void _blend_apply();
virtual void _blend_post_process();
void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred);
public:
/* ---- Data lists ---- */
Dictionary *get_animation_libraries();
void get_animation_library_list(List<StringName> *p_animations) const;
Ref<AnimationLibrary> get_animation_library(const StringName &p_name) const;
bool has_animation_library(const StringName &p_name) const;
StringName find_animation_library(const Ref<Animation> &p_animation) const;
Error add_animation_library(const StringName &p_name, const Ref<AnimationLibrary> &p_animation_library);
void remove_animation_library(const StringName &p_name);
void rename_animation_library(const StringName &p_name, const StringName &p_new_name);
void get_animation_list(List<StringName> *p_animations) const;
Ref<Animation> get_animation(const StringName &p_name) const;
bool has_animation(const StringName &p_name) const;
StringName find_animation(const Ref<Animation> &p_animation) const;
/* ---- General settings for animation ---- */
void set_active(bool p_active);
bool is_active() const;
void set_deterministic(bool p_deterministic);
bool is_deterministic() const;
void set_root_node(const NodePath &p_path);
NodePath get_root_node() const;
void set_callback_mode_process(AnimationCallbackModeProcess p_mode);
AnimationCallbackModeProcess get_callback_mode_process() const;
void set_callback_mode_method(AnimationCallbackModeMethod p_mode);
AnimationCallbackModeMethod get_callback_mode_method() const;
void set_audio_max_polyphony(int p_audio_max_polyphony);
int get_audio_max_polyphony() const;
/* ---- Root motion accumulator for Skeleton3D ---- */
void set_root_motion_track(const NodePath &p_track);
NodePath get_root_motion_track() const;
Vector3 get_root_motion_position() const;
Quaternion get_root_motion_rotation() const;
Vector3 get_root_motion_scale() const;
Vector3 get_root_motion_position_accumulator() const;
Quaternion get_root_motion_rotation_accumulator() const;
Vector3 get_root_motion_scale_accumulator() const;
/* ---- Blending processor ---- */
void make_animation_instance(const StringName &p_name, const PlaybackInfo p_playback_info);
void clear_animation_instances();
virtual void advance(double p_time);
virtual void clear_caches(); ///< must be called by hand if an animation was modified after added
#ifdef TOOLS_ENABLED
void set_editing(bool p_editing);
bool is_editing() const;
void set_dummy(bool p_dummy);
bool is_dummy() const;
void set_reset_on_save_enabled(bool p_enabled);
bool is_reset_on_save_enabled() const;
bool can_apply_reset() const;
void _build_backup_track_cache();
Ref<AnimatedValuesBackup> make_backup();
Ref<AnimatedValuesBackup> apply_reset(bool p_user_initiated = false);
void restore(const Ref<AnimatedValuesBackup> &p_backup);
void reset();
#endif // TOOLS_ENABLED
AnimationMixer();
~AnimationMixer();
};
#ifdef TOOLS_ENABLED
class AnimatedValuesBackup : public RefCounted {
GDCLASS(AnimatedValuesBackup, RefCounted);
HashMap<NodePath, AnimationMixer::TrackCache *> data;
public:
void set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data) { data = p_data; };
HashMap<NodePath, AnimationMixer::TrackCache *> get_data() const { return data; };
};
#endif
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeProcess);
VARIANT_ENUM_CAST(AnimationMixer::AnimationCallbackModeMethod);
#endif // ANIMATION_MIXER_H

View file

@ -665,8 +665,8 @@ bool AnimationNodeStateMachinePlayback::_make_travel_path(AnimationTree *p_tree,
}
}
double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
double rem = _process(p_base_path, p_state_machine, p_time, p_seek, p_is_external_seeking, p_test_only);
double AnimationNodeStateMachinePlayback::process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
double rem = _process(p_base_path, p_state_machine, p_playback_info, p_test_only);
start_request = StringName();
next_request = false;
stop_request = false;
@ -674,10 +674,14 @@ double AnimationNodeStateMachinePlayback::process(const String &p_base_path, Ani
return rem;
}
double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
_set_base_path(p_base_path);
AnimationTree *tree = p_state_machine->state->tree;
AnimationTree *tree = p_state_machine->process_state->tree;
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;
// Check seek to 0 (means reset) by parent AnimationNode.
if (p_time == 0 && p_seek && !p_is_external_seeking) {
@ -767,6 +771,8 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
}
}
AnimationMixer::PlaybackInfo pi = p_playback_info;
if (teleport_request) {
teleport_request = false;
// Clear fadeing on teleport.
@ -774,7 +780,13 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
fading_pos = 0;
// Init current length.
pos_current = 0; // Overwritten suddenly in main process.
len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, true);
pi.time = 0;
pi.seeked = true;
pi.is_external_seeking = false;
pi.weight = 0;
len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true);
// Don't process first node if not necessary, insteads process next node.
_transition_to_next_recursive(tree, p_state_machine, p_test_only);
}
@ -818,12 +830,16 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
// Main process.
double rem = 0.0;
pi = p_playback_info;
pi.weight = fade_blend;
if (reset_request) {
reset_request = false;
len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, p_is_external_seeking, fade_blend, AnimationNode::FILTER_IGNORE, true, p_test_only);
pi.time = 0;
pi.seeked = true;
len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
rem = len_current;
} else {
rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, p_time, p_seek, p_is_external_seeking, fade_blend, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
}
// Cross-fade process.
@ -837,12 +853,14 @@ double AnimationNodeStateMachinePlayback::_process(const String &p_base_path, An
}
float fading_from_rem = 0.0;
pi = p_playback_info;
pi.weight = fade_blend_inv;
if (_reset_request_for_fading_from) {
_reset_request_for_fading_from = false;
fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, 0, true, p_is_external_seeking, fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
} else {
fading_from_rem = p_state_machine->blend_node(fading_from, p_state_machine->states[fading_from].node, p_time, p_seek, p_is_external_seeking, fade_blend_inv, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
pi.time = 0;
pi.seeked = true;
}
fading_from_rem = p_state_machine->blend_node(p_state_machine->states[fading_from].node, fading_from, pi, AnimationNode::FILTER_IGNORE, true); // Blend values must be more than CMP_EPSILON to process discrete keys in edge.
// Guess playback position.
if (fading_from_rem > len_fade_from) { /// Weird but ok.
@ -900,6 +918,7 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
bool is_state_changed = false;
AnimationMixer::PlaybackInfo pi;
NextInfo next;
Vector<StringName> transition_path;
transition_path.push_back(current);
@ -926,7 +945,11 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
} else {
if (reset_request) {
// There is no possibility of processing doubly. Now we can apply reset actually in here.
p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, p_test_only);
pi.time = 0;
pi.seeked = true;
pi.is_external_seeking = false;
pi.weight = 0;
p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, p_test_only);
}
fading_from = StringName();
fading_time = 0;
@ -949,16 +972,25 @@ bool AnimationNodeStateMachinePlayback::_transition_to_next_recursive(AnimationT
len_fade_from = len_current;
if (next.switch_mode == AnimationNodeStateMachineTransition::SWITCH_MODE_SYNC) {
p_state_machine->blend_node(current, p_state_machine->states[current].node, MIN(pos_current, len_current), true, false, 0, AnimationNode::FILTER_IGNORE, true);
pi.time = MIN(pos_current, len_current);
pi.seeked = true;
pi.is_external_seeking = false;
pi.weight = 0;
p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true);
}
// Just get length to find next recursive.
double rem = 0.0;
pi.time = 0;
pi.is_external_seeking = false;
pi.weight = 0;
if (next.is_reset) {
len_current = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, true, false, 0, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
pi.seeked = true;
len_current = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
rem = len_current;
} else {
rem = p_state_machine->blend_node(current, p_state_machine->states[current].node, 0, false, false, 0, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
pi.seeked = false;
rem = p_state_machine->blend_node(p_state_machine->states[current].node, current, pi, AnimationNode::FILTER_IGNORE, true, true); // Just retrieve remain length, don't process.
}
// Guess playback position.
@ -1158,7 +1190,7 @@ Ref<AnimationNodeStateMachine> AnimationNodeStateMachinePlayback::_get_parent_st
Vector<String> split = base_path.split("/");
ERR_FAIL_COND_V_MSG(split.size() < 3, Ref<AnimationNodeStateMachine>(), "Path is too short.");
split = split.slice(1, split.size() - 2);
Ref<AnimationNode> root = p_tree->get_tree_root();
Ref<AnimationNode> root = p_tree->get_root_animation_node();
ERR_FAIL_COND_V_MSG(root.is_null(), Ref<AnimationNodeStateMachine>(), "There is no root AnimationNode in AnimationTree: " + String(p_tree->get_name()));
String anodesm_path = String("/").join(split);
Ref<AnimationNodeStateMachine> anodesm = !anodesm_path.size() ? root : root->find_node_by_path(anodesm_path);
@ -1590,14 +1622,14 @@ Vector2 AnimationNodeStateMachine::get_graph_offset() const {
return graph_offset;
}
double AnimationNodeStateMachine::_process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only) {
double AnimationNodeStateMachine::_process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only) {
Ref<AnimationNodeStateMachinePlayback> playback_new = get_parameter(playback);
ERR_FAIL_COND_V(playback_new.is_null(), 0.0);
playback_new->_set_grouped(state_machine_type == STATE_MACHINE_TYPE_GROUPED);
if (p_test_only) {
playback_new = playback_new->duplicate(); // Don't process original when testing.
}
return playback_new->process(base_path, this, p_time, p_seek, p_is_external_seeking, p_test_only);
return playback_new->process(node_state.base_path, this, p_playback_info, p_test_only);
}
String AnimationNodeStateMachine::get_caption() const {

View file

@ -210,7 +210,7 @@ public:
void set_graph_offset(const Vector2 &p_offset);
Vector2 get_graph_offset() 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;
virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const override;
@ -297,8 +297,8 @@ class AnimationNodeStateMachinePlayback : public Resource {
bool _travel_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_is_allow_transition_to_self, bool p_is_parent_same_state, bool p_test_only);
void _start_children(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, const String &p_path, bool p_test_only);
double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only);
double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only);
double process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
double _process(const String &p_base_path, AnimationNodeStateMachine *p_state_machine, const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only);
bool _check_advance_condition(const Ref<AnimationNodeStateMachine> p_state_machine, const Ref<AnimationNodeStateMachineTransition> p_transition) const;
bool _transition_to_next_recursive(AnimationTree *p_tree, AnimationNodeStateMachine *p_state_machine, bool p_test_only);

View file

@ -0,0 +1,76 @@
/**************************************************************************/
/* animation_player.compat.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void AnimationPlayer::_set_process_callback_bind_compat_80813(AnimationPlayer::AnimationProcessCallback p_mode) {
set_callback_mode_process(static_cast<AnimationMixer::AnimationCallbackModeProcess>(static_cast<int>(p_mode)));
}
AnimationPlayer::AnimationProcessCallback AnimationPlayer::_get_process_callback_bind_compat_80813() const {
return static_cast<AnimationProcessCallback>(static_cast<int>(get_callback_mode_process()));
}
void AnimationPlayer::_set_method_call_mode_bind_compat_80813(AnimationPlayer::AnimationMethodCallMode p_mode) {
set_callback_mode_method(static_cast<AnimationMixer::AnimationCallbackModeMethod>(static_cast<int>(p_mode)));
}
AnimationPlayer::AnimationMethodCallMode AnimationPlayer::_get_method_call_mode_bind_compat_80813() const {
return static_cast<AnimationMethodCallMode>(static_cast<int>(get_callback_mode_method()));
}
void AnimationPlayer::_set_root_bind_compat_80813(const NodePath &p_root) {
set_root_node(p_root);
}
NodePath AnimationPlayer::_get_root_bind_compat_80813() const {
return get_root_node();
}
void AnimationPlayer::_seek_bind_compat_80813(double p_time, bool p_update) {
seek(p_time, p_update, false);
}
void AnimationPlayer::_bind_compatibility_methods() {
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationPlayer::_set_process_callback_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationPlayer::_get_process_callback_bind_compat_80813);
ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::_set_method_call_mode_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::_get_method_call_mode_bind_compat_80813);
ClassDB::bind_method(D_METHOD("set_root", "path"), &AnimationPlayer::_set_root_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_root"), &AnimationPlayer::_get_root_bind_compat_80813);
ClassDB::bind_compatibility_method(D_METHOD("seek", "seconds", "update"), &AnimationPlayer::_seek_bind_compat_80813, DEFVAL(false));
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);
BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_DEFERRED);
BIND_ENUM_CONSTANT(ANIMATION_METHOD_CALL_IMMEDIATE);
}
#endif // DISABLE_DEPRECATED

File diff suppressed because it is too large Load diff

View file

@ -31,211 +31,52 @@
#ifndef ANIMATION_PLAYER_H
#define ANIMATION_PLAYER_H
#include "animation_mixer.h"
#include "scene/2d/node_2d.h"
#include "scene/3d/mesh_instance_3d.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
#include "scene/resources/animation_library.h"
#include "scene/resources/audio_stream_polyphonic.h"
#ifdef TOOLS_ENABLED
class AnimatedValuesBackup : public RefCounted {
GDCLASS(AnimatedValuesBackup, RefCounted);
struct Entry {
Object *object = nullptr;
Vector<StringName> subpath; // Unused if bone
int bone_idx = -1; // -1 if not a bone
Variant value;
};
Vector<Entry> entries;
friend class AnimationPlayer;
protected:
static void _bind_methods();
public:
void update_skeletons();
void restore() const;
};
#endif
class AnimationPlayer : public Node {
GDCLASS(AnimationPlayer, Node);
class AnimationPlayer : public AnimationMixer {
GDCLASS(AnimationPlayer, AnimationMixer);
#ifndef DISABLE_DEPRECATED
public:
enum AnimationProcessCallback {
ANIMATION_PROCESS_PHYSICS,
ANIMATION_PROCESS_IDLE,
ANIMATION_PROCESS_MANUAL,
};
enum AnimationMethodCallMode {
ANIMATION_METHOD_CALL_DEFERRED,
ANIMATION_METHOD_CALL_IMMEDIATE,
};
#endif // DISABLE_DEPRECATED
private:
enum {
NODE_CACHE_UPDATE_MAX = 1024,
BLEND_FROM_MAX = 3
};
HashMap<StringName, StringName> animation_next_set; // For auto advance.
enum SpecialProperty {
SP_NONE,
SP_NODE2D_POS,
SP_NODE2D_ROT,
SP_NODE2D_SCALE,
};
uint32_t setup_pass = 1;
struct TrackNodeCache {
NodePath path;
uint32_t id = 0;
Ref<Resource> resource;
Node *node = nullptr;
Node2D *node_2d = nullptr;
#ifndef _3D_DISABLED
Node3D *node_3d = nullptr;
Skeleton3D *skeleton = nullptr;
MeshInstance3D *node_blend_shape = nullptr;
int blend_shape_idx = -1;
#endif // _3D_DISABLED
int bone_idx = -1;
// accumulated transforms
bool loc_used = false;
bool rot_used = false;
bool scale_used = false;
Vector3 init_loc = Vector3(0, 0, 0);
Quaternion init_rot = Quaternion(0, 0, 0, 1);
Vector3 init_scale = Vector3(1, 1, 1);
Vector3 loc_accum;
Quaternion rot_accum;
Vector3 scale_accum;
float blend_shape_accum = 0;
uint64_t accum_pass = 0;
bool audio_playing = false;
float audio_start = 0.0;
float audio_len = 0.0;
bool animation_playing = false;
struct PropertyAnim {
TrackNodeCache *owner = nullptr;
SpecialProperty special = SP_NONE; //small optimization
Vector<StringName> subpath;
Object *object = nullptr;
Variant value_accum;
uint64_t accum_pass = 0;
Variant capture;
};
HashMap<StringName, PropertyAnim> property_anim;
struct BezierAnim {
Vector<StringName> bezier_property;
TrackNodeCache *owner = nullptr;
float bezier_accum = 0.0;
Object *object = nullptr;
uint64_t accum_pass = 0;
};
HashMap<StringName, BezierAnim> bezier_anim;
struct PlayingAudioStreamInfo {
AudioStreamPlaybackPolyphonic::ID index = -1;
double start = 0.0;
double len = 0.0;
};
struct AudioAnim {
Ref<AudioStreamPolyphonic> audio_stream;
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
HashMap<int, PlayingAudioStreamInfo> playing_streams;
Object *object = nullptr;
uint64_t accum_pass = 0;
double length = 0.0;
double time = 0.0;
bool loop = false;
bool backward = false;
};
HashMap<StringName, AudioAnim> audio_anim;
uint32_t last_setup_pass = 0;
TrackNodeCache() {}
};
struct TrackNodeCacheKey {
ObjectID id;
int bone_idx = -1;
int blend_shape_idx = -1;
static uint32_t hash(const TrackNodeCacheKey &p_key) {
uint32_t h = hash_one_uint64(p_key.id);
h = hash_murmur3_one_32(p_key.bone_idx, h);
return hash_fmix32(hash_murmur3_one_32(p_key.blend_shape_idx, h));
}
inline bool operator==(const TrackNodeCacheKey &p_right) const {
return id == p_right.id && bone_idx == p_right.bone_idx && blend_shape_idx == p_right.blend_shape_idx;
}
inline bool operator<(const TrackNodeCacheKey &p_right) const {
if (id == p_right.id) {
if (blend_shape_idx == p_right.blend_shape_idx) {
return bone_idx < p_right.bone_idx;
} else {
return blend_shape_idx < p_right.blend_shape_idx;
}
} else {
return id < p_right.id;
}
}
};
HashMap<TrackNodeCacheKey, TrackNodeCache, TrackNodeCacheKey> node_cache_map;
TrackNodeCache *cache_update[NODE_CACHE_UPDATE_MAX];
int cache_update_size = 0;
TrackNodeCache::PropertyAnim *cache_update_prop[NODE_CACHE_UPDATE_MAX];
int cache_update_prop_size = 0;
TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX];
int cache_update_bezier_size = 0;
TrackNodeCache::AudioAnim *cache_update_audio[NODE_CACHE_UPDATE_MAX];
int cache_update_audio_size = 0;
HashSet<TrackNodeCache *> playing_caches;
Vector<Node *> playing_audio_stream_players;
uint64_t accum_pass = 1;
float speed_scale = 1.0;
double default_blend_time = 0.0;
bool is_stopping = false;
struct AnimationData {
String name;
StringName next;
Vector<TrackNodeCache *> node_cache;
Ref<Animation> animation;
StringName animation_library;
uint64_t last_update = 0;
struct PlaybackData {
AnimationData *from = nullptr;
double pos = 0.0;
float speed_scale = 1.0;
};
HashMap<StringName, AnimationData> animation_set;
struct AnimationLibraryData {
StringName name;
Ref<AnimationLibrary> library;
bool operator<(const AnimationLibraryData &p_data) const { return name.operator String() < p_data.name.operator String(); }
struct Blend {
PlaybackData data;
double blend_time = 0.0;
double blend_left = 0.0;
};
LocalVector<AnimationLibraryData> animation_libraries;
struct Playback {
PlaybackData current;
StringName assigned;
bool seeked = false;
bool started = false;
List<Blend> blend;
} playback;
struct BlendKey {
StringName from;
@ -257,117 +98,57 @@ private:
HashMap<BlendKey, double, BlendKey> blend_times;
struct PlaybackData {
AnimationData *from = nullptr;
double pos = 0.0;
float speed_scale = 1.0;
};
struct Blend {
PlaybackData data;
double blend_time = 0.0;
double blend_left = 0.0;
};
struct Playback {
List<Blend> blend;
PlaybackData current;
StringName assigned;
bool seeked = false;
bool started = false;
} playback;
List<StringName> queued;
List<StringName> playback_queue;
ObjectID tmp_from;
bool end_reached = false;
bool end_notify = false;
String autoplay;
StringName autoplay;
bool reset_on_save = true;
AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
AnimationMethodCallMode method_call_mode = ANIMATION_METHOD_CALL_DEFERRED;
int audio_max_polyphony = 32;
bool movie_quit_on_finish = false;
bool processing = false;
bool active = true;
NodePath root;
void _animation_process_animation(AnimationData *p_anim, double p_prev_time, double p_time, double p_delta, float p_interp, bool p_is_current = true, bool p_seeked = false, bool p_started = false, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
void _ensure_node_caches(AnimationData *p_anim, Node *p_root_override = nullptr);
void _animation_process_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started);
void _animation_process2(double p_delta, bool p_started);
void _animation_update_transforms();
void _animation_process(double p_delta);
void _node_removed(Node *p_node);
void _clear_audio_streams();
void _stop_playing_caches(bool p_reset);
// bind helpers
Vector<String> _get_animation_list() const {
List<StringName> animations;
get_animation_list(&animations);
Vector<String> ret;
while (animations.size()) {
ret.push_back(animations.front()->get());
animations.pop_front();
}
return ret;
}
void _animation_changed(const StringName &p_name);
void _set_process(bool p_process, bool p_force = false);
void _process_playback_data(PlaybackData &cd, double p_delta, float p_blend, bool p_seeked, bool p_started, bool p_is_current = false);
void _blend_playback_data(double p_delta, bool p_started);
void _stop_internal(bool p_reset, bool p_keep_state);
bool playing = false;
uint64_t animation_set_update_pass = 1;
void _animation_set_cache_update();
void _animation_added(const StringName &p_name, const StringName &p_library);
void _animation_removed(const StringName &p_name, const StringName &p_library);
void _animation_renamed(const StringName &p_name, const StringName &p_to_name, const StringName &p_library);
void _rename_animation(const StringName &p_from_name, const StringName &p_to_name);
TypedArray<StringName> _get_animation_library_list() const;
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _validate_property(PropertyInfo &p_property) const;
virtual void _validate_property(PropertyInfo &p_property) const override;
void _get_property_list(List<PropertyInfo> *p_list) const;
void _notification(int p_what);
static void _bind_methods();
GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, Object *, int);
Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1);
virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1);
// Make animation instances.
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
virtual void _blend_post_process() override;
virtual void _animation_removed(const StringName &p_name, const StringName &p_library) override;
virtual void _rename_animation(const StringName &p_from_name, const StringName &p_to_name) override;
#ifndef DISABLE_DEPRECATED
void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode);
AnimationProcessCallback _get_process_callback_bind_compat_80813() const;
void _set_method_call_mode_bind_compat_80813(AnimationMethodCallMode p_mode);
AnimationMethodCallMode _get_method_call_mode_bind_compat_80813() const;
void _set_root_bind_compat_80813(const NodePath &p_root);
NodePath _get_root_bind_compat_80813() const;
void _seek_bind_compat_80813(double p_time, bool p_update = false);
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
public:
StringName find_animation(const Ref<Animation> &p_animation) const;
StringName find_animation_library(const Ref<Animation> &p_animation) const;
Error add_animation_library(const StringName &p_name, const Ref<AnimationLibrary> &p_animation_library);
void remove_animation_library(const StringName &p_name);
void rename_animation_library(const StringName &p_name, const StringName &p_new_name);
Ref<AnimationLibrary> get_animation_library(const StringName &p_name) const;
void get_animation_library_list(List<StringName> *p_animations) const;
bool has_animation_library(const StringName &p_name) const;
Ref<Animation> get_animation(const StringName &p_name) const;
void get_animation_list(List<StringName> *p_animations) const;
bool has_animation(const StringName &p_name) const;
void animation_set_next(const StringName &p_animation, const StringName &p_next);
StringName animation_get_next(const StringName &p_animation) const;
void set_blend_time(const StringName &p_animation1, const StringName &p_animation2, double p_time);
double get_blend_time(const StringName &p_animation1, const StringName &p_animation2) const;
void animation_set_next(const StringName &p_animation, const StringName &p_next);
StringName animation_get_next(const StringName &p_animation) const;
void set_default_blend_time(double p_default);
double get_default_blend_time() const;
@ -380,11 +161,9 @@ public:
void stop(bool p_keep_state = false);
bool is_playing() const;
String get_current_animation() const;
void set_current_animation(const String &p_anim);
void set_current_animation(const String &p_animation);
String get_assigned_animation() const;
void set_assigned_animation(const String &p_anim);
void set_active(bool p_active);
bool is_active() const;
void set_assigned_animation(const String &p_animation);
bool is_valid() const;
void set_speed_scale(float p_speed);
@ -394,46 +173,23 @@ public:
void set_autoplay(const String &p_name);
String get_autoplay() const;
void set_reset_on_save_enabled(bool p_enabled);
bool is_reset_on_save_enabled() const;
void set_process_callback(AnimationProcessCallback p_mode);
AnimationProcessCallback get_process_callback() const;
void set_method_call_mode(AnimationMethodCallMode p_mode);
AnimationMethodCallMode get_method_call_mode() const;
void set_audio_max_polyphony(int p_audio_max_polyphony);
int get_audio_max_polyphony() const;
void set_movie_quit_on_finish_enabled(bool p_enabled);
bool is_movie_quit_on_finish_enabled() const;
void seek(double p_time, bool p_update = false);
void seek_delta(double p_time, double p_delta);
void seek(double p_time, bool p_update = false, bool p_update_only = false);
double get_current_animation_position() const;
double get_current_animation_length() const;
void advance(double p_time);
void set_root(const NodePath &p_root);
NodePath get_root() const;
void clear_caches(); ///< must be called by hand if an animation was modified after added
void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#ifdef TOOLS_ENABLED
Ref<AnimatedValuesBackup> backup_animated_values(Node *p_root_override = nullptr);
Ref<AnimatedValuesBackup> apply_reset(bool p_user_initiated = false);
bool can_apply_reset() const;
#endif
AnimationPlayer();
~AnimationPlayer();
};
#ifndef DISABLE_DEPRECATED
VARIANT_ENUM_CAST(AnimationPlayer::AnimationProcessCallback);
VARIANT_ENUM_CAST(AnimationPlayer::AnimationMethodCallMode);
#endif // DISABLE_DEPRECATED
#endif // ANIMATION_PLAYER_H

View file

@ -0,0 +1,64 @@
/**************************************************************************/
/* animation_tree.compat.inc */
/**************************************************************************/
/* 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. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
void AnimationTree::_set_process_callback_bind_compat_80813(AnimationTree::AnimationProcessCallback p_mode) {
set_callback_mode_process(static_cast<AnimationMixer::AnimationCallbackModeProcess>(static_cast<int>(p_mode)));
}
AnimationTree::AnimationProcessCallback AnimationTree::_get_process_callback_bind_compat_80813() const {
return static_cast<AnimationProcessCallback>(static_cast<int>(get_callback_mode_process()));
}
void AnimationTree::_set_tree_root_bind_compat_80813(const Ref<AnimationNode> &p_root) {
const Ref<AnimationRootNode> rn = Ref<AnimationRootNode>(p_root.ptr());
if (rn.is_null()) {
return;
}
return (set_root_animation_node(rn));
}
Ref<AnimationNode> AnimationTree::_get_tree_root_bind_compat_80813() const {
const Ref<AnimationRootNode> rn = Ref<AnimationNode>(get_root_animation_node().ptr());
return rn;
}
void AnimationTree::_bind_compatibility_methods() {
ClassDB::bind_method(D_METHOD("set_process_callback", "mode"), &AnimationTree::_set_process_callback_bind_compat_80813);
ClassDB::bind_method(D_METHOD("get_process_callback"), &AnimationTree::_get_process_callback_bind_compat_80813);
ClassDB::bind_compatibility_method(D_METHOD("set_tree_root", "root"), &AnimationTree::_set_tree_root_bind_compat_80813);
ClassDB::bind_compatibility_method(D_METHOD("get_tree_root"), &AnimationTree::_get_tree_root_bind_compat_80813);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_PHYSICS);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_IDLE);
BIND_ENUM_CONSTANT(ANIMATION_PROCESS_MANUAL);
}
#endif // DISABLE_DEPRECATED

File diff suppressed because it is too large Load diff

View file

@ -31,24 +31,22 @@
#ifndef ANIMATION_TREE_H
#define ANIMATION_TREE_H
#include "animation_player.h"
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "animation_mixer.h"
#include "scene/resources/animation.h"
#include "scene/resources/audio_stream_polyphonic.h"
#define HUGE_LENGTH 31540000 // 31540000 seconds mean 1 year... is it too long? It must be longer than any Animation length and Transition xfade time to prevent time inversion.
#define HUGE_LENGTH 31540000 // 31540000 seconds mean 1 year... is it too long? It must be longer than any Animation length and Transition xfade time to prevent time inversion for AnimationNodeStateMachine.
class AnimationNodeBlendTree;
class AnimationNodeStartState;
class AnimationNodeEndState;
class AnimationPlayer;
class AnimationTree;
class AnimationNode : public Resource {
GDCLASS(AnimationNode, Resource);
public:
friend class AnimationTree;
enum FilterAction {
FILTER_IGNORE,
FILTER_PASS,
@ -60,61 +58,47 @@ public:
String name;
};
bool closable = false;
Vector<Input> inputs;
friend class AnimationTree;
struct AnimationState {
Ref<Animation> animation;
double time = 0.0;
double delta = 0.0;
Vector<real_t> track_blends;
real_t blend = 0.0;
bool seeked = false;
bool is_external_seeking = false;
Animation::LoopedFlag looped_flag = Animation::LOOPED_FLAG_NONE;
};
struct State {
int track_count = 0;
HashMap<NodePath, int> track_map;
List<AnimationState> animation_states;
bool valid = false;
AnimationPlayer *player = nullptr;
AnimationTree *tree = nullptr;
String invalid_reasons;
uint64_t last_pass = 0;
};
Vector<real_t> blends;
State *state = nullptr;
bool is_testing = false;
double _pre_process(const StringName &p_base_path, AnimationNode *p_parent, State *p_state, double p_time, bool p_seek, bool p_is_external_seeking, const Vector<StringName> &p_connections, bool p_test_only = false);
//all this is temporary
StringName base_path;
Vector<StringName> connections;
AnimationNode *parent = nullptr;
HashMap<NodePath, bool> filter;
bool filter_enabled = false;
bool closable = false;
// Temporary state for blending process which needs to be stored in each AnimationNodes.
struct NodeState {
StringName base_path;
AnimationNode *parent = nullptr;
Vector<StringName> connections;
Vector<real_t> track_weights;
} node_state;
// Temporary state for blending process which needs to be started in the AnimationTree, pass through the AnimationNodes, and then return to the AnimationTree.
struct ProcessState {
AnimationTree *tree = nullptr;
HashMap<NodePath, int> track_map; // TODO: Is there a better way to manage filter/tracks?
bool is_testing = false;
bool valid = false;
String invalid_reasons;
uint64_t last_pass = 0;
} *process_state = nullptr;
Array _get_filters() const;
void _set_filters(const Array &p_filters);
friend class AnimationNodeBlendTree;
double _blend_node(const StringName &p_subpath, const Vector<StringName> &p_connections, AnimationNode *p_new_parent, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, real_t *r_max = nullptr, bool p_test_only = false);
double _blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationNode *p_new_parent, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false, real_t *r_activity = nullptr);
double _pre_process(ProcessState *p_process_state, AnimationMixer::PlaybackInfo p_playback_info);
protected:
virtual double _process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false);
double process(double p_time, bool p_seek, bool p_is_external_seeking, bool p_test_only = false);
virtual double _process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
double process(const AnimationMixer::PlaybackInfo p_playback_info, bool p_test_only = false);
void blend_animation(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
double blend_node(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
double blend_input(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
void blend_animation(const StringName &p_animation, AnimationMixer::PlaybackInfo p_playback_info);
double blend_node(Ref<AnimationNode> p_node, const StringName &p_subpath, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
double blend_input(int p_input, AnimationMixer::PlaybackInfo p_playback_info, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
// Bind-able methods to expose for compatibility, moreover AnimationMixer::PlaybackInfo is not exposed.
void blend_animation_ex(const StringName &p_animation, double p_time, double p_delta, bool p_seeked, bool p_is_external_seeking, real_t p_blend, Animation::LoopedFlag p_looped_flag = Animation::LOOPED_FLAG_NONE);
double blend_node_ex(const StringName &p_sub_path, Ref<AnimationNode> p_node, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
double blend_input_ex(int p_input, double p_time, bool p_seek, bool p_is_external_seeking, real_t p_blend, FilterAction p_filter = FILTER_IGNORE, bool p_sync = true, bool p_test_only = false);
void make_invalid(const String &p_reason);
AnimationTree *get_animation_tree() const;
@ -175,7 +159,7 @@ public:
VARIANT_ENUM_CAST(AnimationNode::FilterAction)
//root node does not allow inputs
// Root node does not allow inputs.
class AnimationRootNode : public AnimationNode {
GDCLASS(AnimationRootNode, AnimationNode);
@ -196,240 +180,102 @@ class AnimationNodeEndState : public AnimationRootNode {
GDCLASS(AnimationNodeEndState, AnimationRootNode);
};
class AnimationTree : public Node {
GDCLASS(AnimationTree, Node);
void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred);
class AnimationTree : public AnimationMixer {
GDCLASS(AnimationTree, AnimationMixer);
#ifndef DISABLE_DEPRECATED
public:
enum AnimationProcessCallback {
ANIMATION_PROCESS_PHYSICS,
ANIMATION_PROCESS_IDLE,
ANIMATION_PROCESS_MANUAL,
};
#endif // DISABLE_DEPRECATED
private:
struct TrackCache {
bool root_motion = false;
uint64_t setup_pass = 0;
Animation::TrackType type = Animation::TrackType::TYPE_ANIMATION;
Object *object = nullptr;
ObjectID object_id;
TrackCache() {
}
virtual ~TrackCache() {}
};
struct TrackCacheTransform : public TrackCache {
#ifndef _3D_DISABLED
Node3D *node_3d = nullptr;
Skeleton3D *skeleton = nullptr;
#endif // _3D_DISABLED
int bone_idx = -1;
bool loc_used = false;
bool rot_used = false;
bool scale_used = false;
Vector3 init_loc = Vector3(0, 0, 0);
Quaternion init_rot = Quaternion(0, 0, 0, 1);
Vector3 init_scale = Vector3(1, 1, 1);
Vector3 loc;
Quaternion rot;
Vector3 scale;
TrackCacheTransform() {
type = Animation::TYPE_POSITION_3D;
}
};
struct RootMotionCache {
Vector3 loc = Vector3(0, 0, 0);
Quaternion rot = Quaternion(0, 0, 0, 1);
Vector3 scale = Vector3(1, 1, 1);
};
struct TrackCacheBlendShape : public TrackCache {
MeshInstance3D *mesh_3d = nullptr;
float init_value = 0;
float value = 0;
int shape_index = -1;
TrackCacheBlendShape() { type = Animation::TYPE_BLEND_SHAPE; }
};
struct TrackCacheValue : public TrackCache {
Variant init_value;
Variant value;
Vector<StringName> subpath;
bool is_discrete = false;
bool is_using_angle = false;
TrackCacheValue() { type = Animation::TYPE_VALUE; }
};
struct TrackCacheMethod : public TrackCache {
TrackCacheMethod() { type = Animation::TYPE_METHOD; }
};
struct TrackCacheBezier : public TrackCache {
real_t init_value = 0.0;
real_t value = 0.0;
Vector<StringName> subpath;
TrackCacheBezier() {
type = Animation::TYPE_BEZIER;
}
};
// Audio stream information for each audio stream placed on the track.
struct PlayingAudioStreamInfo {
AudioStreamPlaybackPolyphonic::ID index = -1; // ID retrieved from AudioStreamPlaybackPolyphonic.
double start = 0.0;
double len = 0.0;
};
// Audio track information for mixng and ending.
struct PlayingAudioTrackInfo {
HashMap<int, PlayingAudioStreamInfo> stream_info;
double length = 0.0;
double time = 0.0;
real_t volume = 0.0;
bool loop = false;
bool backward = false;
bool use_blend = false;
};
struct TrackCacheAudio : public TrackCache {
Ref<AudioStreamPolyphonic> audio_stream;
Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Key is Animation resource ObjectID.
TrackCacheAudio() {
type = Animation::TYPE_AUDIO;
}
};
struct TrackCacheAnimation : public TrackCache {
bool playing = false;
TrackCacheAnimation() {
type = Animation::TYPE_ANIMATION;
}
};
RootMotionCache root_motion_cache;
HashMap<NodePath, TrackCache *> track_cache;
HashSet<TrackCache *> playing_caches;
Vector<Node *> playing_audio_stream_players;
Ref<AnimationNode> root;
Ref<AnimationRootNode> root_animation_node;
NodePath advance_expression_base_node = NodePath(String("."));
AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
bool active = false;
NodePath animation_player;
int audio_max_polyphony = 32;
AnimationNode::State state;
bool cache_valid = false;
void _node_removed(Node *p_node);
void _setup_animation_player();
void _animation_player_changed();
void _clear_caches();
void _clear_playing_caches();
void _clear_audio_streams();
bool _update_caches(AnimationPlayer *player);
void _process_graph(double p_delta);
uint64_t setup_pass = 1;
AnimationNode::ProcessState process_state;
uint64_t process_pass = 1;
bool started = true;
NodePath root_motion_track;
Vector3 root_motion_position = Vector3(0, 0, 0);
Quaternion root_motion_rotation = Quaternion(0, 0, 0, 1);
Vector3 root_motion_scale = Vector3(0, 0, 0);
Vector3 root_motion_position_accumulator = Vector3(0, 0, 0);
Quaternion root_motion_rotation_accumulator = Quaternion(0, 0, 0, 1);
Vector3 root_motion_scale_accumulator = Vector3(1, 1, 1);
friend class AnimationNode;
bool properties_dirty = true;
void _tree_changed();
void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name);
void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node);
void _update_properties();
List<PropertyInfo> properties;
HashMap<StringName, HashMap<StringName, StringName>> property_parent_map;
HashMap<ObjectID, StringName> property_reference_map;
HashMap<StringName, Pair<Variant, bool>> property_map; // Property value and read-only flag.
bool properties_dirty = true;
void _update_properties();
void _update_properties_for_node(const String &p_base_path, Ref<AnimationNode> p_node);
void _tree_changed();
void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name);
void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node);
struct Activity {
uint64_t last_pass = 0;
real_t activity = 0.0;
};
HashMap<StringName, Vector<Activity>> input_activity_map;
HashMap<StringName, Vector<Activity> *> input_activity_map_get;
void _update_properties_for_node(const String &p_base_path, Ref<AnimationNode> node);
NodePath animation_player;
ObjectID last_animation_player;
void _setup_animation_player();
void _animation_player_changed();
protected:
bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const;
virtual void _validate_property(PropertyInfo &p_property) const override;
void _notification(int p_what);
static void _bind_methods();
GDVIRTUAL5RC(Variant, _post_process_key_value, Ref<Animation>, int, Variant, Object *, int);
Variant post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1);
virtual Variant _post_process_key_value(const Ref<Animation> &p_anim, int p_track, Variant p_value, const Object *p_object, int p_object_idx = -1);
virtual void _set_active(bool p_active) override;
// Make animation instances.
virtual bool _blend_pre_process(double p_delta, int p_track_count, const HashMap<NodePath, int> &p_track_map) override;
#ifndef DISABLE_DEPRECATED
void _set_process_callback_bind_compat_80813(AnimationProcessCallback p_mode);
AnimationProcessCallback _get_process_callback_bind_compat_80813() const;
void _set_tree_root_bind_compat_80813(const Ref<AnimationNode> &p_root);
Ref<AnimationNode> _get_tree_root_bind_compat_80813() const;
static void _bind_compatibility_methods();
#endif // DISABLE_DEPRECATED
public:
void set_tree_root(const Ref<AnimationNode> &p_root);
Ref<AnimationNode> get_tree_root() const;
void set_active(bool p_active);
bool is_active() const;
void set_process_callback(AnimationProcessCallback p_mode);
AnimationProcessCallback get_process_callback() const;
void set_animation_player(const NodePath &p_player);
void set_animation_player(const NodePath &p_path);
NodePath get_animation_player() const;
void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node);
NodePath get_advance_expression_base_node() const;
void set_root_animation_node(const Ref<AnimationRootNode> &p_animation_node);
Ref<AnimationRootNode> get_root_animation_node() const;
void set_audio_max_polyphony(int p_audio_max_polyphony);
int get_audio_max_polyphony() const;
void set_advance_expression_base_node(const NodePath &p_path);
NodePath get_advance_expression_base_node() const;
PackedStringArray get_configuration_warnings() const override;
bool is_state_invalid() const;
String get_invalid_state_reason() const;
void set_root_motion_track(const NodePath &p_track);
NodePath get_root_motion_track() const;
Vector3 get_root_motion_position() const;
Quaternion get_root_motion_rotation() const;
Vector3 get_root_motion_scale() const;
Vector3 get_root_motion_position_accumulator() const;
Quaternion get_root_motion_rotation_accumulator() const;
Vector3 get_root_motion_scale_accumulator() const;
real_t get_connection_activity(const StringName &p_path, int p_connection) const;
void advance(double p_time);
uint64_t get_last_process_pass() const;
AnimationTree();
~AnimationTree();
};
VARIANT_ENUM_CAST(AnimationTree::AnimationProcessCallback)
#ifndef DISABLE_DEPRECATED
VARIANT_ENUM_CAST(AnimationTree::AnimationProcessCallback);
#endif // DISABLE_DEPRECATED
#endif // ANIMATION_TREE_H

View file

@ -33,12 +33,12 @@
#include "scene/animation/animation_tree.h"
#include "scene/resources/material.h"
void RootMotionView::set_animation_path(const NodePath &p_path) {
void RootMotionView::set_animation_mixer(const NodePath &p_path) {
path = p_path;
first = true;
}
NodePath RootMotionView::get_animation_path() const {
NodePath RootMotionView::get_animation_mixer() const {
return path;
}
@ -93,20 +93,20 @@ void RootMotionView::_notification(int p_what) {
if (has_node(path)) {
Node *node = get_node(path);
AnimationTree *tree = Object::cast_to<AnimationTree>(node);
if (tree && tree->is_active() && tree->get_root_motion_track() != NodePath()) {
if (is_processing_internal() && tree->get_process_callback() == AnimationTree::ANIMATION_PROCESS_PHYSICS) {
AnimationMixer *mixer = Object::cast_to<AnimationMixer>(node);
if (mixer && mixer->is_active() && mixer->get_root_motion_track() != NodePath()) {
if (is_processing_internal() && mixer->get_callback_mode_process() == AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_PHYSICS) {
set_process_internal(false);
set_physics_process_internal(true);
}
if (is_physics_processing_internal() && tree->get_process_callback() == AnimationTree::ANIMATION_PROCESS_IDLE) {
if (is_physics_processing_internal() && mixer->get_callback_mode_process() == AnimationMixer::ANIMATION_CALLBACK_MODE_PROCESS_IDLE) {
set_process_internal(true);
set_physics_process_internal(false);
}
transform.origin = tree->get_root_motion_position();
transform.basis = tree->get_root_motion_rotation(); // Scale is meaningless.
diff = tree->get_root_motion_rotation_accumulator();
transform.origin = mixer->get_root_motion_position();
transform.basis = mixer->get_root_motion_rotation(); // Scale is meaningless.
diff = mixer->get_root_motion_rotation_accumulator();
}
}
@ -170,8 +170,8 @@ AABB RootMotionView::get_aabb() const {
}
void RootMotionView::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_path);
ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_path);
ClassDB::bind_method(D_METHOD("set_animation_path", "path"), &RootMotionView::set_animation_mixer);
ClassDB::bind_method(D_METHOD("get_animation_path"), &RootMotionView::get_animation_mixer);
ClassDB::bind_method(D_METHOD("set_color", "color"), &RootMotionView::set_color);
ClassDB::bind_method(D_METHOD("get_color"), &RootMotionView::get_color);
@ -185,7 +185,7 @@ void RootMotionView::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_zero_y", "enable"), &RootMotionView::set_zero_y);
ClassDB::bind_method(D_METHOD("get_zero_y"), &RootMotionView::get_zero_y);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationTree"), "set_animation_path", "get_animation_path");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "animation_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "AnimationMixer"), "set_animation_path", "get_animation_path");
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater,suffix:m"), "set_cell_size", "get_cell_size");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.1,16,0.01,or_greater,suffix:m"), "set_radius", "get_radius");

View file

@ -55,8 +55,8 @@ private:
static void _bind_methods();
public:
void set_animation_path(const NodePath &p_path);
NodePath get_animation_path() const;
void set_animation_mixer(const NodePath &p_path);
NodePath get_animation_mixer() const;
void set_color(const Color &p_color);
Color get_color() const;

View file

@ -74,6 +74,7 @@
#include "scene/animation/animation_blend_space_1d.h"
#include "scene/animation/animation_blend_space_2d.h"
#include "scene/animation/animation_blend_tree.h"
#include "scene/animation/animation_mixer.h"
#include "scene/animation/animation_node_state_machine.h"
#include "scene/animation/animation_player.h"
#include "scene/animation/animation_tree.h"
@ -454,8 +455,6 @@ void register_scene_types() {
#endif
/* REGISTER ANIMATION */
GDREGISTER_CLASS(AnimationPlayer);
GDREGISTER_CLASS(Tween);
GDREGISTER_ABSTRACT_CLASS(Tweener);
GDREGISTER_CLASS(PropertyTweener);
@ -463,6 +462,8 @@ void register_scene_types() {
GDREGISTER_CLASS(CallbackTweener);
GDREGISTER_CLASS(MethodTweener);
GDREGISTER_ABSTRACT_CLASS(AnimationMixer);
GDREGISTER_CLASS(AnimationPlayer);
GDREGISTER_CLASS(AnimationTree);
GDREGISTER_CLASS(AnimationNode);
GDREGISTER_CLASS(AnimationRootNode);

View file

@ -44,7 +44,7 @@ class AnimationLibrary : public Resource {
void _animation_changed(const StringName &p_name);
friend class AnimationPlayer; //for faster access
friend class AnimationMixer; // For faster access.
HashMap<StringName, Ref<Animation>> animations;
protected: