Search code examples
game-physicsgame-developmentgodotgdscriptgodot4

How to make 2D character do one step of 32 pixels when pressing a button once in Godot's 4 GDScript?


I wrote a script where I move my 2D character after once pressing a button one step. But it moves only 1 pixels instead of 32 and also jumps up for 1 pixel.

There is a function that measures the distance the character has walked and supposed to stop the character when reaching this distance. Now I am struggling to implement the logic, as this script seems correct, but the character walks only 1 pixel, but the debug says it did 32. Somehow problem expands even on velocity.y value, because character jumps only 1 pixel (roughly 1 frame). I know I am missing something but can't understand where exactly.

I am relatively new in this Godot 4 engine, please help me as I already spent days troubleshooting this issue and watching tutorials. If you see other minor issues I don't see or things that can be optimized please also let me know. If you know a good tutorial on this topic, I will appreciate the sharing.

Here is the function that do the step:

func step(dir, delta):
    #if 32 or -32 pixels move character, if not stop character
    if dir != 0:
        velocity.x = dir * speed * delta #move player 32 pixels left or right
        measured_distance += abs(velocity.x) #add walked value to a measurer (and make it always positive)
        measured_distance = min(measured_distance, abs(dir)) #if measurer exceeds 32 pixels make it 32
        print ("measured_distance: ", measured_distance) #debug , always works
        #if measurer equals to 32 pixels or exceeds it
        if measured_distance >= abs(dir):
            print(measured_distance, " px passed") #debug, always works
            dir_distance = 0 #reset universal directory distance to 0
            stepping = false #announcing the end of stepping process
    else:
        stop_step() 

Here is the full script:

extends CharacterBody2D

#MOVEMENT
@export var speed: int = 100
@export var step_distance: int = 32 #distance of 32 pixels
@export var jump_velocity: int = -230
var measured_distance: int = 0 #variable used to measure walked distance each step
var stepping: bool = false
var dir_distance: int = 0 #variable that inherits step_distance but changes direction to negative or positive

#PHYSICS
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")

#function called from the _physics_process one, it resets the measured_distance and announce stepping process
func start_measure ():
    stepping = true
    measured_distance = 0

#stops player movement along x value (horizontal)
func stop_step ():
    velocity.x = move_toward(velocity.x, 0, speed)


func step(dir, delta):
    #if 32 or -32 pixels move character, if not stop character
    if dir != 0:
        velocity.x = dir * speed * delta #move player 32 pixels left or right
        measured_distance += abs(velocity.x) #add walked value to a measurer (and make it always positive)
        measured_distance = min(measured_distance, abs(dir)) #if measurer exceeds 32 pixels make it 32
        print ("measured_distance: ", measured_distance) #debug , always works
        #if measurer equals to 32 pixels or exceeds it
        if measured_distance >= abs(dir):
            print(measured_distance, " px passed") #debug, always works
            dir_distance = 0 #reset universal directory distance to 0
            stepping = false #announcing the end of stepping process
    else:
        stop_step() 

#script starts here
func _physics_process(delta):
        #if not standing on floor add gravity
    if !is_on_floor():
        velocity.y += gravity * delta
    #if jump pressed once...
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity * delta

    #if not doing any step currently allow to press left or right buttons
    if !stepping:
        if Input.is_action_just_pressed("right"):
            dir_distance = step_distance #inherit value with positive value (right)
            start_measure()
        elif Input.is_action_just_pressed("left"):
            dir_distance = -step_distance #inherit value with negative value (left)
            start_measure()

        #constantly update movement
    step(dir_distance, delta) #passes direction (32 pixels left or right) and delta value
    move_and_slide()

Here is how it looks in the game


Solution

  • I am not 100% sure, but I think move_and_slide already adds delta to the velocity, so your actually moved distance differs from the measured_distance.

    I am not 100% sure, but I thought move_and_slide already applies delta, which would explain, why you are just moving 1 pixel. But removing delta from velocity.x did not do it, so I have another solution for you. Instead of adding a distance, you are not sure of, you could save the start position, when you are starting to move and check on each iteration, if the player moved 32 px to the left or right. That way you can actually hard set the position of the player, when he reached the 32 px.

    your step part would look like this:

    func step(dir, delta):
    #if 32 or -32 pixels move character, if not stop character
    if dir != 0:
        velocity.x = dir * speed * delta #move player 32 pixels left or right
        
        if abs(position.x - start_pos.x) >= abs(dir):
            print(measured_distance, " px passed") #debug, always works
            dir_distance = 0 #reset universal directory distance to 0
            stepping = false #announcing the end of stepping process
            position.x = start_pos.x + dir #hard set the position to start+32
            velocity.x = 0 #destination reached, so no need to move further
    else:
        stop_step() 
    

    start_pos will be set in the pysics_process part which checks for stepping:

    if !stepping:
        if Input.is_action_just_pressed("right"):
            start_pos = position
            dir_distance = step_distance #inherit value with positive value (right)
            start_measure()
        elif Input.is_action_just_pressed("left"):
            start_pos = position
            dir_distance = -step_distance #inherit value with negative value (left)
            start_measure()
    

    Note: you should check if move_and_slide collided with something and end the movement, if so. Otherwise manupulating the position directy could lead to unwanted behaviour (getting stuck in a wall for example).

    Edit: Including my whole script, because it does not seem to work for Mimo

    extends CharacterBody2D
    
    #MOVEMENT
    @export var speed: int = 100
    @export var step_distance: int = 32 #distance of 32 pixels
    @export var jump_velocity: int = -230
    var measured_distance: int = 0 #variable used to measure walked distance each step
    var stepping: bool = false
    var dir_distance: int = 0 #variable that inherits step_distance but changes direction to negative or positive
    
    #PHYSICS
    var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
    
    #function called from the _physics_process one, it resets the measured_distance and announce stepping process
    func start_measure ():
        stepping = true
        measured_distance = 0
    
    #stops player movement along x value (horizontal)
    func stop_step ():
        velocity.x = move_toward(velocity.x, 0, speed)
    
    var start_pos
    
    func step(dir, delta):
        #if 32 or -32 pixels move character, if not stop character
        if dir != 0:
            velocity.x = dir * speed * delta #move player 32 pixels left or right
            
            if abs(position.x - start_pos.x) >= abs(dir):
                print(measured_distance, " px passed") #debug, always works
                dir_distance = 0 #reset universal directory distance to 0
                stepping = false #announcing the end of stepping process
                position.x = start_pos.x + dir
                velocity.x = 0
        else:
            stop_step() 
    
    #script starts here
    func _physics_process(delta):
            #if not standing on floor add gravity
        if !is_on_floor():
            velocity.y += gravity * delta
        #if jump pressed once...
        if Input.is_action_just_pressed("jump") and is_on_floor():
            velocity.y = jump_velocity * delta
    
        #if not doing any step currently allow to press left or right buttons
        if !stepping:
            if Input.is_action_just_pressed("right"):
                start_pos = position
                dir_distance = step_distance #inherit value with positive value (right)
                start_measure()
            elif Input.is_action_just_pressed("left"):
                start_pos = position
                dir_distance = -step_distance #inherit value with negative value (left)
                start_measure()
    
            #constantly update movement
        step(dir_distance, delta) #passes direction (32 pixels left or right) and delta value
        if not velocity == Vector2.ZERO:
            print(velocity)
        move_and_slide()