Shmup Physics in Unity3D

My 2011 homebrewed shmup engine included some unique physics features.

Last year, I decided I wanted to try and revisit the shmup (shoot-em-up) genre, this time through the use of Unity, which would handle many of the things that ate up my time on the previous game.  This time, there would be no worrying about physics engines, editors, scripting language integrations, shaders, audio APIs.  And I would think more carefully this time about my use of physics.

Physics Decisions

Though some would say that there’s no question that you should avoid using a physics engine for a shmup, I wanted to keep some of the things that it afforded me.

  1. Well-defined ship & environment collision shapes.
  2. Continuous bullet collision (more on this later).
  3. “Blowback” effects from weapon fire & explosions.
  4. Interactive props
  5. Dynamic camera boundaries

There were, however, some problems that physics presented for use in a shmup.  In particular, shmup gameplay is all about precision and predictability.  When you press the button or interact with the world, it needs to react consistently, or you risk punishing the player at random.

Player Movement

In a shmup, the player typically immediately moves at full speed when the joystick is fully extended in a direction.  If you ramp the speed, the feeling of precision input is lost.  This can be accomplished by placing the player ship explicitly each frame, but if you’re using a physics engine, you’ll lose the ability to have the player collide with enemies, the environment, and camera boundaries, and need to come up with another solution.  Typically, this is solved in shmups by limiting playable space in code and literally blowing up the player when they touch anything.  For my game, however, this is not the intended design.

I accomplished both goals by giving the player a dynamic Rigidbody2D, which will stop when it collides with external forces.  Player input is transferred into motion by directly setting the velocity of the player’s ship.  This means that collision with other objects works correctly, and the speed of motion reacts immediately and predictably.

The PushModule creates a sense of weight and reaction where the physics are faked.

I still wanted to impart “blowback” physics on the player.  Since velocity is set directly each frame, forces imparted by, say, an explosion, would immediately be negated on the next frame when velocity was re-applied.  To enable this, I created a MonoBehaviour component called “PushModule” that contains an additive velocity property which is added to the input-derived velocity each frame.  This allows both the “pushing” effect, and the “predictable input” effect to work together harmoniously.

Bullet Collision

This part took a great deal of research and time to land on a consistent solution.  First, I tried setting up bullets as triggers, but I found that at low framerates, the bullets could skip right over a target without dealing any damage.  Next, I tried removing the Collider2D and Rigidbody2D.  Each frame in the FixedUpdate function, I would Raycast between the previous position and the new position.  This mostly worked, but when a bullet was fired at a fast-moving object, there was still a chance they could skip over each other.

After conducting some research on the matter, I found that the solution came in the form of CollisionDetectionMode2D.Continuous along with Rigidbody2D.isKinematic.

Continuous collision means that any time a Rigidbody2D would collide with another, even at fast speed and low frame rate, the collision is detected and correctly reported.  It’s more expensive than Discrete collision, but in the case of a shmup, accuracy is extremely important.

As with all things in a shmup, bullets need to behave predictably, which makes them a great candidate to be moved by game code, not by the physics engine.  Without using the physics engine, we cannot achieve Continuous collision detection, do what do we do?

The concept of a Kinematic Rigidbody2D is a bit difficult to describe, but ultimately, it means that it can affect others, but will not be affected by others.  In other physics engines, I’ve heard this called “keyframed” or “infinite mass” collision.  This allows the object to be placed by code while still being visible to the physics engine.

The last piece of the bullet collision puzzle came in the form of Rigidbody2D.MovePosition.  In order for Continuous collision detection to work correctly when setting position by code, you need to use this function to update the body’s position, rather than setting the transform’s position.  This informs the physics engine that a position change was made, allowing it to calculate everything that would have happened between the old and new positions.

NPC Movement

NPCs move along precisely-scripted paths.

If you’ve played any old-school shmups, you know that enemy movement is predictable and precise, allowing you to plan your attack well.  To achieve this, I wanted to make sure enemy movement was done through updating the position in game code, not through physics forces.

From here, I decided to scale back the notion of what mattered in an NPCs physics.  Since their motion is being tightly scripted, the notion of stopping when they hit another object is meaningless.  Because of this, I can get away with NPCs having Collider2D on them, but no Rigidbody2D.  Motion is achieved entirely through script (following splines, ‘homing’ toward a player, or simply rocketing forward) and no physical simulation is necessary.

The one thing I did want was the ability to “blowback” some NPCs from explosions.  To achieve this, I simply used the same “PushModule” as I had used on the player, this time interacting with the Transform rather than the Rigidbody2D.

Playing with Perspective

In assembling a particular boss fight, I ran into a problem that arose from the perspective.  The game is rendered in perspective 3D, but played in flat 2D.  This boss would move to one side of the screen and fire a laser beam.  Due to the perspective of the camera, simply flattening the z-values of the laser’s start and end positions was insufficient.  Doing it that way resulted in the action of the laser not lining up correctly with the visuals of the laser.

This boss proved problematic when the lasers didn’t align nicely along the gameplay plane.

To fix this, instead of raycasting from the start position of the laser to the end position of the laser, I instead take the start and end positions of the beam and project them from their respective camera-parallel planes (not always the same plane), into what I call the “gameplay plane”, which is the camera-parallel plane in which the player flies.

Player Motion vs. Auto-Scrolling

I had always had auto-scrolling segments as part of this game.  For a tightly-scripted shmup experience, autoscroll is vitally important to keeping the pace as-intended.

When ignoring the physics engine, it’s fairly simple.  All player motion happens relative to the camera, rather than the world, but with a physics engine, this becomes more complicated.

For my initial implementation, I simplified the player physics vastly by keeping the camera totally stationary and moving the world around it.  What I found, however, was that I was losing a great deal of goodness that you get from working with a static environment.

  1. Static environments can bake in Global Illumination & Reflection Probes.
  2. Rendering of static meshes can be optimized better than moving meshes.
  3. If the camera doesn’t move, the skybox doesn’t move.
  4. Enemies that move independently are no trouble, but enemies that interact with the environment introduce strange parenting structures when the environment is constantly moving.
  5. How do we handle cases where we want to allow the player to explore?  Do we keep the player still and move everything else?  What does this mean for camera drift?

I had so much trouble getting the physics to play nice with camera movement in my original shmup game back in 2011 that I was dreading making the transition back to a dynamic camera.  Unity3D’s core systems, however, offered me a cheap hack that, seems to be working quite well so far.

List<Transform> originalParents=newList<Transform>();
foreach(Transformtargetintargets) {
    originalParents.Add(target.parent);
    target.SetParent(transform);
}

transform.position=rigidTarget.position;
transform.rotation=rigidTarget.rotation;

for(inti=0;i<targets.Count;i++) {
    targets[i].SetParent(originalParents[i]);
}

I believe my next step from here is to skip the parenting element, and simply recalculate the new position and orientation based on relativity between the camera Transform and the ship transform, then apply the positions using Rigidbody2D.MovePosition and Rigidbody2D.MoveRotation, though as it is today, it does not seem to have any significant adverse effects to use the parenting hack.

Do you have any physics tricks or tips that you’ve used in side-scrolling games in Unity3D?  Let me know in the Facebook post!

Restarting the Devlog

It’s been a while since I really shared what was going on with Slonersoft.  I’ve had a shmup in development for a long time.  Helios Warp made a short debut at the Northwest Pinball and Arcade Show.  I’ve done a lot of sketching as well.   Will post more soon.