+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).

asked May 17, 2018 in Engine by Ertain (1,129 points)
edited May 17, 2018 by Ertain

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
answered May 17, 2018 by SIsilicon (3,775 points)
selected May 20, 2018 by Ertain

So a value of 1 means it takes 1 second to go to the next tile.
And a value of zero means you travel instantly.

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

```

0 votes

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)
answered Jun 24 by Christian Tietze (18 points)
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 How to use this Q&A? before posting your first questions.