godotengine / godot

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

Set Animation Save Paths breaks looping #75912

Open rcorre opened 1 year ago

rcorre commented 1 year ago

Godot version

4.0.dev

System information

Linux

Issue description

Importing animations to .res files removes the looping attribute that was inferred from the "-loop" suffix

Steps to reproduce

  1. Open a .glb model that contains animations with the -loop suffix as a new inherited scene
  2. Verify that animations loop
  3. Open the advanced import settings
  4. Click Actions > Set Import Save Paths
  5. Select a folder
  6. Reimport
  7. Reopen the model as an inherited scene
  8. See that animations no longer loop

Minimal reproduction project

example.zip

MossFrog commented 1 year ago

You need to save the model as a local resource in the project preferably as a .tscn file, you can then remove the source .glb from the project directory and use the model locally. Any changes made to the animation tracks including looping will be saved then. Simply create a new scene, chuck the model in there and then right click -> make local

rcorre commented 1 year ago

Saving the model as a local resource would break the inheritance relationship with the GLTF model. I want to be able to make future changes to the model and reimport it while keeping animation tracks I've added. The setting "Keep on Reimport" seems to imply this is possible. Is that not the case?

rcorre commented 1 year ago

In Godot3, an animation named "run-loop" in GLTF would be called "run-loop" in Godot, and would loop. In Godot4, it removes the "-loop" suffix, but still sets it to loop. I'm guessing that when you save the animations to ".scn" files, it loses the loop information which is no longer encoded in the name.

MossFrog commented 1 year ago

I usually just set the model up again after making changes keeping any custom nodes I created, if you are using scripts and or animation trees to control the model you can just re-attach them to the model/animation player node after re-importing and replacing the old model. It is cumbersome but it suffices for the time being. This issue persists in versions 3.x too, if you set an animation to loop within the editor, if the resource has not been localized it will lose that information upon restart.

dragonforge-dev commented 2 months ago

I was having the same problem with importing models. As a workaround, I have found that you can run the Advanced import and save all the external files, and then just click the Reimport button in the Import tab. This worked most of the time for me. Though I had a post import script running to set all the loops. I've attached the import script in case seeing it is helpful for anyone else encountering the problem.

@tool
extends EditorScenePostImport

enum LoopMode { LOOP_NONE, LOOP_LINEAR, LOOP_PINGPONG }
const LOOPMODE = ["LOOP_NONE", "[b][color=green]LOOP_LINEAR[/color][/b]", "[b][color=orange]LOOP_PINGPONG[/color][/b]"]
var scene_name: StringName

# Import script for characters to set up everything so no editing is needed once the character is
# instantiated.
func _post_import(scene):
    scene_name = scene.name
    print_rich("\n[b][color=red]Begin Post-import[/color] -> [color=purple]%s[/color] as [color=green]%s[/color][/b]" % [scene_name, scene.get_class()])
    print_rich("[b]Time/Date Stamp: %s[/b]\n" % [Time.get_datetime_string_from_system(false, true)])
    iterate(scene)
    return scene

func iterate(node):
    if node != null:
        # Put the character on the character layer (5) and make sure they can't walk through
        # walls (2) or destructibles (3), they can pick up loot (4), and they will bump into
        # other players (5), NPCs (6), and enemies (7).
        if node is CharacterBody3D:
            node.set_collision_layer_value(1, false)
            node.set_collision_layer_value(5, true)
            for i in range(2,8):
                node.set_collision_mask_value(i, true)
                print_rich("Post-import: [b]Masked Layer [color=green]%s[/color] [color=yellow]%s[/color][/b] -> [color=yellow][b]%s[/b][/color]" % [node.get_class(), i, get_color_string(true)])
        # Add looping animations 
        if node is AnimationPlayer:
            for animation_name in node.get_animation_list():
                if animation_name.contains("Idle") or animation_name.contains("ing") and not animation_name.contains("Standing"):
                    node.get_animation(animation_name).set_loop_mode(LoopMode.LOOP_LINEAR)
                print_rich("Post-import: [b]%s[/b] -> [b]%s[/b]" % [animation_name, LOOPMODE[node.get_animation(animation_name).get_loop_mode()]])
        # Append "_Bone" to BoneAttachment3D nodes just to prevent duplicate names in our scene tree.
        if node is BoneAttachment3D:
            node.name = node.name + "_Bone"
            print_rich("Post-import: [b]Renamed [color=green]%s[/color] [color=yellow]%s[/color][/b] -> [color=yellow][b]%s[/b][/color]" % [node.get_class(), node.name, node.name + "_Bone"])
        # We want to hide everything in the character's hands, and everything they are wearing that is removeable.
        if node is MeshInstance3D and node.get_parent() is BoneAttachment3D:
            node.set_visible(false)
            print_rich("Post-import: [b]Visibile [color=green]%s[/color] [color=yellow]%s[/color][/b] -> [color=yellow][b]%s[/b][/color]" % [node.get_class(), node.name, get_color_string(node.visible)])
        # Rotating the model to face the other direction so it moves in the correct direction when animated.
        if node is Skeleton3D:
            node.rotate_y(deg_to_rad(-180.0))
            print_rich("Post-import: [b]Rotated [color=green]%s[/color] [color=yellow]-180 degrees[/color][/b] on the [b][color=green]y-axis[/color][/b]" % [node.get_class()])
        # Recursively call this function on any child nodes that exist.
        for child in node.get_children():
            iterate(child)

# Return rich text color string for true (green)/red (false).
func get_color_string(value: bool):
    if value:
        return "[color=green]true[/color]"
    else:
        return "[color=red]false[/color]"