Fire Special Effect in Python
Thursday, 12. November 2009
I have interested in special effects these days, and bought a book “Special Effects Game Programming with DirectX” yesterday. The algorithm of fire effect looks simple. I want to compile and execute bundled source code that is implemented in C++, but it uses DirectX8 whereas my computer has only DirectX10 SDK installed.
Implementing the algorithm in Python was easy except creating a palette. I hadn’t understood how to “make” a palette, rather I thought palettes are provided by OS or libraries. The code from Pygame repository that creates red-palette is good for me to understand how to make a palette.
Anyway my code is as below
'''
Created on Nov 12, 2009
Class for fire special effect
Dependency:
numpy: http://numpy.scipy.org/
pygame: http://pygame.org/
@author: grayger (http://www.grayger.com/)
'''
import random
import sys
import numpy
import pygame
from pygame.locals import *
class FireEffect:
def __init__(self, size=(40, 40), coolingFactor=5, fuelRange=(-31, 32)):
self.__width, self.__height = size
self.coolingFactor = coolingFactor
self.fuelRange = fuelRange
self.__array = numpy.zeros((self.__width, self.__height))
self.__fireSurface = pygame.Surface((self.__width, self.__height), 0, 8 )
self.__fireSurface.set_palette(self.__getPalette())
random.seed()
def __getPalette(self):
gstep, bstep = 75, 150
cmap = numpy.zeros((256, 3))
cmap[:, 0] = numpy.minimum(numpy.arange(256) * 3, 255)
cmap[gstep:, 1] = cmap[:-gstep, 0]
cmap[bstep:, 2] = cmap[:-bstep, 0]
return cmap
def getFireSurface(self):
tempArray = numpy.zeros(self.__array.shape)
for r in range(0, self.__width):
for c in range(0, self.__height):
tempArray[r, max(0, c - 1)] = min(255, max(0, (int(self.__array[max(0, r - 1), c]) + int(self.__array[min(self.__width - 1, r + 1), c]) + int(self.__array[r, max(0, c - 1)]) + int(self.__array[r, min(self.__height - 1, c + 1)])) / 4 - self.coolingFactor))
for r in range (0, self.__width, 2):
fuel = min(255, max(0, self.__array[r, self.__height - 1] + random.randint(*self.fuelRange)))
tempArray[r, self.__height - 1] = fuel
tempArray[r + 1, self.__height - 1] = fuel
self.__array = tempArray
pygame.surfarray.blit_array(self.__fireSurface, self.__array.astype('int'))
return self.__fireSurface
#######################################
pygame.init()
screen = pygame.display.set_mode((320, 120), 0, 8 )
fireEffect = FireEffect()
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_a and fireEffect.coolingFactor < 10:
fireEffect.coolingFactor += 1
elif event.key == K_z and fireEffect.coolingFactor > 1:
fireEffect.coolingFactor -= 1
fireSurface=fireEffect.getFireSurface()
# draw the original fire image
screen.blit(fireSurface, (0,0))
# draw the scaled and mirrored fire image
pos = (0, 40)
fireSurface = pygame.transform.scale(fireSurface, (80, 80))
fireSurface2 = pygame.transform.flip(fireSurface, True, False)
for i in range(0, 4, 2):
screen.blit(fireSurface, (pos[0] + fireSurface.get_width()*i, pos[1]))
screen.blit(fireSurface2, (pos[0] + fireSurface.get_width()*(i + 1), pos[1]))
clock.tick(30)
pygame.display.flip()
#######################################
When creating a FireEffect instance, you can set the size of surface array, cooling factor, and the range of fuel factor. The bigger size requires more CPU resource but produces better quality. The bigger cooling factor, the shorter the fire height. The bigger fuel factor, the brighter the fire.

The upper image is original-sized one and the lower image is scaled/mirrored one.
