🚀 Python Lesson 11: Gravitational Destroyer Game
Welcome to the ultimate fusion of physics and gaming! In this lesson, we'll create "Gravitational Destroyer" - an epic space shooting game that uses real N-Body gravitational physics. This combines everything we've learned about physics simulation with thrilling game mechanics.
🎯 What You'll Learn
- Advanced game development with Python and Tkinter
- Real-time physics simulation in games
- Object-oriented programming for game entities
- Collision detection and response systems
- Game state management and progression
- Strategic gameplay design using physics
🎮 Game Concept
Gravitational Destroyer is an Asteroids-style shooting game where you pilot a spaceship through gravitational fields created by planets. Your mission: destroy all planets while avoiding collisions and managing your fuel supply. The twist? Everything follows real gravitational physics!
🌟 Game Features
- Realistic Physics: All objects affected by gravitational forces
- Progressive Levels: 3, 5, 10, 15, 20+ planets as you advance
- Special Planets: Fuel depots and explosive chain-reaction planets
- Strategic Gameplay: Use gravity to curve shots around obstacles
- Fuel Management: Limited thrust requires careful planning
⚗️ The Physics Behind the Game
Our game implements several key physics concepts:
1. Gravitational Force
Every planet exerts gravitational force on the spaceship and bullets:
Where G is the gravitational constant, m₁ and m₂ are masses, and r is distance.
2. Orbital Mechanics
Planets follow realistic orbital paths, creating dynamic gravitational fields that change over time.
3. Projectile Motion in Gravity Fields
Bullets curve around planets, allowing for strategic "slingshot" shots and gravity-assisted targeting.
💻 The Complete Game Code
Here's the full implementation of Gravitational Destroyer:
import math
import tkinter as tk
from tkinter import Canvas, Label, Frame
import time
import random
class GameObject:
"""Base class for all game objects"""
def __init__(self, x, y, vx=0, vy=0, radius=5, color="white"):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.radius = radius
self.color = color
self.alive = True
def update(self, dt):
"""Update position based on velocity"""
self.x += self.vx * dt
self.y += self.vy * dt
def draw(self, canvas):
"""Draw the object"""
canvas.create_oval(
self.x - self.radius, self.y - self.radius,
self.x + self.radius, self.y + self.radius,
fill=self.color, outline="white"
)
class Planet(GameObject):
"""A planet with gravitational effects"""
def __init__(self, x, y, vx, vy, mass, color, planet_type="normal"):
radius = max(8, math.sqrt(mass) * 1.2)
super().__init__(x, y, vx, vy, radius, color)
self.mass = mass
self.planet_type = planet_type
self.trail = []
self.points = int(mass / 10) # Points based on mass
def update(self, dt):
super().update(dt)
# Screen wrapping - planets reappear on opposite side
if self.x < -50:
self.x = 1050
elif self.x > 1050:
self.x = -50
if self.y < -50:
self.y = 690
elif self.y > 690:
self.y = -50
# Add to trail for visual effect
self.trail.append((self.x, self.y))
if len(self.trail) > 30:
self.trail.pop(0)
def draw(self, canvas):
# Draw orbital trail
if len(self.trail) > 1:
for i in range(1, len(self.trail)):
fade = i / len(self.trail)
alpha = int(255 * fade * 0.3)
x1, y1 = self.trail[i-1]
x2, y2 = self.trail[i]
canvas.create_line(x1, y1, x2, y2,
fill="#" + format(alpha, '02x') * 3, width=1)
# Draw planet with special effects
if self.planet_type == "fuel":
# Pulsing green fuel depot
pulse = 1 + 0.3 * math.sin(time.time() * 5)
canvas.create_oval(
self.x - self.radius * pulse, self.y - self.radius * pulse,
self.x + self.radius * pulse, self.y + self.radius * pulse,
fill=self.color, outline="lime", width=2
)
elif self.planet_type == "explosive":
# Red explosive planet
canvas.create_oval(
self.x - self.radius, self.y - self.radius,
self.x + self.radius, self.y + self.radius,
fill=self.color, outline="red", width=2
)
else:
# Normal planet
super().draw(canvas)
class Spaceship(GameObject):
"""Player-controlled spaceship"""
def __init__(self, x, y):
super().__init__(x, y, 0, 0, 8, "cyan")
self.angle = 0 # Ship orientation
self.thrust_power = 200
self.fuel = 100
self.max_fuel = 100
self.shield = False
self.shield_time = 0
self.invulnerable_time = 0
def apply_thrust(self, dt):
"""Apply thrust in the direction the ship is facing"""
if self.fuel > 0:
thrust_x = math.cos(self.angle) * self.thrust_power * dt
thrust_y = math.sin(self.angle) * self.thrust_power * dt
self.vx += thrust_x
self.vy += thrust_y
self.fuel -= 20 * dt
self.fuel = max(0, self.fuel)
return True
return False
def rotate(self, direction, dt):
"""Rotate the ship"""
self.angle += direction * 3 * dt
def update(self, dt):
"""Update spaceship with screen wrapping"""
super().update(dt)
# Screen wrapping - spaceship reappears on opposite side
if self.x < -50:
self.x = 1050
elif self.x > 1050:
self.x = -50
if self.y < -50:
self.y = 690
elif self.y > 690:
self.y = -50
def draw(self, canvas):
# Draw ship as a triangle pointing in movement direction
size = self.radius
tip_x = self.x + size * math.cos(self.angle)
tip_y = self.y + size * math.sin(self.angle)
left_x = self.x + size * 0.6 * math.cos(self.angle + 2.5)
left_y = self.y + size * 0.6 * math.sin(self.angle + 2.5)
right_x = self.x + size * 0.6 * math.cos(self.angle - 2.5)
right_y = self.y + size * 0.6 * math.sin(self.angle - 2.5)
# Draw shield if active
if self.shield:
canvas.create_oval(
self.x - size * 1.5, self.y - size * 1.5,
self.x + size * 1.5, self.y + size * 1.5,
outline="blue", width=2
)
# Draw ship (flashing if invulnerable)
if self.invulnerable_time <= 0 or int(time.time() * 10) % 2:
canvas.create_polygon(
tip_x, tip_y, left_x, left_y, right_x, right_y,
fill=self.color, outline="white"
)
class GravitationalDestroyer:
"""Main game class implementing the complete game loop"""
def __init__(self):
self.root = tk.Tk()
self.root.title("Gravitational Destroyer")
self.root.geometry("1000x700")
self.root.configure(bg='black')
# Game state
self.level = 1
self.score = 0
self.lives = 3
self.game_over = False
# Physics constants
self.G = 300 # Gravitational constant
# Game objects
self.spaceship = None
self.planets = []
self.bullets = []
self.setup_ui()
self.start_level()
self.bind_controls()
def apply_gravity(self, dt):
"""Apply gravitational forces to all objects"""
# Standard mode: only spaceship and bullets affected by planets
all_objects = [self.spaceship] + self.bullets
for obj in all_objects:
if not obj or not obj.alive:
continue
# Calculate gravitational force from each planet
ax = ay = 0
for planet in self.planets:
if not planet.alive:
continue
dx = planet.x - obj.x
dy = planet.y - obj.y
distance_sq = dx*dx + dy*dy
distance = math.sqrt(distance_sq)
if distance > planet.radius + obj.radius:
# Newton's Law of Universal Gravitation
force = self.G * planet.mass / distance_sq
ax += force * dx / distance
ay += force * dy / distance
# Apply acceleration to velocity
obj.vx += ax * dt
obj.vy += ay * dt
# Realistic mode: planets also attract each other (N-body physics)
if self.realistic_mode:
for i, planet1 in enumerate(self.planets):
if not planet1.alive:
continue
ax = ay = 0
for j, planet2 in enumerate(self.planets):
if i == j or not planet2.alive:
continue
dx = planet2.x - planet1.x
dy = planet2.y - planet1.y
distance_sq = dx*dx + dy*dy
distance = math.sqrt(distance_sq)
if distance > planet1.radius + planet2.radius:
# Gravitational force between planets
force = self.G * planet2.mass / distance_sq
ax += force * dx / distance
ay += force * dy / distance
# Apply acceleration to planet velocity
planet1.vx += ax * dt
planet1.vy += ay * dt
def game_loop(self):
"""Main game loop running at ~60 FPS"""
current_time = time.time()
if hasattr(self, 'last_time'):
dt = current_time - self.last_time
else:
dt = 0.016
self.last_time = current_time
# Update game state
self.handle_input(dt)
self.apply_gravity(dt)
self.update_objects(dt)
self.check_collisions()
self.draw_game()
# Schedule next frame
self.root.after(16, self.game_loop)
def setup_ui(self):
"""Create the game UI elements"""
# Create main frame
self.main_frame = Frame(self.root, bg='black')
self.main_frame.pack(fill=tk.BOTH, expand=True)
# Create info panel
self.info_frame = Frame(self.main_frame, bg='black', height=60)
self.info_frame.pack(fill=tk.X, side=tk.TOP)
self.info_frame.pack_propagate(False)
# Game info labels
self.score_label = Label(self.info_frame, text="Score: 0",
fg='white', bg='black', font=('Arial', 14))
self.score_label.pack(side=tk.LEFT, padx=10, pady=10)
self.level_label = Label(self.info_frame, text="Level: 1",
fg='white', bg='black', font=('Arial', 14))
self.level_label.pack(side=tk.LEFT, padx=10, pady=10)
self.lives_label = Label(self.info_frame, text="Lives: 3",
fg='white', bg='black', font=('Arial', 14))
self.lives_label.pack(side=tk.LEFT, padx=10, pady=10)
self.fuel_label = Label(self.info_frame, text="Fuel: 100%",
fg='white', bg='black', font=('Arial', 14))
self.fuel_label.pack(side=tk.LEFT, padx=10, pady=10)
# Create game canvas
self.canvas = Canvas(self.main_frame, bg='black', width=1000, height=640)
self.canvas.pack(fill=tk.BOTH, expand=True)
# Game status
self.paused = False
self.keys_pressed = set()
self.realistic_mode = False # Toggle for N-body physics
def start_level(self):
"""Initialize a new level"""
# Create spaceship at center
self.spaceship = Spaceship(500, 320)
# Clear existing objects
self.planets = []
self.bullets = []
# Generate planets for this level
num_planets = min(3 + self.level * 2, 20)
for i in range(num_planets):
# Random position (not too close to center)
angle = random.uniform(0, 2 * math.pi)
distance = random.uniform(150, 300)
x = 500 + distance * math.cos(angle)
y = 320 + distance * math.sin(angle)
# Random orbital velocity
orbital_speed = random.uniform(20, 60)
vx = -orbital_speed * math.sin(angle)
vy = orbital_speed * math.cos(angle)
# Random mass and color
mass = random.uniform(50, 200)
colors = ['orange', 'yellow', 'red', 'purple', 'blue']
color = random.choice(colors)
# Special planet types
planet_type = "normal"
if random.random() < 0.1: # 10% chance for fuel depot
planet_type = "fuel"
color = "green"
elif random.random() < 0.05: # 5% chance for explosive
planet_type = "explosive"
color = "red"
mass *= 1.5
planet = Planet(x, y, vx, vy, mass, color, planet_type)
self.planets.append(planet)
def bind_controls(self):
"""Set up keyboard controls"""
self.root.focus_set()
self.root.bind('<KeyPress>', self.key_press)
self.root.bind('<KeyRelease>', self.key_release)
def key_press(self, event):
"""Handle key press events"""
self.keys_pressed.add(event.keysym.lower())
if event.keysym.lower() == 'p':
self.paused = not self.paused
elif event.keysym.lower() == 'r':
if self.game_over:
self.restart_game()
else:
self.start_level()
elif event.keysym.lower() == 'n':
self.realistic_mode = not self.realistic_mode
def key_release(self, event):
"""Handle key release events"""
self.keys_pressed.discard(event.keysym.lower())
def handle_input(self, dt):
"""Process continuous input"""
if self.paused or self.game_over or not self.spaceship:
return
# Rotation
if 'left' in self.keys_pressed:
self.spaceship.rotate(-1, dt)
if 'right' in self.keys_pressed:
self.spaceship.rotate(1, dt)
# Thrust
if 'up' in self.keys_pressed:
self.spaceship.apply_thrust(dt)
# Shooting
if 'space' in self.keys_pressed:
self.shoot_bullet()
def shoot_bullet(self):
"""Create a new bullet"""
if not self.spaceship or len(self.bullets) >= 5:
return
# Create bullet at ship position with ship velocity + bullet speed
bullet_speed = 300
bullet_vx = self.spaceship.vx + bullet_speed * math.cos(self.spaceship.angle)
bullet_vy = self.spaceship.vy + bullet_speed * math.sin(self.spaceship.angle)
bullet = GameObject(self.spaceship.x, self.spaceship.y, bullet_vx, bullet_vy, 3, "yellow")
self.bullets.append(bullet)
def update_objects(self, dt):
"""Update all game objects"""
if self.paused:
return
# Update spaceship
if self.spaceship:
self.spaceship.update(dt)
if self.spaceship.invulnerable_time > 0:
self.spaceship.invulnerable_time -= dt
# Update planets
for planet in self.planets[:]:
planet.update(dt)
# Update bullets
for bullet in self.bullets[:]:
bullet.update(dt)
# Remove bullets that go off screen
if (bullet.x < -50 or bullet.x > 1050 or
bullet.y < -50 or bullet.y > 690):
self.bullets.remove(bullet)
def check_collisions(self):
"""Check for collisions between objects"""
if not self.spaceship or self.spaceship.invulnerable_time > 0:
return
# Check spaceship-planet collisions
for planet in self.planets[:]:
if not planet.alive:
continue
dx = self.spaceship.x - planet.x
dy = self.spaceship.y - planet.y
distance = math.sqrt(dx*dx + dy*dy)
if distance < self.spaceship.radius + planet.radius:
if planet.planet_type == "fuel":
# Fuel depot - refuel ship
self.spaceship.fuel = min(100, self.spaceship.fuel + 50)
planet.alive = False
self.planets.remove(planet)
self.score += 50
else:
# Collision with planet - lose life
self.lives -= 1
self.spaceship.invulnerable_time = 2.0
if self.lives <= 0:
self.game_over = True
# Check bullet-planet collisions
for bullet in self.bullets[:]:
for planet in self.planets[:]:
if not planet.alive:
continue
dx = bullet.x - planet.x
dy = bullet.y - planet.y
distance = math.sqrt(dx*dx + dy*dy)
if distance < bullet.radius + planet.radius:
# Hit!
self.score += planet.points
self.bullets.remove(bullet)
if planet.planet_type == "explosive":
# Chain reaction - destroy nearby planets
self.explode_planet(planet)
else:
planet.alive = False
self.planets.remove(planet)
break
# Check if level complete
if len(self.planets) == 0:
self.level += 1
self.start_level()
def explode_planet(self, explosive_planet):
"""Handle explosive planet chain reaction"""
explosion_radius = explosive_planet.radius * 3
planets_to_remove = [explosive_planet]
for planet in self.planets:
if planet == explosive_planet:
continue
dx = planet.x - explosive_planet.x
dy = planet.y - explosive_planet.y
distance = math.sqrt(dx*dx + dy*dy)
if distance < explosion_radius:
planets_to_remove.append(planet)
self.score += planet.points * 2 # Bonus for chain reaction
for planet in planets_to_remove:
if planet in self.planets:
self.planets.remove(planet)
def draw_game(self):
"""Draw all game objects"""
self.canvas.delete("all")
if self.paused:
self.canvas.create_text(500, 320, text="PAUSED",
fill="white", font=('Arial', 48))
return
if self.game_over:
self.canvas.create_text(500, 280, text="GAME OVER",
fill="red", font=('Arial', 48))
self.canvas.create_text(500, 340, text=f"Final Score: {self.score}",
fill="white", font=('Arial', 24))
self.canvas.create_text(500, 380, text="Press R to restart",
fill="white", font=('Arial', 18))
return
# Draw planets
for planet in self.planets:
if planet.alive:
planet.draw(self.canvas)
# Draw spaceship
if self.spaceship:
self.spaceship.draw(self.canvas)
# Draw bullets
for bullet in self.bullets:
bullet.draw(self.canvas)
# Update UI labels
self.score_label.config(text=f"Score: {self.score}")
self.level_label.config(text=f"Level: {self.level}")
self.lives_label.config(text=f"Lives: {self.lives}")
if self.spaceship:
fuel_percent = int(self.spaceship.fuel)
self.fuel_label.config(text=f"Fuel: {fuel_percent}%")
# Show physics mode indicator
mode_text = "N-BODY PHYSICS" if self.realistic_mode else "GAME MODE"
mode_color = "yellow" if self.realistic_mode else "cyan"
self.canvas.create_text(500, 30, text=mode_text,
fill=mode_color, font=('Arial', 12, 'bold'))
def restart_game(self):
"""Restart the entire game"""
self.level = 1
self.score = 0
self.lives = 3
self.game_over = False
self.start_level()
def run(self):
"""Start the game"""
self.game_loop()
self.root.mainloop()
# Run the game
if __name__ == "__main__":
game = GravitationalDestroyer()
game.run() 📋 Copy this code into Spyder:
- 1. Open Spyder from Anaconda Navigator
- 2. Copy the code above into the editor
- 3. Save as
gravitational_destroyer.py - 4. Press F5 to run the game
- 5. Use arrow keys to move, SPACE to shoot!
🕹️ Gameplay Mechanics
Controls
- Arrow Keys: Rotate ship and apply thrust
- SPACE: Fire bullets
- P: Pause/unpause game
- R: Restart level (or game if game over)
- N: Toggle N-Body Physics Mode (planets attract each other)
🎯 Strategic Elements
- Fuel Management: Thrust consumes fuel - use gravity to conserve it
- Gravity Assists: Use planet gravity to curve bullets around obstacles
- Orbital Mechanics: Planets move in realistic orbits, creating dynamic challenges
- Chain Reactions: Explosive planets can trigger cascading destructions
⚗️ Physics Modes
🎮 Game Mode (Default)
- Planets affect spaceship and bullets only
- Predictable, stable planetary orbits
- Balanced gameplay experience
- Easier to complete levels
🌌 N-Body Physics Mode (Press N)
- Planets gravitationally attract each other
- True N-body orbital dynamics
- Chaotic, evolving planetary systems
- Scientifically accurate but challenging
🧠 Educational Value
This game teaches advanced programming and physics concepts:
Programming Concepts
- Object-oriented design patterns
- Game loop architecture
- Event-driven programming
- Real-time graphics and animation
- State management systems
Physics Concepts
- Newton's Law of Universal Gravitation
- Orbital mechanics and trajectories
- Conservation of momentum and energy
- Vector mathematics in 2D space
- Collision detection algorithms
🚀 Possible Extensions
Challenge yourself by adding these features:
- Power-ups: Shield generators, weapon upgrades, speed boosts
- Multiple Ship Types: Different ships with unique characteristics
- Boss Battles: Massive planets with special abilities
- Multiplayer Mode: Two-player cooperative or competitive gameplay
- Level Editor: Create and share custom gravitational challenges
- Particle Effects: Explosions, thruster flames, and debris
🌐 Web Version
Experience Gravitational Destroyer directly in your browser! The web version includes all the same physics and gameplay mechanics, optimized for smooth browser performance.
🎉 Conclusion
Congratulations! You've created a sophisticated physics-based game that combines entertainment with education. Gravitational Destroyer demonstrates how real physics can create engaging and strategic gameplay mechanics. The game serves as both a fun experience and a practical demonstration of gravitational physics in action.
This project showcases advanced Python programming techniques, game development principles, and real-world physics simulation - skills that are valuable in both game development and scientific computing fields.