I recently started learning godot
I am currently using Brackeys 2D godot introductin to learn
I am facing some issues in rolling. The concept works fine but the animation for the rolling plays after the character has moved
Here is the code:
extends CharacterBody2D
@export var SPEED = 130.0
@export var rollingSPEED = 300
@export var deacceleration = 0.1
const JUMP_VELOCITY = -300.0
var dead: bool = false
var jumpCounter: int = 0
var rolling: bool = false
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var roll_buffer_timer: Timer = $RollBufferTimer
func playerDied():
dead = true
func roll(direction):
if direction < 0:
animated_sprite_2d.flip_h = true
elif direction > 0:
animated_sprite_2d.flip_h = false
animated_sprite_2d.play("roll")
rolling = true
func _physics_process(delta: float) -> void:
if dead:
animated_sprite_2d.play("hurt")
print("dying")
else:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
#Reset the jump counter
if is_on_floor() and jumpCounter != 0:
jumpCounter = 0
# Handle jump.
if Input.is_action_just_pressed("jump") and jumpCounter < 2 and not rolling:
SoundManager.play_sound("jump_sound")
velocity.y = JUMP_VELOCITY
jumpCounter += 1
if Input.is_action_just_pressed("roll") and is_on_floor() and not rolling:
roll_buffer_timer.start()
if is_on_floor() and not roll_buffer_timer.is_stopped():
var rollDirection = Input.get_axis("move_left", "move_right")
if rollDirection != 0:
roll(rollDirection)
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
#Get direction: -1, 0, 1
var direction := Input.get_axis("move_left", "move_right")
if direction > 0:
animated_sprite_2d.flip_h = false
elif direction < 0:
animated_sprite_2d.flip_h = true
if rolling:
pass
elif is_on_floor():
if direction == 0:
animated_sprite_2d.play("idle")
else:
animated_sprite_2d.play("run")
else:
animated_sprite_2d.play("jump")
if direction and not rolling:
velocity.x = direction * SPEED
elif direction and rolling:
velocity.x = direction * rollingSPEED
elif not direction and is_on_floor():
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
func _on_animated_sprite_2d_animation_finished() -> void:
rolling = false
what happens when you pull this line animated_sprite_2d.play("roll") from inside func roll(…) into the empty if statement (if rolling) and replace the pass statement (as shown below)?
if rolling:
animated_sprite_2d.play("roll")
Could you also provide a link to your resources? That would help understand what you are actually working on.
Furthermore, it would help if you would set the syntax highlighting of your code snippet to gdscript (see below). It makes the code more readable.
it works the exact same after i put the play animation in the if else loop instead of the function.
I have discovered two more issues. When i am running and then roll, the distance covered is a lot more than when i am standing idle and then roll. Should I relate the roll function to distance and not velocity.x?
Also how can i disable inputs when the character is rolling?
extends CharacterBody2D
@export var SPEED = 130.0
@export var rollingSPEED = 300
@export var deacceleration = 0.1
@export var JUMP_VELOCITY = -300.0
var dead: bool = false
var jumpCounter: int = 0
var rolling: bool = false
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var roll_buffer_timer: Timer = $RollBufferTimer
func playerDied():
dead = true
func roll(direction):
if direction < 0:
animated_sprite_2d.flip_h = true
elif direction > 0:
animated_sprite_2d.flip_h = false
rolling = true
func _physics_process(delta: float) -> void:
if dead:
animated_sprite_2d.play("hurt")
else:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
#Reset the jump counter
if is_on_floor() and jumpCounter != 0:
jumpCounter = 0
# Handle jump.
if Input.is_action_just_pressed("jump") and jumpCounter < 2 and not rolling:
SoundManager.play_sound("jump_sound")
velocity.y = JUMP_VELOCITY
jumpCounter += 1
if Input.is_action_just_pressed("roll") and is_on_floor() and not rolling:
roll_buffer_timer.start()
if is_on_floor() and not roll_buffer_timer.is_stopped():
var rollDirection = Input.get_axis("move_left", "move_right")
if rollDirection != 0:
roll(rollDirection)
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
#Get direction: -1, 0, 1
var direction := Input.get_axis("move_left", "move_right")
if direction > 0:
animated_sprite_2d.flip_h = false
elif direction < 0:
animated_sprite_2d.flip_h = true
if rolling:
animated_sprite_2d.play("roll")
elif is_on_floor():
if direction == 0:
animated_sprite_2d.play("idle")
else:
animated_sprite_2d.play("run")
else:
animated_sprite_2d.play("jump")
if direction and not rolling:
velocity.x = direction * SPEED
elif direction and rolling:
velocity.x = direction * rollingSPEED
elif not direction and is_on_floor():
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
func _on_animated_sprite_2d_animation_finished() -> void:
rolling = false
That issue sounds like you bound the rolling movement to the velocity of the walking animation. Could be a good design choice, could be a bad one, it depends on your intentions.
How you implement the rolling depends on what effect you have in mind. No one can tell you what to do, if you don’t say what you want to achieve first.
While you keep adding more functionality, keep the original intentions for the original variables (like velocity and distance) in mind and intact. I was comparing your script to Brackey’s version on Github and you already added quite some more lines of code. It is generally a good idea to not entangle your data with too much functionality. If possible duplicate data in order to implement new systems before ruining your old systems by adding too much to them.
A simple boolean flag should do the trick.
Something along the lines of the code below.
var block_input : bool = false
func do_the_rolling():
block_input = true
func handle_input():
var direction : float = 0
if not block_input:
direction = Input.get_axis("move_left", "move_right")
Happy tinkering
P.S.: If you do not mind bigger changes to your code you might also want a look at Godot’s AnimationTrees. They provide a clean way to express more complex animation systems.
What if you try just playing a jump animation – which you know to work properly – instead of the rolling animation. Do you get the same effect? If so the problem might be inside how you setup the rolling animation. Otherwise you know it is the way you implemented the rolling in code.
tried replacing the roll animation with a different animation and it still looks like most of the animation is playing at the end. It’s also really hard to tell since the distance is covered in a short time. Maybe I’ll try adding a timer to see if it works right.
Apart from that i am still facing the issue of the player covering more distance, if they were moving before rolling
I recreated your code changes in the original project (see all below) and the animation seems fine to me in both cases (animation plays instantly , same distance covered).
The main change I did was how I set up the timer in code, maybe that’s what is going wrong.
I also call rolling = true immediately after receiving the rolling input and not inside the roll() function.
I don’t see any problems.
The recreation:
extends CharacterBody2D
# constants.
const SPEED : float = 130.0
const JUMP_VELOCITY : float = -300.0
const rollingSpeed : float = 300.0 * 0.5 # NEW -- I made it a bit slower.
const deacceleration : float = 0.1 # NEW.
# NEW.
var rolling: bool = false
var dead : bool = false
var jumpCounter : int = 0
# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
# NEW.
@onready var animated_sprite_2d = $AnimatedSprite2D
@onready var roll_buffer_timer: Timer = null
func _ready() -> void:
roll_buffer_timer = Timer.new()
add_child(roll_buffer_timer)
roll_buffer_timer.one_shot = true
roll_buffer_timer.wait_time = 2.0
roll_buffer_timer.timeout.connect(_on_animated_sprite_2d_animation_finished)
# This essentially is a visual thing, not an input thing.
func roll(direction): # NEW.
if direction < 0:
animated_sprite_2d.flip_h = true
elif direction > 0:
animated_sprite_2d.flip_h = false
#rolling = true
func playerDied(): # NEW.
dead = true
func _on_animated_sprite_2d_animation_finished() -> void:
print("should not roll -- " + str(rolling))
rolling = false
func _physics_process(delta):
# NEW.
if dead:
animated_sprite_2d.play("hurt")
print("dying")
else:
# Add the gravity.
if not is_on_floor():
velocity.y += gravity * delta
# NEW: Reset the jump counter.
if is_on_floor() and jumpCounter != 0:
jumpCounter = 0
# Handle jump.
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction: -1, 0, 1
var direction = Input.get_axis("move_left", "move_right")
# NEW: jump input.
if Input.is_action_just_pressed("roll") and is_on_floor() and not rolling:
rolling = true
roll_buffer_timer.start()
print("now rolling -- " + str(rolling))
if is_on_floor() and not roll_buffer_timer.is_stopped():
var rollDirection = Input.get_axis("move_left", "move_right")
if rollDirection != 0:# NOTE: Contentwise this rather belongs to the flip-the-sprite section below..
roll(rollDirection)
# Flip the Sprite
if direction > 0:
animated_sprite_2d.flip_h = false
elif direction < 0:
animated_sprite_2d.flip_h = true
# Play animations
if rolling:
animated_sprite_2d.play("roll")
elif is_on_floor():
if direction == 0:
animated_sprite_2d.play("idle")
else:
animated_sprite_2d.play("run")
else:
animated_sprite_2d.play("jump")
## Apply movement (the original).
#if direction:
#velocity.x = direction * SPEED
#else:
#velocity.x = move_toward(velocity.x, 0, SPEED)
# NEW -- The new version is very hard to read!
if direction and not rolling:
velocity.x = direction * SPEED
elif direction and rolling:
velocity.x = direction * rollingSpeed
elif not direction and is_on_floor():
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
This didn’t work for me so I used a different method:
extends CharacterBody2D
@export var SPEED = 130.0
@export var deacceleration = 0.1
@export var JUMP_VELOCITY = -300.0
@export var rollSpeed = 100
var dead: bool = false
var jumpCounter: int = 0
var rolling: bool = false
var rollDir = 0
@onready var animated_sprite_2d: AnimatedSprite2D = $AnimatedSprite2D
@onready var roll_buffer_timer: Timer = $RollBufferTimer
@onready var roll_wait_timer: Timer = $RollWaitTimer
var canMove = true
func playerDied():
dead = true
func roll(direction):
rolling = true
canMove = false
rollDir = sign(direction)
if direction < 0:
animated_sprite_2d.flip_h = true
elif direction > 0:
animated_sprite_2d.flip_h = false
roll_wait_timer.start()
roll_buffer_timer.stop()
func customGravity(velocity: Vector2):
return get_gravity() if velocity.y < 0.0 else (get_gravity()*1.5)
func _physics_process(delta: float) -> void:
if dead:
animated_sprite_2d.play("hurt")
else:
# Add the gravity.
if not is_on_floor():
velocity += customGravity(velocity) * delta
#Reset the jump counter
if is_on_floor() and jumpCounter != 0:
jumpCounter = 0
# Handle jump.
if Input.is_action_just_released("jump") and velocity.y < 0.0:
velocity.y = JUMP_VELOCITY/3
if Input.is_action_just_pressed("jump") and jumpCounter < 2 and not rolling:
SoundManager.play_sound("jump_sound")
velocity.y = JUMP_VELOCITY
jumpCounter += 1
if Input.is_action_just_pressed("roll") and is_on_floor() and not rolling:
roll_buffer_timer.start()
if is_on_floor() and not roll_buffer_timer.is_stopped():
var rollDirection = Input.get_axis("move_left", "move_right")
if rollDirection != 0:
roll(rollDirection)
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
#Get direction: -1, 0, 1
var direction := Input.get_axis("move_left", "move_right")
if direction > 0:
animated_sprite_2d.flip_h = false
elif direction < 0:
animated_sprite_2d.flip_h = true
if rolling:
animated_sprite_2d.play("roll")
print("Roll animation playing")
elif is_on_floor():
if direction == 0:
animated_sprite_2d.play("idle")
else:
animated_sprite_2d.play("run")
else:
animated_sprite_2d.play("jump")
if direction and canMove and not rolling:
velocity.x = direction * SPEED
elif rolling and canMove:
velocity.x = rollDir * rollSpeed
elif not direction and is_on_floor():
velocity.x = move_toward(velocity.x, 0, SPEED)
move_and_slide()
func _on_animated_sprite_2d_animation_finished() -> void:
rolling = false
func _on_roll_wait_timer_timeout() -> void:
canMove = true
added a wait timer before rolling and created a new rolling speed which is slower than the normal speed since the rolling speed is being called multiple times
i could have just used a counter and told it to only use the rolling speed once but the current solution feels good to play