Dat PASS!

Much like shooting monsters in Space Invaders, the key to passing is to aim at where your target is going, not where it is.

Pass prediction is working!
Pass prediction is working!

With that, and some handy prediction code courtesy of a stackoverflow user named “Broofa”, I was able to get it working. First I had to translate Broofa’s code into C#. I created a new class called Coord to store the x,y data. I didn’t use Unity’s Vector2 because it isn’t nullable, and Broofa’s code returned null when no target is position can be calculated.

Here’s my code:

using System;
using UnityEngine;
public static class TargetPrediction
{
	public static Coord Intercept(Vector3 sourcePosition, Vector3 targetPosition, Vector3 targetVelocity, float projectileSpeed)
	{
		float tx = targetPosition.x - sourcePosition.x;
		float ty = targetPosition.z - sourcePosition.z;
		float tvx = targetVelocity.x;
		float tvy = targetVelocity.z;

		// Get quadratic equation components
		var a = tvx * tvx + tvy * tvy - projectileSpeed * projectileSpeed;
		var b = 2 * (tvx * tx + tvy * ty);
		var c = tx * tx + ty * ty;

		// Solve quadratic
		Coord ts = quad(a, b, c); // See quad(), below

		// Find smallest positive solution
		Coord sol = null;
		if (ts != null)
		{
			float t0 = ts.x;
			float t1 = ts.y;
			float t = Mathf.Min(t0, t1);
			if (t < 0) t = Mathf.Max(t0, t1); 			if (t > 0)
			{
				sol = new Coord(targetPosition.x + targetVelocity.x * t, targetPosition.z + targetVelocity.z * t);
			}
		}


		return sol;
	}


	/**
	 * Return solutions for quadratic
	 */
	private static Coord quad(float a, float b, float c)
	{
		Coord sol = null;
		if (Math.Abs(a) < 1e-6)
		{
			if (Math.Abs(b) < 1e-6)
			{
				sol = Math.Abs(c) < 1e-6 ? new Coord(0, 0) : null; 			} 			else 			{ 				sol = new Coord(-c / b, -c / b); 			} 		} 		else 		{ 			var disc = b * b - 4 * a * c; 			if (disc >= 0)
			{
				disc = Mathf.Sqrt(disc);
				a = 2 * a;
				sol = new Coord((-b - disc) / a, (-b + disc) / a);
			}
		}
		return sol;
	}
	
public class Coord
{
	public float x;
	public float y;
	public Coord(float x, float y)
	{
		this.x = x;
		this.y = y;
	}
}

}

Note that I’m using x & z from the Unity vectors, since that’s where my players are in 3D space. Now that I had that code, I needed to get the values for this method. I had the source and target positions, and the target velocity is easily pulled from rigidbody.velocity. But I had to figure out the ball velocity. This was a tiny bit trickier than I imagined because the ball velocity is of course governed by the physics engine. Luckily for my weary brain, Unity forums user Brian Stone had the answer I needed with this equation:

float v = (F/rigidbody.mass)*Time.fixedDeltaTime;

F is the force you want to throw the ball at, and everything else is provided by Unity. Handy! After plugging all this in, it still didn’t work quite right. It was way over-predicting where to throw the ball. That was when I remembered Brian mentioning the fixed timestep in Unity being 0.02 by default. Sure enough, when I multiplied my target’s rigidbody.velocty by Time.fixedDeltaTime, everything worked beautifully!

With that done, my general player controls are almost feature complete. Bonus points to any readers who A) exist, B) made it this far and C) noticed the new soccer man sprite!

Add a Comment