35. OpenGL Tiling Engine (Version 2.0)

Introduction

In this tutorial, we’ll create a simple 2D tiling engine in OpenGL. Tiling is a common approach in game development, used to construct game worlds by arranging a grid of tiles with different textures. While OpenGL doesn’t provide built-in support for tiled maps, we can achieve this by rendering a grid of textured quads and binding textures dynamically based on a predefined map.

We’ll use a 10×10 map to demonstrate this technique, assigning two textures (grass and dirt) to represent different tile types.

Defining the Map

The map is represented as a 2D integer array where each number corresponds to a specific texture:

int cMap[10][10] = {
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // Row 1
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, // Row 2
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, // Row 3
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, // Row 4
    {1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, // Row 5
    {1, 0, 1, 1, 1, 1, 1, 1, 1, 1}, // Row 6
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // Row 7
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // Row 8
    {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // Row 9
    {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // Row 10
};

Explanation:
– Each number represents a tile type, where `1` corresponds to dirt and `0` corresponds to grass.
– You can easily modify this array to create custom maps, adding more tile types as needed.

Loading Textures

To represent different tile types, we use two textures: one for grass and one for dirt. These are loaded into OpenGL using a texture loading function:

GLuint texture, texture2; // Texture handles

texture = LoadTexture("texture.raw", 256, 256); // Load grass texture
texture2 = LoadTexture("texture2.raw", 256, 256); // Load dirt texture

Explanation:
– The textures are stored as `GLuint` handles.
– Replace `”texture.raw”` and `”texture2.raw”` with your actual texture file paths.

Texture Loader Function

The `LoadTexture` function reads a `.raw` file and uploads the texture data to OpenGL:

GLuint LoadTexture(const char *filename, int width, int height) {
    GLuint texture;
    unsigned char *data;
    FILE *file;

    file = fopen(filename, "rb");
    if (file == NULL) return 0; // Handle missing files

    // Allocate memory and read texture data
    data = (unsigned char *)malloc(width * height * 3);
    fread(data, width * height * 3, 1, file);
    fclose(file);

    // Generate and bind the texture
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);

    // Set texture parameters
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    // Upload texture data to OpenGL
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

    free(data); // Free allocated memory
    return texture;
}

Explanation:
– `fopen` reads the raw texture file. Ensure the file exists in the specified path.
– `glTexParameterf` sets texture parameters like filtering and wrapping modes for smooth rendering.

Rendering the Tiles

To render the map, we loop through each row and column in the map array and draw a textured quad for each tile:

void drawTiles(void) {
    for (int i = 0; i < 10; i++) { // Loop through rows
        for (int j = 0; j < 10; j++) { // Loop through columns
            // Bind the appropriate texture
            if (cMap[i][j] == 0) {
                glBindTexture(GL_TEXTURE_2D, texture); // Grass texture
            } else {
                glBindTexture(GL_TEXTURE_2D, texture2); // Dirt texture
            }

            // Draw the quad
            glPushMatrix();
            glTranslatef(j, -i, 0); // Position each tile
            glBegin(GL_QUADS);
            glTexCoord2d(0.0, 0.0); glVertex3f(0.0, 0.0, 0.0); // Bottom-left
            glTexCoord2d(1.0, 0.0); glVertex3f(1.0, 0.0, 0.0); // Bottom-right
            glTexCoord2d(1.0, 1.0); glVertex3f(1.0, 1.0, 0.0); // Top-right
            glTexCoord2d(0.0, 1.0); glVertex3f(0.0, 1.0, 0.0); // Top-left
            glEnd();
            glPopMatrix();
        }
    }
}

Explanation:
- `glTranslatef(j, -i, 0)` positions the quad based on its row and column in the map array. Negative `i` flips the Y-axis for correct orientation.
- The texture coordinates (`glTexCoord2d`) map the texture to the quad’s corners.

Tutorial Code

Here’s the full program:

#include <GL/gl.h>
#include <GL/glut.h>
#include <cstdlib>
#include <cstdio>

GLuint texture, texture2; // Texture handles
int cMap[10][10] = { ... }; // Map definition

GLuint LoadTexture(const char *filename, int width, int height); // Function prototype

void drawTiles(void) { ... }

void display(void) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glEnable(GL_TEXTURE_2D);
    glTranslatef(-5, 4, -20); // Center the map in view
    drawTiles(); // Render all tiles
    glutSwapBuffers();
}

void reshape(int w, int h) {
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60, (GLfloat)w / (GLfloat)h, 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char **argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);
    glutInitWindowSize(500, 500);
    glutCreateWindow("OpenGL Tiling Engine");

    texture = LoadTexture("texture.raw", 256, 256);
    texture2 = LoadTexture("texture2.raw", 256, 256);

    glutDisplayFunc(display);
    glutIdleFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();

    return 0;
}

If you have any questions, feel free to email me at swiftless@gmail.com. Happy coding!

Download Texture(.RAW file)

Download the second Texture (.RAW file)

  • March 25, 2010
  • 15