# imports import pygame import sys # Functions class Bullet: def __init__(self, pos, vel, lifetime=1.2): self.pos = pygame.Vector2(pos) self.vel = pygame.Vector2(vel) self.lifetime = lifetime self.radius = 3 def update(self, dt, screen_size): self.pos = self.vel * dt self.lifetime -= dt w, h = screen_size if self.pos.x < 0: self.pos.x += w elif self.pos.x >= w: self.pos.x -= w if self.pos.y < 0: self.pos.y += h elif self.pos.y >= h: self.pos.y -= h return self.lifetime > 0 def draw(self, surface): pygame.draw.circle(surface, (220, 240, 120), (int(self.pos.x), int(self.pos.y)), self.radius) def get_collision_circle(self): return self.pos, float(self.radius) class Player: def __init__(self, pos): # Physics self.pos = pygame.Vector2(pos) self.vel = pygame.Vector2(0, 0) # Ship orientation (degrees). 0 = pointing right. self.angle = -90.0 # start pointing up # Tuning self.turn_speed = 220.0 # degrees per second self.thrust_accel = 520.0 # pixels per second^2 self.max_speed = 420.0 # clamp velocity magnitude self.damping = 0.995 # slight drift reduction per frame; 0.5% reduction per frame # Rendering / collision self.radius = 16 # pixels # Shooting self.fire_cooldown = 0.18 self._fire_timer = 0.0 self.bullet_speed = 650.0 # pixels per second self.bullet_spawn_offset = self.radius + 4 def update(self, dt, keys, screen_size): self._fire_timer = max(0.0, self._fire_timer - dt) # Rotation if keys[pygame.K_LEFT] or keys[pygame.K_a]: self.angle -= self.turn_speed * dt if keys[pygame.K_RIGHT] or keys[pygame.K_d]: self.angle += self.turn_speed * dt # Thrust (acceleration in facing direction) if keys[pygame.K_UP] or keys[pygame.K_w]: forward = pygame.Vector2(1, 0).rotate(self.angle) self.vel += forward * self.thrust_accel * dt # Clamp speed if self.vel.length() > self.max_speed: self.vel.scale_to_length(self.max_speed) # Move self.pos += self.vel * dt # Mild damping to keep things controllable self.vel *= self.damping # Screen wrap w, h = screen_size if self.pos.x < 0: self.pos.x += w elif self.pos.x >= w: self.pos.x -= w if self.pos.y < 0: self.pos.y += h elif self.pos.y >= h: self.pos.y -= h def try_fire(self): if self._fire_timer > 0.0: return None forward = pygame.Vector2(1, 0).rotate(self.angle) spawn_pos = self.pos + forward * self.bullet_spawn_offset bullet_vel = self.vel + forward * self.bullet_speed self._fire_timer = self.fire_cooldown return Bullet(spawn_pos, bullet_vel) def _ship_points(self): """ Returns 3 points (triangle) in world space. We'll draw a simple simple triangle ship """ # Define ship triangle in local space (pointing right), # then rotate by angle and translate by pos tip = pygame.Vector2(self.radius, 0) left = pygame.Vector2(-self.radius * 0.8, self.radius * 0.6) right = pygame.Vector2(-self.radius * 0.8, -self.radius * 0.6) pts = [tip, left, right] return [p.rotate(self.angle) + self.pos for p in pts] def draw(self, surface): pygame.draw.polygon(surface, (220, 220, 240), self._ship_points(), width=2) # Optional: draw a tiny center dot (helps see pos) pygame.draw.circle(surface, (220, 220, 240), (int(self.pos.x), int(self.pos.y)), 2) def get_collision_circle(self): return self.pos, float(self.radius) class Game: # always start with self! def __init__(self, width=800, height=450, caption="Pygame OOP"): pygame.init() self.width = width self.height = height self.screen = pygame.display.set_mode((self.width, self.height)) self.clock = pygame.time.Clock() self.running = True # We'll use dt (delta time) in later sessions for smooth movement self.dt = 0.0 # center spawn ship self.player = Player((self.width / 2, self.height / 2)) def run(self): """Main game loop""" while self.running: # dt in seconds (e.g., 0.016 at ~60 FPS) # dt is also called frametime self.dt = self.clock.tick(60) / 1000.0 # 1. Handle events (inputs) self.handle_events() # 2. Update all objects self.update(self.dt) # 3. Draw frame self.draw() self.quit() def handle_events(self): """Handle all events (inputs)""" for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False elif event.type == pygame.KEYDOWN: if event.key in {pygame.K_ESCAPE, pygame.K_q}: self.running = False def update(self, dt): """Update game state.""" keys = pygame.key.get_pressed() self.player.update(dt, keys, (self.width, self.height)) def draw(self): """Draw everything each frame""" self.screen.fill((25, 25, 35)) # Background color # In later sessions we'll draw entities here. self.player.draw(self.screen) pygame.display.flip() def quit(self): """Clean shutdown""" pygame.quit() sys.exit() # Main loop def main(): game = Game() game.run() # Script entry point if __name__ == "__main__": main()