Articles Archive
Articles Search
Director Wiki
 

Creating Starfields and Galaxies from Scratch

May 31, 2000
by Gary Rosenzweig

Many Shockwave developers, like me, are obsessed with file size. I'm regularly appalled by 500K-1MB applications and games that don't deliver much bang for the buck. So, every once in a while I try to create tiny little games -- tiny only in file size, not in features. I've created many games for less than 20K, and even a few for 5K.

To facilitate this, I am always looking for new ways to create content inside games without increasing the file size. One such way is to use Lingo to draw on the Stage. In Director 7 and before, trails were used to accomplish this, or perhaps several hundred unused sprites at the end of the Score. With Director 8, we can use imaging Lingo. The simplest example I could think of was a starfield. Instead of using a Stage-sized bitmap for a background, you can use Imaging Lingo to create a random starfield that will look good, but take up little or no file space. The way to do this is to generate a random horizontal and vertical location, along with a random shade of gray, and draw it on to a bitmap. This handler will take n as a parameter and draw n stars.

-- build a starfield with n stars

on makeStarfield n
  
  -- clear image
  member("text").text = "Creating Starfield..."
  image = member("starfield").image
  image.fill(rect(0,0,image.width,image.height), rgb("000000"))
  updateStage
  
  -- add n stars of random color and location
  repeat with i = 1 to n
    c = random(255)
    x = random(image.width)
    y = random(image.height)
    image.setPixel(point(x,y),rgb(c,c,c))
  end repeat
  
  member("text").text = "Done"
  
end

The following Shockwave movie demonstrates this handler, as well as all of the others I will be showing you this week. The "starfield" button draws 1,000 stars, while the "dense starfield" draws 3,000 stars.

A Director 8 sample movie is available for download in Mac or PC format.

Other things can be done with similar code. Suppose you wanted the stars to favor the middle of the screen. This would create a star cluster. Instead of generating random horizontal and vertical positions, it generates an angle and a distance from the center. Then, it uses sin and cos to convert this to a screen position. Here is the code to do that:

-- make a starfield with stars bunched toward center

on makeCluster
  
  member("text").text = "Creating Cluster..."
  image = member("starfield").image
  image.fill(rect(0,0,image.width,image.height), rgb("000000"))
  updateStage
  
  repeat with i = 1 to 10000
    r = power(1.006,random(1000)-1)-1
    a = float(random(628))/100
    x = cos(a)*r
    y = sin(a)*r
    placeStar(image,point(x,y))
  end repeat
  
  member("text").text = "Done"
  
end

The "power(1.006,random(1000)-1)-1" is merely a math trick. It will return a number between 0 and 392, but over a series of calls to the function the numbers returned will not be evenly distributed. Instead, the random numbers will tend to be lower, thus grouping more stars toward the center of the image.

Instead of using setPixel to draw the star, the handler calls a handler named "on placeStar". This handler will essentially call setPixel. But, before it does, it will check the current color of the pixel. If there is already a star there (that is, if the pixel is non-black) it will intensify the color of the pixel, not replace it. This will prevent the middle of the star cluster from having too much dark gray that may be randomly generated by the handler.

-- add to the brightness of a pixel in an image

on placeStar image,p
  
  -- make point relative to center of image
  center = point(image.width/2,image.height/2)
  newPoint = p+center
  
  -- ignore points outside of image
  if not inside(newPoint,image.rect) then exit
  
  -- get current color and add to it
  oldColor = (image.getPixel(p+center)).red
  c = min(oldColor/2+random(255),255)
  
  -- set pixel
  image.setPixel(p+center,rgb(c,c,c))
  
end

The cluster is interesting, and certainly looks realistic if you are an astronomy buff. However, a more interesting way to draw stars is in the shape of a double spiral -- a galaxy. To do this, we will have three repeat loops. The first will simply go from 0 to 1, with the first loop creating one spiral arm, and the second creating another. Then, it will loop through numbers from 0 to 1,000. This will create points from the center of the image out through the spiral. Spirals are created by making the angle and the distance from the center from the same number. The third loop will create a number of stars, each a random distance from the point on the spiral. Like the cluster handler, it will create these with the random numbers weighted so that more stars are right on the spiral arm than off it.

-- make a starfield where pixels are laid out along a spiral
on makeGalaxy
  
  member("text").text = "Creating Galaxy..."
  image = member("starfield").image
  image.fill(rect(0,0,image.width,image.height), rgb("000000"))
  updateStage
  
  -- repeat for each spiral arm
  repeat with n = 0 to 1
    
    -- repeat for 100 points along each arm
    repeat with i = 0 to 1000
      
      r = i*.12
      a = i*.01+pi*n
      x = cos(a)*r
      y = sin(a)*r
      
      repeat with d = 1 to 10-.001*i
        r = power(1.006,random(500)-1)-1
        a = float(random(628))/100
        dx = cos(a)*r
        dy = sin(a)*r
        placeStar(image,point(x+dx,y+dy))
      end repeat
      
    end repeat
    
  end repeat
  
  member("text").text = "Done"
  
end

The result of this handler is beautiful, and it gets better. You can "tilt" the galaxy so that you don't feel as if you are looking at it from above. To do this, the "y" variable is divided by a number (3 in this case) to make the horizontal positions of the stars closer to the center of the screen. Check it out in the example movie above, and then download and examine the code in the movie to see the minor changes that made this galaxy different from the previous one.

But wait, there's more. What about an animated, rotating galaxy? To do this, all you need to do is create a series of images, 18 in this case, all with an offset to the angle of the star positions in the first galaxy. This can get rather complex, but the code is in the example movie for those who want to see it. I create a smaller galaxy image with an "on galaxyImage" handler. Then, using a frame behavior, I call that handler 18 times to get galaxy images for every 20 degrees of rotation. These images are stored a property of the behavior. The resulting list of images is then animated by setting the image of the sprite on the Stage. In order to make sure that the 18 images all use exactly the same stars, and not a new random set, I set the randomSeed property to a constant at the start of the "on galaxyImage" handler.

So, going back to the file size discussion that started this column, the resulting .dcr on this page is a mere 5,141 bytes. 1K of that is the buttons. The result is mostly the Lingo code. No amount of JPEG compression will get you two starfields, a star cluster, two galaxies and a rotating galaxy for 5K, not to mention the fact that you can actually generate near-infinite variations of all of these. You can even color them by setting the color of the sprite to tint them, or by allowing more variety than just gray in the color of the stars.

Gary Rosenzweig's latest book is "Advanced Lingo for Games." In it, you can find the source code for more than 20 complete games. More information about the book can be found at http://clevermedia.com/resources/bookstore/book4.html. It can be purchased there, or in your local bookstore.

Gary Rosenzweig is the Chief Engineer, founder, and owner of CleverMedia, a game and multimedia development company in Denver, Colorado. He is the author of ten books on Macromedia Director and Flash, including his latest, Special Edition Using Director MX.

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