What is the best way to build terrain?

I have decided to randomly generate a world which will be a specific size each time. The generator will open a for loop placing a piece of terrain at x 1, 2, 3, until it reaches the edge, and then it restarts x, but adds to y. Basically, it completes a row going across the screen, and then returns to build another row below that. It does this until it reaches the end of the last row at the max y distance.

What’s the best way to place them? As separate game objects? (I assume that would be extremely memory-taxing) Pull them out of an atlas? I couldn’t find samples for what I’m trying to do. I haven’t done much graphic work in Defold yet, so any tips would be greatly appreciated!

The main issue with tons of Game Objects is that each has to have a world transform so if you have many thousands it can get taxing on CPU.

You can use tilemaps and then chunk the tilemaps which I think will save some on world transform as each tilemap is a single mesh but it would have to be tested to see for sure. You can have a tilemap of for example 32x32 tiles which will reduce number of game objects you need, and chunking tilemaps also allows for as large of a tile world as you want dynamic in size.

Game Objects with sprites do work though.

Either way you will use a factory to generate and place the tiles.

I’ve attached some unpolished scripts (which could be converted into an in engine tile based level editor) from a game I was working on a while ago which uses plain GOs to place tiles in a tile based world without tilemaps. To use tilemaps instead you would use similar concepts but set each tile within the tilemap chunk based on world data, and generate new tilemap based GOs based on how far your world goes out.

camera_helper.script (5.5 KB)
world.lua (4.9 KB)

5 Likes

Thanks.

A tilesouce is like a collection of images, right? If I made a tilesource could I pull out of it with a factory/script, or can you only place tilesource pieces manually? Then, if I used the tilesource to generate the world it would render as one large image (Instead of many individual pieces), correct? It would also generate the overall collision shape with the tiles?

Reading up more on factories, they only spawn one specified object. Also, they only spawn GOs. If I went the route of a tilesource I’d use a script to randomly pick and place the tiles, right?

Thanks for the scripts too! It looks much more sophisticated relating to the other components I’ve written.

Your tilemap components would still be children of a single GO each. In my example, if you had a 32x32 tilemap chunk size you would be using 1 GO to manage that instead of 1024 GOs.

The tilesource file is the image files which your tilemap files draw from. Tilesource is like an atlas for tilemaps.

Tilemaps are optimized when they are generated, they can be resized, which is why I suggest using something like 32x32 size tilemaps and then chunk them so this means with a factory you generate adjacent tilemaps next to each other in a grid for as large as you want your level to be.

You would have a manager script which keeps track of everything. It would use a factory to create new tilemap GOs, position them correctly, and set tiles correctly. In the scripts I gave, they are able to handle any number of tiles in - and + directions. Most likely you want to do something like that too depending on what you are doing.

When generating your world data you want to do it based on a Lua table before messing with placing of the tiles. Then you transpose your world data into tiles placed.

I would personally not rely on collision information with tilemaps but it’s possible to do still. I need to know exactly how you want to use collision in your game to give a better suggestion.

Right now for you I suggest playing with the individual parts and testing their features so you can learn more about them. Then attempt to build what you want in small increments until you have something which works. Then with new knowledge attempt to refactor to make it better.

4 Likes

Ok. It sounds like I’d have to preset them, correct? Then I’d use the presets and chunk them together randomly.

I tried generating the tilemap with a script today and it gave me the error:

ERROR:GAMESYS: Could not set the tile since the supplied tile was out of range.

Here is what I tried to do.

local tile_size = 124 -- pixels

function init(self)
	yy = 0
	min,max = 1,5 -- first few blocks in tilesource, the crust of the world
	for xx=0,100,1 do -- place ~100 blocks on first row
		x = xx * tile_size
		if xx == 99 and y < 20 then -- bump down, and reset for next row until 20 tiles
			yy = yy + 1
			xx = 0
		end
		y = yy * tile_size
		if y > 0 then
			min,max = 6,10 -- last few blocks in initial ten out of 100 in tilesource, the mud/underground of the world
		end
		tile = math.random(min,max) -- call tile, numbered 1-100 in tilesource
		tilemap.set_tile("#Terrain_map","ground",x,y,tile) -- place the tile in the tilemap at position x,y
		print("MADE BLOCK " .. tile .. " AT: " .. x .. " , " .. y)
	end
end

What do you mean by world data? I do not have special terrain yet (Water with physics, etc), if I ever do, so every tile is just for randomized looks and a static ground collision; if that’s what you mean’t by world data.

I watched GamesFromScratch’s tutorial on tilemaps and collisions/physics and learned a lot. I’ll be watching more of his tutorials now ;). I try to do that, and as much as I can (documentation, tutorials, previous topics, etc) without asking for help. I made this topic because I guess I’m trying to use tilemaps in a special way. I suppose I’m misunderstanding the tilemap.set_tile function. I thought it placed tiles, was I incorrect?

Thanks for all your help.

I forgot to say that you need to pre-initilize your tilemap to be within the range you want to use it in game. So either draw a 32x32 rectangle with any tile in the tilemap, or edit the file directly to play once at 32x32. When the engine runs it generates the mesh of AxZ size and this can’t be resized on runtime so that’s why you have to pre-init its size in the tilemap file.

What I mean by world data is the total data of your tile world however you want it to be.

1 Like

Instead of making multiple tilemaps I was trying to generate one large tilemap at the beginning of the game with flat terrain, placing the crust at the top and all the rest dirt. I’m trying to do a side-view game, not top-down, if that clears some things up. Now, presetting the tilemaps, could I not preset one the size of the limit I installed in my for statement? (~100, 20) And then place tiles into it randomized?

So I couldn’t use the for statement to place one at a time? I’d have use it to insert the tilesource tile number and it’s position in a table for all 2000 (100x20) world tiles, and then use it for tile placement in the tilemap? The reason I’m trying to be so specific is because I can’t place any tile anywhere because it’s a side view game. I have to have an order (Crust on top, dirt below), but beyond that the rest is randomized. Once I have the flat terrain made, I populate it with rock and tree objects in randomized places at the crust of the world.

I’m probably trying to do things the wrong way, so I’ll explain what I’m doing. I have a tilesource which is 10x10. The top 10 are (1-5) crust images, and (6-10) dirt images. The other 90 are free spaces for future blocks or anything else relatable. I then have an empty tilemap. Inside my object, World, I have the empty tilemap component, and a World_gen script. Inside the script is the for statement I showed you above. It’s trying to call a crust or dirt tile from the tilesource and then place it in the tilemap to create the randomized world. Before I continue, can I do this after presetting the tilemap, or am I doing things entirely wrong?

Sorry for my misunderstanding.

I believe you cannot use an empty tile map to start, but must place 1 tile in each corner in order to set the outer limits of the map, which is probably the cause of the “tile out of range” error. Then your basic intuition is correct, you would loop through each coordinate and place a tile from your tile source (including blank tiles) to create a new 32x32 map. Then make as many of those as you need and tile their parent GOs together to make an infinitely large map.

1 Like

@ryanscottlandry is right you need to edit your tilemap file in the editor or directly to place at least a single tile at the furthest distance in your tilemap as you want to use in engine.

Using a single huge tilemap may work for your use case you’ll have to test to see it has no performance issues.

1 Like

@ryanscottlandry @Pkeod

I’m learning a programmer’s life…

One problem solved (Thanks to you two!)…
and on to the next!

function init(self)
	local x,y,w,h = tilemap.get_bounds("#Terrain_map")
	print("TILEMAP BOUNDS: (CUBED) "..w.."x"..h.." | (ORIGIN) X: "..x.." Y: "..y)
	local count = 0
	local xx = 0 -- Made local for testing
	local yy = 0
	min,max = 1,5
	for xx=0,101,1 do -- looped using xx as the key
		print("LOOP: "..xx) -- loop first prints loop number, incorrect after row bump
		x = xx * tile_size
		if xx == 100 and y > -20 then
			yy = yy - 1
			xx = 0 -- resets when each row is finished
			print("ROW BUMP | LOOP: "..xx) -- prints reset loop number, correctly
		end
		y = yy * tile_size
		if y < 0 then
			min,max = 6,10
		end
		tile = math.random(min,max)
		tilemap.set_tile("#Terrain_map","ground",xx,yy,tile)
		count = count + 1
		print("MADE TILE " .. tile .. " AT: " .. x .. " , " .. y .. " | COUNT: " .. count)
	end
end

After the row bump, the loop reset is ignored and acts as if xx was never reset/changed. I realize I can change it to a while loop easily, but I’m curious as to why this is. I’m sure I’ve used a for loop in this way before and it worked perfectly. Did I miss something?

EDIT: Changed to a while loop and works perfectly. Thanks a lot! Still curious why the for loop didn’t work correctly, though.

I decided to experiment a bit myself and created an example with a single infinitely scrolling tilemap with perlin noise terrain and some game objects on top of it.

The tilemap is sized to cover the screen with a one tile margin. Every time the player passes over the edge of two tiles the tilemap contents will get updated. The tilemap position is kept more or less fixed at origo and only moving within the range of the size of a single tile.

The player and enemies on the map are kept parented to a root game object and they are moving freely. The root game object is offset against the player position to keep the player centred on the tilemap (and at world coordinates around origo).

Not sure if it can be of some use for you but here it is:

CODE: https://github.com/britzl/publicexamples/tree/master/examples/infinite_map
DEMO: http://britzl.github.io/publicexamples/infinite_map/index.html

6 Likes

I appreciate it. Thanks for putting the work into it to test it out.

OK, so I am totally new to Defold and I am going to bump a 5yr old question, gulp. I think that the answer to the question of why the for-loop did not work is that re-setting x (the loop var) inside the loop is seriously bad form, much worse than bumping this! Whereas while is just a logical test with no temp internal vars involved.