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()