This is the first in a
series of articles about writing a Minecraft mapping tool in Python. I'm aiming for somewhere between a tutorial and a general discussion of the things that I think are cool or interesting. Today I'll describe how we're going to render the map.
To understand what we're doing, you'll probably want to be familiar with
Minecraft. To recap briefly, playing Minecraft involves exploring and building in a world of axis-aligned 1m side cubic blocks. Each cube might be filled with one of various
materials, such as earth, air, stone, water or sand. The world extends indefinitely in both horizontal directions and has a vertical extent of 128m, with mostly impenetrable rock blocking access to the lower boundary and merely empty space into which blocks may not be placed above the upper boundary.
In subsequent posts we'll talk more about exactly how we're going to take this 3D world and flatten it into a 2D map. For now, we're going to suppose that we have a 2D grid of square cells and in each cell we have the Minecraft block ID that describes the content of that cell and we want to render the corresponding Minecraft block texture in it. (We will also ignore the fact that some Minecraft blocks are partially transparent for now.)
There are various ways we could do this:
- Load the Minecraft textures into a Pygame Surface, loop through the map of block IDs and for each one blit the corresponding texture onto the corresponding position on-screen. Pretty easy, possibly quite slow, and not enough fun.
- Load the Minecraft textures into an OpenGL texture as a "texture atlas", loop through the map of block IDs and create a pair of triangles for each with appropriate texture coordinates to apply the right texture from the atlas. This is a bit more tricky, but once we've created the vertices and indices that make up the triangles, it should be pretty fast, because we can render everything frame after frame without needing to send any large amount of data to the graphics card. However, it's still not very fun.
- Load the Minecraft textures into one OpenGL texture as above, but load the block ID map into another texture. Render one big quad and write a pixel shader that first looks up the block ID map and then uses the value it finds there to look up the block texture atlas. This is still quite tricky, but it also lets us do some other interesting stuff, and it's fun. Since I have very little experience writing shaders, I thought this would be a great idea to try.
To get a bit more concrete, we're going to feed in two textures. One is a texture atlas containing a 16x16 grid of textures – so 256×256 pixels if each texture is 16x16 - which will be very similar to the terrain.png file from Minecraft. You can see mine on the right. (As I mentioned before, I'm using the awesome
Painterly Pack. All the cool textures are from that. Only a few special top-down views in the bottom-right were drawn by me.) The other texture that we will feed in will be a specially encoded 32-bit RGBA format texture where the values in the red channel (between 0 and 255) specify one of the textures in the texture atlas. (Later on we'll use the other channels for various other things.) We'll number the texture atlas textures from 0 in the top left through to 15 in the top right and in rows down to 255 in the bottom right. The dimensions of this texture will depend on what size of map we want to render in one go. A Minecraft region is 512×512 and this is a convenient enough size for us, so we'll go with that for now.
The next article will be some preparatory groundwork –
creating a useful diagnostic texture which we can use in place of terrain.png to properly understand what's going on and more easily spot when we've made a mistake. After that, we'll move on to doing OpenGL using
PyOpenGL.