Compensating for linear_velocity in a "homing" rigidbody2D

Godot Version

4.2.1

Question

I’m building a 2D shoot-em-up, and I want to have a physics-aware homing misile.

my first simple implementation was to simply add a force along the x axis of the rigidBody2D, and in _integrate_forces do transform = transform.looking_at(target).

which behaves pretty much like you would expect: it enters a semi-stable orbit around the target. (it usually hits the target after 2-3 orbits, unless the target moves)

to make this homing missile a bit more efficient, i thought I’d try to compensate for it’s current velocity, but I cant get it to work the way I want.

I’ve tried a couple of things, like subtracting linear_velocity from transform.looking_at(target).x but that doesnt seem to work at all.

I have this hunch that the vector returned from transform.looking_at(target).x' and transform.x.direction_to(target)use the rigidbodys local coordinate space, whilelinear_velocity` uses global coordinate space? I’ve been trying to translate between the 2 but to no avail, not sure if that’s because I suck at vector math, or if I’ve somehow misunderstood the docs and the vectors dont mean quite what I think they do, or are represented in some other coordinate space than what i’m expecting.

so far all my experiments have ended in one of 2 behaviours: my missile either completely ignores it’s target and just flies straigh, or it starts to “jitter” back and forth, more or less perpendicular to the target, and slowly drifting in a fixed direction ( it seems to do continous 180 degree turns so quickly it looks like there are 2 missiles stuck to eachother by the tail, i guess the forces cancel eachother out, and it just drifts in some random direction)

any idea on how to get my homing missile to actuall hit it’s target without removing the rigidBody?

Can you explain your first attempt in a little more detail?

here is my current code, it’s not working very well though.

func _integrate_forces(state: PhysicsDirectBodyState2D):
	if isStarted:
		if !isTimeLimited || timelimit>0:
			if is_instance_valid(target):
				var velocity = Vector2(linear_velocity.x*transform.x.y,linear_velocity.y*transform.x.x )
				var targetDir = transform.x.direction_to(target.global_position)
				transform.x = targetDir + (targetDir-velocity)


func _physics_process(delta):
	if isStarted && is_instance_valid(target):
		timelimit -= delta
		var dist = position.distance_to(target.global_position)
		var velocityToTargetAngle = linear_velocity.angle_to_point(target.position)
		var force = thrust * delta * abs(velocityToTargetAngle) + min(dist, thrust)
		apply_central_force(transform.x * force)

my other attempts have been various failed attempts at figuring out the direction I should turn it to.

to clarify what I want to do, basically if the current linear velocity is in a direction to the right of the target, i want to turn the rigid body somewhat to the left of the target, so that when I apply a force along x, it will actually hit the target instead of entering a “orbit” around it.

So, when your velocity and acceleration are perpendicular to each other, you will “orbit”.

And if they arent exactly pointing in the same direction, there will be a portion that is perpendicular, giving an orbit like effect of varying degree.

Tldr: in the end, you just need to add a perpendicular acceleration to the current acceleration (opposing the orbit) to cancel it.

Also, I am sure there is an easier way than outlined below. But it was just my thinking off the top of my head. I can revisit this later when I have time to streamline it some.

Assuming the total thrust is limited, this is what I would do:

(Edited)

  1. find unit vector pointing to target
  • just divide any vector pointing to target by its magnitude (length)
  1. find paralell and perpindicular components of current velocity with respect to that vector
  • more than one way to do this
  • take dot product to get paralell portion
  • subtract that portion from original to get perpendicular
  1. Use that result to determine how much acceleration should be used to accelerate toward target vs how much to break orbit
  • personally I would multiply the perpindicular portion by a variable to adjust how much breaking the orbit is prioritized.
  • add perpendicular and parallel portions together, normalize, and multiply by total desired thrust (max acceleration?).

Thanks for your help! works like a charm now! in case anyone wants to copy my work, here is my homing class in it’s entirety: (including some functionality not discussed in this thread, cleanup is left as an exercise for the reader :slight_smile:

class_name PhysicsHoming extends RigidBody2D

@export var target: Node2D
@export var timelimit:float = 0 #only needed if isTimeLimited is set to true
@export var isTimeLimited:bool = false
@export var thrust:float
@export var overshootingThrustMultiplier:float = 5

@export var isStarted:bool = false

func _integrate_forces(state: PhysicsDirectBodyState2D):
	if isStarted && is_instance_valid(target):
		if !isTimeLimited || timelimit>0:
			var targetRelativePos = transform.looking_at(target.global_position).x.normalized()
			var directionDiff = (targetRelativePos.normalized() - linear_velocity.normalized())			
			transform.x = targetRelativePos + directionDiff

func _physics_process(delta):
	if isStarted && is_instance_valid(target):
		timelimit -= delta
		#this will be -0.2 if heading straight towards target, and -2.2 if heading in the opposite direction
		var velocityOffset = linear_velocity.normalized().dot(transform.x.normalized()) -1.2
		#multiply velocityOffset by a negative number, so that the worse the current trajectory, the stronger the thrust
		var thrustModifier = velocityOffset*-overshootingThrustMultiplier
		#maximum possible thrust is 2.2*overshootingThrustMultiplier*thrust, but if our trajectory is correct, thrust will be 0.2* overshootingThrustMultiplier * thrust
		apply_central_force(transform.x * thrust * thrustModifier)

func start():
	isStarted = true


Edit: made some improvements to the script, the missile now adjusts it’s thrust based on it current trajectory relative the target, so that if it misses the target (if the target dodges for example), the missile will increase it’s thrust to counteract overshooting. this allows it to have a realistic effect, while avoiding overshooting the target by too huge an amount.

You’ll have to play around with overshootingThrustMultiplier and the Mass of your rigidbody, along with other parameters, to get the desired effect. I’m currently using Thrust=50, OvershootingThrustMultiplier=5 and Mass=0.4kg, i suggest starting there and then experimenting until you get the effect you want.

possible improvements of this script is to move targeting logic from _integrate_forces to _physics_process and use apply_torque instead of just overriding the transform like I do now, to achieve a more realistic effect when the missile is turning. I might come back to update this post if I get around to doing that, but for now this is good enough for my purposes.

Thanks again to nutlike4693 for making this script possible, let me know if this script helped you make a game!

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.