The Godot Q&A is currently undergoing maintenance!

Your ability to ask and answer questions is temporarily disabled. You can browse existing threads in read-only mode.

We are working on bringing this community platform back to its full functionality, stay tuned for updates.

godotengine.org | Twitter

+1 vote

I'm trying to do what GDquest did in one of his videos: I'm trying to do grid-based movement. I've tried adapting his code, but I couldn't get that to work. Then I tried the code of airvikar, but I couldn't get that to work. Finally, I went to the official Discord channel and got the input of eons. He gave me some code and pointed me in a good direction, but I still can't get this code to function properly.

extends KinematicBody2D

const MOTION_SPEED = 160

func _physics_process(delta):
    var motion_vector = Vector2()
    var le_input = false
    var moving = false
    var direction = Vector2()
    var step_size = 0
    var motion_amount = 0

    # Let's do some moving
    if (Input.is_action_pressed("ui_up")):
        motion_vector += Vector2( 0, -1)
    if (Input.is_action_pressed("ui_down")):
        motion_vector += Vector2( 0, 1)
    if (Input.is_action_pressed("ui_left")):
        motion_vector += Vector2( -1, 0)
    if (Input.is_action_pressed("ui_right")):
        motion_vector += Vector2( 1, 0)

    if motion_vector != Vector2() and !moving:
        direction = motion_vector.normalized()
        step_size = MOTION_SPEED
        moving = true
    if step_size >= 0:
        motion_amount = step_size * delta
        move_and_collide(direction * motion_amount)
    elif moving:
        step_size = 0
        moving = false

This code is on a player scene which is added to the main scene. I'm not entirely certain where the code isn't properly moving the player sprite from tile to tile. The player sprite should just move from tile to tile, and not really have any intermediary movement (i.e. walking slowly in between tiles).

in Engine by (3,164 points)
edited by

2 Answers

+1 vote
Best answer

I know the type of movement your talking about. And I believe I have a solution.

var moving = false

func _physics_process(delta):
    if !moving:
        var motion_vector = Vector2()
        if Input.is_action_pressed("ui_up"):
            motion_vector += Vector2( 0, -1)
        if Input.is_action_pressed("ui_down"):
            motion_vector += Vector2( 0, 1)
        if Input.is_action_pressed("ui_left"):
            motion_vector += Vector2( -1, 0)
        if Input.is_action_pressed("ui_right"):
            motion_vector += Vector2( 1, 0)

        if motion_vector != Vector2():
            #tile_size is the size of the tilemap in pixels.
            var new_position = position + motion_vector * tile_size
            #Yes. I'm assuming you have a Tween node as a child.
            $Tween.interpolate_property ( self, 'position', position, new_position, 0.6, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
            #That last method's fifth property is how long it takes to go from one tile to the other in seconds.
            $Tween.start()
            moving = true

#This function is connected to the tween node's tween_completed signal.
func _on_Tween_tween_completed(object, key):
    moving = false
by (3,938 points)
selected by

I eventually learned that. :P As for collisions, well, that's another monster.

I gotcha covered there.

Assuming your player is a kinematic body, you should have access to the method test_move(Transform2D from, Vector2 rel_vec). Returns true if a collision occurs.

Which mean you will change this-

        #tile_size is the size of the tilemap in pixels.
        var new_position = position + motion_vector * tile_size
        #Yes. I'm assuming you have a Tween node as a child.
        $Tween.interpolate_property ( self, 'position', position, new_position, 0.6, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
        #That last method's fifth property is how long it takes to go from one tile to the other in seconds.
        $Tween.start()
        moving = true

To this.

        #tile_size is the size of the tilemap in pixels.
        motion_vector *= tile_size
        if not test_move(global_transform, motion_vector):
            #Yes. I'm assuming you have a Tween node as a child.
            $Tween.interpolate_property ( self, 'position', position, position+motion_vector, 0.6, Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
            #That last method's fifth property is how long it takes to go from one tile to the other in seconds.
            $Tween.start()
            moving = true

Notice that the new_position variable was omitted and in the Tween's function replaced with position+motion_vector.

Thank you for that fix, Sisilicon.

```
extends KinematicBody2D

var motion = Vector2()
var talking = false
var move_speed = 50
var facing = "down"

func physicsprocess(delta):
#<!-- CHECK TO SEE IF PLAYER IS TALKING --!>#
if talking != true:
#<!-- CHECK TO SEE IF PLAYER IS PRESSING A BUTTON --!>#
if Input.isactionpressed("uiright"):
facing = "right"
$Sprite.play("walk
right")
motion.y = 0
motion.x = movespeed
elif Input.is
actionpressed("uileft"):
$Sprite.play("walkleft")
facing = "left"
motion.y = 0
motion.x = -move
speed
elif Input.isactionpressed("uiup"):
$Sprite.play("walk
up")
facing = "up"
motion.x = 0
motion.y = -movespeed
elif Input.is
actionpressed("uidown"):
$Sprite.play("walkdown")
facing = "down"
motion.x = 0
motion.y = move
speed
else:
#<!-- IF THE USER IS NOT PRESSING THE BUTTONS SET PLAYER TO IDLE --!>#
if facing == "right":
$Sprite.play("idleright")
elif facing == "left":
$Sprite.play("idle
left")
elif facing == "up":
$Sprite.play("idleup")
elif facing == "down":
$Sprite.play("idle
down")
motion.x = 0
motion.y = 0

    #<!-- MOVE PLAYER ON THE X AND Y AXIS--!>#
    move_and_slide(motion)
pass

```

Thank you all so much for this, it has been very helpful. I too got the single movement the first time I ran the code. After that it just worked. My issue is that if I hold down an arrow key, it makes the first move, stops, and then continues on. How do I make sure that the motion is smooth throughout?

Edit:
Nevermind I'm an idiot. I was playing the wrong scene

+1 vote

Pixel-by-tile movement

The key to pixel-by-tile movement is to block input while the movement is going on.

The following code assumes

Then you can use move_and_collide physics and do not need to use tweens. The delta in _physics_process is a fraction of a second. You want to move X pixels per second (your speed, here: _step_size). So you accumulate (+) deltas until the _step_size is reached and then move a pixel.

This approach helps to not draw between-pixel or half-pixel positions and makes sure your big, chunky pixel sprites are moving on a proper pixel grid.

You don't need to use KinematicBody2D and can make your player a simple Area2D, and set position directly in _process instead of using move_and_collide in _physics_process. Depending on the game you're making that might be a lot simpler look just as good.

Animated demo

Code

extends KinematicBody2D

const TILE_SIZE = 16

var direction: Vector2 = Vector2.ZERO
var pixels_per_second: float setget _pixels_per_second_changed

var _step_size: float
func _pixels_per_second_changed(value: float) -> void:
    pixels_per_second = value
    _step_size = (1 / pixels_per_second)

# Accumulates delta, aka fractions of seconds, to time movement
var _step: float = 0
# Count movement in distinct integer steps
var _pixels_moved: int = 0 

func is_moving() -> bool:
    return self.direction.x != 0 or self.direction.y != 0

func _ready() -> void:
    self.pixels_per_second = 1 * TILE_SIZE

func _physics_process(delta: float) -> void:
    if not is_moving(): return
    # delta is measured in fractions of seconds, so for a speed of
    # 4 pixels_per_second, we need to accumulate _step until 
    # it reaches 0.25, and then we can move a pixel
    _step += delta
    if _step < _step_size: return

    # Move a pixel
    _step -= _step_size
    _pixels_moved += 1
    move_and_collide(direction)

    # Complete movement
    if _pixels_moved >= TILE_SIZE:
        direction = Vector2.ZERO
        _pixels_moved = 0
        _step = 0

func _input(event: InputEvent) -> void:
    if is_moving(): return
    if Input.is_action_pressed("ui_right"):
        direction = Vector2(1, 0)
    elif Input.is_action_pressed("ui_left"):
        direction = Vector2(-1, 0)
    elif Input.is_action_pressed("ui_down"):
        direction = Vector2(0, 1)
    elif Input.is_action_pressed("ui_up"):
        direction = Vector2(0, -1)
by (22 points)

Thanks for the answer. But this question was solved years ago. I guess your answer could help out other would-be Godot developers.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.