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.
  • GIMP Script to Apply Normal Maps and Directional Lighting
    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()
    
  • GIMP Script to Resize Images
    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()