I’ve got AI working, conquerable Cities, and a winnable game. Now the biggest problem (again) is the graphics: time to upgrade them. What do we need? Beaches!
Beaches: Innocent, relaxing, sandy. Yellow.
Also: painful and difficult to render in a tilemap. But I got it working in the end … here’s the How-To…
Context / Starting Point
The game looked something like this:
…or, on a zoomed-out scale showing a whole map, something like this:
- It’s … not pretty. This shouldn’t matter, but it does (c.f. intro paragraphs).
- Compare it to Civ 5
- Compare it even to Civ 1!
- Graphics + ambient sounds were one of my favourite bits of Civ4; I want to make something at least as good
- Way too “hex-y”: like an old-fashioned board game about “WAR! OLD FASHIONED ARMIES!”
- …although compared to how bad Civ5 gets when you zoom out, it’s not looking too bad
- If you take away the colour, it’s almost impossible to tell what’s water, what’s land, where the hills are, etc
- This is going to be a BIG problem for the Fog-of-War that I just added
- The mountains (which are one-shade-of-grey, only the lighting makes them look different) look better than the rest put together; that’s a bit embarassing
My hexes are currently drawn by putting 6 triangles around a center point:
…which has allowed me to have a center point that varies (following the terrain). This gave me two things. Firstly, the map looks “smoother” than pure hexes (which are really quite ugly to most people) – it follows the contours of the underlying smooth mathematical-terrain. Secondly, it allowed me to easily vary the “sharpness” of each tile based on altitude and terrain: hills are knobly, mountains are positively spikey. And all this procedurally generated – every hex is unique!
I wanted to add beaches. Beaches are traditionally the most graphically pretty part of Civilization games. Until Civ5, the rest of the graphics tended to suck, but Civ has a long tradition of beautifully rendered ocean waves crashing on bright sandy beaches. Its shallow, but it works – the game is more pleasant to play (try playing freeciv, or any one where you can replace the graphics with crappy ones – the beaches matter!)
Beaches hug the outside edge of a hex, so I needed something like:
- Split my rendering: continue drawing the grass/plains/whatever in the center of the hex, and draw an outer ring/strip of beach around the edge.
- If beach doesn’t go all the way, split the outer ring into two sections: one that’s beach, one that is grass/plains/whatever. Assume that each hex has only one continuous section of water around the edge
- Set the heights of the beach strip to be level with the water, allowing the grass/plains/etc to slope up steeply to the height it ought to be at (otherwise the beach appears to be painted-on to the hills/etc, instead of being flat as it ought to be)
- Add support for tiles that have beach, then no-beach, then more beach, then more no-beach (i.e that border two separate, non-joined pieces of water)
- Tweak it until it looks good
Step 1: Split into two rings
Well … first of all, I need to split it into rings, and I need to find out where my rings start (left edge of hex? right edge? top edge?) – otherwise debugging is a nightmare (*).
(*) – debugging was a nightmare until I figured out that index 0 wasn’t the corner I thought it was, and that I was looking at index 3.
Render the first 5 segments of each ring, then first 4, to check exactly which is segment 0:
Step 2: detect what is Prime beach-front Real-Estate
Walking around the corners of the hexagon, I check the two neighbouring hexes. If the first one is dry, and the second is water, then this is the “start” of a beach-strip. If it’s the opposite way around, this is the “end” of a beach-strip. And any other combination doesn’t matter. That gives me:
RESULT! Mostly working already.
Step 3: Something weird with 4-sided beaches
In the screenshot above, look at the two left-most beach tiles. The top one is very odd. I scrolled around the map to see if it was a one-off, or if it happens with a pattern. Here’s another screenshot showing that the behaviour happens every time there’s a 4-sided beach tile:
Eventually I realised the problem. My original mesh code had an “obvious” optimization that all graphics coders do: I assumed that the last segment would join the last point back to the first point. This meant I sent 6 vertices (one for each corner of the hex) and it rendered 6 segments:
- Vertex 1 to Vertex 1
- Vertex 2 to Vertex 2
- Vertex 3 to Vertex 3
- Vertex 4 to Vertex 4
- Vertex 5 to Vertex 5
- Vertex 6 to Vertex 1
To render only one segment, I needed to sent 2 points (so it had a beginning and end). I figured that sending 5 points would give me 4 segments, and sending 6 points would give me … 5 … oh, damn.
So I refactored all my code to send 7 points when it wants the full ring rendered: it now explicitly duplicates the first point and hands it in as point 7. My final mesh-generating code is now simpler, and can be re-used in more places without changes.
Step 4: Multi-sided beach tiles
What about those pesky buggers in the causeway near the top right? The ones with two separate bodies of water, one either side, giving two UNCONNECTED beaches?
Benefit of starting simple and ignoring this case: I had code that worked for detecting beaches, now I could extend it gradually into generating not just “start and end coords of beach” but “a list of start/end coord pairs” and test it step by step. First iteration not quite there:
My code was simply too convoluted; I had two code-paths, one for when the start-of-beach was first, one for when the end-of-beach was first (forces you to wrap-around over the boundary of last vertex / first vertex). Making sure both code-paths had ALL the fixes so far got me almost there. Causeway now fixed, but look what’s happened to the second causeway near the bottom of screen – two full-beach tiles (incorrect!):
With a little tweaking, I realised I could convert ALL my code to read the “next vertex index” modulus 6, i.e.: 0 = 0 % 6 … 1 = 1 %6 … 6 = 0 % 6 … 7 = 1 % 6. I had to re-write some of the algorithms, but it’s worth it to cut-out all that duplication. Now I only had one code-path, and both causeways calculated correctly:
Step 5: stretching to the edges
The inner-corners now look terrible: beaches start and stop sudenly every time you move from one hex to the next. Will I need to write an additional custom-mesh-maker for the “tiles that aren’t beach, but are adjacent to ones that are?” – ugh. BUT … no need to panic, those “inner” corners are actually inside the beach-hexes themselves.
After a bit of pen-and-paper doodling, I was confident that any time a hex “starts” a strip of beach, that guarantees that its neighbour “ended” a strip of beach at same point. So we can safely convert the single-vertex corner of the beach into a line, and have the beach a wide strip, instead of a pointy-ended strip. Worked (almost) first time:
Looks better than I hoped for, given this was a first attempt!
But I don’t like how the causeway in center of screen has two beaches that touch; that implies that boats could – maybe – travel across the land here. I love that idea as a potential for some tiles, but not as the default for all such tiles. Lets make the beach a bit narrower, and…
…let’s fill-in the grass behind it.
Step 6: Modify the original algorithm/plan
At this point, I’d spent so much time thinking about wide and narrow beaches, whether or not beach strips run continuously around edges, etc … that I now saw beaches differently. Beaches are “strips” that are squeezed-in to the edge of an existing tile. The center of the tile shrinks away from that strip, but is still the same shape it was.
This gave me a much simpler algorithm for the rest of the mesh: instead of messing around with multiple inner/outer rings etc, simply define the main part of tile as initially occupying full hexagon. When you add beach, you simultaneously shove the “main tile corners” inwards to make room. Then you can fill whatever shape you end up with, and it will automagically fit everywhere:
I also decided – because I could – to make beaches get wider the longer they run on a single tile. Put together, we finally get:
Now let’s make it look GOOD
Remember step 5?
Tweak it until it looks good
Note: “good” here only means “good enough for current stage of development”. This is still a long way short of what I’d like to end up with, but it’s enough that it’s no longer the squeakiest wheel
I changed the texture to a proper sand one, courtesy of the free CGTextures.com, giving us this:
Then I added a second, “mini beach”, strip between the beach and the main tile (shrinking the main tile further inwards). This one I hand-painted a texture that has wisps of grass overhanging a sand background, to hide the ugly sharp outline:
Although this looks much better zoomed-in, from above … in-game, it’s too subtle, and looks poor. So I moved some of the code parameters into
variables so I could tweak them in the Unity Inspector and get quicker feedback. Here’s three successive versions, with me tweaking the beach thickness, and the beach/grass border width:
NB: click each to view fullscreen / large – originals are 1700 pixels wide
Final results in-game
Concerned by how weak it looked when viewed at an angle in game, I tried switching to a perspective camera, and seeing how it looked. I was pleasantly surprised – I was spurning this view because it makes it harder to see the game map in Civ5 and Civ4 (although Civ5 handles it better), but it looks so … epic … that now I’m more open to trying it in future. And on a big-enough screen, you can still see plenty of the gameplay:
NB: click each to view fullscreen / large – originals are 1500 pixels wide
…and finally, here it is on the default gameplay camera (non-perspective) … but with a sneak peak of next week’s blog post / topic: making the rest of the terrain feel more “epic” by changing the size and the extremism of the terrain: