To Parallax or Not to Parallax

I’ve always though it would be cool to use 2D parallax techniques in Solaroids, but I always had reservations.  Who doesn’t like stars whizzing by at light speed and the sense of depth that multiple layers of background elements can provide.

When I started developing Solaroids I knew I wanted nebula to provide some kind of “ground” for the player. Solaroids being an inertia based game, and having a larger than single screen playing area, I felt it was key to provide a stable background in order to maintain a sense of location. As the player thrusts and drifts a long in a heavily populated arena of moving asteroids, enemies, and hazards, how else are they going to keep track of their current velocity. It was for these reasons, the time it would take to try it out to confirm or debunk my theories, and only a few requests for it, that I didn’t until just recently set out to experiment with it.

Solaroids has 6 dedicated layers that make up the current visuals.

  1. Backdrop stars / distant gas clouds
  2. Sprite based stars
  3. Nebula’s, galaxies, planets
  4. Gameplay
  5. Energy
  6. Lens effects

If parallax was to be added it was going to be to layers 1-3 only.  So after a recent reminder from a dedicated player that it needed it, and recent completion of some higher priority features, I set out to experiment by incorporating parallax math into those three layers. This wouldn’t make individual elements (stars, nebula, etc) exist at there own simulated depth, but it would be able help prove out whether the technique would ruin gameplay or not, and also let me play with adjusting the amount. It ended up looking pretty good. Musing about with the ship, at moderate amounts it didn’t seem to affect my ability to play the game either.  At extreme amounts it did give more of a “floaty” sensation, and some might even experience a bit of nausea, so having a way to turn it off, or control the amount would definitely need to be in the final version. A quick check with a player on a restricted alpha branch, and it was confirmed it needed more!

Convinced that the parallax had merit now, and knowing it would be a user option, I set out to take those three layers and allow individual elements to have unique depths within the layer. This really took it to the next level, so I posted a preview video to YouTube and twitter (above).  Based on the feedback and response I got I was definitely on the right track.  Even my wife, when she saw it for the first time, said that I needed to redo my trailer with parallax as it looked so much better.

All would seem sunny and bright at this point, but of course there’s always something that doesn’t go smoothly.  For this feature, that was the Seamless Wrapping mode I had just got done putting in the game before Christmas.

Let’s go back a bit. Originally, Solaroids had a wrapping arena where traveling through one edge of the world would end up with the player ending up on the other side. Just like the original Asteroids.  The caveat to this is that you can’t see what’s on the other side because Solaroids plays out on a large scrolling arena.  Is it a feature or a bug?  Well many players felt it was disorienting while fighting enemy ships, especially in the corners, and they are right. So a feature dubbed Seamless Wrapping was introduced that when active would eliminate the edges, and instead allow the player to keep flying as if they hadn’t wrapped, even though they really had.

From the above video one would think it was plain and simple, but that was the final product. It ends up that seamless wrapping and parallax don’t really go well hand in hand, which makes some sense, since it’s kind of like folding space and not something that I don’t have a lot of personal experience with.

One way of creating parallax in background elements is to dampen the elements behind the gameplay layer which is scrolling by at the nominal rate. Imagine your flying along and the coordinates of you ship wrap around.  So at that moment when you cross over there is a discontinuity on any layer that isn’t at the nominal scroll rate. A star in the background can’t be offset appropriately for both sides of the wrap. This can be easily mitigated if each layer is a seamless texture, but I needed to be able to put actual objects and landmarks in the background.  I solve this by keeping track of the original unwrapped position of the camera, and then using that value to compute the amount of parallax offset to apply.

One caveat of this method, is that if you keep flying in the same direction, the background elements continue to shift.  This isn’t noticeable since the world is being seamlessly wrapped, but it does pose a challenge for position tracking of any elements in the background with respect to the game play layer.  In Solaroids a prime example of this is the sun.  The sun is in the background, but it is also tracked in the player reticle showing the direction to it. The perceived position of the sun, taking into account parallax, has to be computed (projected) from parallax space into game space in order to correctly find the direction to the sun.

So the lesson to be learned is: seemingly simple features by themselves can take on new complexity when combined together.

Solaroids is available on Steam Early Access and is still being actively developed by Chad Yates as part-time Indie game developer.  Come try out the free Demo, join the Community HUB on steam, or join the ranks of brave fighter pilots defending the solar system and competing for high scores and prestige!


XNA Quick Tip – XNA SpriteBatch and Garbage

The XNA SpriteBatch class maintains an internal array to hold sprites to be drawn between calls to Begin() and End() if a sort mode is used. This array will grow as necessary, however, the reallocation will inevitably result in extra garbage that will eventually need to be collected.  On the XBox 360 this is quite expensive and so in general it is recommended to limit garbage wherever possible.

If you know the upper bound on the number of sprites to be drawn in a single batch, you can force the draw before the game begins.  This is pretty easy to do, however to make it even easier when you have multiple SpriteBatch instances for different purposes I use the following simple wrapper class in Solaroids that has a new constructor that takes an extra argument for the expected number of active sprites:

/// <summary>
/// Wrapper around a SpriteBatch that pre-allocates internal sorting buffers by drawing a set of sprites thus forcing the buffers to be expanded.
/// </summary>
public class PreallocatingSpriteBatch : SpriteBatch
{
  /// <summary>
  /// Initializes a new instance of the <see cref="PreallocatingSpriteBatch"/> class.
  /// </summary>
  /// <param name="graphicsDevice">The graphics device.</param>
  /// <param name="expectedActiveSprites">The expected number of active sprites.</param>
  public PreallocatingSpriteBatch(GraphicsDevice graphicsDevice, int expectedActiveSprites)
    : base(graphicsDevice)
  {
    ForceBufferAllocation(expectedActiveSprites);
  }

  /// <summary>
  /// Forces the buffer allocation.
  /// </summary>
  /// <param name="expectedActiveSprites">The expected number of active sprites.</param>
  private void ForceBufferAllocation(int expectedActiveSprites)
  {
    Texture2D tex = new Texture2D(GraphicsDevice, 16, 16);

    Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);

    for (int i = 0; i < expectedActiveSprites; i++)
    {
      Draw(tex, Vector2.Zero, Color.White);
    }

    End();
  }
}