Need help with predictive aim algorithm

Godot Version

4.5.1.stable

Question

hi, i’m trying to make an enemy shoot where the player is going instead of where they are, however when i stay still the bullet dosen’t move at all and if i move the bullet shoots in the wrong direction.

this is the formula i’m using

func attackWithPrediction(target: Entity) -> void:
	var bullet: Projectile = NEEDLE.instantiate()
	var timeToHit: float
	var predictedPosition: Vector2
	bullet.global_position = global_position
	timeToHit = global_position.distance_to(target.global_position) / bullet.speed
	predictedPosition = target.velocity * timeToHit
	bullet.direction = predictedPosition.normalized()
	get_tree().root.add_child(bullet)

here is a video of the behavior

edit:

ok, the behavior has improved a lot now, it still isn’t perfect, but i prefer the enemies to have imperfect aim, at least now they are trying to hit you :sweat_smile:

i’ll leave the code here in case someone needs it

##predics where the target will be and attacks there
func attackWithPrediction(target: Entity) -> void:
	var bullet: Projectile = NEEDLE.instantiate()
	bullet.global_position = global_position
	bullet.direction = interceptTarget(target.global_position,global_position,target.velocity,bullet.speed)
	if not bullet.direction.is_zero_approx():
		get_tree().root.add_child(bullet)

##uses data to intercept target and returns the predicted location
func interceptTarget(targetPosition: Vector2,shooterPosition: Vector2, targetVelocity: Vector2,projectileSpeed: float) -> Vector2:
	var targetToShooter: Vector2 = shooterPosition - targetPosition
	var DC: float = targetToShooter.length()
	var alpha: float = targetPosition.angle_to(targetVelocity)
	var SA: float = targetVelocity.length()
	var r: float = SA / projectileSpeed
	var positionToShoot: Vector2 = aimMath.solveQuadratic(1 - r * r,2 * r * DC * cos(alpha),-(DC * DC))
	var directionToShoot: float = max(positionToShoot.x,positionToShoot.y)
	var timeToHit: float = directionToShoot / projectileSpeed
	var predictedTargetLocation: Vector2 = targetPosition + targetVelocity * timeToHit
	return shooterPosition.direction_to(predictedTargetLocation)

class aimMath:
	##retruns a vector containing (root1,root2)
	static func solveQuadratic(a: float, b: float, c: float) -> Vector2:
		var root1: float
		var root2: float
		var discriminant: float = b * b - 4 * a * c
		if discriminant < 0:
			return Vector2(0,0)
		else:
			root1 = (-b + sqrt(discriminant))/(2 * a)
			root2 = (-b - sqrt(discriminant))/(2 * a)
			return Vector2(root1,root2)

Hi,

Unfortunately I don’t know how to fix your current function, I wish I could but, I can share a resource that worked for me when I implemented such a feature a few years ago on a project of mine.
I used that video which worked like a charm, it’s made in Unity but the maths will be the same for any engine, you’ll just need to translate the code to GDScript which should be fairly easy.

I hope that helps.

2 Likes

In your current code, the bullet’s direction will always be set to the direction of the target’s velocity.

When calculating the predictedPosition, you need to add the target’s current position.
And bullet.direction should receive the direction towards the predictedPosition (instead of just the normalized position).

	predictedPosition = target.global_position + target.velocity * timeToHit
	bullet.direction = bullet.global_position.direction_to(predictedPosition)

(This is still a very rough estimation for the bullet’s direction. If you need something more precise, you should take a look at the video @sixrobin shared.)

1 Like

Your version has 2 problems:

func attackWithPrediction(target: Entity) -> void:
	var bullet: Projectile = NEEDLE.instantiate()
	var timeToHit: float
	var predictedPosition: Vector2
	bullet.global_position = global_position
	timeToHit = global_position.distance_to(target.global_position) / bullet.speed

#	This is incorrect:
#	predictedPosition = target.velocity * timeToHit
#	bullet.direction = predictedPosition.normalized()

#	This is what you wanted:
	predictedPosition = target.global_position + target.velocity * timeToHit
	bullet.direction = global_position.direction_to(predictedPosition)
	
	get_tree().root.add_child(bullet)

However, you’ll see that it still doesn’t work correctly and this is because you are calculating the travel time of the bullet to the position where the player WAS at the time of shooting the bullet, which is not where he WILL BE when the bullet reaches that place, if he continues to move.

That’s where it gets a little more complicated, as we’re entering trigonometry and that’s where @sixrobin’s video comes into play to properly fix it.

1 Like

i understand the math, the thing is i can’t find the gdscript equivalent to out in c# when he implements the class function

Then just implement it on your own.

Instead of declaring function arguments as out you can simply return an array or an object.

i ended up returning a vector2 with the 2 roots

this fixed it, thx

1 Like