How to set ALL 3D animation tracks to "nearest" interpolation?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Ian_Kovich

Hello, everyone! I’m experimenting with art and animation styles in Godot and am trying to achieve a result similar to Arc System Works’ Guilty Gear Xrd where the models are animated frame-by-frame rather than interpolated over time.

Example:

I’m very close, but there’s one problem: any type of 3D model with animations imported into Godot is automatically set to “linear” interpolation instead of “nearest.” In Blender, if I animate a model using constant interpolation for the keyframes, I get the desired result, but Godot will still import and set all 3D transformation tracks to linear and the effect is lost.

The obvious solution is to manually set the tracks to nearest, but a completed character model can have dozens if not hundreds of bones, so doing all of that manually for every character for every animation can be a nightmare, especially if I wind up updating a model or an animation and everything gets reset.

I found some solutions on my own, but they still require some work; I believe that there ought to be an option somewhere that will simply set all 3D transformation tracks to nearest with a click of a button, but I can’t seem to find it anywhere. If anyone knows how to do so or where I can make a request for such a feature, I would greatly appreciate it. Thank you in advance!

:bust_in_silhouette: Reply From: johnygames

I struggled a bit to find the solution, but here it is:

Briefly explained:
What you want to do is access the AnimationPlayer’s Animations. Then you want to access the respective Animations’ tracks and alter the INTERPOLATION_MODE property of each track. You can do this during runtime or, better yet, create a script that works from within the editor, without running the game. I am going to explain the latter.

Detailed Instructions

  1. Go to the Script workspace (near the 3D workspace on the top)

  2. Create a new script resource (File–> New Script)

  3. Write the following code in the newly created script:

tool     # makes script run from within the editor
extends EditorScript    # gives you access to editor functions



func _run():   # this is the main function
	var selection = get_editor_interface().get_selection() * the selected node. In your case, select the AnimationPlayer
	selection = selection.get_selected_nodes()  # get the actual AnimationPlayer node
	if selection.size()!=1 and not selection is AnimationPlayer: # if the wrong node is selected, do nothing
		return
	else:
		interpolation_change(selection) # run the funstion in question



func interpolation_change(selection):
	var anim_track_1 = selection[0].get_animation("default") # get the Animation that you are interested in (change "default" to your Animation's name)
	var count  = anim_track_1.get_track_count() # get number of tracks (bones in your case)
	print(count)
	for i in count:
		
		anim_track_1.track_set_interpolation_type(i, 1) # change interpolation mode for every track
		print(anim_track_1.track_get_interpolation_type(i))
  1. Go to File–>Run. You should see the Interpolation mode change from linear to nearest.

How it works:

This script iterates through the tracks of your Animation and it changes the INTERPOLATION_MODE for each of them to 0 (nearest).

Check the doc fro more info here:

For more info on EditorScript, namely scripts that run from the editor, check this out:

If this helps you, please mark the answer as best.

That did it! Awesome work! This is much faster than every other solution I came up with. I hope that in the future there can just be a simple menu selection that will accomplish this rather than writing a script, but this is fantastic nonetheless. Thank you again!

—Edit!—
Here’s a video showing the script in action. The ball on the left is what happens when a frame-by-frame animation is imported from Blender to Godot. The ball on the right is what it’s supposed to look like and what happens when the script is run.
https://www.youtube.com/watch?v=qb_8bl4NvuI&feature=youtu.be

Ian_Kovich | 2019-12-28 21:32

I am glad that it worked and I’d be happy to see what beautiful art you create with it :slight_smile:

johnygames | 2019-12-28 22:30

:bust_in_silhouette: Reply From: SalgueiroAzul

Hello! Sorry for the bump, just wanted to add a bit of code to the immensely useful comment above.

I found it a bit annoying having to type each animation name before running the script.
Especially since I want to reimport my animations often and have to change the interpolation every time.

So I made an Array with all the animation names. Then the function repeats until all the animations are done!

Should be pretty easy to use for your own projects! Just change the name of the animations.

tool     # makes script run from within the editor
extends EditorScript    # gives you access to editor functions

var a1 = "Fall"
var a2 = "FallLand"
var a3 = "Idle"
var a4 = "Jump"
var a5 = "PunchA1a"
var a6 = "Run2a"
var a7 = "RunStop"
var a8 = "Stance"

var name = a1 ### The selected animation
var n = 0 ### The position in the Array
var array = [a1, a2, a3, a4, a5, a6, a7, a8]


func _run():   # this is the main function
    var selection = get_editor_interface().get_selection() # the selected node. In your case, select the AnimationPlayer
    selection = selection.get_selected_nodes()  # get the actual AnimationPlayer node
    if selection.size()!=1 and not selection is AnimationPlayer: # if the wrong node is selected, do nothing
        return
    else:
        interpolation_change(selection) # run the funstion in question


func interpolation_change(selection):
	
	name = array[n] ### Set the selected animation to one of the array's elements
	print(name)
	
	var anim_track_1 = selection[0].get_animation(name) # get the Animation that you are interested in (change "default" to your Animation's name)
	var count  = anim_track_1.get_track_count() # get number of tracks (bones in your case)
	#print(count)
	for i in count:

		anim_track_1.track_set_interpolation_type(i, 0) # change interpolation mode for every track
		#print(anim_track_1.track_get_interpolation_type(i))
	
	n += 1 ### Moves to the next element in the array
	print(n)
	
	if n < array.size(): ### If the selected animation is not the last of the array, repeat the function
		interpolation_change(selection)

Hello, old tread i know, but ive made some changes to use a loop based on all the animations found on the animation player, instead of naming them all individually, also work with godot 4:

@tool     # makes script run from within the editor 
extends EditorScript    # gives you access to editor functions

func _run():   # this is the main function
	var selection = get_editor_interface().get_selection()
	selection = selection.get_selected_nodes()  # get the actual AnimationPlayer node
	if selection.size()!=1 and not selection is AnimationPlayer: # if the wrong node is selected, do nothing
		return
	else:
		var animlist = selection[0].get_animation_list() # finds every animation on the selected AnimationPlayer
		print("animations found: "+str(animlist))
		for anm in animlist: # loops between every animation and apply the fix
			interpolation_change(selection,anm)

func interpolation_change(selection,strnum):
	var anim_track_1 = selection[0].get_animation(strnum) # get the Animation that you are interested in (change "default" to your Animation's name)
	var count  = anim_track_1.get_track_count() # get number of tracks (bones in your case)
	print("fixed "+strnum)
	for i in count:
		anim_track_1.track_set_interpolation_type(i, 0) # change interpolation mode for every track

Lady Strella | 2023-05-27 14:43

Yes! That’s incredibly useful!

Thank you so much for sharing :slight_smile:

SalgueiroAzul | 2023-05-29 15:45