0 votes

Hello All,

Main Purpose: I need an entity to move along a given path. The path is known. And it comes in the form of an Array of Vector2. However, it is my main challenge, the movement can only occur horizontally or vertically and not diagonally. In this case the movement flow would be: Move to the next point. If the direction to the next is different, rotate, then move again. So until the end of the path.

One entity that I've long wanted to move a path to Spider - KinematicBody2D.

I made a line drawn along the path just allow for viewing.

My script:

extends KinematicBody2D

onready var tilemap = get_node("../TileMap")
onready var tank = get_node("../Tank")
onready var line = get_parent().get_node("./Line2D")

# Movement Variables
var direction = Vector2.UP
var velocity = Vector2.ZERO
var speed = 5000
var threshold = 16

var direction_to_next_point
var world_next_point

func _ready():
    pass

func _process(delta):
    draw_path_line(path_to_target)

func _physics_process(delta):
    # Pathfinding
    path_to_target = tilemap._get_path(tilemap.world_to_map(global_position),
                                       tilemap.world_to_map(tank.global_position))
    # I've tested the path and it's returning a correct Vector2 Array.

    # Movement Logic
    if path_to_target.size() > 1:
        world_next_point = tilemap.map_to_world(path_to_target[1])
        if position.distance_to(world_next_point) > 10:
            direction_to_next_point = position.direction_to(world_next_point)
        velocity = direction_to_next_point
    velocity = _to_4way(velocity)
    velocity = move_and_slide(velocity * speed * delta)

    # Input Test
    if Input.is_action_just_pressed("test") :
        print('path: ', path_to_target)
        print('direction to next point: ', direction_to_next_point)

# I've created this function to convert diagonal directions on the closer pure horizontal 
# our pure vertical. 
func _to_4way(direction: Vector2):
    var x = direction.x
    var y = direction.y
    if x > 0 and y > 0 :
        if x > y: return Vector2.RIGHT
        if x < y: return Vector2.DOWN
        if x == y: return Vector2.RIGHT
    if x > 0 and y < 0:
        if x > y: return Vector2.RIGHT
        if x < y: return Vector2.UP
        if x == y: return Vector2.RIGHT
    if x < 0 and y > 0 :
        if abs(x) < abs(y) : return Vector2.DOWN
        if abs(x) > abs(y) : return Vector2.LEFT
    if x < 0 and y < 0 :
        if abs(x) < abs(y) : return Vector2.UP
        if abs(x) > abs(y) : return Vector2.LEFT
    return Vector2.ZERO

func draw_path_line(path: Array):
    line.clear_points()
    path.push_front(tilemap.world_to_map(self.global_position))
    for point in path:
        point = tilemap.map_to_world(point)
        point.x += tilemap.cell_size.x/2
        point.y += tilemap.cell_size.y/2
        if !line.points.has(point): line.add_point(point)

But for some reason along the way the velocity has its value changed repeatedly to the equivalent of Vector2.RIGHT and Vector2.DOWN. This creates a strangeness in the spider's movement and makes it move diagonally. I understand that this is because velocity is being recalculated all the time but I don't know how to solve it.

Could you help me find a more efficient way to make the movement flow in 4 directions?

Godot version 3.5
in Engine by (12 points)

You would be better off with astar pathfinding. With astar You will just define location of points on a grid and connect them in square pattern. You will have to learn a bit from docs or tutorials, but it is a simple and ellegant system once You understand it.

Astar is cool. However, I wouldn't make any assumptions what pathfinding algorithm they are using here. It may be astar or Godot's built-in or anything else that fits the project. The depicted movement pattern can be achieved with any pathfinding method.

Thanks for your answer!

I'm actually using it! The function tilemap._get_path is working just like you said. I'm getting neighbours on four way direction :
# on tilemap _get_path that extends a custom AStar var neighbours = [Vector2.UP, Vector2.RIGHT, Vector2.DOWN, Vector2.LEFT, ]

Glad to be of assistance. Don't forget to select the answer if it solved the problem! This way someone else might find it easier if they are having similiar problem.

1 Answer

+1 vote

The problem seems to be emerging, when x and y difference is more or less equal. Then spider moves in one frame right and the next frame down, alternating every frame. This would look like it's going at a 45 degree angle.

You'd want to remember the direction which was last taken, and more likely continue that way. At some point, change direction. You can do this randomly to make movement more natural. Something along these lines (untested):

export(float, 0, 1) var turning_chance = 0.05
var current_direction = "x"
func _to_4way(direction: Vector2):
    var x = direction.x
    var y = direction.y

    # At a default 5% chance to change direction randomly
    if randf() < turning_chance:
        current_direction = "x" if current_direction == "y" else "y"

    # Make 2 tries to move
    # (without this spider would randomly stop for one frame)
    for i in 2:
        match current_direction:
            "x":
                if x > 0:
                    return Vector2.RIGHT
                elif x < 0:
                    return Vector2.LEFT
                else:
                    # Target on x position, try moving at y axis
                    current_direction = "y"
            "y":
                if y > 0:
                    return Vector2.DOWN
                elif y < 0:
                    return Vector2.UP
                else:
                    # Target on x position, try moving at x axis
                    current_direction = "x"
    # If we get to end after 2 tries, we must be on target
    return Vector2.ZERO 

The code above could be shortened to a few lines, but I tried to write it as understandable as possible. The turning chance variable affects a lot how the movement feels. Smaller chances for turning yield more determinent movement, bigger chances chaotic ( =1 results in previous behaviour).

by (810 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 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.