Hovercraft Glide Turbo Racing Postmortem

For the time being, this page will host the lessons learned while creating this project that may be helpful to other developers.

GIMP Scripts

While developing this game, a number of tasks needed to be done repeatedly, and I quickly grew tired of performing them by hand. So I developed a number of simple scripts for GIMP to automate these tasks. This was the first time I had scripted in GIMP, and I wanted to share them in case they could be of use to anybody else.

Pre-Render Script

The tiles used in this game were generated using bump maps and directional lighting, however, the tiles are all pre-rendered to improve performance. This process of pre-rendering proved to be very time-consuming and tedious, so I created some GIMP scripts to automate the process.

Code


#!/usr/bin/env python

import math
from gimpfu import *
from array import array

def apply_normal_map(
        image,
        layer,
        outputFile,
        textureFile,
        normalMapFile,
        dirLightX,
        dirLightY,
        dirLightZ,
        r,
        g,
        b,
        i):

    texture = pdb.file_png_load(textureFile, textureFile)
    normalMap = pdb.file_png_load(normalMapFile, normalMapFile)
    outputImage = pdb.gimp_image_new(texture.width, texture.height, 0)
    layer=gimp.Layer(outputImage, "Background", texture.width, texture.height, RGB_IMAGE, 100, NORMAL_MODE)
    pdb.gimp_image_add_layer(outputImage, layer, 0)
    pdb.gimp_layer_add_alpha(layer)

    pdb.gimp_message("Loaded texture and normal map")

    tRegion = texture.layers[0].get_pixel_rgn(0, 0, texture.width, texture.height, False, False)
    nRegion = normalMap.layers[0].get_pixel_rgn(0, 0, normalMap.width, normalMap.height, False, False)
    outputRegion = outputImage.layers[0].get_pixel_rgn(0, 0, outputImage.width, outputImage.height, True, False)

    pdb.gimp_message("Got regions")

    tPixels = array("B", tRegion[0:texture.width, 0:texture.height])
    nPixels = array("B", nRegion[0:normalMap.width, 0:normalMap.height])
    tp_size = len(tRegion[0, 0])
    np_size = len(nRegion[0, 0])
    dest_pixels = array("B", "\x00" * (texture.width * texture.height * tp_size))

    pdb.gimp_message("Built arrays")

    for y in range(texture.height):
        pdb.gimp_message(y)
        for x in range(texture.width):
            t_src_pos = (x + texture.width * y) * tp_size
            n_src_pos = (x + normalMap.width * y) * np_size

            nx = (nPixels[n_src_pos] - 127.0) / 127.0
            ny = (nPixels[n_src_pos + 1] - 127.0) / 127.0
            nz = (nPixels[n_src_pos + 2] - 127.0) / 127.0

            magnitude = math.sqrt(nx * nx + ny * ny + nz * nz)
            nx = nx / magnitude
            ny = ny / magnitude
            nz = nz / magnitude

            dotProduct = nx * dirLightX + ny * dirLightY + nz * dirLightZ

            # pdb.gimp_message(nPixels[n_src_pos])
            # pdb.gimp_message(nx)
            # pdb.gimp_message(dirLightX)
            # pdb.gimp_message(dotProduct)

            if dotProduct >= 0:
                dr = min(1.0, r * i * (tPixels[t_src_pos] / 255.0) * dotProduct)
                dg = min(1.0, g * i * (tPixels[t_src_pos + 1] / 255.0) * dotProduct)
                db = min(1.0, b * i * (tPixels[t_src_pos + 2] / 255.0) * dotProduct)
                da = tPixels[t_src_pos + 3] / 255.0
            else:
                dr = 0
                dg = 0
                db = 0
                da = tPixels[t_src_pos + 3] / 255.0

            # pdb.gimp_message(dr)

            dest_pixels[t_src_pos] = int(dr * 255)
            dest_pixels[t_src_pos + 1] = int(dg * 255)
            dest_pixels[t_src_pos + 2] = int(db * 255)
            dest_pixels[t_src_pos + 3] = int(da * 255)

    outputRegion[0:texture.width, 0:texture.height] = dest_pixels.tostring()

    outputImage.layers[0].flush()
    outputImage.layers[0].merge_shadow(True)
    outputImage.layers[0].update(0, 0, texture.width, texture.height)

    pdb.file_png_save(outputImage, outputImage.layers[0], outputFile, outputFile, 0, 0, 0, 0, 0, 0, 0)

def apply_normal_maps(image, layer):
    try:
        nx = -1
        ny = 1
        nz = 1
        magnitude = math.sqrt(nx * nx + ny * ny + nz * nz)
        nx = nx / magnitude
        ny = ny / magnitude
        nz = nz / magnitude

        apply_normal_map(image,
                         layer,
                         “output.png”,
                         “texture.png”,
                         “normal.png”,
                         nx, ny, nz,
                         1, 0.93, 0.84, 2)

        pdb.gimp_message("Done")
    except Exception as err:
        pdb.gimp_message("Unexpected error: " + str(err))

register(
    "python_fu_game_apply_normal_maps",
    "Apply Normal Maps",
    "Applies normal maps for game",
    "",
    "",
    "2017",
    "/Filters/Game/Apply Normal",
    "*",
    [],
    [],
    apply_normal_maps)

main()

Resize Script

For this game, images were required for the mini-map display. Resizing the larger map images into smaller images was very tedious. The following script automated the process for me in GIMP.

Code


#!/usr/bin/env python

from gimpfu import *

def resize_mini_map(inFile, outFile, newHeight):
    fileImage = pdb.file_png_load(inFile, inFile)
    pdb.plug_in_autocrop(fileImage, fileImage.layers[0])
    pdb.gimp_context_set_interpolation(3)
    aspect = float(fileImage.width) / float(fileImage.height)
    newWidth = int(aspect * float(newHeight))
    pdb.gimp_image_scale(fileImage, newWidth, newHeight)
    pdb.file_png_save(fileImage, fileImage.layers[0], outFile, outFile, 0, 0, 0, 0, 0, 0, 0)

def resize_mini_maps(image, layer, newHeight):
    try:
        resize_mini_map("track1_final_raw.png", "track1_final.png", newHeight)
        resize_mini_map("track2_final_raw.png", "track2_final.png", newHeight)
    except Exception as err:
        pdb.gimp_message("Unexpected error: " + str(err))
    
register(
    "python_fu_game_resize_mini_maps",
    "Resize Minimaps",
    "Resizes mini-maps for game",
    "",
    "",
    "2017",
    "/Filters/Game/Resize Minimaps",
    "*",
    [
        (PF_INT32, "newHeight", "New Height", "")
    ],
    [],
    resize_mini_maps)

main()