Starfields and Galaxies with Python

19.12.2007 by kerim in natural | pygame | python | tutorial

A long time ago i spend a lot of my resources programming fractals and "natural" phenomenons. Since my last adventure with pygame i was playing around with the idea of implementing some algorithms of old times in python anew and use pygame to test them. A small module that creates screens with stars on it is what i will speak about here.

I implemented three small but usefull algorithms:

  1. A simple function that fills a screen with stars simulating a normal night
  2. A function that creates clouds of stars similar to eliptic galaxies
  3. A function that creates spiral galaxies

Although i used pygame for the implementation and although you need it if you want to test my code, it is independend of any (external) library you might use for your game, app, rendering. All you have to do is to overwrite the "draw" method in your own class.

So here is the way to do it:

  1. Pygame Framework to test and Starfields-skeleton

    Use the following code below for testing. Just take the comments away from the function call you want to use. As you can see i have put my class in a module called starfields located in a package called natural.

    #! /usr/bin/env python
    import pygame, sys  
    from natural.starfields import Starfields
    from pygame.color import THECOLORS
    from math import pi
    def main():
      screen = pygame.display.set_mode(displaymode)
      #an array of colors for the stars. probability is determined by the amount of times a color is mentioned
      colors=[THECOLORS["white"],THECOLORS["yellow"], THECOLORS["white"],THECOLORS["red"],THECOLORS["white"]]
      #s.createRandomStars(100, colors)
      #s.createStarsByProbability(10, colors)
      turn = 45.0 * 2 * pi / 360.0 #calculate in rad
      deg = 270.0 * 2 * pi / 360.0
      #s.createElipticStarfield(1000, colors, (300,200), (200,100), turn)
      s.createSpiralGalaxy(colors,(300,200), (100,50),turn,deg)
      while True:  
        event = pygame.event.poll()  
        if event.type == pygame.QUIT:  
    if __name__=="__main__":

    Code for the Starfields - class (Skeleton):

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import random
    from math import sin, cos,pi
      import pygame
      print "pygame not available, drawing won't work"
    class Starfields():
      def __init__(self,screen, width, height):
    overwrite this for your own graphics library
      def draw(self,x,y,color):,color,(x,y),0)

    Initialization needs some "screen" object and the width and height of it. pygame is NOT needed for you LATER. I only included it because i wanted to test the module. You can later adapt rather easily by supplying whatever "surface" you want to draw on and adapting the draw method. For example you might provice a screen of None and in the draw method only assemble an array of data etc.

  2. Doing the starfield

    First i used a somewhat simple way to create a starfield. I iterated over the whole screen (pixel by pixel) and rolled some dice checking if there would be a star at that pixel. If so then i would determine which color it had and draw it. The way works, but it costs you a huge amount of time. So instead of doing that you should rather implement a simple algorithm that takes the amount of stars to set and then just determines where to put them and how they should look like.

    def createRandomStars(self,amount,colors):
    for star in range(0,amount):

2: Doing the cloud

A cloud is normally deternmined by an elipse with a radius in x direction and one in y, as well as a center. The algorithm is rather simple again. For each star we pick one point at the outer border of the elipse. We first determine the degree randomly. If now we would calculate the positions for x and y position by multilying the radiuses (rx and ry) with the cosinus or sinus then we would create a dotted elipse with the stars all on the edge of it. So we need to move the stars randomly toward the center, with the majority of stars near it and only few at the edge of the elipse. This is done when calculating the insideFactor. It is allways below or equal to 1 and multiplying it with itself will further decrease the value. Around 50 % of all stars should be in the first 25% of the distance between center and outer rim. Finally i also included a variable "turn" which will turn the cloud by a given angle, so that you do not always have the standard "left to right" cloud.

def createElipticStarfield(self, amount, colors, center, radius, turn=0):
    for star in range(0,amount):
      degree= 2.0 * degree * pi / 360.0
      #(sin(degree)*rx/ypos=cos(degree)*ry) would form the elipse
      #since we need to draw inside with the density increaing near the center we must include 
      #some factor (0..1) 
      if turn!=0:

3: Doing the spiral galaxy

Now THIS was rather complicated. The first step is to create ONE single cloud in the center of the spiral galaxy. Then we must create the arms. thats done by putting smaller clouds on the path of those arms. In each iteration we swap between the two spiral arms when putting the clouds. The factors for sizes are actually just the result of several hours of trying.

def createSpiralGalaxy(self, colors, center, size, turn=0, deg=0, dynsizefactor=50, sPCFactor=8):
    yp1=round(deg/pi*sy/1.7)*dynsizefactor #factors for dynamic sizing
    print xp1,yp1
    #this was the central cloud
    #now for the smaller ones in the spiral arms
    mulStarAmount=(xp1+yp1)/sPCFactor #factor for amount of stars per cloud 
    while n<=deg:
      swap = not swap
      xp1=cos(turn)*xpos  + sin(turn)*ypos 
      yp1=-sin(turn)*xpos + cos(turn)*ypos
      if swap:
        self.createElipticStarfield(int(sizetemp/2),colors,(x+xp1,y+yp1),(sizetemp,sizetemp), turn)
        self.createElipticStarfield(int(sizetemp/2),colors,(x-xp1,y-yp1),(sizetemp,sizetemp), turn)
      n+= 2.0* angle *pi / 360.0


I would like to have some feedback on this. If you have a good picture send it to me. If you have an idea on how to improove the code or add some functionality to beautify the result ... send it to me.

comments powered by Disqus