Autotile Tutorial

Showcase your libraries, tools and other projects that help your fellow love users.
Post Reply
User avatar
Kadoba
Party member
Posts: 399
Joined: Mon Jan 10, 2011 8:25 am
Location: Oklahoma

Autotile Tutorial

Post by Kadoba »

I’ve recently started implementing autotiles in one of my projects so I decided to do a tutorial over them. This may not be best method but it worked for me. If you have ever used RPG maker then you should be familiar with these. If you don’t know what autotiles are they are basically a way to make tiles transition smoothly depending on what other tiles they are adjacent to.

Example:
LinQD.png
LinQD.png (20.45 KiB) Viewed 775 times
Each autotile is a set of six base tiles that are cut up and reformed into result tiles. This is what I call each base tile:
ogpSY.png
ogpSY.png (5.12 KiB) Viewed 775 times
Island – The island base tile is what a result tile looks like if it is not connected to any surrounding tiles. This is the only tile that is not cut apart.
Cross – This is the base tile in the top-right of the image that looks like a +. This is what a result tile looks like if it is connected to the tiles above, below, left, and right of it.
Corners – These are the remaining four base tiles. They represent what a result tile looks like if it is the “corner” piece.

The basic idea is that you can make tiles look correct in any context simply by cutting apart these base tiles into 4 quarter pieces that I call chunks, and then stitching different chunks together to form a result tile that looks natural and connected to other result tiles.
ln24T.png
ln24T.png (10.52 KiB) Viewed 775 times
As a whole, the autotile needs to know about it's 8 surrounding adjacent tiles. However, chunks only need to know about the three tiles they are touching. Each of these tiles can be labeled as left, right, or center depending on the placement from the chunk.
Rrer4.png
Rrer4.png (11.95 KiB) Viewed 775 times
Notice how an adjacent tile can be labeled differently depending on which chunk you are looking at. For example, the top adjacent tile is to the “right” of the top-left chunk but to the “left” of the top-right chunk.

All right. It’s important that you understand everything up to this point before we continue.
  • Autotile sets consist of 6 base tiles which are Island, Cross, and 4 Corners.
  • Each tile is separated into 4 pieces called chunks, except for the island tile.
  • Each chunk needs to know about the three adjacent tiles it’s touching. These tiles are labeled left, right, and center.
  • Depending on the tile’s surroundings, chunks are reformed into a result tile.
Implentation
The first thing we will do is cut out the whole island tile and then the rest of the tiles we will cut apart into chunks. We will be using quads to do this. We will separate chunks into 4 arrays, one for each quadrant the chunks are cut from. We will call the arrays TL (TopLeft), TR (TopRight), BL (BottomLeft), and BR (BottomRight).
How we index each chunk inside the array is important. We will index them by what I call their adjacent value. The adjacent value is a number that represents which adjacent tiles the chunk is connected to. The value ranges from 0 to 4.
  • 0 - No connected tiles
  • 1 - Left tile is connected
  • 2 - Right tile is connected
  • 3 - Left and Right tiles are connected
  • 4 - Left, Right, and Center tiles are connected.
Notice that the center tile only matters if both the left and right tiles are connected.

Here is a chart that shows what each chunk’s value is and what array it belongs to.
FK3Zn.png
FK3Zn.png (5.6 KiB) Viewed 775 times
Here some example code that cuts out the chunks and indexes them:

Code: Select all

local autotileImage = love.graphics.newImage("Autotile.png")	-- Autotile image
local width = autotileImage:getWidth()	-- Image width
local height = autotileImage:getHeight() -- Image height
local size = 32						        -- Tile size
local hsize = math.floor(size/2)			-- Half tile size

-- Chunk arrays
local chunk = {TL={}, TR={}, BL={}, BR={}}

-- Island tile
chunk.island = love.graphics.newQuad(0, 0, size, size, width, height)

-- This cuts a tile into chunks
local function cutTile(x,y)
	local TL = love.graphics.newQuad(x, y, hsize, hsize, width, height)
	local TR = love.graphics.newQuad(x+hsize, y, hsize, hsize, width, height)
	local BL = love.graphics.newQuad(x, y+hsize, hsize, hsize, width, height)
	local BR = love.graphics.newQuad(x+hsize, y+hsize, hsize, hsize, width, height)
	return TL,TR,BL,BR
end

-- Cut out the chunks and index them by their adjacent tile value
chunk.TL[3], chunk.TR[3], chunk.BL[3], chunk.BR[3] = cutTile(size,0)
chunk.TL[0], chunk.TR[2], chunk.BL[1], chunk.BR[4] = cutTile(0,size)
chunk.TL[1], chunk.TR[0], chunk.BL[4], chunk.BR[2] = cutTile(size, size)
chunk.TL[2], chunk.TR[4], chunk.BL[0], chunk.BR[1] = cutTile(0, size*2)
chunk.TL[4], chunk.TR[1], chunk.BL[2], chunk.BR[0] = cutTile(size, size*2)
Okay, now after the chunks are cut out and indexed by their adjacent value we can now easily draw them by simply looking at the adjacent tiles and figuring their adjacent value and using it as a table index for the arrays. The code below demonstrates how to calculate this value and draw the autotile:

Code: Select all

-- A 2-d grid used to represent connected tiles. Values can either contain true for
-- connected tiles or nil for unconnected tiles.
local grid = {}

-- This function calculates the adjacent tile values and draws the autotile
function drawAutotile(x,y)

	-- Calculate the adjacent tile values for each chunk
	local val = {TL=0, TR=0, BL=0, BR=0}

	if grid[x] and grid[x][y-1] then val.TL = val.TL + 2; val.TR = val.TR + 1 end	-- top
	if grid[x] and grid[x][y+1] then val.BL = val.BL + 1; val.BR = val.BR + 2 end	-- bottom
	if grid[x-1] and grid[x-1][y] then val.TL = val.TL + 1; val.BL = val.BL + 2 end -- left
	if grid[x+1] and grid[x+1][y] then val.TR = val.TR + 2; val.BR = val.BR + 1 end	-- right
	if grid[x-1] and grid[x-1][y-1] and val.TL == 3 then val.TL = 4 end	-- topleft
	if grid[x+1] and grid[x+1][y-1] and val.TR == 3 then val.TR = 4 end	-- topright
	if grid[x-1] and grid[x-1][y+1] and val.BL == 3 then val.BL = 4 end	-- bottomleft
	if grid[x+1] and grid[x+1][y+1] and val.BR == 3 then val.BR = 4 end	-- bottomright
	
	-- If isolated then draw the island.
	if val.TL == 0 and val.TR == 0 and val.BL == 0 and val.BR == 0 then
		love.graphics.draw(autotileImage, chunk.island, x*size,y*size)
	
	-- Otherwise, draw the chunks
	else
		love.graphics.draw(autotileImage, chunk.TL[val.TL], x*size,y*size)
		love.graphics.draw(autotileImage, chunk.TR[val.TR], x*size+hsize,y*size)
		love.graphics.draw(autotileImage, chunk.BL[val.BL], x*size,y*size+hsize)
		love.graphics.draw(autotileImage, chunk.BR[val.BR], x*size+hsize,y*size+hsize)
	end
	
end


Now it's a very simple matter to draw the tiles. We'll just finish up by defining a couple of love functions. To test out the tiles let's have the left mouse button place down an autotile and the right mouse button remove it.

Code: Select all

function love.update()

	-- If a tile is leftclicked then make it an autotile
	if love.mouse.isDown('l') then
		local x,y = math.floor(love.mouse.getX()/size), math.floor(love.mouse.getY()/size)
		if not grid[x] then grid[x] = {} end
		grid[x][y] = true
	end
	
	-- If a tile is rightclicked then erase the autotile
	if love.mouse.isDown('r') then
		local x,y = math.floor(love.mouse.getX()/size), math.floor(love.mouse.getY()/size)
		if grid[x] then grid[x][y] = nil end
	end
	
end

function love.draw()
 
	-- Draw the autotiles
	local endx = math.ceil(love.graphics.getWidth() /size)
	for x = 0, math.ceil(love.graphics.getWidth() /size) do
		for y = 0, math.ceil(love.graphics.getHeight() /size) do
			if grid[x] and grid[x][y] then
				drawAutotile(x,y)
			else
				love.graphics.draw(floorImage, x*size, y*size)
			end
		end
	end
    
   --Draw the cursor
   local mx, my = love.mouse.getPosition()
   love.graphics.setColor(0,0,0,255)
   love.graphics.rectangle("line", mx+1 - mx % size, my+1 - my % size, size, size)
   love.graphics.setColor(255,255,255,255)
   love.graphics.rectangle("line", mx - mx % size, my - my % size, size, size)
	
	-- Instructions
	love.graphics.setColor(0,0,0,100)
	love.graphics.rectangle('fill',0,0,350,20)
	love.graphics.setColor(255,255,255,255)
	love.graphics.print("Left click to place tiles. Right click to delete them",5,5)

 end
I’ve included a sample love that contains the code above and the autotile image used.

A couple of other things: Drawing the autotiles this way without the aid of sprite batches or canvases is likely very slow. You would probably only want to find the adjacent tile value when the autotile or adjacent tiles change. The other thing is that chunks do not necessarily have to be cut into 4 equal parts. For some types of tiles you might want to cut them off center a bit (such as perspective walls).
Attachments
Autotile.love
(54.98 KiB) Downloaded 345 times
Last edited by Kadoba on Thu Dec 26, 2013 10:01 pm, edited 5 times in total.
coffee
Party member
Posts: 1206
Joined: Wed Nov 02, 2011 9:07 pm

Re: Autotile Tutorial

Post by coffee »

Thank you for this. My rpg in dev have actually already a map editor but change map tiles is for now one by one. I gonna read your tutorial and perhaps with your teachings approach I gonna add some auto-tiling to increase map making speed. :)
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Autotile Tutorial

Post by kikito »

I wholeheartly recommend giving a look at the tile-related resources that are linked in the Free Game Resources wiki page. I prefer the methods explained in those (no offense, kadoba!). The bitwise method simplifies the calculation of "what tile to draw" enormously, and the other one allows any number of terrains, instead of just two.
When I write def I mean function.
User avatar
Kadoba
Party member
Posts: 399
Joined: Mon Jan 10, 2011 8:25 am
Location: Oklahoma

Re: Autotile Tutorial

Post by Kadoba »

kikito wrote:I wholeheartly recommend giving a look at the tile-related resources that are linked in the Free Game Resources wiki page. I prefer the methods explained in those (no offense, kadoba!). The bitwise method simplifies the calculation of "what tile to draw" enormously, and the other one allows any number of terrains, instead of just two.
I've seen those articles and I wasn't completely comfortable with their methods.

The first article expects you to make a tile for every possible transition. That isn't too bad if you only have 4 adjacent tiles since that means you only have to make 16 different transition tiles. The real issue comes when you want to have smooth corners. To do that you have to detect 8 adjacent tiles which means you need 256 different transition tiles. The author addressed this slightly but more or less glazed over it when it's a pretty big issue.

In the second article the author recognizes this issue straight away. He goes on to say that you can reduce the 256 different transitions down to 32 by only creating the corners and edges and combining them. Well that's exactly what I did, but I took it a step further and instead of making tiles with multiple edges and corners I simply cut out the single edges/corners and pasted them together (which is exactly what you would do in an image editor to make the multiple tiles anyway). So I reduced those 32 tiles even further down to a measly 6 tiles. Much less work in the long run. There's also nothing stopping someone from implementing priority tiles with my method.

For my way though, since a result tile is really just made out of different chunks you can't apply a single bitwise value to them. I actually tried a bitwise method with assigning each chunk 3 binary digits in a 12 digit number. But in the end I have to separate the values anyway so it wasn't very useful.

Here is the old code using that bitwise method. You'll notice that it's very messy compared to my current code.
AutotilesOld.love
(7.43 KiB) Downloaded 266 times
Last edited by Kadoba on Fri Feb 03, 2012 6:10 am, edited 1 time in total.
User avatar
AaronWizard
Citizen
Posts: 68
Joined: Sun Nov 06, 2011 2:45 pm
Location: Canada

Re: Autotile Tutorial

Post by AaronWizard »

I've often used the same technique, though slightly rearranged. The tile is already split apart into chunks, 13 in total. So, if my tiles are 32x32, I'd make 13 16x16 chunks.
tile_transistions_32x32.png
tile_transistions_32x32.png (548 Bytes) Viewed 8068 times
One plus over Kadoba's method is that I only ever have to make one chunk for a given direction (i.e. north, south, east, west, and middle). I'd just use a side chunk twice when a tile's entire side needs the transition, or the middle chunk four times when the tile has no transitions, while these chunks work seamlessly with the corner chunks.

This only works with tiles that merge along the cardinal directions, of course. I have no idea how I'd put in transitions between tiles that are diagonal to each other.
User avatar
Kadoba
Party member
Posts: 399
Joined: Mon Jan 10, 2011 8:25 am
Location: Oklahoma

Re: Autotile Tutorial

Post by Kadoba »

Updated for love 0.9.0
Post Reply

Who is online

Users browsing this forum: No registered users and 3 guests