Predict enemy position on multiple variables

Started by
6 comments, last by Fulcrum.013 5 years, 10 months ago

How do i predict the enemy position, so the bullet will always hit it - regardless how fast the enemy moves, including other variables such as:

- Enemy speed

- Weapon cooldown

- Bullet speed

- Gun facing direction

- Gun rotation speed

My current approach is very simple, so does it not work always:


// @TODO(final): Much better prediction scheme
// Multiply delta time by some factor computed by:
// - Gun cooldown
// - Bullet Speed
// - Gun Rotation Speed/Delta
Vec2f predictedPosition = enemy->position + enemy->facingDirection * enemy->speed * input.deltaTime;
Vec2f distanceToEnemy = predictedPosition - tower.position;
Vec2f directionToEnemy = Vec2Normalize(distanceToEnemy);
float angleToEnemy = ArcTan2(directionToEnemy.y, directionToEnemy.x);
constexpr float tolerance = 0.1f;
float delta = angleToEnemy - tower.facingAngle;
if(Abs(delta) > tolerance) {
	float inc = delta < 0 ? -1.0f : 1.0f;
	float value = Min(input.deltaTime, Abs(delta));
	tower.facingAngle += tower.data->gunRotationSpeed * value * inc;
}

Is there a better approach?

Advertisement

The approach I took is detailed here:

If you know the direction you must aim for a successful shot, just don't fire until the aim is bang on. My turret turning was fairly simple, move yaw towards target yaw, and limit the change to a certain amount. So if the aim is within that amount on the tick, you aim directly where needed, and fire the bullet.

Note mine is only 2d and doesn't take into account height. I did think about having 3d paths with splash damage but haven't put in.

Also if the enemy changes direction or speed, the bullet is likely to miss. But that kind of thing is hard to predict in real life too.

One thing I use is line intersection. You know, putting those simultaneous equations to use that they taught in school. It's one of the few uses of simultaneous equations.

 

Fun AI:

So what I do is I start Slerping towards the target. Then use Dot-product to see if I am at least in a 0-45 degree angle(This is based on speed of target) then I would calculate a intersect point using simultaneous equations.

Hit rate +/- 60% of the time.

 

Hard AI:

My fun AI was adapted from the "Dot-Product Intercepting". With a quick search I found this tutorial, that explains it very well: http://danikgames.com/blog/how-to-intersect-a-moving-target-in-2d/ It's easy to follow. It also has a 3D link: http://danikgames.com/blog/moving-target-intercept-in-3d/

As always, if you plan to learn it, first do the 2D before moving to 3D.

 

So the reason for my "Fun" version is that the AI is too smart. It hits with a +/-90% rate; especially when it can move towards the intercept point; then it is a 100% rate.

 

I almost forgot. I have a lerp version:

Spoiler

 



	void OnTriggerStay(Collider Other){

		if(Other.CompareTag("Player")){
			//Other speed as a percentage
			float SpeedPer = Other.GetComponent<Rigidbody> ().velocity.magnitude /
			                 Other.GetComponent<ShipEngine> ().StatsPlug.MaxSpeed;

			//Target ship or lead based on ship speed
			NewTargetPosition = Vector3.Lerp(Other.transform.position, 
				Other.GetComponent<ShipEngine>().LeadTargetPoint, SpeedPer);

			//The larger the difference between the target-
			DifBetweenTargets = (Vector3.Dot (LastTargetPosition.normalized,
				(NewTargetPosition - this.transform.position).normalized) + 1f) / 2f;

			//-the slower it turns to face the new target
			if (LockTarget < 1f) {
				LockTarget += DifBetweenTargets* Time.fixedDeltaTime;
			}
			//lerp between old and new exponentially as difference shrinks
			Offset = Vector3.Lerp(LastTargetPosition, NewTargetPosition - this.transform.position, LockTarget);
			this.transform.rotation = Quaternion.LookRotation (Offset);
		}
	}

 

 

 

I am very proud of this one even if it is only a prototype. It targets objects in sight quickly and objects behind it slowly. This gives it a "tracking target" feel.

Hit rate = +/- 50%

You can improve the hit rate with line intersection.

I nailed it:


if(tower.hasTarget) {
	assert(tower.targetEnemy != nullptr);
	Creep *enemy = tower.targetEnemy;

	// First we compute how many frames we need until we can actually fire (Weapon cooldown)
	float framesRequiredToFire = (tower.gunTimer / input.deltaTime);
	float timeScale = Max(framesRequiredToFire, 1.0f);

	// Now we predict the enemy position based on the enemy speed and the number of frames required to fire
	Vec2f predictedPosition = enemy->position + enemy->facingDirection * enemy->speed * input.deltaTime * timeScale;
	Vec2f distanceToEnemy = predictedPosition - tower.position;

	// Second we compute how many frames we need the bullet to move to the predicted position
	assert(tower.data->bullet.speed > 0);
	float bulletDistance = Vec2Length(distanceToEnemy) / tower.data->bullet.speed;
	float framesRequiredForBullet = (bulletDistance / input.deltaTime);

	// Now we recompute the time scale and the predicted enemy position
	timeScale = Max(framesRequiredToFire + framesRequiredForBullet, 1.0f);
	predictedPosition = enemy->position + enemy->facingDirection * enemy->speed * input.deltaTime * timeScale;
	distanceToEnemy = predictedPosition - tower.position;

	// Smoothly rotate the gun
	Vec2f directionToEnemy = Vec2Normalize(distanceToEnemy);
	float angleToEnemy = Vec2AxisToAngle(directionToEnemy);
	float deltaAngle = angleToEnemy - tower.facingAngle;
	float anglePositiveIncrement = deltaAngle < 0 ? -1.0f : (deltaAngle > 0.0f ? 1.0f : 0.0f);
	if(!tower.gunIsRotating) {
		tower.rotationTimer[0] = 0;
		tower.rotationTimer[1] = 1.0f / tower.data->gunRotationSpeed;
		float anglePerFrame = Min(Pi32 / tower.rotationTimer[1], Abs(deltaAngle));
		tower.targetAngle[0] = tower.facingAngle;
		tower.targetAngle[1] = tower.facingAngle + anglePerFrame * anglePositiveIncrement;
		tower.gunIsRotating = true;
	} else {
		assert(tower.rotationTimer[1] > 0);
		tower.rotationTimer[0] += input.deltaTime;
		if(tower.rotationTimer[0] > tower.rotationTimer[1]) {
			tower.rotationTimer[0] = tower.rotationTimer[1] = 0;
			tower.targetAngle[0] = tower.targetAngle[1] = 0;
			tower.gunIsRotating = false;
		} else {
			float t = tower.rotationTimer[0] / tower.rotationTimer[1];
			tower.facingAngle = ScalarLerp(tower.targetAngle[0], t, tower.targetAngle[1]);
		}
	}

	// Fire when we have a target in sight
	if(tower.canFire) {
		Vec2f lookDirection = Vec2AngleToAxis(tower.facingAngle);
		float maxDistance = Vec2Length(distanceToEnemy) + enemy->data->collisionRadius;
		LineCastInput input = {};
		input.p1 = tower.position + lookDirection * tower.data->gunTubeLength;
		input.p2 = input.p1 + lookDirection * maxDistance;
		input.maxFraction = 1.0f;
		LineCastOutput output = {};
		bool willHit = LineCastCircle(input, predictedPosition, enemy->data->collisionRadius, output);
		if(willHit) {
			towers::ShootBullet(*state, tower);
		}
	}
}

 

I only had a quick look but does your code roughly guess where the enemy is, calculate the time of a bullet moving to that position, then recalculate the enemy predicted position based on its speed and that bullet travel time?

If so then it does not take account that the enemy could be moving closer or further away from the shooter, and thus the bullet travel time will be less or more than that, i.e. the enemy predicted position and the bullet travel time 'vary together'. So you will miss a lot I think.

I may be wrong on that though I only had a brief look. That's what the simultaneous equations sort out for you (I must admit it's beyond me mathematically, decades since I did it at school!).

1 hour ago, lawnjelly said:

I only had a quick look but does your code roughly guess where the enemy is, calculate the time of a bullet moving to that position, then recalculate the enemy predicted position based on its speed and that bullet travel time?

If so then it does not take account that the enemy could be moving closer or further away from the shooter, and thus the bullet travel time will be less or more than that, i.e. the enemy predicted position and the bullet travel time 'vary together'. So you will miss a lot I think.

I may be wrong on that though I only had a brief look. That's what the simultaneous equations sort out for you (I must admit it's beyond me mathematically, decades since I did it at school!).

Indeed there was a bug when i computed the bullet distance (Speed was not converted to a distance), now it is:


float bulletDistance = Vec2Length(distanceToEnemy) / (tower.data->bullet.speed / input.deltaTime);

 

6 hours ago, Finalspace said:

Is there a better approach?

In case projectile velocity is constant try to use a proportional aiming algo. It have perfect accuracy for targets with  low trajectory curvature and also not require distance measurment. It usualy used for guided missiles aiming, but can be extended to unguided projectiles with assumption of constant projectile velocity.

Key of method - when projectile goes to interseption point, line that connect the projectile and the target  is not rotating. So wee need just 2 consecutive trajectory points of the missile and the target to calculate viewing line angle difference, and than turn the missile proportionally to angle difference. It how "Sidewinder", "Stinger" and any other IR-guided missile works. Following this algo, a missile, guided to nonimmunithing target, starting part of his trajectory finding a vector to interseption  point, and than moves about lieary. So it is possible to extend this algo for unguided progectils, by finding а interception vector by proportionally rotating a barrel before shoot. Only that we need for it is to select а 2 tragectory points for projectile. First point is the muzzle of the barrel point. Second can be eaesly calculated too. It just a muzzle point moved on opposite direction by one bullet timeframe step distance, to match time of previous target point. So we need just make 2 lines that connecting points and calculate angle betwin them, then rotate barrel propotionally to it angle. Barrel will follow a interception point for selected target. When angle absolute value becomes less then tolerance angle we can fire a bullet. 

Also this algo give ability to control accuracy and aiming response time, by modifying a proporionl regulation coefficient and tolerance angle.  



 

#define if(a) if((a) && rand()%100)

This topic is closed to new replies.

Advertisement