Wednesday, January 10, 2018

Isometrish: Automatic walls

Obviously, when you're building a game level (and especially if, like I hope to, you're building it procedurally), you can't spend ages making sure you've put the correct-facing wall object down at every part of the level, or fixing them every time you move a bit of floor around.

Well, you could, but wouldn't it be much better to just have a single wall object that could automatically detect which sides it needed to match to the neighbouring floor tiles?

Yes! And luckily, this is really easy to do!

There is some heavy lifting involved, though: you'll still need a sprite with a separate frame for each possible combination of walls - north on its own, east on its own, north and east together, south on its own, north and south together…

(If you want a shorter notation, you need 2n-1 sprites, where n is the number of sides you need to consider. For most cases, this'll be four, so you need 24-1, or 15 images - plus one with no walls.)

But how do you know which image to draw based on the combination of faces required? Binary!

Assign each face a binary number - for a square, this'll be 1 for North, 2 for East, 4 for South and 8 for West. The neat trick with binary numbers like this is, the sum of any subset of them is unique! Which means we just need to add up the numbers corresponding to each 'active' face, and that tells us which image to load.

In GameMaker Studio 2, sprites have subimages - this is most commonly used for animation frames, but we can also use it here to keep a collection of images together in a common object. You need to make sure the various subimages are added in the correct order (the easiest way to do this in GMS2, I found, was to save the images into a common folder numbered 1.png to 15.png, then use the 'Create sprite from image(s)' option).

A hideous example of a 16-frame wall sprite

Now that our sprite is set up, we can add the following to the object's create event:

iso_sprite = sWall;
var f = 0;
if (place_meeting(x, y - grid_size/2, oFloor)) f += 1;
if (place_meeting(x + grid_size/2, y, oFloor)) f += 2;
if (place_meeting(x, y + grid_size/2, oFloor)) f += 4;
if (place_meeting(x - grid_size/2, y, oFloor)) f += 8;
iso_subimg = f;

This checks the room for a floor on each side of our wall, and adds that face's value to the total, resulting in the correct subimage index that we need to draw, which is done in the draw event:

draw_sprite(iso_sprite, iso_subimg, screen_x, screen_y);

(In this example, screen_x and screen_y are the pre-converted grid-to-screen coordinates.)

And presto - a single wall object that you can drop into your room, which will automagically show the correct wall faces for any surrounding floor tiles!

No comments: