Point-&-Click Player Movement with Turn Radius

Godot Version

4.2

Question

The summary of this question is: How do I add a turn radius to point-&-click movement, and is a NavigationRegion2D the best choice for what I’m trying to do? I’m happy to just be pointed towards some resources, as my searches have not been very successful.

Game system is a top-down 2D multiplayer space game, basically a mostly open plane with the occasional small obstacle. I am developing this at the request of a friend, taking the project on as something of a challenge for myself, so switching to wasd movement would be undesirable. I’ve already told him that I almost certainly will not be making an entire end-product game for him, but I would like to try and deliver on a basic concept (end goal = multiple players in a server that can “mine” asteroids and attack each other).

The first hurdle I have reached is my friend’s rather specific vision for movement. He is adamant that the game be point-&-click, but also wants ships to have variable maneuverability based on engine stats. Currently I’m not really worried about the engine stats, I’m having enough trouble just trying to implement point-&-click that isn’t moving in a straight line.

All navigation systems I have found through tutorials give the character no turn radius, you simply gain velocity directly towards the target location and the sprite adjusts to match. I am looking for a system where the character only moves in the direction it is facing, and instead curves towards the destination (curve radius would later be determined by ship engine stats).

Is NavigationServer/Region2D even the correct choice for this? Do I need to be defining my own pathfinding if I want this style of movement, or is there another option that I haven’t run into?

Below is the relevant portions of my current attempt at emulating this:

const BASE_SPEED = 400
var acceleration = 1 # Too floaty, overshoots target

func _ready():
	navigation_agent.path_desired_distance = 35.0 # Just messing with the numbers here
	navigation_agent.target_desired_distance = 30.0

func _physics_process(delta):
	if navigation_agent.is_navigation_finished():
		return

	var current_agent_position: Vector2 = global_position
	var next_path_position: Vector2 = navigation_agent.get_next_path_position()

    # Issue here is that the path is a straight line to target position 
    # velocity should maybe be determined by orientation instead of the other way around?
	var direction = current_agent_position.direction_to(next_path_position)
	velocity = velocity.lerp(direction * BASE_SPEED, acceleration * delta)
	rotation_degrees = determine_orientation(velocity)
	move_and_slide()

func determine_orientation(pos: Vector2):
	var face = atan2(pos.y,pos.x) * 180/PI
	return face

func set_movement_target(movement_target: Vector2):
	navigation_agent.target_position = movement_target

func _input(event):
	if event.is_action_pressed("left_click"):
		set_movement_target(get_global_mouse_position())

My next step was going to be to implement it as “turn first, then move to position”, and then try to convert that into turning and moving at the same time.

NavigationAgent expects the actor to be able to stay within set desired distances to the current path segments.

That is usually a criteria that steering or turn-radius actors by default can hardly fulfil. They are just too random in their movement behavior to follow a navigation path closely enough.

That said is not impossible to use the NavigationAgent node for this but you need to expect that when your actor will regularly get out of distance that it will request many new navigation paths.

Also if you set the desired distances too large you actor will skip corners, if you set it too small your turn-radius or steering might get in the way of the actor reaching the next path index to proceed to the next path point, so it might start to backtrack.

In most cases you will start to fight the NavigationAgent behavior because it is not made to support actors that can not follow paths closely.

At that point it is better to not use the NavigationAgent node. Instead, query a navigation path from the NavigationServer and use that path with a custom agent script.

In that script you can look ahead the path segments to set better steering or turn-radius points to target for the movement. Or you can increase the path index when you see that your actor has not hit a point but is still past it.

As soon as you learned how to handle segment checks in your paths adding this to a point&click is trivial, you just need to set a good movement point to target for your actor and limit its rotation per frame.

2 Likes

Thank you so much! You’re an absolute treasure btw, just going through your comment history is more educational than most of what I find on my own.

1 Like