I’m learning how to make a platformer and I’ve been stuck on adding a roll animation, I want the animation to only work while pressing a direction and down. Here is my code:
extends CharacterBody2D
const SPEED = 130.0
const JUMP_VELOCITY = -300.0
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
func _physics_process(delta: float) → void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# Handle jump.
if Input.is_action_just_pressed("Jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var direction := Input.get_axis("move_left", "move_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
#direction
if Input.is_action_just_pressed("move_left"):
animated_sprite.flip_h = true
if Input.is_action_just_pressed("move_right"):
animated_sprite.flip_h = false
#animations
if is_on_floor():
if direction == 0:
animated_sprite.play("idle")
else:
animated_sprite.play("run")
else:
animated_sprite.play("jump")
move_and_slide()
You can define an action in your input mapping for rolling, and check if player is rolling based on a few conditions. A typical example is that you first want the character to be on ground.
This would be something like this:
var rolling: bool = false
func _physics_process(delta: float) → void:
# Gravity, jump, move, etc.
# Check if character is rolling.
if is_on_floor() and Input.is_action_pressed("roll"): # and any other condition...
rolling = true
else:
rolling = false
# Animations
if rolling:
animated_sprite.play("rolling")
else:
# Other animations...
move_and_slide()
And of course, if you need to check two inputs as you mentionned, simply add another “Input.is_something_pressed()” function in the condition.
Then, based on rolling value, I suppose you also need to change the speed of the character or anything else. Having a boolean is very convenient here as you only need to check it to know what to do with any parameter afterward.
I believe that would do the job for a roll that lasts as long as you’re pressing the correct inputs, which is what I understand here but I might be wrong.
Let me know if you were talking about a roll that’s triggered once when pressing a key and stopping after the roll itself is finished, as this would be done differently.
Okay, then you would need to store the info that the character is currently rolling.
Basically, you can define a boolean:
var is_rolling: bool = false
And change its value to true based on the same condition as above:
if is_on_floor() and Input.is_action_pressed("roll"): # and any other condition...
is_rolling = true
The difference is now, to set the boolean back to false, you need to wait the roll animation is done.
One simple way of doing that is using a Timer, and use its timeout signal to detect the roll is over.
To make it more readable, define two functions roll and roll_end, and call the roll function instead of setting is_rolling to true (which is now done in the function). Something like this:
func roll():
is_rolling = true
var timer: Timer = Timer.new()
timer.wait_time = 1.0 # 1 second roll
timer.timeout.connect(roll_end)
add_child(timer)
timer.start()
func roll_end():
is_rolling = false
Then, use the is_rolling boolean to change animations and anything else.
Let me know if that helps. Keep in mind that you can write your own code if it suits your need better than mine, I just hope you have a better understanding on how you could do your roll mechanic
I have a little problem it doesn’t take time to roll did i do something wrong in my code?
extends CharacterBody2D
const SPEED = 130.0
const JUMP_VELOCITY = -300.0
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
@onready var is_rolling: bool = false
var rolling: bool = false
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# Handle jump.
if Input.is_action_just_pressed("Jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
func roll():
is_rolling = true
var timer: Timer = Timer.new()
timer.wait_time = 1.0 # 1 second roll
timer.timeout.connect(roll_end)
add_child(timer)
timer.start()
func roll_end():
is_rolling = false
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var direction := Input.get_axis("move_left", "move_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
#rolling
if is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_left"):
is_rolling = true
elif is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_right"):
is_rolling = true
#movement
if is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_left"):
rolling = true
elif is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_right"):
rolling = true
else:
rolling = false
if Input.is_action_just_pressed("move_left"):
animated_sprite.flip_h = true
if Input.is_action_just_pressed("move_right"):
animated_sprite.flip_h = false
#animations
if rolling:
animated_sprite.play("rolling")
elif is_on_floor():
if direction == 0:
animated_sprite.play("idle")
elif is_on_floor() and rolling:
animated_sprite.play("rolling")
else:
animated_sprite.play("run")
else:
animated_sprite.play("jump")
move_and_slide()
@onready var is_rolling: bool = false
var rolling: bool = false
You should have only one.
–
2/ You don’t seem to be calling roll() at all: you’re just setting the is_rolling boolean to true but not using the function (thus not triggering the timer).
In your code, you have:
#rolling
if is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_left"):
is_rolling = true
elif is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_right"):
is_rolling = true
while you should have:
#rolling
if is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_left"):
roll()
elif is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_right"):
roll()
–
3/ All of your logic is now in roll_end(), for some reason I don’t get. What was before in _physics_process() (animations, velocity, etc.) should not be in roll_end().
The roll end should only reset the is_rolling value to false, and then, the physics process is checking that boolean to know what animations to play. Nothing more.
–
Last detail: you probably don’t want to roll while already rolling, so you could add a condition before triggering the roll:
#rolling
if not is_rolling:
if is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_left"):
roll()
elif is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_right"):
roll()
Everything that’s related to the roll starting (is_rolling to true, starting the timer, etc.) is done in roll, which should be called once on start.
Everything that’s related to the roll ending (is_rolling to false, maybe a specific animation, etc.) is done in roll_end, which should be called once on end.
The rest can be done in _physics_process since you have a is_rolling boolean to know that you’re rolling, that function being called every on physics update.
sorry I feel like I’m pestering you but I think I did everything correctly but it still doesn’t wait a second to play a new animation
here’s all my code:
extends CharacterBody2D
const SPEED = 130.0
const JUMP_VELOCITY = -300.0
@onready var animated_sprite: AnimatedSprite2D = $AnimatedSprite2D
var is_rolling: bool = false
func roll():
is_rolling = true
var timer: Timer = Timer.new()
timer.wait_time = 1.0 # 1 second roll
timer.timeout.connect(roll_end)
add_child(timer)
timer.start()
func _physics_process(delta: float) -> void:
# Add the gravity.
if not is_on_floor():
velocity += get_gravity() * delta
# Handle jump.
if Input.is_action_just_pressed("Jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
# Get the input direction and handle the movement/deceleration.
# As good practice, you should replace UI actions with custom gameplay actions.
var direction := Input.get_axis("move_left", "move_right")
if direction:
velocity.x = direction * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
#movement
if is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_left"):
roll()
elif is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_right"):
roll()
else:
is_rolling = false
if Input.is_action_just_pressed("move_left"):
animated_sprite.flip_h = true
if Input.is_action_just_pressed("move_right"):
animated_sprite.flip_h = false
#animations
if is_rolling:
animated_sprite.play("rolling")
elif is_on_floor():
if direction == 0:
animated_sprite.play("idle")
else:
animated_sprite.play("run")
else:
animated_sprite.play("jump")
move_and_slide()
#rolling
func roll_end():
is_rolling = false
if is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_left"):
roll()
elif is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_right"):
roll()
else:
is_rolling = false
That code is saying: “if is on floor and pressing roll input, then roll, else stop rolling”. However, you don’t want to stop rolling if no input is pressed.
You only want that part:
if is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_left"):
roll()
elif is_on_floor() and Input.is_action_just_pressed("roll") and Input.is_action_pressed("move_right"):
roll()
since the roll end will be triggered after the one second delay.
For readability and code factoring purpose, you can improve your code by nesting two conditions (that’s just an advice on the fly, that’s not mandatory):
if is_on_floor() and Input.is_action_just_pressed("roll"):
if Input.is_action_pressed("move_left") or Input.is_action_pressed("move_right"):
roll()