Articles Archive
Articles Search
Director Wiki
 

Create Particle Systems Dynamically

October 10, 2000
by Charles Forman

Particle systems can simulate rain, fire, fireworks, explosions, smoke, clouds and other gaseous effects. In a particle system, one particle exists for each raindrop or bit of smoke. That particle moves, rotates, and blends in a certain way to simulate a real raindrop or smoke particle. In a traditional particle system rendering engine, each particle is very small, with a unique behaviour based on physics and its interaction with the other particles. Unfortunately, that process can take anywhere from one second to one hour per frame, which makes it impossible to create a truly accurate real-time particle system.

Go and rent a Die Hard movie (or other action thriller) to study the explosions. With little explosions, notice that one mass of fire expands and spirals out into nothingness. With huge climactic explosions, notice that there appear to be several masses of fire, each one acting similarly but uniquely.

The key to creating real-time particle systems in Director is to understand that particles can be averaged into larger groups of particles. Because Director would not be able to handle keeping track of and displaying a billion particles, it is important to average groups of particles. One particle in this demonstration represents billions of particles in a real life system. The more particles you display, the more realistic your simulation becomes.

This is actually very easy to comprehend when you think about the life of a particle. The particle starts out very small, and invisible at its originating point. It begins to grow and rotate, becoming more opaque as it moves away from its originating point. Just as the particle reaches its most opaque form, it begins to dissipate and slow down. Gravity, momentum, wind, and other forces can affect the movement. You can make the particle system as simple or complex as you want.

Director 8 download for Mac or Windows.

In Director, because sprites are so easily manipulated, I chose to have one sprite represent each particle in the system. Each particle sprite has a behavior script that tells the particle how to act. Here's how it's done.

property PointX, PointY, SpinSpeed, Momentum, Radius, Angle
property SpriteBlend, BlendMomentum, Scale

on beginsprite me

  PointX = the MouseH
  PointY = the MouseV
  SpinSpeed = Random(60)-30
  Momentum = Random(Value(field("Momentum")))+25

  Radius = 5
  Angle = Random(Value(field("Spread")))+ Value(field("Angle"))
  SpriteBlend = 0
  BlendMomentum = Value(field("BlendMomentum"))
  Scale = 0

  sprite(me.spritenum).MemberNum = Member("Fire" & Random(4)).number

end

The beginSprite event sets the values at the birth of the particle: the initial location, momentum, distance, direction, blend and color. Note the liberal use of random() in creating values for the properties of the system. Random movements make the fire look organic and realistic.

on exitframe me

  Momentum = Momentum * Value(field("MomentumDampen"))
  Radius = Radius + Momentum
  Scale = Scale + Momentum
  SpriteBlend = SpriteBlend + BlendMomentum

  sprite(me.spritenum).width = Scale
  sprite(me.spritenum).height = Scale
  sprite(me.spritenum).rotation = sprite(me.spritenum).rotation + SpinSpeed
  sprite(me.spritenum).blend = min(max(SpriteBlend, 0), 100)
  sprite(me.spritenum).locH = Radius*cos(Angle*pi()/180) + PointX
  sprite(me.spritenum).locV = Radius*sin(Angle*pi()/180) + PointY

  if SpriteBlend > 100 then BlendMomentum = -(Random(16) + 4)
  if SpriteBlend < 0 then BeginSprite me

end

The exitFrame event affects the properties of the particle as each frame passes. When setting the blend I use the min and max functions, because the spriteBlend property will often be a little more than 100 or less than 0. If the particle's spriteBlend has become greater than 100, the blendMomentum is set to a negative number and the particle will fade out. When the particle fades out completely, it recycles itself.

The Particle Image

The actual particle image is designed to appear very much like a puff of smoke. It is circular and has feathered edges, while having some texture. I created a 200x200 pixel Photoshop file with a white background, then used the airbrush tool to dab small dots on the canvas until I achieved the desired effect. It is important that the image has no distinct features. Remember that the image will be displayed over and over again; each particle should appear to be unique. Plus, the particles will be scaled upwards of at least 300%; distinct features tend to look very pixilated, scaled and rotated.

Dynamic Particle Image Creation

The particle's Cast member is assigned as follows:

sprite(me.spritenum).MemberNum = Member("Fire" & Random(4)).number

If you examine the cast before running, you will notice that the members Fire1 through Fire4 does not exist. They are dynamically created at run time to conserve disk space and file transfer times. The 8-bit member ParticleAlpha is the only image. Because ParticleAlpha is the alpha channel that will be used for all particles, Imaging Lingo can be used to draw new images, based on the member ParticleAlpha, on the fly.

on createFireImages

  -- Set the four colors for the particles
  color1 = rgb(133, 133, 133)
  color2 = rgb(255, 240, 0)
  color3 = rgb(255, 186, 0)
  color4 = rgb(255, 90, 0)

  repeat with i = 1 to 4
    
    -- Set the alpha
    BaseImageAlpha = member("ParticleAlpha").image
    
    -- Create new cast member, name it, make image
    TargetMember = new(#bitmap)
    TargetMember.name = "Fire" & i
    TargetMember.image = image(member("ParticleAlpha").image.width, member("ParticleAlpha").image.height, 32)
    
    -- Fill the image with the color
    TargetMember.image.fill(TargetMember.image.rect, value("color" & i))
    
    -- Set alpha to nothing and make a copy of it (seems asinine, but
    -- otherwise, the resulting images alpha has noise (BUG??))
    TargetMember.image.setAlpha(0)
    emptyAlpha = targetMember.image.extractAlpha()
    newAlpha = duplicate(emptyAlpha)
    
    -- Use copyPixels to apply the ParticleAlpha to the copied alpha
    newAlpha.copyPixels(BaseImageAlpha,BaseImageAlpha.rect,BaseImageAlpha.rect)
    
    -- Set the Alpha
    targetMember.image.setAlpha(newAlpha)
    targetMember.useAlpha = true
    
  end repeat

end

ParticleAlpha member is 76.1K uncompressed, and I used JPEG compression at 80%.

Each dynamically created cast member is 64.9K. There are four, plus the alpha, which comes to a total of 226.6K. If you didn't dynamically create the cast members, the difference in size would be huge. Granted, JPEG compression will keep the file size down. But what if you wanted 64 different colors of the particles? The time it would take to dynamically generate the 64 members would be less than a second.

Check out this quick example I made. The game is a cheap ripoff of RobotDuck's Yard Invaders (a very nice game). Notice how the smoke is used in the game.

Charles Forman is a freelance designer, developer, and strategist in Chicago, Illinois. He has given speeches on high-level Director/Lingo topics, and has been featured on the front page of the Chicago Tribune. He currently develops within the advertising and game industries and is working on a book. He can be reached at cforman@cforman.com, http://www.setpixel.com, or http://www.cforman.com. He is currently available for freelance work.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.