Untitled
Anonymous
python
02/14/2026 8:46 PM
34.1 KB
16
Indexable
"""
NEON RUNNER - A Procedurally Generated Platformer
100 unique levels with smooth physics and particle effects
"""
import pygame
import random
import math
import sys
from dataclasses import dataclass
from typing import List, Tuple
from enum import Enum
# Initialize Pygame
pygame.init()
# Constants
WINDOW_WIDTH = 1280
WINDOW_HEIGHT = 720
FPS = 60
# Colors - Neon/Cyberpunk theme
BG_COLOR = (10, 10, 25)
PLAYER_COLOR = (0, 255, 200)
PLATFORM_COLOR = (100, 50, 200)
SPIKE_COLOR = (255, 50, 100)
GOAL_COLOR = (255, 200, 0)
PARTICLE_COLORS = [
(0, 255, 200),
(100, 200, 255),
(255, 100, 200),
(255, 200, 100)
]
class ParticleType(Enum):
JUMP = 1
LAND = 2
WALL_SLIDE = 3
DEATH = 4
GOAL = 5
@dataclass
class Particle:
x: float
y: float
vx: float
vy: float
lifetime: int
max_lifetime: int
color: Tuple[int, int, int]
size: float
particle_type: ParticleType
class Platform:
def __init__(self, x: float, y: float, width: float, height: float, moving: bool = False):
self.rect = pygame.Rect(x, y, width, height)
self.moving = moving
self.start_x = x
self.move_range = 200
self.move_speed = 2
self.direction = 1
def update(self):
if self.moving:
self.rect.x += self.move_speed * self.direction
if abs(self.rect.x - self.start_x) > self.move_range:
self.direction *= -1
def draw(self, screen: pygame.Surface, camera_x: float):
# Draw platform with glow effect
glow_rect = self.rect.copy()
glow_rect.x -= camera_x
# Outer glow
for i in range(3):
glow = pygame.Surface((glow_rect.width + i*4, glow_rect.height + i*4), pygame.SRCALPHA)
alpha = 30 - i*10
color = (*PLATFORM_COLOR, alpha)
pygame.draw.rect(glow, color, glow.get_rect(), border_radius=8)
screen.blit(glow, (glow_rect.x - i*2, glow_rect.y - i*2))
# Main platform
draw_rect = self.rect.copy()
draw_rect.x -= camera_x
pygame.draw.rect(screen, PLATFORM_COLOR, draw_rect, border_radius=8)
pygame.draw.rect(screen, (150, 100, 255), draw_rect, 2, border_radius=8)
class Spike:
def __init__(self, x: float, y: float, width: float = 30):
self.rect = pygame.Rect(x, y, width, 20)
def draw(self, screen: pygame.Surface, camera_x: float):
draw_rect = self.rect.copy()
draw_rect.x -= camera_x
# Draw spikes as triangles
num_spikes = self.rect.width // 15
for i in range(num_spikes):
x_offset = i * 15
points = [
(draw_rect.x + x_offset, draw_rect.bottom),
(draw_rect.x + x_offset + 7.5, draw_rect.top),
(draw_rect.x + x_offset + 15, draw_rect.bottom)
]
pygame.draw.polygon(screen, SPIKE_COLOR, points)
pygame.draw.polygon(screen, (255, 100, 150), points, 2)
class Goal:
def __init__(self, x: float, y: float):
self.rect = pygame.Rect(x, y, 40, 60)
self.animation_offset = 0
def update(self):
self.animation_offset = (self.animation_offset + 0.1) % (2 * math.pi)
def draw(self, screen: pygame.Surface, camera_x: float):
draw_rect = self.rect.copy()
draw_rect.x -= camera_x
# Animated glow
glow_size = int(20 + math.sin(self.animation_offset) * 5)
for i in range(glow_size, 0, -2):
alpha = int(100 * (1 - i / glow_size))
glow = pygame.Surface((draw_rect.width + i*2, draw_rect.height + i*2), pygame.SRCALPHA)
pygame.draw.rect(glow, (*GOAL_COLOR, alpha), glow.get_rect(), border_radius=10)
screen.blit(glow, (draw_rect.x - i, draw_rect.y - i))
# Main goal
pygame.draw.rect(screen, GOAL_COLOR, draw_rect, border_radius=10)
pygame.draw.rect(screen, (255, 255, 150), draw_rect, 3, border_radius=10)
class Player:
def __init__(self, x: float, y: float):
self.width = 30
self.height = 40
self.rect = pygame.Rect(x, y, self.width, self.height)
self.vel_x = 0
self.vel_y = 0
self.on_ground = False
self.on_wall = False
self.wall_direction = 0
self.facing_right = True
# Physics constants
self.move_speed = 6
self.jump_power = 15
self.wall_jump_x = 10
self.wall_jump_y = 14
self.gravity = 0.8
self.max_fall_speed = 20
self.air_resistance = 0.95
self.wall_slide_speed = 2
# Animation
self.squash = 1.0
self.stretch = 1.0
self.rotation = 0
def handle_input(self, keys):
# Horizontal movement
if keys[pygame.K_LEFT] or keys[pygame.K_a]:
self.vel_x = -self.move_speed
self.facing_right = False
elif keys[pygame.K_RIGHT] or keys[pygame.K_d]:
self.vel_x = self.move_speed
self.facing_right = True
else:
if self.on_ground:
self.vel_x *= 0.8
else:
self.vel_x *= self.air_resistance
def jump(self, particles: List[Particle]):
if self.on_ground:
self.vel_y = -self.jump_power
self.squash = 0.6
self.stretch = 1.4
self._spawn_particles(particles, ParticleType.JUMP, 8)
elif self.on_wall:
# Wall jump
self.vel_y = -self.wall_jump_y
self.vel_x = self.wall_jump_x * -self.wall_direction
self.squash = 0.7
self.stretch = 1.3
self._spawn_particles(particles, ParticleType.JUMP, 6)
def update(self, platforms: List[Platform], particles: List[Particle]):
# Apply gravity
if not (self.on_wall and self.vel_y > 0):
self.vel_y += self.gravity
if self.vel_y > self.max_fall_speed:
self.vel_y = self.max_fall_speed
else:
# Wall slide
self.vel_y = min(self.vel_y, self.wall_slide_speed)
if random.random() < 0.3:
self._spawn_particles(particles, ParticleType.WALL_SLIDE, 1)
# Update position
self.rect.x += self.vel_x
self._check_horizontal_collisions(platforms)
self.rect.y += self.vel_y
self._check_vertical_collisions(platforms, particles)
# Update animation
self.squash += (1.0 - self.squash) * 0.2
self.stretch += (1.0 - self.stretch) * 0.2
# Rotation based on velocity
target_rotation = -self.vel_x * 2
self.rotation += (target_rotation - self.rotation) * 0.1
def _check_horizontal_collisions(self, platforms: List[Platform]):
self.on_wall = False
self.wall_direction = 0
for platform in platforms:
if self.rect.colliderect(platform.rect):
if self.vel_x > 0: # Moving right
self.rect.right = platform.rect.left
self.on_wall = True
self.wall_direction = 1
elif self.vel_x < 0: # Moving left
self.rect.left = platform.rect.right
self.on_wall = True
self.wall_direction = -1
self.vel_x = 0
def _check_vertical_collisions(self, platforms: List[Platform], particles: List[Particle]):
was_on_ground = self.on_ground
self.on_ground = False
for platform in platforms:
if self.rect.colliderect(platform.rect):
if self.vel_y > 0: # Falling
self.rect.bottom = platform.rect.top
self.vel_y = 0
self.on_ground = True
if not was_on_ground:
self.squash = 1.3
self.stretch = 0.7
self._spawn_particles(particles, ParticleType.LAND, 10)
elif self.vel_y < 0: # Jumping up
self.rect.top = platform.rect.bottom
self.vel_y = 0
def _spawn_particles(self, particles: List[Particle], particle_type: ParticleType, count: int):
for _ in range(count):
if particle_type == ParticleType.JUMP:
vx = random.uniform(-3, 3)
vy = random.uniform(1, 4)
lifetime = random.randint(15, 25)
elif particle_type == ParticleType.LAND:
vx = random.uniform(-5, 5)
vy = random.uniform(-2, 0)
lifetime = random.randint(10, 20)
elif particle_type == ParticleType.WALL_SLIDE:
vx = random.uniform(-2, 2) * self.wall_direction
vy = random.uniform(-1, 1)
lifetime = random.randint(8, 15)
elif particle_type == ParticleType.DEATH:
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(2, 8)
vx = math.cos(angle) * speed
vy = math.sin(angle) * speed
lifetime = random.randint(20, 40)
else:
vx = vy = 0
lifetime = 20
particles.append(Particle(
x=self.rect.centerx,
y=self.rect.bottom if particle_type != ParticleType.WALL_SLIDE else self.rect.centery,
vx=vx,
vy=vy,
lifetime=lifetime,
max_lifetime=lifetime,
color=random.choice(PARTICLE_COLORS),
size=random.uniform(2, 5),
particle_type=particle_type
))
def draw(self, screen: pygame.Surface, camera_x: float):
draw_x = self.rect.centerx - camera_x
draw_y = self.rect.centery
# Apply squash and stretch
width = self.width * self.squash
height = self.height * self.stretch
# Create surface for rotation
surf_size = int(max(width, height) * 1.5)
player_surf = pygame.Surface((surf_size, surf_size), pygame.SRCALPHA)
# Draw glow
for i in range(3):
glow_rect = pygame.Rect(
surf_size//2 - width//2 - i*2,
surf_size//2 - height//2 - i*2,
width + i*4,
height + i*4
)
alpha = 40 - i*10
pygame.draw.rect(player_surf, (*PLAYER_COLOR, alpha), glow_rect, border_radius=10)
# Draw player body
player_rect = pygame.Rect(
surf_size//2 - width//2,
surf_size//2 - height//2,
width,
height
)
pygame.draw.rect(player_surf, PLAYER_COLOR, player_rect, border_radius=10)
pygame.draw.rect(player_surf, (100, 255, 230), player_rect, 2, border_radius=10)
# Draw eyes
eye_y = surf_size//2 - height//4
eye_offset = width // 6
if self.facing_right:
eye1_x = surf_size//2 + eye_offset
eye2_x = surf_size//2 + eye_offset + 8
else:
eye1_x = surf_size//2 - eye_offset - 8
eye2_x = surf_size//2 - eye_offset
pygame.draw.circle(player_surf, (255, 255, 255), (int(eye1_x), int(eye_y)), 4)
pygame.draw.circle(player_surf, (255, 255, 255), (int(eye2_x), int(eye_y)), 4)
# Rotate and blit
rotated = pygame.transform.rotate(player_surf, self.rotation)
rotated_rect = rotated.get_rect(center=(draw_x, draw_y))
screen.blit(rotated, rotated_rect)
class Level:
def __init__(self, level_number: int):
self.level_number = level_number
self.platforms: List[Platform] = []
self.spikes: List[Spike] = []
self.goal: Goal = None
self.spawn_x = 100
self.spawn_y = 500
self.width = 3000 + level_number * 100 # Levels get longer
self._generate()
def _generate(self):
random.seed(self.level_number) # Consistent levels
# Starting platform
self.platforms.append(Platform(0, 550, 200, 40))
# Generate path
current_x = 250
current_y = 500
difficulty = min(self.level_number / 100, 1.0) # 0 to 1
while current_x < self.width - 300:
# Decide next platform type and position
gap_x = random.randint(80 + int(difficulty * 50), 200 + int(difficulty * 100))
gap_y = random.randint(-150, 100)
next_x = current_x + gap_x
next_y = max(200, min(600, current_y + gap_y))
# Platform dimensions
platform_width = random.randint(80, 150 - int(difficulty * 30))
platform_height = 20
# Add moving platforms occasionally
moving = random.random() < 0.3 * difficulty
platform = Platform(next_x, next_y, platform_width, platform_height, moving)
self.platforms.append(platform)
# Add spikes on some platforms
if random.random() < 0.4 * difficulty and not moving:
spike_x = next_x + random.randint(20, int(platform_width - 50))
self.spikes.append(Spike(spike_x, next_y - 20, 30))
# Sometimes add floating spikes
if random.random() < 0.2 * difficulty:
spike_x = current_x + gap_x // 2
spike_y = random.randint(int(min(current_y, next_y)) - 100, int(max(current_y, next_y)))
self.spikes.append(Spike(spike_x, spike_y, 30))
current_x = next_x
current_y = next_y
# Goal at the end
self.goal = Goal(self.width - 200, current_y - 80)
self.platforms.append(Platform(self.width - 250, current_y, 200, 40))
def update(self):
for platform in self.platforms:
platform.update()
self.goal.update()
def draw(self, screen: pygame.Surface, camera_x: float):
for platform in self.platforms:
platform.draw(screen, camera_x)
for spike in self.spikes:
spike.draw(screen, camera_x)
self.goal.draw(screen, camera_x)
class Game:
def __init__(self):
self.screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("NEON RUNNER")
self.clock = pygame.time.Clock()
self.running = True
self.current_level = 1
self.total_levels = 100
self.level: Level = None
self.player: Player = None
self.particles: List[Particle] = []
self.camera_x = 0
self.state = "menu" # menu, playing, level_complete, game_over
self.transition_timer = 0
# Font
self.font_large = pygame.font.Font(None, 72)
self.font_medium = pygame.font.Font(None, 48)
self.font_small = pygame.font.Font(None, 32)
self._start_level()
def _start_level(self):
self.level = Level(self.current_level)
self.player = Player(self.level.spawn_x, self.level.spawn_y)
self.particles.clear()
self.camera_x = 0
self.state = "playing"
def _next_level(self):
self.current_level += 1
if self.current_level > self.total_levels:
self.state = "game_won"
else:
self._start_level()
def _reset_level(self):
self.player = Player(self.level.spawn_x, self.level.spawn_y)
self.particles.clear()
self.camera_x = 0
self.state = "playing"
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.running = False
elif event.key == pygame.K_SPACE or event.key == pygame.K_w or event.key == pygame.K_UP:
if self.state == "menu":
self.state = "playing"
elif self.state == "playing":
self.player.jump(self.particles)
elif self.state == "level_complete":
self._next_level()
elif self.state == "game_over":
self._reset_level()
elif self.state == "game_won":
self.current_level = 1
self._start_level()
elif event.key == pygame.K_r:
if self.state == "playing" or self.state == "game_over":
self._reset_level()
def update(self):
if self.state == "menu":
# Spawn some ambient particles
if random.random() < 0.1:
self.particles.append(Particle(
x=random.randint(0, WINDOW_WIDTH),
y=random.randint(0, WINDOW_HEIGHT),
vx=random.uniform(-1, 1),
vy=random.uniform(-1, 1),
lifetime=random.randint(60, 120),
max_lifetime=120,
color=random.choice(PARTICLE_COLORS),
size=random.uniform(2, 4),
particle_type=ParticleType.GOAL
))
elif self.state == "playing":
keys = pygame.key.get_pressed()
self.player.handle_input(keys)
self.player.update(self.level.platforms, self.particles)
self.level.update()
# Camera follow player
target_camera_x = self.player.rect.centerx - WINDOW_WIDTH // 3
target_camera_x = max(0, min(target_camera_x, self.level.width - WINDOW_WIDTH))
self.camera_x += (target_camera_x - self.camera_x) * 0.1
# Check death
if self.player.rect.top > WINDOW_HEIGHT + 50:
self.state = "game_over"
self.player._spawn_particles(self.particles, ParticleType.DEATH, 30)
# Check spike collision
for spike in self.level.spikes:
if self.player.rect.colliderect(spike.rect):
self.state = "game_over"
self.player._spawn_particles(self.particles, ParticleType.DEATH, 30)
# Check goal
if self.player.rect.colliderect(self.level.goal.rect):
self.state = "level_complete"
self.transition_timer = 60
elif self.state == "level_complete":
self.transition_timer -= 1
if self.transition_timer <= 0:
self._next_level()
# Spawn celebration particles
if random.random() < 0.3:
self.particles.append(Particle(
x=self.level.goal.rect.centerx,
y=self.level.goal.rect.centery,
vx=random.uniform(-5, 5),
vy=random.uniform(-8, -2),
lifetime=random.randint(30, 60),
max_lifetime=60,
color=random.choice(PARTICLE_COLORS),
size=random.uniform(3, 6),
particle_type=ParticleType.GOAL
))
# Update particles
for particle in self.particles[:]:
particle.x += particle.vx
particle.y += particle.vy
particle.vy += 0.2 # Gravity
particle.lifetime -= 1
if particle.lifetime <= 0:
self.particles.remove(particle)
def draw(self):
self.screen.fill(BG_COLOR)
if self.state == "menu":
self._draw_menu()
else:
# Draw level
self.level.draw(self.screen, self.camera_x)
# Draw player
if self.state != "game_over":
self.player.draw(self.screen, self.camera_x)
# Draw particles
for particle in self.particles:
alpha = int(255 * (particle.lifetime / particle.max_lifetime))
color = (*particle.color, alpha)
size = int(particle.size * (particle.lifetime / particle.max_lifetime))
if size > 0:
surf = pygame.Surface((size*2, size*2), pygame.SRCALPHA)
pygame.draw.circle(surf, color, (size, size), size)
draw_x = particle.x - self.camera_x
self.screen.blit(surf, (draw_x - size, particle.y - size))
# Draw UI
self._draw_ui()
if self.state == "level_complete":
self._draw_level_complete()
elif self.state == "game_over":
self._draw_game_over()
elif self.state == "game_won":
self._draw_game_won()
pygame.display.flip()
def _draw_menu(self):
# Draw particles
for particle in self.particles:
alpha = int(255 * (particle.lifetime / particle.max_lifetime))
color = (*particle.color, alpha)
size = int(particle.size)
if size > 0:
pygame.draw.circle(self.screen, color, (int(particle.x), int(particle.y)), size)
title = self.font_large.render("NEON RUNNER", True, PLAYER_COLOR)
title_rect = title.get_rect(center=(WINDOW_WIDTH//2, 200))
self.screen.blit(title, title_rect)
subtitle = self.font_medium.render("100 Procedurally Generated Levels", True, (150, 150, 255))
subtitle_rect = subtitle.get_rect(center=(WINDOW_WIDTH//2, 280))
self.screen.blit(subtitle, subtitle_rect)
instructions = [
"ARROW KEYS or WASD - Move",
"SPACE or W or UP - Jump",
"Hold against wall to WALL SLIDE",
"Jump while on wall for WALL JUMP",
"R - Restart Level",
"ESC - Quit",
"",
"Press SPACE to Start"
]
y = 380
for line in instructions:
text = self.font_small.render(line, True, (200, 200, 255))
text_rect = text.get_rect(center=(WINDOW_WIDTH//2, y))
self.screen.blit(text, text_rect)
y += 40
def _draw_ui(self):
level_text = self.font_small.render(f"Level {self.current_level}/{self.total_levels}", True, PLAYER_COLOR)
self.screen.blit(level_text, (20, 20))
hint = self.font_small.render("R - Restart", True, (150, 150, 200))
self.screen.blit(hint, (20, 60))
def _draw_level_complete(self):
overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 128))
self.screen.blit(overlay, (0, 0))
text = self.font_large.render("LEVEL COMPLETE!", True, GOAL_COLOR)
text_rect = text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 - 50))
self.screen.blit(text, text_rect)
next_text = self.font_medium.render("Press SPACE for next level", True, (200, 200, 255))
next_rect = next_text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 + 50))
self.screen.blit(next_text, next_rect)
def _draw_game_over(self):
overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 128))
self.screen.blit(overlay, (0, 0))
text = self.font_large.render("GAME OVER", True, SPIKE_COLOR)
text_rect = text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 - 50))
self.screen.blit(text, text_rect)
retry_text = self.font_medium.render("Press SPACE to retry", True, (200, 200, 255))
retry_rect = retry_text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 + 50))
self.screen.blit(retry_text, retry_rect)
def _draw_game_won(self):
overlay = pygame.Surface((WINDOW_WIDTH, WINDOW_HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 200))
self.screen.blit(overlay, (0, 0))
# Spawn celebration particles
if random.random() < 0.5:
self.particles.append(Particle(
x=random.randint(0, WINDOW_WIDTH),
y=-10,
vx=random.uniform(-2, 2),
vy=random.uniform(2, 5),
lifetime=random.randint(60, 120),
max_lifetime=120,
color=random.choice(PARTICLE_COLORS),
size=random.uniform(4, 8),
particle_type=ParticleType.GOAL
))
text = self.font_large.render("CONGRATULATIONS!", True, GOAL_COLOR)
text_rect = text.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 - 100))
self.screen.blit(text, text_rect)
complete = self.font_medium.render("You completed all 100 levels!", True, PLAYER_COLOR)
complete_rect = complete.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2))
self.screen.blit(complete, complete_rect)
again = self.font_small.render("Press SPACE to play again", True, (200, 200, 255))
again_rect = again.get_rect(center=(WINDOW_WIDTH//2, WINDOW_HEIGHT//2 + 100))
self.screen.blit(again, again_rect)
def run(self):
while self.running:
self.handle_events()
self.update()
self.draw()
self.clock.tick(FPS)
pygame.quit()
sys.exit()
if __name__ == "__main__":
game = Game()
game.run()
Editor is loading...
Leave a Comment