+1 vote

I'm trying to work about a way of moving a sprite along a Path2D based on a mouse click, So if the player clicks at a point on the screen at say, 800px on the X axis, the sprite would move to the corresponding (or closest) point on the Path2D.

Basically trying to get an adventure game type of thing going, where the player can only move along a fairly basic path.

Is this even possible? If not, what would be a better Godot approach?

Thanks in advance

in Engine by (20 points)

Could you be a little more specific?
When you say move to the closet Path2D, you mean move to the predefined path or make their own path.

Sorry - I mean on a predefined Path2D.

So for example a scene would have a simple, single Path2D with fixed points that player sprite can navigate along. When the player clicks on the scene, the sprite moves to the closest baked point on the Path2D.

Does that make sense?

Yeah Ok.
Lemme come up with something.

Update: I've added an answer below illustrating the approach I took in the end

2 Answers

+1 vote
Best answer

Ok how about this.
After making your path with the mentioned Path2D node, create a navigation2D node and 2 NavigationPolygons as a child of that. They will act as walls to surround the Path2D node, but don't let the path inside of the polygons. The covered parts are where the navigation avoids walking into. So if you start from inside the navigation polygons, then you should end up in the closest uncovered parts. In this case, the surrounded path. Just be sure to cover areas outside of your view too or else your navigation may walk outside of the screen.

That should be enough for node setup. Now to.. Oh wait! Almost forgot. We will also want a second Path2D node with a PathFollow2D node as a child. Is will be needed when-


The code will be inside your player node/scene. We will need a reference to the Navigation 2D, the second Path2D, and it's PathFollow2D to start with.

Now after we get those important stuff, we will now create code to get a path. From your player's current position, to a point where the mouse has clicked. We will do that in a function when the mouse is clicked.

func _input(event):
    if event is InputEventMouseButton:
        if event.pressed:
            #nav is reference to Navigation2D node.
            var path_points = nav.get_simple_path(position, event.position)
            #we will now create a Curve2D resource with these points.
            var curve = Curve2D.new()
            for point in path_points:
            #and now we put the curve into the Path2D node(second one).
            path.curve = curve
            path_follow.offset = 0
            following_path = true

Now that the path is in the path node, we can now follow it to its end with the pathfollow2D node. Code for this will be run continuously; so we will put it in either _process or _physics_process.

func _process(delta):
    if following_path:
        path_follow.offset += 50*delta #This number is the player's speed. Which you can change.
        if path_follow.unit_offset >= 1:
            path_follow.unit_offset = 1
            following_path = false

        global_transform = path_follow.global_transform

And that should be all! BTW I made this all up without Godot on me right now so if there is anything wrong, let me know.

I hope all this typing was not all in vain. I'm doing it on a phone!

by (3,910 points)
selected by

Wow - thanks! Excellent commitment using your phone :)

So it kind of works. I may not have set it up as you imagined. Here's the structure:

Scene structure
And heres the code (on the sprite):

extends Sprite

var following_path = false
var path_follow
export (int) var SPEED

func _process(delta):
    path_follow = get_parent().get_node("Path2D2/PathFollow2D")
    if following_path:
        path_follow.offset += SPEED * delta
        if path_follow.unit_offset >= 1:
            path_follow.unit_offset = 1
            following_path = false

        global_transform = path_follow.global_transform

func _input(event):
    if event is InputEventMouseButton:
        if event.pressed:
            var path_points = get_parent().get_node('Navigation2D').get_simple_path(position, event.position)
            var curve = Curve2D.new()
            for point in path_points:
            get_parent().get_node("Path2D2").curve = curve
            path_follow.offset = 0
            following_path = true

And the result:

Scene gif

As you can see it works on the first one, but then resets back to the beginning. On the second one it it gets to the right click location, but seems to ignore the third point on the Path2d.

That second one works exactly as how it should. You see it's not really following the path, but the area around the path. If you want it any closer, then you should have the navigationpolygon closer to the path.

And I also know why it resets to the beginning. Your pathfollow's loop option is enabled. Once unit_offset becomes >= 1 then it would *loop* back.

And I bet you don't want that scale to change. In that it case, instead of doing-

global_transform = path_follow.global_transform

Instead do-

position = path_follow.position
rotation = path_follow.rotation

Don't forget to vote n' select ;)

Nice. It's exactly the kind of effect I'm trying to achieve. Thanks a lot!

0 votes

In case anyone comes across this, I played a bit more with this problem and went for a more programmatic approach. Basically I just got the baked points of the Path2D and found the closest one to the mouse click event then looped through them all with a slight delay to simulate movement. Maybe not super efficient, but follows the path in a way I'm happy with. Code:

extends Sprite

export (int) var index = 0
export (int) var speed = 30
export (int) var click_offset = 5
export (String) var path_name
var path
var points

# the moving variables
var counter = 0
var is_moving = false
var t = 0 # the delta counter
var selected_point
var forward = true

func _ready():
    # The sprite needs to share the same parent as the path2d
    path = get_parent().get_node(path_name)
    points = path.curve.get_baked_points()
    var initial_pos = path.curve.get_point_position(index)
    position = initial_pos

func _process(delta):
    # if the counter has reached the point we want to get to, stop
    if counter == selected_point:
        is_moving = false
    # otherwise keep looping through the points until we reach it
    if is_moving:
        t += delta
        if t > speed / 100:
            if forward:
                counter += 1
                counter -= 1
            position = points[counter]

func _input(event):
    if event.is_action_released("touch"):
        # get the event position and find the closest baked point
        var click_pos = event.position
        for i in range(points.size()):
            var p = points[i]
            if p.x >= click_pos.x - click_offset or p.x <= click_offset + click_offset:
                # determine if going forward or backwards along the path
                if selected_point and i < selected_point:
                    forward = false
                    forward = true
                selected_point = i
                is_moving = true
by (20 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.