Move a sprite along a Path2D to a point based on mouse click

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By bl-rd

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

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.

SIsilicon | 2018-05-17 14:02

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?

bl-rd | 2018-05-17 14:43

Yeah Ok.
Lemme come up with something.

SIsilicon | 2018-05-17 16:26

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

bl-rd | 2018-05-31 20:20

:bust_in_silhouette: Reply From: SIsilicon

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-

GDScripting

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:
                curve.add_point(point)
            #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!

Wow - thanks! Excellent commitment using your phone :slight_smile:

So it kind of works. I may not have set it up as you imagined. Here’s the 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:
				curve.add_point(point);
			get_parent().get_node("Path2D2").curve = curve
			path_follow.offset = 0
			following_path = true

And the result:

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.

bl-rd | 2018-05-17 20:45

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 :wink:

SIsilicon | 2018-05-17 20:54

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

bl-rd | 2018-05-17 21:13

:bust_in_silhouette: Reply From: bl-rd

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
		    else:
			    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
			    else:
				    forward = true
			    selected_point = i
			    is_moving = true
			    break