Creating a Solar System Simulation with Python and Pygame

This project is a fantastic demonstration of using object-oriented programming to create infinitely scalable programs by instantiating classes based on various sets of data. Ready to explore the galaxies from the comfort of your home? Let’s get started!

Here’s what you can expect by the end of this tutorial:

A screen recording showing the simulation running

If you’re eager to grab the code for yourself to run it or use it for your projects, you can find the GitHub repo here.

Step 1: Setting Up the Basics

We begin by importing essential modules such as math and random which are used for calculating ‘orbits’ and setting initial positions for various celestial bodies. To bring our simulation to life on screen, we’ll use Pygame. We also import json to manage our configuration files conveniently.

from math import sin, cos, pi, radians
from random import randrange, uniform
import pygame as pg
import json

Our simulation reads two JSON files, settings.json and bodies.json. The settings.json file contains essential settings for our Pygame display, while the bodies.json file holds the data for the celestial bodies in our solar system, such as size, colour, distance from the sun, velocity, etc. Make sure these files are in the same directory as your Python file for these relative imports to work.

with open("settings.json", "r") as file:
    SETTINGS = json.load(file)
with open("bodies.json", "r") as file:
    BODIES = json.load(file)

#SETUP WINDOW
FPS = SETTINGS["Screen"]["FPS"]
WIN = pg.display.set_mode((SETTINGS["Screen"]["Width"], SETTINGS["Screen"]["Height"]), pg.RESIZABLE)
pg.display.set_caption(SETTINGS["Screen"]["Title"])

Step 2: Creating Classes for Our Simulation

We define a MoveableCameraGroup class to handle camera movements. This class is important because it allows users to interact with the simulation through mouse inputs. It also controls the zoom function and the camera reset operation, handled by the space bar press. Additionally, it handles the creation of celestial bodies such as stars, asteroids, planets, and moons.

class MoveableCameraGroup(pg.sprite.Group):
    """
    SPRITE GROUP SUBCLASS TO HANDLE MOUSE INPUTS AS OFFSETS FOR A 'CAMERA'
        - CLICK AND DRAG CAMERA
        - SCROLL WHEEL ZOOM
        - SPACE BAR TO RESET POSITIONS
    """
    def __init__(self, SETTINGS):
        super().__init__()
        self.SETTINGS       = SETTINGS
        self.incriment_up   = SETTINGS["Scales"]["Incriments"]["Up"]
        self.incriment_down = SETTINGS["Scales"]["Incriments"]["Down"]
        
        self.offset             = pg.math.Vector2() #TO APPLY TO SPRITES
        self.clickstart_offset  = pg.math.Vector2() #NORMALISE AFTER CLICK
        self.dragging           = False #WHEN TO APPLY OFFSET TO SPRITES
        self.reset_scales()
        
        self.astroid_distance_start = BODIES["Asteroids"]["Distance"]["Start"]
        self.astroid_distance_end   = BODIES["Asteroids"]["Distance"]["End"]

    #... GET FULL CODE ON GITHUB: https://github.com/jmwhitworth/Pygame-Solar-System

Step 3: Creating Objects for Stars and Celestial Bodies

Stars in our simulation are objects of the star class, which is a subclass of Pygame’s Sprite class. Each star has properties such as size, position, and colour. This basic class allows stars to be statically generated and displayed in any single location on the viewport.

class star(pg.sprite.Sprite):
    def __init__(self, sprite_group, name, size, x, y, colour):
        super().__init__(sprite_group)
        self.sprite_group   = sprite_group
        self.base_size      = size
        self.base_radius    = 0
        self.base_velocity  = 0
        self.size           = self.base_size * self.sprite_group.scale_size
        self.radius         = 0
        self.velocity       = 0
        
        self.name           = name
        self.x              = x
        self.y              = y
        self.colour         = colour
        self.surface        = pg.display.get_surface()
    
    #... GET FULL CODE ON GITHUB: https://github.com/jmwhitworth/Pygame-Solar-System

Celestial bodies that orbit around other bodies, such as planets and their moons, are objects of the satellite class. This is a subclass of the star class. These bodies use the star classes basic properties such as colour and position, but they also have additional properties like orbit radius and velocity. This arrangement allows us to create moving sprites without duplicating code.

class satellite(star):
    def __init__(self, sprite_group, name, parent_name, size, radius, velocity, colour):
        super().__init__(sprite_group, name, size, 0, 0, colour)
        self.base_radius    = radius
        self.base_velocity  = velocity
        self.radius         = self.base_radius * sprite_group.scale_distance
        self.velocity       = self.base_velocity * sprite_group.scale_velocity
        
        self.parent         = sprite_group.get_body_by_name(parent_name)
        self.center_of_rotation_x = self.parent.x
        self.center_of_rotation_y = self.parent.y
        self.angle = radians(uniform(0,360))
        self.x = self.center_of_rotation_x + self.radius * cos(self.angle)
        self.y = self.center_of_rotation_y - self.radius * sin(self.angle)

    #... GET FULL CODE ON GITHUB: https://github.com/jmwhitworth/Pygame-Solar-System

Step 4: Executing Our Simulation

Lastly, our main function, run(), creates a MoveableCameraGroup object and populates it with celestial bodies, using the data from our bodies.json file. It then enters a loop where it updates the screen, handles events, and applies camera controls.

This run function is then called using an if statement that ensures the code is only executed when this Python file is called directly, allowing this Python file and its classes to be imported into other files if required.

def run():
    visible_sprites = MoveableCameraGroup(SETTINGS)
    visible_sprites.create_sprites()
    
    clock = pg.time.Clock()
    run = True
    while run:
        clock.tick(FPS)
        
        WIN.fill((0, 0, 0))
        visible_sprites.update()
        pg.display.update()
        
        for event in pg.event.get():
            if event.type == pg.QUIT:
                run = False
            visible_sprites.camera_controller(event)
    quit()

if __name__ == '__main__':
    run()

To execute the program, navigate to the directory containing your Python file in your terminal and type python filename.py.

Conclusion

Now that you have successfully created your solar system simulation, feel free to experiment with the code and see what other celestial bodies you can add or customize.

Remember, coding is all about problem-solving and creativity. The integration of Python with Pygame provides a powerful and flexible platform to create stunning simulations like our solar system simulation.

If you run into any common errors such as file not found, make sure your JSON files are in the same directory as your Python file.

Thanks for joining us on this cosmic journey! Don’t forget to check out the GitHub repo for the full code of this project. Explore the solar system from your computer screen and let us know what cool modifications you make!