I created the levels for my AppWeek game using Paint.NET and a variant of the TerrainProcessor from the Heightmap Collision with Normals sample.
I wanted hills and valleys in specific places, so I drew a crude approximation in Paint.NET. I started with a circular gradient fill to make my landscape higher in the middle and lower toward the edges. I drew a couple of white peaks using the pencil tool with a giant brush, and some valleys in black. After a Gaussian blur to smooth this out:
This is roughly the contours I wanted, but it's terribly boring!
I made a second layer, and pasted this noise pattern (copied from a sample) into it:
I can't remember how this noise was originally created. We might have used some kind of fractal or plasma cloud filter, or perhaps a program like Terragen.
I combined my two images by setting the second layer to 'screen' blend mode with 50% opacity, producing this final result:
Notice the colored dots which I added on a third layer? These mark locations that are important to gameplay:
My enhanced version of the TerrainProcessor scans for these specially marked locations, stores their positions, then replaces the marker pixel with an average of its four neighbors to avoid messing up the regular terrain generation.
I also added new processor parameters, for specifying a normalmap texture in addition to the base terrain texture, controlling how much the normalmap is tiled, specifying the sky texture, rotating the sky so I could make its brightest part match the light direction used by the game, and specifying what background music goes with each level:
My TerrainProcessor automatically chains to other custom processors, using the SkyProcessor from the Generated Geometry sample to build the sky texture into a 3D model, and the NormalMapProcessor from the Sprite Effects sample to convert the normalmap texture from grayscale displacement format.
When its work is complete, TerrainProcessor spits out this strongly typed object:
[ContentSerializerRuntimeType("SampleSmashup.Level, SampleSmashup")] class LevelContent { public ModelContent Terrain; public ExternalReference<SkyContent> Sky; public HeightMapInfoContent HeightMap; public Vector3[] BasePositions; public List<Vector3> TankSpawnPoints; public List<Vector3> ShipSpawnPoints; public string SongName; }
Thanks to the magic of automatic XNB serialization, my game can load the resulting .xnb file directly as a Level object:
class Level { public Model Terrain; public Sky Sky; public HeightMapInfo HeightMap; public Vector3[] BasePositions; public List<Vector3> TankSpawnPoints; public List<Vector3> ShipSpawnPoints; public string SongName; }
This is how the first level looked in game, with gently rolling hills thanks to my aggressive use of Gaussian blur:
Once this custom processor was in place, it was trivial to add a second level. I started with the same noise pattern as before:
I drew this on a second layer, again making heavy use of Gaussian blur:
This time I set the layer blend mode to 'glow', with 50% opacity. There is no particular reason for choosing that blend mode: I just tried a bunch of different ones until I got a result I liked. Here is the combined heightmap texture:
And the second level in game:
Summary: