Search code examples
animationgame-developmentgodotgdscript

Why doesn't my code use the correct animation?


I had a functioning fighting game written in Python with movement, frame data, attacks (that didn't work) but no gravity. I'm trying to rewrite it with Godot, but I'm really lost.

I had a system of implementing frame data into my attacks so I could actually transition from idle to attacks and vice versa easily. Right now, my issue is I want to attack and play that animation, but it keeps constantly playing the idle animation. I want to get this one thing working before I start doing hitboxes and collision stuff. Here is my code:

extends KinematicBody2D


export var walking_speed = 100
export var falling_speed = 0
var time = 0
onready var anim = $Sprite
var state = "Idle"

func _ready():
    set_physics_process(true)
    set_process(true)
    $AnimationTree.active = true

func gravity():
    pass

func _physics_process(delta):
    time += delta
    print(time)
    # If you press both A and D, you will stop entirely
    if Input.is_action_pressed("ui_left") and Input.is_action_pressed("ui_right"):
        walking_speed == 0
        $AnimationTree.set("parameters/Neutral/current", 0)
    #if you press A or D, you will move left or right accordingly
    elif Input.is_action_pressed("ui_left"):
        move_and_collide(Vector2(-walking_speed * delta, 0))
        anim.flip_h = true
        $AnimationTree.set("parameters/Neutral/current", 1)
    elif Input.is_action_pressed("ui_right"):
        move_and_collide(Vector2(walking_speed * delta, 0))
        anim.flip_h = false
        $AnimationTree.set("parameters/Neutral/current", 1)
    else:
        walking_speed = walking_speed

    if Input.is_action_just_pressed("Horizontal"):
        $AnimationTree.set("parameters/Neutral/current", 2)

This code is basically just a mishmash of a bunch of tutorials of things I was told to do (Like for instance, I was told not use AnimatedSprite and instead use AnimationTree, but it seems the code is a lot more complicated and is just giving me the same problems as last time). If you need me to, I can also post my Python code as well, but it's a giant mess with no comments on it at all.


Solution

  • You can make animations with AnimatedSprite. You have my blessing. AnimatedSprite is there to be used. And "I was told so" is not a good reason not to.

    Also, AnimatedSprite vs AnimationPlayer and AnimationTree is not an either or. Yes, there are somethings for which you would need AnimationPlayer and AnimationTree, however you can always have the AnimationPlayer trigger an AnimatedSprite animation. In fact, AnimationPlayer can call any method (and AnimationTree will control the AnimationPlayer). Thus, any work done with AnimatedSprite is not wasted.


    Calling set_physics_process will enable or disable _physics_process. And similarly set_process enables or disables _process. However, if you have a the method then it is enabled by default. Thus, you only need to enable them if you disabled them.


    We can compress a lot of your _physics_process code:

    func _physics_process(_delta:float) -> void:
        var walking_speed := (
            Input.get_action_strength("ui_right") -
            Input.get_action_strength("ui_left")
        )
        anim.flip_h = walking_speed < 0
        var velocity := Vector2(walking_speed, 0)
        move_and_collide(velocity * delta)
    

    The line anim.flip_h = walking_speed < 0 will be setting flip_h to true when walking_speed is negative, and to false otherwise.

    And we are setting walking_speed based on get_action_strength will return a number between 0 and 1 that represents how strongly the action is pressed. Which also means that if they are mapped to a joystick or similar, this gives you sensitivity support.

    If you prefer to not have that sensitivity, sign will do:

        var walking_speed := sign(
            Input.get_action_strength("ui_right") -
            Input.get_action_strength("ui_left")
        )
    

    With KinematicBody2D you would have to handle gravity. Basically give it some downwards velocity.

    var gravitational_acceleration := 100 # pixels per second squared
    var falling_speed := 0
    
    func _physics_process(_delta:float) -> void:
        falling_speed += gravitational_acceleration * delta
        var walking_speed := (
            Input.get_action_strength("ui_right") -
            Input.get_action_strength("ui_left")
        )
        anim.flip_h = walking_speed < 0
        var velocity := Vector2(walking_speed, falling_speed)
        move_and_collide(velocity * delta)
    

    However, it is better to implement gravity with move_and_slide:

    var gravitational_acceleration := 100 # pixels per second squared
    var falling_speed := 0
    
    func _physics_process(_delta:float) -> void:
        falling_speed += gravitational_acceleration * delta
        var walking_speed := (
            Input.get_action_strength("ui_right") -
            Input.get_action_strength("ui_left")
        )
        anim.flip_h = walking_speed < 0
        var velocity := Vector2(walking_speed, falling_speed)
        velocity = move_and_slide(velocity)
        falling_speed = velocity.y
    

    I have removed the multiplication with delta at the end because move_and_slide does that internally. That is, it takes a velocity not a displacement.

    This way the falling_speed will not keep growing out of control.

    You can also add an up vector (so Godot knows what is a ceiling, what is a floor, and what is a wall), and then you can check is_on_ceiling, is_on_floor and is_on_wall.

    Also, you can tell move_and_slide that the floor_max_angle is 0, so it does not slide (nothing counts as a slope, only perfectly horizontal floors are floors), if that is what you want.


    And, of course, the animation.

    If you are going to use an AnimationTree, I suggest to set the root as an AnimationNodeStateMachine. Then, from code, you can get the state machine playback object, like this:

    var state_machine = $AnimationTree.get("parameters/playback")
    

    You can tell it you want it to go to a different state, like this:

    state_machine.travel("name_of_some_state")
    

    And you can ask it what the current state is with:

    var state:String = state_machine.get_current_node()
    

    You can, for example have an "idle" state in your state machine, and an "attacking" state in your state machine, with a transition from "idle" to "attacking", and another transition from "attacking" to "idle", but that one set to transition "at the end". You can, of course, have other states. Then use the current state (from get_current_node) to figure out what the player can do.

    With an AnimationPlayer you can check what is the current_animation and is_playing; you can also connect the "animation_finished" signal. With an AnimatedSrite you can check what is the current animation and whether or not it is playing; you can also connect the "animation_finished" signal.