ID:1172305
 
Keywords: geohex, grid, hex, hexagon, map, ter13, tile
BYOND can do two types of maps, right? Wrong.

We have tiled maps, and isometric maps, but we're not limited to just those. With some really simple tricks, I plan to show you how you can take advantage of BYOND's large icon system to make some easy hex-based maps with some minor tweaks.

Fooling the grid:

The first step is to set up our environment for handling hex cells. Let's just make up some quick graphics for testing our system:

The tiles are pretty easy to set up from an overhead perspective. I'll cover 3/4 perspective later.

Let's set up the grid in paint real fast:

I'm using 64x64 grids to draw my hex tiles:



Let's take a look at the dimensions of the tile:



Now, for the sake of speed, I'm leaving the grid pretty plain:





The Grid Ain't Fooled:

Now, if we just set our tile size to 64x64, we'll have these oddly shaped gaps:



Let's fix this with some quick code.

What we're going to do, is make BYOND think that tiles are a bit slimmer than they really are, offset them to the left a bit in order to recenter the tile, then we're going to have to move half of the tiles on the map half of their height upward.

Sounds a bit much, right? Well, it's about two minutes worth of mucking with code:

Let's get our dimensions for our offsets from our tile:



Tile width: 3/4th of image width
Tile x offset: 1/4th of grid size *-1
Tile Height: image height
Tile y offset: 1/2 of image size (neg or positive)

Plugging it all in:

First, we need to change the icon-size property of world.

world
icon_size = "48x64"


Second, we need to set up our tiles to show up in the right places:

#define TILE_HALF_HEIGHT 32
#define TILE_X_OFFSET 8

turf
pixel_x = -TILE_X_OFFSET

New(loc)
. = ..(loc)
spawn()
//check if x is even
if(src.x%2==0)
src.pixel_y = TILE_HALF_HEIGHT
return .


So, let's jump over to our map editor:



Now, this definitely looks way worse, right? There's gotta be another step, you say? Nope.



And now, let's run our game and bask in the glory of all our hard work:



Make sure you set the tile size of the map element to the width of your tile, or it'll get stretched.


A Bit of Perspective:

Now, you aren't limited to top-down tiles at all! Let's change it up a bit.

I'm going to make two new tiles:

3/4:



Then modify the code just a bit:

#define TILE_HALF_HEIGHT 24

world
icon_size = "48x48"


Result:




Also, for a more skewed perspective, I decided to try this out:

1/2:



can't forget the code:

#define TILE_HALF_HEIGHT 16

world
icon_size = "48x32"


Result:




Isometric Hexes? Because why not?

Not impressed? Fine. Let's take it 2.5D. Guess what? No major changes to our code required. This one's just about getting your tile template right.



Code revisions:

#define TILE_HALF_HEIGHT 32

world
icon_size = 64
map_format = ISOMETRIC_MAP


(Remember to swap the interface back to a tile width of 64!)

The Result:



Wasn't that exciting?

Putting it into practice:

I spend a day having some fun, and made some tiles using a 128px isometric grid.

These tiles aren't a set, and probably never will be, but I'm pretty happy with them none the less.



And this is what they look like in BYOND at runtime:




Stepping BYOND:

You are going to have to modify the way that oview()/view()/range() type procs operate if you plan on using them, and you are really going to want to modify the way that mobs and objs position themselves in the world, but these are minor tweaks that I might cover at a later time.

There are also some fun things like rotating the map that I'm interested in attempting to cover, but it'll take a total overhaul in the way the system works. Also, varying heights for these terrain pieces is completely possible using pixel_z offsets and taller icons.

I'm really interested in what you guys turn out, so show me what you've done with it in the comments below!
Great tutorial! I've always wondered how people made these.
Thanks. If you didn't notice, I threw in a section about doing it in isometric after you posted. Just to up the ante a bit.

I've never read a tutorial on doing hexes. I just had an idea that I wanted to prototype that required a hex map, and started to go about writing it in C#, then it struck me how fast it would be to prototype in BYOND with just a few lines of code.

If anybody has any improvements, do tell!
A bit of an update for BYOND 500:

#define TILE_HALF_HEIGHT 32
#define TILE_X_OFFSET 8

var
matrix/even_tile_transform = matrix(1,0,-TILE_X_OFFSET,0,1,TILE_HALF_HEIGHT)

turf
transform = matrix(1,0,-TILE_X_OFFSET,0,1,0)

New(loc)
. = ..(loc)
spawn()
//check if x is even
if(src.x%2==0)
src.transform = even_tile_transform
return .


They are functionally equivalent, however.
I love it, Ter13. Definitely one to throw in my bookmarks.
I'm going to save this to my computer to reread offline
Instead of
if(src.x%2==0)

wouldn't
if(!(src.x&1))
be faster? Modulus is expensive compared to binary logic, last I checked.
Yep. Didn't even think about that. Good catch.
Alternatively you can remove the outer !(), if inverting which grids are offset vertically isn't a problem. I left it in since your current example images would not be correct if I did that, though.
Indeed, it's pretty much irrelevant which grids are offset, so you are correct. It just has to be internally consistent.
Also is the "return ." necessary? Wouldn't that have the same effect as omitting the return line entirely?
In response to Topkasa
Topkasa wrote:
Also is the "return ." necessary? Wouldn't that have the same effect as omitting the return line entirely?


yes, actually. I believe LordAndrew ran some tests and determined that implicit returns (just setting . ) is faster...by some amount of milliseconds.
SSX, I'm a little confused. According to what you're saying about those tests, adding "return ." is not necessary, as it slows down the return... but you say is is necessary?
I meant yes to the second question.
I was under the impression that all procs automatically called return . at some point.
In response to Darker Legends
Darker Legends wrote:
I was under the impression that all procs automatically called return . at some point.

Yes. They do. Is anyone saying they don't...?
In response to Super Saiyan X
Super Saiyan X wrote:
Darker Legends wrote:
I was under the impression that all procs automatically called return . at some point.

Yes. They do. Is anyone saying they don't...?


It's not that, but calling it yourself seems pretty redundant?
Calling it yourself is also slower than the implicit call, by a not-insignificant number of machine cycles.
Also, for reference, your hexagons are pretty off

For a hexagon laid out like that, with x width the height will be x*sin(60), so a proper top view should be about 64x56
Which hexagons are you referring to? Or to clarify, which step?
Page: 1 2