VR contains many unknowns, but we have figured out that in an interactive experience, the player will want to interactive with everything. The more you make interactive, the more immersed your player will become. Most games can just slap some physics colliders on their controllers and call it a day, but we need to do things a bit differently in Punchdrunk.
In Punchdrunk, I’m recreating the feel of the old 1990s arcade brawlers, and that means even the interactions have to have that arcade feel. There are two ways the player interacts with the game: Punching and grabbing. Punching is represented through single-frame impact events that have a snappy, over-the-top response. The enemy doesn’t snap back when a blow connects, the minute the hitboxes overlap, their body instantly contorts. Grabbing works a bit differently – and is a blog post for another day.)
It turns out this kind of instant feedback feels wonderful in VR, at least within the player’s arm’s reach. This is because it brings out a kind of visceral/haptic effect that is otherwise missing in current VR. However, once an object is outside of the player’s hands, it looks broken if not physically simulated. This has ultimately proven to be the big challenge of Punchdrunk. It’s not making something arcade-based, or physically simulated, it’s marrying the two concepts together seamlessly.
Word to the purists – this involves a fair amount of shortcuts and ‘pseudo-physics’, but I find this kind of bizarro-math to be the most fun.
Back to environment interaction. I wanted it in Punchdrunk. Players should be able to hit various objects in their environment and knock them around – but we don’t have traditional physics simulation of the player’s hands. What we do have is single-frame collision events with the location of the collision and the velocity of the player’s controllers. How do we translate a single frame’s velocity vector into physics values that feel both ‘arcade-like’ up close and physically believable at a distance?
Rigidbodies are Unity’s component for controlling an object through a physics simulation – and they come with just the function we need: AddForceAtPosition. It does what the name implies – applies a force upon a rigidbody. And because force = mass*acceleration, when an object is heavier, it will receive less acceleration. Just the kind of instant-but-physically based behavior we want (spoiler alert: or is it?).
Now we must figure out how to convert speed to force. Which brings up another challenge – like all brawlers, players choose an avatar from a group of Martial Arts experts/1980s style action heroes. When the player throws a punch, it needs to hit with ‘hollywood’ force, not the power of an average human. This particular detail is another reason why we can’t just try to figure out the weight of a person’s hand and plug that into physics equations directly.
We’ll do a direct conversion instead – take a punching force similar to the ridiculous level given for Ivan Drago (around 4500 newtons), and then calibrate that to the ‘fastest’ punch a user throws. I made a test scene in Unity to measure my own controller speed on impact as seen below.
Using my numbers, the conversion from speed to force is about 740 – that is, multiply my controller speed by 740 to get the force my character should impart on environment objects.
I then made a test scene with a few prop ‘hittables’ of different masses, representing different kinds of objects. A tin can, a television, a car, etc. Below is a video of me hitting these objects with this direct conversion.
Clearly, something is wrong. Even the mightiest action hero is not capable of imparting so much force that an object instantly disappears. Values are too high in general, as even the 1 ton box flips over fairly trivially. More importantly, there is no ‘correct’ scalar using this technique. The speed->force value that would make a tin can behave correctly is so weak that the television weighted object doesn’t even wiggle on impact. What’s the deal?
We need to take a step back and look at the real-life situation to get some insight. Object collision isn’t a single instant response. It’s more of a strong push over a very short period of time. Below is a diagram of how that interaction works with a very light object.
Step 1: Fist (blue object) approaches light object Step 2: Contact is made and force transfer begins Step 3: Force transfer continues as light object begins to accelerate Step 4: Light object has accelerated to match the speed of the fist, no more collision occurs.
This is what we we missing before – the light object, with its small mass, accelerates quickly enough that it moves faster than the fist. It doesn’t absorb the same force as the heavier object. The heavy object won’t be flying off into the air, but it does absorb enough energy over a period of time to be pushed back or tipped.
Now, before we revise our conversion function, there is one more thing to address. RigidBody.AddForceAtPosition – what exactly is it doing? The function applies force for a single frame, but acceleration needs time to have any effect. What period of time is AddForceAtPosition using? Answer turns out to be one second.
Most punches don’t connect for an entire second, so we add another variable into our equation now, combatTimeStep, which represents how ‘long’ our impacts can deliver force. We now have enough information to take another crack at estimating this speed-to-force conversion, and it looks like this:
- Impact detected.
- Get the speed of the user’s controller
- Apply the conversion factor to force, as with our first attempt.
- Further scale this conversion factor by the combatTimeStep.
- Calculate the actual time this force could be applied before the object accelerates to match the fist’s speed.
- The actual force that gets sent to RigidBody.AddForceAtPosition is then scaled based on this period of time. An additional scale is also added here, to account for the fact that less force is delivered if the object is moving away from the fist.
Let’s take a look at some of the bizarro-math here.
Calculating total time force is applied before speed can be matched: In this case we have force=contollerVelocity*conversionValue*combatTimeStep force=mass*acceleration velocity=acceleration*time we want the time when velocity=controllerVelocity Combining equations we get acceleration=force/mass controllerVelocity=(force/mass)*time time = controllerVelocity/(force/mass) time = controllerVelocity*mass/(controllerVelocity*conversionValue*combatTimeStep) time = (mass/conversionValue*combatTimeStep)
That’s right, in our arcade-physics world the actual force doesn’t have an impact on the amount of time it is applied. Weird, right? Now clearly this is taking a lot of liberties, but it gets us the results we’re looking for. Take a look:
Much better. Our action hero is hitting tin-cans like home-runs, throwing TVs across the room, and even making those 1-ton boxes shake a bit.
It’s fairly likely you are trying to solve a similar problem if you’ve found your way to this blog post. In that case, I’ve prepared the above function as a github gist that one should be able to drop into their own Unity project.
Until next time – stay tuned for Punchdrunk.