Unity Physics — Simulating Physics for Trajectory Prediction

Sean Duggan
4 min readMay 30, 2024

--

Since we have access to pretty accurate hard-body physics, it makes sense for us to let the player toss objects around the scene. But honestly, judging where a thrown object is going to wind up is almost as annoying as lining up a platforming jump in a 3D game, so we probably ought to provide a Trajectory prediction for the throw. First, we’ll set the scene.

We have something from which to launch the projectile, a target portal, and a glass window in between with an opening. Setting up the launcher is a matter of having it instantiate a prefab and then adding a (currently fixed) force.

public class LauncherScript : MonoBehaviour
{
[SerializeField] AirmailPackage _projectile;
[SerializeField] float _force;

[SerializeField] Transform _originPoint;

void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
AirmailPackage projectile = Instantiate(_projectile, _originPoint.position, _originPoint.rotation);
projectile.ApplyImpulse(_force * transform.forward);
}
}
}
public class AirmailPackage : MonoBehaviour
{
[SerializeField] Rigidbody _rb;

private void Awake()
{
_rb = GetComponent<Rigidbody>();
if (_rb == null)
{
Debug.LogError("No Rigidbody found");
Debug.Break();
}
}

public void ApplyImpulse(Vector3 force)
{
if (_rb != null)
{
_rb.AddForce(force, ForceMode.Impulse);
}
}
}

As a side note, when I first developed my AirmailPackage, I was getting some odd errors where applying the impulse resulted in no movement. It turns out that Awake is called after an Instantiate, but Start comes later, which mean that instantiating a character, and then immediately calling a function depending on a Rigidbody component on the character didn’t work correctly.

As it stands, this mainly works with a few values plugged in.

But, like I said, that creates a trial-and-error situation where we don’t know just how much power we need to add. One way to solve this is to create an additional scene to track the physics objects to simulate them. The basic steps to do so:

  1. Create a new scene and keep a reference to it.
  2. Get a reference specifically to the physics of the new scene.
  3. Move the simulated objects into the new scene.

So, first, we set up our data structures.

public class SimulatedScene : MonoBehaviour
{
private Scene _simulatedScene;
private PhysicsScene _physicsScene;

// A reference to a collection that holds the items we plan to simulate
[SerializeField] private Transform _targetParent;
}

Initial setup is simple. We’ll assume, for now, that we have this script on one of the objects in the scene we plan to simulate, so we can use the root field to get the root object and to create the simulated physics scene.

void Start()
{
_targetParent = transform.root;

CreateSimulatedPhysicsScene();
}

private void CreateSimulatedPhysicsScene()
{
_simulatedScene = SceneManager.CreateScene("Physics Simulation Scene", new CreateSceneParameters(LocalPhysicsMode.Physics3D));
_physicsScene = _simulatedScene.GetPhysicsScene();
}

Most of this should be fairly straightforward. The second parameter in CreateScene is saying that we do need to create a 3D physics scene for this new scene. Now, we need to move the objects over that we want to simulate. To do this, we’re simply going to iterate through the objects in our scene that have been designated as Obstacles (I’ve tagged them in the system), instantiate new copies, add them to our physics scene and (if present), the MeshRenderer of the obstacle is disabled.

foreach (Transform obstacle in _targetParent)
{
if (obstacle.CompareTag("Obstacle"))
{
GameObject simulatedObject = Instantiate(obstacle.gameObject, obstacle.position, obstacle.rotation);
Renderer renderer = obstacle.GetComponent<Renderer>();
if (renderer != null )
{
renderer.enabled = false;
}

SceneManager.MoveGameObjectToScene(simulatedObject, _simulatedScene);
}
}

Practically, nothing looks different in our scene.

But now, the original objects are invisible, and visible copies have been put in our Physics simulation scene. That’s a good first step, but next, we have to write some code to have a graphical element interact with our physics scene.

--

--