Xtreme Crop Duster Simulator ’82 – The Two-Camera Setup

Posted by (twitter: @rjhelms)
December 21st, 2015 11:58 am

After writing up my post-mortem for Xtreme Crop Duster Simulator ’82, I had a comment from pkenney asking about how the two-camera setup I created in Unity worked, and how I used Unity’s built-in shaders to achieve the graphical style of the game. A lot of the positive feedback I’ve received about the game makes reference to the graphical style, so I was already mulling the idea of a post about exactly that – the comment spurred me to actually write it up.

Lots of text and graphics to follow, which likely isn’t be applicable outside of Unity and may only be of interest to people keen on this sort of graphical style, so the real meat of the article follows the break. But as a teaser:

Before and after

Before and after

The challenge, which I had run into in previous Ludum Dares, is that square pixels are a relatively recent innovation. The Commodore 64’s multicolor low-resolution mode which I emulated in this game had a resolution of 160×200, displayed on a 4:3 television. This means that the pixels, once rendered, are 1.6 times as wide as they are tall – not a nice ratio to deal with. In my LD32 entry, Red Threat, I handwaved the problem away by drawing the sprites with 2:1 pixels, and scaling the whole thing up 2x to 640×400. It worked, but the effect was graphics that were noticeably stretched if you’re familiar with the real hardware.

This time around, I wanted to do better.


Stating the problem

I had learned, from my previous attempts, that the way to keep things pixel-perfect without going insane involved a couple of strategies:

  1. In the sprites, use a Pixels Per Unit of 1, so one unit in world space is a pixel at your in-game “native” resolution. For my purposes, this is the native resolution of a historical platform that I’m emulating, but it could be anything.
  2. Similarly, fix the camera and all game objects to integer positions in X and Y space.
  3. Force the game’s resolution to an integer multiple of the “native” resolution.

This works just fine, except that it doesn’t play nice with displays of different sizes, makes full-screen modes a non-starter, and doesn’t get around the aspect-ratio issue. So, I quickly realized that, while strategies #1 and #2 were still going to serve me well, strategy #3 had to go.

Instead, I needed an approach that allowed me to keep all the advantages of using sprite pixels as my unit in world space, but get the aspect-ratio correction in place and keep things looking pretty at arbitrary resolutions. Using shaders to achieve a CRT-style effect was a bit of an afterthought, but it worked really well. After trying some simpler ideas out involving just putting scaling factors on the one camera, I was able to meet these requirements with…

A two-camera approach

A picture says a thousand words, so here’s a picture of how the main scene looks in Unity, showing both cameras:

The view from the world camera.

The view from the WorldCamera…

… and from the RenderCamera.

The more distant one, or the top preview, is the one that I called the WorldCamera. This is the one that actually has all sprites and text drawn to it, and so it exists at the 1:1 pixel-to-unit scale. Since my target “native” resolution is 160×200, it’s size in Unity is set to 100 units – more generally, the size needs to be 1/2 of whichever dimension is larger in the target resolution.

Rather than having this camera render to the screen, it renders to a RenderTexture. According to the Unity docs, RenderTextures need to be a square size that’s a power of two on each dimension – so much for being pixel-perfect using my strategies above. (As an aside, I’m not actually sure this is true – the editor lets you assign any arbitrary sizes to each dimension of a RenderTexture. I just chose to follow what the docs said to eliminate a possible bug source.)

The RenderTexture is, therefore, square – so it’s actually rendering an area of 200 pixels x 200 pixels in world space. While that’s not what I’m after, it means that the WorldCamera is completely independent of the aspect ratio and resolution of the display – which is what makes the rest of this approach possible.

The second camera, which actually renders to the screen, is aptly named the RenderCamera. This camera is set way back from the rest of the scene (-100 Z), with a close enough clipping plane that it won’t catch any of the action going on in the background. All that’s within the clipping planes of this camera is a single Quad, with an X scale of 1.33 – meaning it has a 4:3 aspect ratio. All that’s left to do to get things looking spot on, then, is to scale and offset the RenderTexture so it trims off the surplus 20 pixels captured on the left and right sides of the WorldCamera.

Thankfully, Unity gives a very simple way to do this: to map the RenderTexture to the Quad, it needs to be a Material, not a RenderTexture, and a Material using the “Unlit/Texture” shader has handy scale and offset properties. With that done, the RenderCamera displays the perfectly aspect-ratio-adjusted version of exactly the part of the WorldCamera’s viewport that matches our target “native” resolution.

The Commodore 64, like many computers of it’s era, always had solid-colour borders around the graphics. I simulated this by setting the RenderCamera’s size to 0.6 units, while the Quad was only 1 unit high – this meant that there’s 0.1 units of the RenderCamera’s background colour peeking through around the edges. On a 4:3 window, this is a nice even border – at other resolutions, it may not be so authentic to the C64 experience, but it means that the full quad is rendered, with a nice chunky border, at any resolution and aspect ratios all the way from 5:4 to 16:9.

Mission accomplished.

One final aside about this two-camera setup: the calculations for the size of the WorldCamera, and offset and scale values of the render material, are very straight-forward to implement in code. I did so in lines 71-84 of my game controller. With that approach, it would theoretically be possible to change the “native” resolution on the fly at runtime, if you found a use-case for that.

Shaders for CRT effects

There’s certainly must more sophisticated ways to get a CRT effect than what I used this go-around: VHSPro in the Unity Asset Store looks amazing, but it costs $50 and using paid shaders isn’t really in the spirit of Ludum Dare, and the libretro project has an amazing collection of open-source shaders that could probably be ported to Unity if you know how shaders work – but I don’t.

Instead, I just used a handful of the ones in the Image Effects section of Unity’s standard assets. Applying these filters is as simple as adding the relevant scripts as components on whichever camera you’re looking to modify – in this case, they all went on the RenderCamera. Specifically, in order, I used the following:

  • Noise and Grain
  • Vignette and Chromatic Aberration
  • Fisheye
  • Bloom

With some tweaking of the settings for these shaders, I pretty darn close to a good CRT effect. The only thing I’d possibly want beyond that is possibly some sort of scanline effect, but in my experience those often don’t look as good as you’d hope.

From left to right, no shaders, then enabling them one by one.

From left to right, no shaders, then enabling them one by one in order.

A final note on “authenticity”

I hate that word, so I feel it’s necessary to call out precisely all the ways that Xtreme Crop Duster Simulator ’82 cheats compared to what the real Commodore 64 could do.

  • I mixed up text and graphics modes:
    • The text mode is 320×200, so all the “regular size” text rendered in the game should properly be twice as wide as it appears in the finished game.
    • While you could mix modes on screen at once, they couldn’t be on top of each other. It would be more realistic to have all the text at the top and bottom 8 or 16 pixels of the screen, with the sprites only appearing in between.
  • I played fast-and-loose with the rendering of sprites and colours:
    • “Sprites” on the Commodore 64 were properly one colour, while mine get free run of the 16 colours of the C64 palette.
    • With appropriate trickery, you could of course render them in multiple colours. However, it’s not as simple as any pixel can be any colour. Each 8×8 block of pixels on the screen could be assigned 4 unique colours – so sprites either had to move 8 pixels at a time, risk colour artifacting as they crossed the block boundaries, or just use a limited palette across all sprites so it’s a non-issue.
  • I only half did proper SID emulation:
    • The SID is the famous and amazing 3-voice sound chip in the Commodore 64. While all the sound in the game is limited to three voices at once, the “sound effects” voice (engine sounds, crashes, etc) is playing sounds made in ChipTone so they’re not as accurate as they could be.

None of that probably matters to anyone but me, but I have a keen appreciation for the gaming platforms of yesteryear, so it keeps me humble to remain aware of all the ways I “cheated” even when staying within the constraints of a relatively “authentic” style.

Whew!

That’s a whole lot than I thought I would write. Hopefully some of you find this interesting – since the graphical style is a defining feature of my game, and probably the place where I learned the most during this most recent Ludum Dare, I thought it may be valuable knowledge to share.

And hey, if you’ve made it this far without playing and rating the game – why not do that now?


Final bonus screenshot

I mentioned that this approach supports arbitrary “native” resolutions. For a larf I plopped in a screenshot of my very first Ludum Dare game, Smugglers!, which emulated the 4-colour 320×200 appearance of an early IBM PC with a CGA card. While the CRT effects are probably overdone here, the aspect ratio correction makes it a lot closer to my original intention:

Maybe I should go back and retcon this into all my old LD games?

Tags: , , , , , , ,


2 Responses to “Xtreme Crop Duster Simulator ’82 – The Two-Camera Setup”

  1. pkenney says:

    Thanks for the great write-up and the links to some shader resources. I loved the screenshot of your Unity scene, it’s a looker.

    I respect this kind of attention to aesthetic detail; since it’s not my own approach to things I always like living it vicariously when folks get into the weeds on it, so I enjoyed reading.

    Seeing the before and after shots also highlights how much this work paid off. The game really gets that throwback feeling due to the effort.

    I’ll definitely experiment since I’d love to be able to add just a touch of this kind of stuff to my games. I kind of wanted to achieve a more “neon lights” look to my own game, but I wasn’t quite sure how to go about it so I invested the time elsewhere. Sounds easy to check out the default options though!

    It’s interesting that you start with a firm desire for pixel perfect setup in your mind. I throw that to the wind because I like to be able to rotate and scale-tween my sprites for juice, and I have this unexplored belief that I can’t have both, plus I feel I’ve never really noticed the non-pixel perfect outcome. Is it one of those things where once you learn to see it you can’t stand it?

    • rjhelms says:

      Glad you enjoyed the writeup!

      I always get a kick out of how 2D Unity scenes look like with the editor in 3D mode – this one is especially cool because it’s like there’s a tiny little TV showing the action in the background. I guess, conceptually, that’s exactly what it is.

      Sounds like for a neon lights feel, some bloom might work well in your game – it’s an effect that gets a lot of flack as it’s been overused in some AAA titles, but it can really do wonders for the look of a 2D game when applied appropriately.

      With respect to keeping things pixel-perfect: it’s really just a constraint that I place on myself when I go for a heavily retro-inspired style. If I’m emulating a style that has a really low resolution, like I did here, it breaks the immersion in subtle but noticeable ways if I don’t try to keep everything lined up with big, crisp, chunky pixels. It doesn’t really bother me in other games, unless they’re trying to accomplish similar but falling short for that reason.

Leave a Reply

You must be logged in to post a comment.

[cache: storing page]