Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import pygame
- import sys
- import random
- import math
- # Initialize PyGame
- pygame.init()
- screen_width, screen_height = 800, 600
- screen = pygame.display.set_mode((screen_width, screen_height))
- pygame.display.set_caption("Top-Down Shooter")
- clock = pygame.time.Clock()
- # Fonts for displaying score, level, and messages
- font = pygame.font.SysFont(None, 36)
- big_font = pygame.font.SysFont(None, 72)
- # --------------------------------------------------
- # Initialize Joystick (Controller) if available
- # --------------------------------------------------
- joystick = None
- if pygame.joystick.get_count() > 0:
- joystick = pygame.joystick.Joystick(0)
- joystick.init()
- print("Joystick connected:", joystick.get_name())
- # Variables for twin-stick firing:
- last_shot_time = 0
- fire_interval = 150 # milliseconds between shots while the right stick is held
- # --------------------------------------------------
- # Helper Drawing Functions for Custom Shapes
- # --------------------------------------------------
- def draw_star(surface, color, center, outer_radius, inner_radius, num_points=5):
- """Draws a star shape with alternating outer and inner points."""
- points = []
- angle = math.radians(-90) # start so the top point is upward
- angle_step = math.pi / num_points
- for i in range(num_points * 2):
- r = outer_radius if i % 2 == 0 else inner_radius
- x = center[0] + r * math.cos(angle)
- y = center[1] + r * math.sin(angle)
- points.append((x, y))
- angle += angle_step
- pygame.draw.polygon(surface, color, points)
- def draw_hexagon(surface, color, center, radius):
- """Draws a regular hexagon with a flat top."""
- points = []
- for i in range(6):
- angle = math.radians(60 * i - 30)
- x = center[0] + radius * math.cos(angle)
- y = center[1] + radius * math.sin(angle)
- points.append((x, y))
- pygame.draw.polygon(surface, color, points)
- # --------------------------------------------------
- # Player Class
- # --------------------------------------------------
- class Player:
- def __init__(self, pos):
- self.pos = pygame.math.Vector2(pos)
- self.direction = pygame.math.Vector2(0, -1) # Initially facing up.
- self.speed = 5
- def update(self, keys):
- # Fallback keyboard movement (arrow keys).
- dx, dy = 0, 0
- if keys[pygame.K_LEFT]:
- dx -= 1
- if keys[pygame.K_RIGHT]:
- dx += 1
- if keys[pygame.K_UP]:
- dy -= 1
- if keys[pygame.K_DOWN]:
- dy += 1
- move = pygame.math.Vector2(dx, dy)
- if move.length_squared() > 0:
- move = move.normalize() * self.speed
- self.direction = move.normalize() # update facing direction
- self.pos += move
- def draw(self, screen, camera_offset):
- # Always drawn at the center of the screen.
- center = pygame.math.Vector2(screen_width // 2, screen_height // 2)
- points = [pygame.math.Vector2(0, -15),
- pygame.math.Vector2(-10, 10),
- pygame.math.Vector2(10, 10)]
- angle = math.degrees(math.atan2(self.direction.y, self.direction.x)) + 90
- rotated_points = [p.rotate(angle) for p in points]
- transformed_points = [(center.x + p.x, center.y + p.y) for p in rotated_points]
- pygame.draw.polygon(screen, (0, 255, 0), transformed_points)
- # --------------------------------------------------
- # Enemy Classes (Types 1, 2, and 3)
- # --------------------------------------------------
- class EnemyType1:
- def __init__(self, pos):
- self.pos = pygame.math.Vector2(pos)
- self.speed = 1.2 # Base speed; will be scaled.
- self.size = 10
- self.color = (255, 0, 0)
- def update(self, player_pos, player_direction=None):
- direction = player_pos - self.pos
- if direction.length() != 0:
- direction = direction.normalize()
- self.pos += direction * self.speed
- def draw(self, screen, camera_offset):
- screen_pos = self.pos - camera_offset
- rect = pygame.Rect(int(screen_pos.x - self.size),
- int(screen_pos.y - self.size),
- 2 * self.size, 2 * self.size)
- pygame.draw.rect(screen, self.color, rect)
- class EnemyType2:
- def __init__(self, pos):
- self.pos = pygame.math.Vector2(pos)
- self.speed = 1.2 # Base speed; will be scaled.
- self.size = 10
- self.color = (255, 165, 0)
- def update(self, player_pos, player_direction):
- to_enemy = self.pos - player_pos
- if to_enemy.length() != 0:
- to_enemy_norm = to_enemy.normalize()
- else:
- to_enemy_norm = pygame.math.Vector2(0, 0)
- threshold = 0.7
- dot_val = player_direction.dot(to_enemy_norm)
- if dot_val > threshold:
- move_dir = self.pos - player_pos # retreat.
- else:
- move_dir = player_pos - self.pos # approach.
- if move_dir.length() != 0:
- move_dir = move_dir.normalize()
- self.pos += move_dir * self.speed
- def draw(self, screen, camera_offset):
- screen_pos = self.pos - camera_offset
- draw_hexagon(screen, self.color, (int(screen_pos.x), int(screen_pos.y)), self.size)
- class EnemyType3:
- def __init__(self, pos):
- self.pos = pygame.math.Vector2(pos)
- self.size = 10
- self.color = (128, 0, 128)
- self.state = "waiting"
- self.state_start_time = pygame.time.get_ticks()
- self.waiting_duration = 2000 # milliseconds.
- self.dash_duration = 1000
- self.dash_speed = 5 # Base dash speed; will be scaled.
- self.dash_direction = pygame.math.Vector2(0, 0)
- def update(self, player_pos, player_direction=None):
- current_time = pygame.time.get_ticks()
- if self.state == "waiting":
- if current_time - self.state_start_time >= self.waiting_duration:
- direction = player_pos - self.pos
- self.dash_direction = direction.normalize() if direction.length() != 0 else pygame.math.Vector2(0, 0)
- self.state = "dashing"
- self.state_start_time = current_time
- elif self.state == "dashing":
- self.pos += self.dash_direction * self.dash_speed
- if current_time - self.state_start_time >= self.dash_duration:
- self.state = "waiting"
- self.state_start_time = current_time
- def draw(self, screen, camera_offset):
- screen_pos = self.pos - camera_offset
- pygame.draw.circle(screen, self.color,
- (int(screen_pos.x), int(screen_pos.y)), self.size)
- # --------------------------------------------------
- # Projectile and PowerUp Classes (for Player)
- # --------------------------------------------------
- class Projectile:
- def __init__(self, pos, direction):
- self.pos = pygame.math.Vector2(pos)
- self.direction = pygame.math.Vector2(direction).normalize()
- self.speed = 10
- self.size = 5
- def update(self):
- self.pos += self.direction * self.speed
- def draw(self, screen, camera_offset):
- screen_pos = self.pos - camera_offset
- pygame.draw.circle(screen, (255, 255, 0),
- (int(screen_pos.x), int(screen_pos.y)), self.size)
- class PowerUp:
- def __init__(self, pos, p_type):
- self.pos = pygame.math.Vector2(pos)
- self.type = p_type # "shield" or "triple"
- self.radius = 8
- def draw(self, screen, camera_offset):
- screen_pos = self.pos - camera_offset
- center = (int(screen_pos.x), int(screen_pos.y))
- if self.type == "shield":
- color = (0, 255, 0)
- elif self.type == "triple":
- color = (0, 0, 255)
- draw_star(screen, color, center, self.radius, self.radius * 0.5)
- # --------------------------------------------------
- # Boss and Boss Projectile Classes (for Boss Levels)
- # --------------------------------------------------
- class Boss:
- def __init__(self, pos):
- self.pos = pygame.math.Vector2(pos)
- self.hp = 20
- self.last_projectile_time = pygame.time.get_ticks()
- self.projectile_interval = 1000 # ms
- self.width = 200
- self.top_width = 150
- self.height = 100
- self.move_speed = 2
- self.move_direction = pygame.math.Vector2(random.uniform(-1, 1), random.uniform(-1, 1))
- if self.move_direction.length() != 0:
- self.move_direction = self.move_direction.normalize()
- self.last_move_change_time = pygame.time.get_ticks()
- self.move_change_interval = 1000
- def update(self, player_pos):
- current_time = pygame.time.get_ticks()
- if current_time - self.last_move_change_time >= self.move_change_interval:
- angle = random.uniform(0, 2 * math.pi)
- self.move_direction = pygame.math.Vector2(math.cos(angle), math.sin(angle))
- self.last_move_change_time = current_time
- self.pos += self.move_direction * self.move_speed
- if current_time - self.last_projectile_time >= self.projectile_interval:
- proj_type = "standard" if random.random() < 0.7 else "homing"
- direction = (player_pos - self.pos).normalize() if (player_pos - self.pos).length() != 0 else pygame.math.Vector2(0, 0)
- bp = BossProjectile(self.pos, direction, proj_type)
- boss_projectiles.append(bp)
- self.last_projectile_time = current_time
- def draw(self, screen, camera_offset):
- screen_pos = self.pos - camera_offset
- half_width = self.width / 2
- half_top = self.top_width / 2
- half_height = self.height / 2
- points = [
- (screen_pos.x - half_width, screen_pos.y + half_height),
- (screen_pos.x + half_width, screen_pos.y + half_height),
- (screen_pos.x + half_top, screen_pos.y - half_height),
- (screen_pos.x - half_top, screen_pos.y - half_height)
- ]
- pygame.draw.polygon(screen, (255, 105, 180), points)
- hp_text = font.render("HP: " + str(self.hp), True, (0, 0, 0))
- text_rect = hp_text.get_rect(center=(screen_pos.x, screen_pos.y))
- screen.blit(hp_text, text_rect)
- class BossProjectile:
- def __init__(self, pos, direction, proj_type):
- self.pos = pygame.math.Vector2(pos)
- self.direction = pygame.math.Vector2(direction).normalize()
- self.proj_type = proj_type # "standard" or "homing"
- self.spawn_time = pygame.time.get_ticks()
- self.to_remove = False
- if self.proj_type == "standard":
- self.speed = 7
- else:
- self.speed = 5 * 0.7 # 3.5 (slowed by 30%)
- self.size = 8
- def update(self, player_pos):
- if self.proj_type == "standard":
- self.pos += self.direction * self.speed
- else:
- target_dir = (player_pos - self.pos)
- if target_dir.length() != 0:
- target_dir = target_dir.normalize()
- else:
- target_dir = pygame.math.Vector2(0, 0)
- current_angle = math.atan2(self.direction.y, self.direction.x)
- target_angle = math.atan2(target_dir.y, target_dir.x)
- angle_diff = (target_angle - current_angle + math.pi) % (2 * math.pi) - math.pi
- max_turn = math.radians(2)
- if abs(angle_diff) > max_turn:
- angle_diff = max_turn if angle_diff > 0 else -max_turn
- new_angle = current_angle + angle_diff
- self.direction = pygame.math.Vector2(math.cos(new_angle), math.sin(new_angle))
- self.pos += self.direction * self.speed
- current_time = pygame.time.get_ticks()
- if current_time - self.spawn_time >= 5000:
- self.to_remove = True
- def draw(self, screen, camera_offset):
- screen_pos = self.pos - camera_offset
- if self.proj_type == "standard":
- pygame.draw.circle(screen, (255, 105, 180),
- (int(screen_pos.x), int(screen_pos.y)), self.size)
- else:
- tip = pygame.math.Vector2(0, -self.size)
- left = pygame.math.Vector2(-self.size, self.size)
- right = pygame.math.Vector2(self.size, self.size)
- angle = math.degrees(math.atan2(self.direction.y, self.direction.x)) + 90
- tip = tip.rotate(angle)
- left = left.rotate(angle)
- right = right.rotate(angle)
- center = pygame.math.Vector2(screen_pos.x, screen_pos.y)
- points = [(center.x + tip.x, center.y + tip.y),
- (center.x + left.x, center.y + left.y),
- (center.x + right.x, center.y + right.y)]
- pygame.draw.polygon(screen, (255, 105, 180), points)
- # --------------------------------------------------
- # Global Difficulty Variables and Spawning Function
- # --------------------------------------------------
- enemy_speed_multiplier = 1.0
- def spawn_enemies(num, player_pos):
- enemy_list = []
- for _ in range(num):
- angle = random.uniform(0, 2 * math.pi)
- distance = random.uniform(300, 500)
- pos = pygame.math.Vector2(
- player_pos.x + math.cos(angle) * distance,
- player_pos.y + math.sin(angle) * distance
- )
- enemy_class = random.choice([EnemyType1, EnemyType2, EnemyType3])
- enemy = enemy_class(pos)
- if hasattr(enemy, 'speed'):
- enemy.speed *= enemy_speed_multiplier
- if hasattr(enemy, 'dash_speed'):
- enemy.dash_speed *= enemy_speed_multiplier
- enemy_list.append(enemy)
- return enemy_list
- # --------------------------------------------------
- # Game Initialization and Global Variables
- # --------------------------------------------------
- player = Player((0, 0))
- level_number = 1
- level_enemy_kill_target = 10 # For normal levels.
- enemies = []
- projectiles = []
- powerups = []
- score = 0
- destroyed_count = 0
- player_health = 3 # Three health hearts.
- player_shield_active = False
- player_triple_shot_active = False
- player_triple_shot_end_time = 0
- boss = None
- boss_projectiles = []
- level_won = False
- win_time = 0
- level_lost = False
- lose_time = 0
- last_enemy_spawn_time = pygame.time.get_ticks()
- enemy_spawn_interval = 1500 # milliseconds
- last_powerup_spawn_time = pygame.time.get_ticks()
- powerup_spawn_interval = 10000 # milliseconds
- # --------------------------------------------------
- # Main Game Loop
- # --------------------------------------------------
- while True:
- dt = clock.tick(60)
- current_time = pygame.time.get_ticks()
- # --- Event Handling ---
- for event in pygame.event.get():
- if event.type == pygame.QUIT:
- pygame.quit()
- sys.exit()
- # Fallback keyboard shooting if no controller is connected.
- if event.type == pygame.KEYDOWN:
- if event.key == pygame.K_SPACE and not (level_won or level_lost) and (joystick is None):
- if player_triple_shot_active:
- directions = [player.direction,
- player.direction.rotate(30),
- player.direction.rotate(-30)]
- for d in directions:
- projectiles.append(Projectile(player.pos, d))
- else:
- projectiles.append(Projectile(player.pos, player.direction))
- # Compute camera offset so that the player remains centered.
- camera_offset = player.pos - pygame.math.Vector2(screen_width/2, screen_height/2)
- boss_level = (level_number % 4 == 0)
- if not (level_won or level_lost):
- keys = pygame.key.get_pressed()
- # --- Movement: Use left analog stick (axes 0 and 1) if available, else keyboard ---
- if joystick is not None:
- lx = joystick.get_axis(0)
- ly = joystick.get_axis(1)
- deadzone = 0.2
- if abs(lx) < deadzone:
- lx = 0
- if abs(ly) < deadzone:
- ly = 0
- move_input = pygame.math.Vector2(lx, ly)
- if move_input.length() > 0:
- move = move_input.normalize() * player.speed
- player.direction = move_input.normalize()
- player.pos += move
- else:
- player.update(keys)
- else:
- player.update(keys)
- # --- Twin-Stick Shooting: Use right analog stick (axes 2 and 3) ---
- if joystick is not None:
- rx = joystick.get_axis(2)
- ry = joystick.get_axis(3)
- deadzone_shoot = 0.2
- shoot_input = pygame.math.Vector2(rx, ry)
- if shoot_input.length() < deadzone_shoot:
- shoot_input = pygame.math.Vector2(0, 0)
- if shoot_input.length() > 0:
- # Continuously fire while the right stick is held, using fire_interval.
- if current_time - last_shot_time >= fire_interval:
- if player_triple_shot_active:
- directions = [shoot_input.normalize(),
- shoot_input.normalize().rotate(30),
- shoot_input.normalize().rotate(-30)]
- for d in directions:
- projectiles.append(Projectile(player.pos, d))
- else:
- projectiles.append(Projectile(player.pos, shoot_input.normalize()))
- last_shot_time = current_time
- # --- Update player projectiles ---
- for proj in projectiles:
- proj.update()
- if boss_level:
- if boss is None:
- boss = Boss((player.pos.x, player.pos.y - 200))
- boss.update(player.pos)
- for bp in boss_projectiles[:]:
- bp.update(player.pos)
- if bp.to_remove:
- boss_projectiles.remove(bp)
- # Collision Detection for Boss Level:
- for proj in projectiles[:]:
- if proj.pos.distance_to(boss.pos) < 100:
- boss.hp -= 1
- try:
- projectiles.remove(proj)
- except ValueError:
- pass
- for proj in projectiles[:]:
- for bp in boss_projectiles[:]:
- if bp.proj_type == "homing" and proj.pos.distance_to(bp.pos) < (bp.size + proj.size):
- try:
- boss_projectiles.remove(bp)
- except ValueError:
- pass
- try:
- projectiles.remove(proj)
- except ValueError:
- pass
- break
- for bp in boss_projectiles[:]:
- if bp.pos.distance_to(player.pos) < (15 + bp.size):
- try:
- boss_projectiles.remove(bp)
- except ValueError:
- pass
- if player_shield_active:
- player_shield_active = False
- else:
- player_health -= 1
- if boss.hp <= 0:
- level_won = True
- win_time = current_time
- else:
- if current_time - last_enemy_spawn_time >= enemy_spawn_interval:
- new_enemy = spawn_enemies(1, player.pos)[0]
- enemies.append(new_enemy)
- last_enemy_spawn_time = current_time
- if current_time - last_powerup_spawn_time >= powerup_spawn_interval:
- spawn_x = random.uniform(player.pos.x - screen_width/2, player.pos.x + screen_width/2)
- spawn_y = random.uniform(player.pos.y - screen_height/2, player.pos.y + screen_height/2)
- p_type = "shield" if random.random() < 0.5 else "triple"
- powerups.append(PowerUp((spawn_x, spawn_y), p_type))
- last_powerup_spawn_time = current_time
- for enemy in enemies:
- enemy.update(player.pos, player.direction)
- if player_triple_shot_active and current_time >= player_triple_shot_end_time:
- player_triple_shot_active = False
- player_triple_shot_end_time = 0
- for proj in projectiles[:]:
- for enemy in enemies[:]:
- if proj.pos.distance_to(enemy.pos) < (enemy.size + proj.size):
- try:
- enemies.remove(enemy)
- except ValueError:
- pass
- try:
- projectiles.remove(proj)
- except ValueError:
- pass
- score += 200
- destroyed_count += 1
- break
- for enemy in enemies[:]:
- if enemy.pos.distance_to(player.pos) < (15 + enemy.size):
- try:
- enemies.remove(enemy)
- except ValueError:
- pass
- if player_shield_active:
- player_shield_active = False
- else:
- player_health -= 1
- for pu in powerups[:]:
- if pu.pos.distance_to(player.pos) < (15 + pu.radius):
- if pu.type == "shield":
- player_shield_active = True
- elif pu.type == "triple":
- player_triple_shot_active = True
- player_triple_shot_end_time = current_time + 8000
- try:
- powerups.remove(pu)
- except ValueError:
- pass
- if destroyed_count >= level_enemy_kill_target:
- level_won = True
- win_time = current_time
- projectiles = [p for p in projectiles if p.pos.distance_to(player.pos) < 1000]
- if player_health <= 0:
- level_lost = True
- lose_time = current_time
- else:
- if level_won and current_time - win_time >= 5000:
- level_won = False
- destroyed_count = 0
- if not boss_level:
- level_enemy_kill_target = int(level_enemy_kill_target * 1.1)
- enemy_speed_multiplier *= 1.05
- level_number += 1
- enemies.clear()
- projectiles.clear()
- powerups.clear()
- boss_projectiles.clear()
- last_enemy_spawn_time = current_time
- last_powerup_spawn_time = current_time
- player_shield_active = False
- player_triple_shot_active = False
- player_triple_shot_end_time = 0
- boss = None
- elif level_lost and current_time - lose_time >= 5000:
- level_lost = False
- destroyed_count = 0
- player_health = 3
- player.pos = pygame.math.Vector2(0, 0)
- enemies.clear()
- projectiles.clear()
- powerups.clear()
- boss_projectiles.clear()
- last_enemy_spawn_time = current_time
- last_powerup_spawn_time = current_time
- player_shield_active = False
- player_triple_shot_active = False
- player_triple_shot_end_time = 0
- boss = None
- # --- Drawing ---
- screen.fill((0, 0, 0))
- if boss_level:
- if boss is not None:
- boss.draw(screen, camera_offset)
- for bp in boss_projectiles:
- bp.draw(screen, camera_offset)
- else:
- for enemy in enemies:
- enemy.draw(screen, camera_offset)
- for pu in powerups:
- pu.draw(screen, camera_offset)
- for proj in projectiles:
- proj.draw(screen, camera_offset)
- player.draw(screen, camera_offset)
- score_text = font.render("Score: " + str(score), True, (255, 255, 255))
- screen.blit(score_text, (screen_width - score_text.get_width() - 10, 10))
- level_text = font.render("Level " + str(level_number), True, (255, 255, 255))
- level_rect = level_text.get_rect(midtop=(screen_width/2, 10))
- screen.blit(level_text, level_rect)
- heart_width = 20
- heart_height = 30
- for i in range(player_health):
- heart_x = 10 + i*(heart_width + 5)
- heart_y = 10
- pygame.draw.rect(screen, (255, 0, 0), (heart_x, heart_y, heart_width, heart_height))
- if player_shield_active:
- center = (screen_width//2, screen_height//2)
- pygame.draw.circle(screen, (0, 0, 255), center, 25, 3)
- if level_won:
- win_text = big_font.render("You Win!", True, (255, 255, 255))
- win_rect = win_text.get_rect(center=(screen_width/2, screen_height/2))
- screen.blit(win_text, win_rect)
- if level_lost:
- lose_text = big_font.render("You Lose", True, (255, 255, 255))
- lose_rect = lose_text.get_rect(center=(screen_width/2, screen_height/2))
- screen.blit(lose_text, lose_rect)
- pygame.display.flip()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement