Lightweight Physics
Trick Shot II runs on a microcontroller simulating real-time 2D physics at 60Hz.
The key parts of this engine were:
- A solver (integrator)
- Equations of motion
- Collision detection
- Collision handling
Simulation Structure
The Simulation was a simple struct made up of two lists of objects:
Dynamic- objects which can move aroundStatic- objects which have infinite mass
At the beginning of each level in Trick Shot, the simulation is cleared and then populated with these objects.
On each update step, the Simulation follows the following procedure:
- For all
Dynamicobjects,Solvefor your next state - For all
Dynamicobjects, check forDynamicCollisionwith all otherDynamicobjects- This sound pricey because it is an
O(N^2)operation, but in practice I rarely had more than one dynamic object; and when I did there were no more than 4.
- This sound pricey because it is an
- For all
Dynamicobjects, check forStaticCollisonwith allStaticobjects - For all
Dynamicobjects,Committhe next state
Calling Solve results in two states:
currentnext- what our integrator would choose as the next state without any collisions
Then we determine changes in next state based on collision interactions.
Finally, we Commit the next state by making current = next.
Solver and Equations of Motion
The selected solver was Runge-Kutta 2nd Order, this solver is quite simple; but so were the physics in Trick Shot.
I wanted a solver which had a deterministic execution time. Afterall, this was for a Jam, and I didn’t want something complex to debug if it broke.
The model used was a simple projectile model. I’ve derived this in Lagrange’s Equations, but the general formula are:
\[\begin{aligned} \ddot{x} &= -\frac{1}{2M}k_D\dot{x} \\ \ddot{y} &= -g - \frac{1}{2M}k_D\dot{y} \end{aligned}\]For the balloons, I set gravity \(g\) to 0, and bumped the drag right up, so they kind of just float around and help bounce the ball around as they get pushed.
Collisions
Collision detection is discussed in Geometry and Collisions.
An important part of the collision detection is determining the points of contact, which the “closest point to” methods are very helpful for doing.
Momentum Equations
We have two objects colliding where:
- \(p_1\), \(v_1\), \(m_1\) are the position, velocity and mass of object 1
- \(p_2\), \(v_2\), \(m_2\) are the position, velocity and mass of object 2
Compute the collision normal:
\[n = \frac{p_2 - p_1}{\|p_2 - p_1\|}\]Compute the projected velocity magnitude on that normal:
\[v_{collide} = (v_2 - v_1) \cdot n\]If \(v_{collide} >= 0\) the points are moving away from each other, so no collision occurs.
Compute the impulse:
\[j = -(1 + e)\frac{vrel}{\frac{1}{m_1} + \frac{1}{m_2}}\]Where:
eis the restitution, \(e \in [0, 1]\)- if
eis 0, totally inelastic, maximum energy is lost in the collision - if
eis 1, totally elastic, no energy is lost and they bounce
- if
Finally, we can compute new velocities:
\[\begin{aligned} v_1' &= v_1 - (j/m_1) \cdot n \\ v_2' &= v_2 - (j/m_2) \cdot n \\ \end{aligned}\]Handling Collisions
The momentum equations work well for two dynamic point masses, but in the physics engine, we were dealing with shaped objects, some static and some dynamic.
Dynamic on Static Collisions
Dynamic/Static collisions are easier to handle, I used the momentuum equations but with the following changes:
- Place dynamic object in the
nextstate - Check if there is a collision. If there is:
- Find the closest point \(p_{c2}\) on the other object.
- The normal vector is given by \(n = \frac{p_{c2} - p_1}{\|p_{c2} - p_1\|}\)
- This works because the dynamic objects were circles
- If the dynamic objects were not circles, we would also need to compute \(p_{c1}\) the closest point on the dynamic body to the static body
- Compute \(v_{collide}\)
- Place dynamic object in the
currentstate - Pass all of this to the static body to determine \(j\)
- If \(j\) is not zero, apply it to the
nextstate
The main reason that the static body determines \(j\) is to allow bouncy, springy and other such obstacles to all affect the dynamic object differently.
In some cases, static objects (like springs) will break the laws of physics and inject energy into the system… it is a game afterall.
Dynamic on Dynamic Collisions
My implementation for Dynamic on Dynamic collisions was similar to Dynamic on Static. The main difference being that both objects adjust thier momentum.
I barely tested how these interactions worked, and I have a feeling they are probably quite buggy under many circumstances.
Like many things in game design, I avoided complexity by not exposing the player to it.