Hi everyone, I would appreciate some help with an issue that I’m having. I’ll try to add as much information to this post as possible.
For background context, I’m learning Godot by making a platformer game in Godot 4.4, and I’m trying to create a special scene with exported variables as a way to automate the creation of moving platforms. To accomplish this, I am creating a new scene called PlatformMover
that extends AnimationPlayer
, with the @tool
annotation so that I can generate the required animation in the editor as well. This scene is designed to be added as a child node to Platform scenes in my game.
The issue that I’m facing is that even though my scene seems to work flawlessly in the editor and my platform can move correctly, when I’m running the game my platform remains stationary and does not move at all.
Here is the code that I have written for my exported variables:
@tool
class_name PlatformMover extends AnimationPlayer
# Value will be initialized in _ready
var animation_library: AnimationLibrary
@export var move_to_y: int:
set(val):
move_to_y = val
if Engine.is_editor_hint():
remake_animation()
@export var move_secs: float = 0:
set(val):
move_secs = val
if Engine.is_editor_hint():
remake_animation()
@export var wait_secs: float = 0:
set(val):
wait_secs = val
if Engine.is_editor_hint():
remake_animation()
move_to_y
is the final y-position of the platform after moving. move_secs
is the number of seconds the platform will use to move in a single direction (up or down). wait_secs
is the number of seconds that a platform will wait after reaching its configured start or end position before moving again.
In addition to this, there is a button to reload the animation, however I don’t think that this is relevant to my issue:
# Create a button to reload the animation settings. Because this was created based on the parent
# position, if the parent node is moved after the animation settings were created, this can
# change the parent's location back to the one before it was moved. So with this handy button we
# can reload the start position by regenerating the animation
@export_tool_button("Reload Start Position") var reload = reload_animation_settings.bind()
func reload_animation_settings() -> void:
remake_animation()
Based on the variables move_to_y
, move_secs
and wait_secs
, the intention of my script is to create an Animation
and dynamically generate the animation keys that will move the platform based on the desired parameters by updating the position
property of the parent Platform
scene. This is the code of the function that creates the animation:
func create_moving_animation() -> Animation:
var parent = get_parent()
var animation = Animation.new()
# Create the track for the movement animation
var track_index = animation.add_track(Animation.TYPE_VALUE)
# Set the property on the node that the animation will update. In this
# case, since we are updating the position of the parent node, we need
# to use the string "<parent_node_path>:position"
#var track_path = "%s:position" % parent.get_path()
var track_path = "../:position"
animation.track_set_path(track_index, track_path)
# Create variables for the various positions and timings that we will be adding
var start_position = parent.position
var end_position = start_position
end_position.y = move_to_y
var rolling_time_counter = 0.0
# Insert keyframes
animation.track_insert_key(track_index, 0.0, start_position)
# If wait, then set a new key that allows the platform to wait at the start position
if wait_secs > 0:
rolling_time_counter += wait_secs
animation.track_insert_key(track_index, rolling_time_counter, start_position)
rolling_time_counter += move_secs
animation.track_insert_key(track_index, rolling_time_counter, end_position)
# If wait, then set a new key that allows the platform to wait at the end position
if wait_secs > 0:
rolling_time_counter += wait_secs
animation.track_insert_key(track_index, rolling_time_counter, end_position)
rolling_time_counter += move_secs
animation.track_insert_key(track_index, rolling_time_counter, start_position)
animation.length = rolling_time_counter
animation.loop_mode = Animation.LoopMode.LOOP_LINEAR
# Debugging print statements
print("move_to_y is %s" % move_to_y)
print("move_secs is %s" % move_secs)
print("wait_secs is %s" % wait_secs)
for i in range(0, animation.track_get_key_count(track_index)):
print("key time is %s" % animation.track_get_key_time(track_index, i))
print("Key value is %s" % animation.track_get_key_value(track_index, i))
return animation
As shown in the code, I have also some print statements for debugging purposes. When I run the game, the following statements are printed:
move_to_y is -65
move_secs is 1.5
wait_secs is 1.0
key time is 0.0
Key value is (2514.0, 136.018)
key time is 1.0
Key value is (2514.0, 136.018)
key time is 2.5
Key value is (2514.0, -65.0)
key time is 3.5
Key value is (2514.0, -65.0)
key time is 5.0
Key value is (2514.0, 136.018)
Based on these statements, it appears to me that the animation keys have been correctly generated and added to the animation.
After the animation is created, it is added to my AnimationLibrary
with the following function:
func remake_animation() -> void:
print("Remaking")
var animation = create_moving_animation()
animation_library.remove_animation("move")
animation_library.add_animation("move", animation)
#if not Engine.is_editor_hint():
#play("animation_lib/move")
#print("Played!")
print("Remade")
In runtime, the “Remaking” and “Remade” statements are printed once, indicating that this function has run exactly once. The play
function is commented out here because it will be called in the _ready()
function instead.
This remake_animation()
function is automatically called in the setters for my exported variables. When I’m using the editor, I can see an animation generated under the Animation tool at the bottom of the screen, and I can play the animation and see it move my platform, as expected.
Finally, in order to run this code in both the editor and during the game’s runtime, I have the following code in the _ready()
function here:
func _ready() -> void:
animation_library = AnimationLibrary.new()
if not has_animation_library("animation_lib"):
add_animation_library("animation_lib", animation_library)
remake_animation()
if not Engine.is_editor_hint():
remake_animation()
if not is_playing():
play("animation_lib/move")
print("Played")
When i run the game, I see the “Played” statement printed, indicating that my if branch has been entered and executed as expected.
An additional debugging step I performed is to implement the _process()
function to print debugging statements:
func _process(delta: float) -> void:
var animation = animation_library.get_animation("move")
var parent = get_parent()
print("Parent position: %s" % parent.position)
print("is playing: %s" % is_playing())
print("Current animation: %s" % current_animation)
print("Assigned animation: %s" % assigned_animation)
print("Animation position %s" % current_animation_position)
Here is a sample of the print statements generated during runtime:
Parent position: (2514.0, 172.0)
is playing: true
Current animation: animation_lib/move
Assigned animation: animation_lib/move
Animation position 0.41515233333326
Parent position: (2514.0, 172.0)
is playing: true
Current animation: animation_lib/move
Assigned animation: animation_lib/move
Animation position 0.42654066666659
Parent position: (2514.0, 172.0)
is playing: true
Current animation: animation_lib/move
Assigned animation: animation_lib/move
Animation position 0.42728666666659
Parent position: (2514.0, 172.0)
is playing: true
Current animation: animation_lib/move
Assigned animation: animation_lib/move
Animation position 0.42798654166979
The animation seems to be playing correctly because of the following reasons:
- The
is_playing()
function returnstrue
- The current and assigned animation names seem to match the animation I generated
- The animation position is continually increasing, and even loops back to 0 after 5 seconds, which is the length of this generated animation
However, even though the animation is playing, the parent position vector is still remaining the same.
Finally, for convenience, here is the entire script code:
@tool
class_name PlatformMover extends AnimationPlayer
var animation_library: AnimationLibrary
@export var move_to_y: int:
set(val):
move_to_y = val
if Engine.is_editor_hint():
remake_animation()
@export var move_secs: float = 0:
set(val):
move_secs = val
if Engine.is_editor_hint():
remake_animation()
@export var wait_secs: float = 0:
set(val):
wait_secs = val
if Engine.is_editor_hint():
remake_animation()
# Create a button to reload the animation settings. Because this was created based on the parent
# position, if the parent node is moved after the animation settings were created, this can
# change the parent's location back to the one before it was moved. So with this handy button we
# can reload the start position by regenerating the animation
@export_tool_button("Reload Start Position") var reload = reload_animation_settings.bind()
func reload_animation_settings() -> void:
remake_animation()
func _process(delta: float) -> void:
var animation = animation_library.get_animation("move")
var parent = get_parent()
print("Parent position: %s" % parent.position)
print("is playing: %s" % is_playing())
print("Current animation: %s" % current_animation)
print("Assigned animation: %s" % assigned_animation)
print("Animation position %s\n" % current_animation_position)
func _ready() -> void:
animation_library = AnimationLibrary.new()
if not has_animation_library("animation_lib"):
add_animation_library("animation_lib", animation_library)
remake_animation()
if not Engine.is_editor_hint():
remake_animation()
if not is_playing():
play("animation_lib/move")
print("Played")
func remake_animation() -> void:
print("Remaking")
var animation = create_moving_animation()
animation_library.remove_animation("move")
animation_library.add_animation("move", animation)
#if not Engine.is_editor_hint():
#play("animation_lib/move")
#print("Played!")
print("Remade")
func create_moving_animation() -> Animation:
var parent = get_parent()
var animation = Animation.new()
# Create the track for the movement animation
var track_index = animation.add_track(Animation.TYPE_VALUE)
# Set the property on the node that the animation will update. In this
# case, since we are updating the position of the parent node, we need
# to use the string "<parent_node_path>:position"
#var track_path = "%s:position" % parent.get_path()
var track_path = "../:position"
animation.track_set_path(track_index, track_path)
# Create variables for the various positions and timings that we will be adding
var start_position = parent.position
var end_position = start_position
end_position.y = move_to_y
var rolling_time_counter = 0.0
# Insert keyframes
animation.track_insert_key(track_index, 0.0, start_position)
# If wait, then set a new key that allows the platform to wait at the start position
if wait_secs > 0:
rolling_time_counter += wait_secs
animation.track_insert_key(track_index, rolling_time_counter, start_position)
rolling_time_counter += move_secs
animation.track_insert_key(track_index, rolling_time_counter, end_position)
# If wait, then set a new key that allows the platform to wait at the end position
if wait_secs > 0:
rolling_time_counter += wait_secs
animation.track_insert_key(track_index, rolling_time_counter, end_position)
rolling_time_counter += move_secs
animation.track_insert_key(track_index, rolling_time_counter, start_position)
animation.length = rolling_time_counter
animation.loop_mode = Animation.LoopMode.LOOP_LINEAR
# Debugging print statements
print("move_to_y is %s" % move_to_y)
print("move_secs is %s" % move_secs)
print("wait_secs is %s" % wait_secs)
for i in range(0, animation.track_get_key_count(track_index)):
print("key time is %s" % animation.track_get_key_time(track_index, i))
print("Key value is %s" % animation.track_get_key_value(track_index, i))
return animation
At this point, I have debugged this problem to the best of my ability and I do not know where else in my code to look for issues. I have also tried searching for similar issues but I could not find anything similar to mine. Most of the issues I found were due to multiple play
calls overwriting and restarting the current animation, making it seem like the animation is not playing, but it does not seem to apply to me as I have the play
function only once in my code, and it is executed in _ready()
so it should be executed only once.
I would appreciate any help on resolving this issue. Thank you!