Accuracy Variable when using look_at() for targeting

Godot Version

v4.1.3

Question

Hello!
I have a 2d top-down game that has the player target enemies with a gun using their mouse cursor.
Currently I have the player’s weapon looking at the mouse cursor, with the following code:

var accuracy = 25 # lower is more accurate
var lerpFactor = 0.1 #smoothing for aim accuracy

func _process(_delta):
var target = get_global_mouse_position()
var aimedTarget = target + Vector2(randf_range(-accuracy, accuracy), randf_range(-accuracy, accuracy))
look_at(lerp(aimedTarget, target, lerpFactor))

This targeting has the “gun” looking at the global mouse position and lerping between that location and the accuracy value.
The problem is that the closer the target is to the player, the worse the accuracy gets, and i am not sure how to fix that.
Preferably I would like the accuracy to stay the same regardless of the distance to the player, or get better the closer the target position is.
Any advice on how to accomplish that?

Hi! So the issue is a mathematical/logical one. If you consider your player at the “origin”, or in other words, at 0, 0, then a target that is 50 pixels along the x axis away could (in the extremes) be at either 25, 50 or -25, 50, which if you calculate it would give you an angle variance of around 90 degrees! Where as a larger distance would result in a smaller angle.

There are two things you could do:

  1. Include distance in your calculation,
var frame_accuracy = accuracy * target.distance_to(global_position) * 0.1
var aimedTarget = target + Vector2(randf_range(-frame_accuracy, frame_accuracy), randf_range(-frame_accuracy, frame_accuracy))

You would probably need to change the values, maybe use a remap so it goes from a “close” accuracy to another based on distance.

  1. Instead of changing the target position, add to the angle instead.
lerp_angle(rotation, rotation + randf(accuracy), lerpFactor)

If I was doing this, I would choose option 2, unless I wanted to get a cursor showing for the target.

1 Like

Thank you for the info! I will try these and see!

So, if anyone else stumbles across this post and wonders how I fixed it… Well I ended up completely changing the way the “gun” handles aiming and accuracy.

So if you are looking for a way to have a 2D gun or projectile firing weapon aim and fire, here is how I did it:

First, the weapon has an Area2D node that gives it its range, and it uses that range area to look for targets, and then find the closest target.
The code for this looks like this:

First I stored all the targets in range in an array:

func findEnemies():
    var enemies = []
	var bodies = $Area2D.get_overlapping_bodies()
	for body in bodies:
		if body.is_in_group("Enemy"):
			enemies.append(body)

Then I used this code to look for the closest enemy.

func findClosestEnemy():
	var closestDistance = INF
	var closestEnemy = null
	if enemies.size() > 0:
		for enemy in enemies:
			if enemy != null and enemy.is_in_group("Enemy"):
				var enemyHealthNode = enemy.get_child(1)
				if enemyHealthNode.currentHealth > 0:
					var distance = player.global_position.distance_to(enemy.global_position)
					if distance < closestDistance:
						closestDistance = distance
						closestEnemy = enemy

Once I had the closest target, I could then use this code to rotate the gun/weapon to look at the target.
You can use the lerp() function to control how fast you want the rotation to happen, so mess with the final value to get the feel you want.

if closestEnemy:
    #find position of closest enemy
	var targetEnemy = closestEnemy.position
	#get vector from self to enemy
	var v = targetEnemy - global_position
	#get angle of vector
	var angle = v.angle()
	#get current rotation
	var r = global_rotation
	#get angle to target
	angle = lerp_angle(r, angle, .06)
	#set rotation
	global_rotation = angle

**Also, my initial desire was to use the mouse position as the target, and I forgot to mention that this is still possible with this code! Simply replace the first two lines of code from the block above with the following:

#get mouse cursor position
var mousePos = get_global_mouse_position()
#get vector from self to mouse position
var v = mousePos - global_position

So all that handles rotation to look at the target, but it does not handle accuracy! How did I do that you ask? Well, I ended up modifying the way the projectile fired by the gun spawns to control the accuracy.
Here is how I did this:

#variables
var accuracy = .1
var currentAmmo = 30

func shoot():
	if currentAmmo > 0:
		var b = Bullet.instantiate()
		owner.add_child(b)
		b.transform = $Barrel.global_transform
		b.rotation += randf_range(-accuracy, accuracy)

The $Barrel node in this code is a Marker2D placed at the end of the barrel of the weapon’s sprite, basically to function as a point where the projectiles spawn. by adding in the randf_range() it randomly changes the direction of each bullet as it is fired, giving us the desired accuracy based on the accuracy variable!

There are other ways of doing this, as I am sure some more experienced programmers can tell you, but this method worked for me and allowed me to figure out a lot of how you can rotate things to look at other things.
I hope this is able to help you if you needed it!