|By Shamus||Nov 3, 2010||Programming||100 comments|
I didn’t post about the project last week. You didn’t miss much. I needed to add non-visual stuff like camera controls, a debug log, play a bunch of minecraft and that sort of thing.
So now we have a hex grid based world.
I’ve got this bit of code which takes a simple integer and returns a color value. It’s set up to be deterministic (the same number always returns the same color) but irregular (the colors for 37 and 38 will be very different) so that it will produce the above “kaleidoscope puke” effect. It’s not pretty, but it’s really handy for debugging. I say this as someone who has spent a lot of time squinting at rolling green hills, trying to find that one bad spot I was looking at last time I ran the program.
Time for some depth. Because the back end is really just a regular grid, I can easily pull in some elevation data. And the quickest way to do this is to bring in an image and use it as a heightmap. I just take some random image of bumpy white stuff:
And import it into my program, using the color values as height values. The brighter the pixel, the higher the point. Take all of the elevations below a certain level and flatten them out, which will produce an “ocean”. The result:
Now, the game is going to be played from a fairly close-in view. The player will never see this much scenery at once. But for development, I want to be able to pull back and see what everything looks like.
Note that much later in the project I’ll want to generate the world procedurally. I’ll be making mountains, valleys, plateaus, lakes, etc. by generating them at startup. But that’s a whole lot of complex stuff and I’d prefer to have the basics done first, which is why we’re using the heightmap for now. For those of you squinting at the previous two images and trying to reconcile them, note that I’m only using one quarter of the heightmap for now.
Now let’s see about making this look more like a landscape and less like a Care Bear took a dump on it.
This is pretty familiar work for me. You can make a visually convincing landscape with a few simple rules…
Anything at or below the water level is water. Anything above water level that is touching water is beach. Anything point that’s on a slope steeper than 45 degrees is a cliff, where grass doesn’t grow. Anything above a certain elevation is rock. Anything above another, higher elevation is snow. Everything else is grass.
This is not a simplification. The previous paragraph could just as easily work as pseudocode, because this is all I’m doing. Follow these rules and you’ll wind up with the same results I do. Again, this will be a lot more sophisticated later, but we’re taking baby steps.
Let’s see how it looks:
Note that I’m applying the above rules on a per-point basis, but I’m drawing the world on a per-hex basis. So, there’s additional detail in the points that make up the borders of hexes that’s not being shown. Here is what it “really” looks like under the hexes:
That’s more interesting, obviously. But it will be irrelevant once we get the real artwork into place. Here is another look at the terrain, without drawing the hex boundaries:
Yawn. Without lighting or contour lines, the surface is very flat looking and it’s tough to get feel for the shape.
Now let’s import the entire heightmap and have a look from higher up.
Let’s do one more. I’m going to feed it a larger heightmap and turn the world up to 512×512 points. (The previous screenshot was a 256×256 world, and all previous ones were 128×128.) This largest size is probably the size of the world in the final game.
A 512×512 grid of points yields a 255×388 grid of hexes. (388? Man, working in hexes is strange.) Each hex is 6 triangles. So we’re drawing 593,640 triangles. And it is stupid slow.
Of course, the player will never see more than a fraction of this at a time. About this much:
Somewhere between 20,000 and 30,000 polygons, assuming I don’t do any optimizations at all.
I could nitpick a lot about how the world looks right now, but this is good enough as a starting point. Next time I’m going to go about implementing the texturing system I have in mind, and from there the look of the program should start to take shape.