godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
88.17k stars 19.96k forks source link

Functions in AnimationPlayer's Call Method Tracks not called in the editor #75479

Open GNSS-Stylist opened 1 year ago

GNSS-Stylist commented 1 year ago

Godot version

v4.1.dev.custom_build [c29866dbc], also 4.0.1 stable

System information

Windows 10

Issue description

Functions in AnimationPlayer's Method Call Tracks seem to never be called when using the editor. Video:

https://user-images.githubusercontent.com/50438441/228674782-27a6a2fa-4fd1-46bb-b57d-7fd6b59f8a26.mp4

I first stumbled upon this when trying to add a function to RESET-animation to get rid of some resources generated using tool-scripts unnecessarily bloating the tscn-file. First I wasn't sure if the Method Call Tracks are even supposed to work on tool-scripts, but for example according to the original RESET-track proposal (https://github.com/godotengine/godot-proposals/issues/1597) they apparently are supposed to work.

I tried to fix this myself, and got it working somewhat by removing Engine::get_singleton()->is_editor_hint()-check from void AnimationPlayer::_animation_process_animation-function, case Animation::TYPE_METHOD (file scene/animation/animation_player.cpp). Although the functions were called after the change when running the animations, I didn't manage to get them to run (called from RESET-track) before saving the scene. Changing the method call mode to immediate didn't fix the problem.

A bit hacky way to work around this is to use a setter (also shown in the video). Setter is also called before saving when called from a RESET-track.

There is another problem with setters not able to set sub-node's properties (in this case $Label_Setter.text) when loading the scene (probably related to creation order of nodes?). Is this a feature rather than a bug? This is also added as a comment in the MRP's code (and seen in the video). If this is a bug I can create an issue about it (or if anyone wants to create it, feel free to do so). If the null-check is removed this error is shown: _Node not found: "LabelSetter" (relative to "Node2D"). res://Main.gd:15 - Invalid set index 'text' (on base: 'null instance') with value of type 'String'.

Steps to reproduce

Add a Method Call Track to an animation, functions to it and play it in the editor. Functions will not be called.

Minimal reproduction project

AnimationPlayerToolFunctionBug.zip

TokageItLab commented 1 year ago

This is the intended behavior so as not to break the editor. However, it has been pointed out previously that this is an inconvenient limitation for functions like Particle that are previewed by a function. See also https://github.com/godotengine/godot/issues/73043.

berarma commented 4 months ago

It would be very convenient to preview work in the editor. Couldn't we make it call the methods only when the script is in tool mode? Additionally, a tool mode could be added to the track itself but I don't think it's really needed.

kravohi commented 1 month ago

i found a solid work around. i gave the animation_started signal idea a try. i'm using godot 3.5 give the AnimationPlayer this script and it should be able to call any function you put on its method track.

extends AnimationPlayer 
tool

func _ready():
    connect("animation_started", self, "animation_started_func")

const EDITOR_PATH = "/root/EditorNode/@@596/@@597/@@605/@@607/@@611/@@615/@@616/@@617/@@633/@@634/@@643/@@644/@@6618/@@6450/@@6451/@@6452/@@6453/@@6454/@@6455/ViewportContainer/"
var function_calls = []
func animation_started_func(dismiss):
#   print("animation_started_func(dismiss: %s)" % dismiss)
    var song_animation = get_animation("song")
#   print(" %s" % song_animation)
    for track_index in song_animation.get_track_count():

        if song_animation.track_get_type(track_index) == Animation.TYPE_METHOD:
            var path = EDITOR_PATH + song_animation.track_get_path(track_index)
#           print("     track_index: %s" % track_index)
#           print("     path: %s" % path)
            var track_key_count = song_animation.track_get_key_count(track_index)
            for key_index in track_key_count:
                var time = song_animation.track_get_key_time(track_index, key_index)
                if time >= current_animation_position:
#                   print("         key_index: %s" % key_index)
                    var _name = song_animation.method_track_get_name(track_index, key_index)
                    var params = song_animation.method_track_get_name(track_index, key_index)
#                   print("             name: %s" % _name)
#                   print("             time: %s" % time)
                    function_calls.append({
                        "path": path,
                        "name": _name,
                        "time": time
                    })

                else:
#                   print('         ...')
        else:
#           print("     ...")

func _process(delta):

    if is_playing():
        var remove_these_function_calls = []

        for i in function_calls.size():
            var function_call = function_calls[i]
            if function_call.time <= current_animation_position:
#               print("AnimationPlayer is calling the function of another node.")
#               print(" path: %s" % function_call.path)
#               print(" name: %s" % function_call.name)
                var node = get_node(function_call.path)
                node.call(function_call.name)
                remove_these_function_calls.append(i)

        for index in remove_these_function_calls:
#           print("AnimationPlayer is removing function call from its queue.")
            function_calls.remove(index)

NOTE: your EDITOR_PATH will probably need to be different than mine. i set mine from reading the error codes and finding what the AnimationPlayer's path was.