Best way to render 2D environmental tile artwork in my game world

Started by
8 comments, last by Steven Ford 5 years, 5 months ago

I purchased a nice tile set for practicing:

306f7b1648308bd9505a9633c3c89c97.png

...and I'm wondering what would be the best way to tile my game world. At the moment my sprite renderer works nicely, I'm able to render only a portion of a texture given the right x,y, width and height. I can even animate my sprites:

chargif.gif

Question

What's the best way to draw my environment though? I can't imagine creating a bunch of game objects just to fill the screen with floor tiles. I thought of perhaps making a few giant quads, scatter them around the world, and texture each quad with an image of the environment tiles already placed. Maybe this could avoid making so many game objects.

Or maybe I'm wrong. Hopefully somebody can point me towards the right direction, no code is needed.

 

I'm not using any particular engine btw, just opengl, c++, and glm for math. Thanks!

Advertisement

This might be a dumb question (apologies if so), but is your sprite sheet actually angled as it appears in your post? Is your game strictly orthogonal, or are you using some sort of angled view? (I'm guessing it's not actually angled that way, but it might help to clarify that.)

No problem at all, it's not a dumb question. I should have specified it was promotional image I took from the site I bought the assets from.

My camera or world objects are not angled in anyway, pretty much a bunch of quads with its Euler angles at 0, 0, 0.

You mentioned game objects. This may go without saying, but since you're coding from scratch (more or less), there are only 'game objects' if you say there are. Maybe you already have a game object system though, or are thinking in terms of a system like Unity's, for example. In that case, I think your intuition is probably correct that a game object per tile isn't necessary or desirable.

Quote

I thought of perhaps making a few giant quads, scatter them around the world, and texture each quad with an image of the environment tiles already placed.

If I'm understanding you correctly, this would involve creating large 'bespoke' textures, either at run time or during development. As far as I know this isn't an approach that's typically used (texture memory usage perhaps being the biggest problem).

I think a common approach for this might be a grid mesh, where each cell has texture coordinates corresponding to the desired tile in the tile map (this is assuming all your tiles are in a single atlas).

A potential issue with this approach is bleeding. I'll admit I haven't done this sort of tile mapping myself, so I can't speak from direct experience here. I do know that a search for e.g. 'tile map bleeding' turns up quite a bit of discussion, along with various suggestions, such as use of 'padding' pixels. I don't know if your atlas contains such padding. It doesn't look like it does, and I suppose it might not if e.g. it's intended to be used with pixel-perfect rendering.

Maybe someone else can comment on the bleeding issue. I don't know off the top of my head what the optimal solution for that is, but I do suspect that a mesh or set of meshes or some sort of batching system is likely how tile maps are typically rendered.

Thanks for the answer!

Quote

You mentioned game objects. This may go without saying, but since you're coding from scratch (more or less), there are only 'game objects' if you say there are.

You're right, I use the word GameObject a lot since I use Unity, sorry about that. In the case of my question, I wrote a system (I think) similar to Unity :) 

Quote

If I'm understanding you correctly, this would involve creating large 'bespoke' textures, either at run time or during development. As far as I know this isn't an approach that's typically used (texture memory usage perhaps being the biggest problem).

You understood perfectly, and I was planning on making the textures in development.  Using a grid mesh as you mentioned makes so much sense. Now I'd like to programmatically create my worlds during runtime thanks to your suggestion.

So if I wanted to make a very large world, I could create one giant grid mesh and map all the tiles to its texture coordinates?

I'll definitely look into the padding situation, I'm not prepared for that issue, hopefully somebody can offer some tips on that, but for now I'm going to work on the tile renderer.

Thanks again Zakwayda, I appreciate it!

To add onto what @Zakwayda posted, a proven method is to take your tile and extrude each one on the tile sheet as opposed to just leaving a border gap between the tiles, or a solid color. If you extrude them and make sure the boarders extend the tile on all four sides you shouldn't have any issues. If you already have pre-made tiles then you can also do a slice of the left and right and copy them over and top and bottom and copy them over on their respective sides to fill the gaps.

 

Programmer and 3D Artist

Whether you need to add any padding depends on whether you are planning on using mipmapping / filtering. If you are using pixel perfect sprites there is no need (providing you line everything up correctly).

Typically for a 2d game like this you want to separate your engine into rendering static elements and dynamic elements. Static elements of a map might be stuff like floors, walls, things that never change.

For static stuff, you can put it all in one vertex buffer with a static usage flag. For dynamic, put it all in one* dynamic vertex buffer with dynamic usage flag.

*You might also want to use 3 mirror dynamic buffers and use them alternately in a circular order 1, 2, 3, 1, 2, 3 etc, this ensures that you can write to a buffer while the previous one is still being used for rendering in a previous frame and prevents a pipeline stall.

There also may be various variations of this to make things even more optimal, but the basic idea is the same. I believe unity does this kind of batching for you behind the scenes.

To actually draw the elements using a vertex buffer is simple, typically just a list of quads with unique tex coords per corner. This way you can swap the tile used on any quad by simply changing the tex coords. You can base the whole background as a bunch of quads on a grid (this is what many people do), you don't need to of course. But if you do, you can render e.g. a long thin part of the tile map as several tiles instead of one. GPUs are so fast these days that they will eat through these tiles, there is no real benefit to rendering different shaped as 'one quad' rather than e.g. 4, and this makes the map editing etc much simpler to code.

To change the dynamic elements on each frame, lock the vertex buffer, then copy across the new tex coords / vertex data, as many as required, then render this many quads. Of course there are more advanced schemes but this should enable you to get started in the fashion of a lot of top down 2d games.

You may also end up batching different parts of your vertex buffer and rendering the parts with a different shader. e.g. 0-100 normal, 101-150 transparent, 151-280 additive etc. Overall there will only be a few draw calls which is optimal. Often you can also get creative with the vertex format / uvs. If for example your texture atlas is on a regular grid, you might e.g. only need to send one integer for uv, and calculate the uvs in the shader.

I did a ton of tilebased stuff in the past, so i can share my ideas how i implemented them. So here we go:

In a few of my game prototypes i used one texture tile sheet for each style. The outside of such sheets had a one pixel transparent space/border around the entire image and one pixel transparent border around each tile, so that every tile inside has two pixels of space between. This border/space gets really important when you use them in your shaders, like for example doing color corrections, doing blending etc. Without border you may get bleeding or other weird artifacts

Also the tile sheet should be a power of two dimension, like for example 256x512 or 256x256 or 512x512, etc. This makes the computing of the UV´s much easier.

 

For environment tiles i used at least 5 layers - each layer had a fixed tile size of 32x32. The first layer was a background layer used for filling background tiles, like gras, sky, etc. The second layer was another background layer used for more detailed backgrounds, like floors, trees, window, flowers, signs, etc.

The next two layers was used for solid tiles only, meaning that it had some sort of collision/boundary. Each tile had a reference to a geometrical collision shape (rectangle, circle, polygon, etc). If you need pixel perfect collision, i highly recommend reading the "Sonic Physics Guide". If you need physics in your game, you should convert your solid tiles into connected line segments - making it very robust and fast for physics system such as Box2D. But computing these line segments from solid tiles are nasty and not that easy, but i did that in the past and released a public domain library:

https://github.com/f1nalspace/final_game_tech/blob/master/demos/FTT_TileTracingDemo/ftt_tiletracingdemo.cpp https://github.com/f1nalspace/final_game_tech/blob/master/final_tiletrace.hpp

 

The last layer was used for entities placed on tile boundaries, such as start-positions, savepoints, enemies, traps, liquid, etc.

 

If you dont have much experience with programming, i highly recommend using the "TileD" editor which is super easy to use, with a fine and simple file format. Of course if you want, you can always write a editor yourself. Its not that hard and the only thing you need to know how to convert your mouse coordinates inside tile coordinates and vice versa + a sort of camera movement to navigate through your worlds. Of course if you use unity 3d or unreal engine, you should use their built-in tile editor ;)

 

Now regarding rendering. Start of simple and draw each tile in the slowest possible way - make it functional first. You will see that you can draw thousands of tiles, without doing any kind of optimizations. These days, most graphics card can handle that. The only thing i would recommend to optimize are to only show the tiles visible on the screen and skip the tile which are outside.

The order of drawing is back-to-front, so you draw the background layers first, then the solid layers and then the entities.

Later on you may consider creating a batched sprite render, so you can render all tiles with just a couple of draw calls. Its as simple as a defining a vertex buffer, push vertices on for each tile and then issue a draw call for one tilesheet.

 

So thats a rough idea how i implemented tile based stuff successfully. If you have more questions, feel free to ask.

Echoing @Finalspace's suggestion of Tiled (although admittedly I did get bored and wrote my own subsequently for other reasons), it's brilliant for being able to knock up 2D maps really quite quickly. The file format is simple to interpret in your own tooling subsequently. What I did was use Tiled to create the initial map, and then have a resource generation step (a custom MSBuild task) which loaded up the file, created the appropriate sprite sheets etc. and then outputted a file which could easily be consumed by my C++ layer. 

In my case, I had 2 layers:

  1. Pure background tiles
  2. Foreground tiles

(Game Objects were handled separately)

These tile layers were then stored in a simple array (offset = (y*width)+x) with zero meaning no tile, any other value being the tile id. At rendering time, I simply had 2 for loops (x and y) and wrote any tile to screen (the loop ranges were based off what was visible on screen) using the DirectX Toolkit. As @Finalspace says, get it working first, then worry about doing speed ups if you have to.  Clearly you can pre-generate the DX11 command list etc. if you need to, but cross that bridge when you come to it.

Note that on my tiles / sprite sheets, I made the conscious decision to handle overlays in the tooling and output a fresh sprite sheet each resource build. Depending on how much you share the sprites with other items in your games you may or may not want to do this. Either way, the principle holds true, you just store multiple values for each tile to allow for overlays etc. (or multiple layers)

This topic is closed to new replies.

Advertisement