NPC following player around obstacles? (2D, top-down RPG)

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

Hi friends!

Working on a top-down pixel action RPG, and I have an NPC who offers to help the player. If you say yes, they move around independently (there’s a timer, and on timeout the NPC gets randomly assigned a new direction), but they stick close to you (if the distance between the NPC and the player is greater than 200, the direction is updated to move the NPC directly towards the player. Here’s my code:

var player_relative_position = player.position - position
     if abs(distance.x) >= 200 || abs(distance.y) >= 200:
            direction = player_relative_position.normalized()

This works fine, unless there’s an obstacle blocking the path between the NPC and the player - then it just gets stuck.

Any best practices on how to navigate around collisions, especially when it comes to following the player down narrow hallways? I suspect I need a RayCast2D here, but I’ve not worked with them a ton and I’m not sure how exactly to script it so they could help the NPC move around obstacles.

Thanks so much for any insights!

:bust_in_silhouette: Reply From: rossunger

Pathfinding is actually a pretty complicated topic! What you’re trying to achieve is not trivial.

Have you looked at any aStar examples? If you google Godot aStar you should find some good tutorials. I think gdquest has one.

You might also want to look at how boids work, for some basic avoidance algorithm.

Godot 4 has some promising new navigation features that I’m hoping to try soon.

If you’re obstacles are simple and there’s only one at a time, you might be able to get away with a simple avoidance algorithm

Another thing to consider is cheating… Like, if the NPC ends up off screen, then teleport them back on screen. The player won’t notice, and you won’t have to do any complicated pathfinding.
Or letting the NPC move through objects instead of being blocked by them
Depending on the object this can look/feel ok

In terms of raycasts, they can tell you if there’s a collision in a specific direction. The question then becomes how do you decide what to do if there is a collision?

Thanks, this is super helpful!

samjmiller | 2022-02-16 00:11

:bust_in_silhouette: Reply From: ichster

It sounds like you are going to need to delve into the exciting (and sometimes painful) world of path finding! Luckily, godot offers some great out of the box options to get path finding working. One such option is using the Navigation2D class. The fastest way to get a path out of this class is to supply it with a child TileMap that contains all areas that are NOT an obstacle, and then call something like this (untested code alert!):

func get_path(current_global_npc_position:Vector2, npc_global_position_goal:Vector2) -> Array:
    current_global_npc_position = $Navigation2D.get_closest_point(current_global_npc_position)
    npc_global_position_goal = $Navigation2D.get_closest_point(npc_global_position_goal)
    return $Navigation2D.get_simple_path(current_global_npc_position, npc_global_position_goal )

This will only get you so far, as there are lots of nuiances to path finding particular to each game’s situation, such as optimization, main thread blocking issues, paths that are to optimized for the size of an NPC, how to get NPCs to actually follow a path without doing the charleston, and how to generate navigation tilemaps without losing your mind.

I recommend checking out this excellent youtube tutorial for more info: https://www.youtube.com/watch?v=0fPOt0Jw52s

Appreciate this!

samjmiller | 2022-02-16 00:11

:bust_in_silhouette: Reply From: Major_Monkey_0720

this might be too simple for what you are trying to do, but my solution would be have an array of positions from the player node, whenever they move.

if Input.is_action_pressed("move"):
     position += Vector2(x, y)
     path_array.append(position)

Have that list only like 35 positions long though, or whatever distance you want the NPC to stop at.

Have the NPC follow behind your player along this path, I’d say use position.move_toward(path_array[index], position.distance_to($player.position) * delta)
Then, when you have the NPC where you want it (when the player is stopped), set a timer, and once it runs out, let the NPC start moving around, but then go back to it’s position once new elements start getting added to the array, once the character starts moving again.

I know this response is written kind of sloppy, but I hope you understood the basic gist.

That’s a really interesting possibility, thank you!

samjmiller | 2022-02-16 21:35