Files
ball_game/launcher.py
T
2026-10-31 23:04:47 +03:30

139 lines
4.5 KiB
Python

# launcher.py
#
# Represents the top-center launcher tube.
#
# Features:
# - Pivot point attached at top-center of the screen (x = width / 2, y = -15),
# extending above the screen top so it is clipped by the screen boundary.
# - Consists ONLY of a short, elongated solid tube/barrel (no base/tank/turret).
# - Continuous smooth rotation between LAUNCHER_MIN_ANGLE and LAUNCHER_MAX_ANGLE.
# - Provides collision parameters for PhysicsWorld (_Solid OBB).
# - Provides ball spawn transform (position + velocity) outward from nozzle exit.
import math
from config import (
LAUNCHER_MIN_ANGLE,
LAUNCHER_MAX_ANGLE,
LAUNCHER_LENGTH,
LAUNCHER_WIDTH,
LAUNCHER_ROTATION_SPEED,
LAUNCHER_BALL_SPAWN_SPEED,
BALL_RADIUS,
BALL_RADIUS_VARIATION,
)
class Launcher:
def __init__(self, screen_width, screen_height):
self.screen_width = float(screen_width)
self.screen_height = float(screen_height)
# Pivot point at top center. Placed slightly above y=0 (-15px) so the upper part
# extends beyond the top of the screen and is clipped by the screen boundary.
self.pivot_x = self.screen_width / 2.0
self.pivot_y = -15.0
self.min_angle = float(LAUNCHER_MIN_ANGLE)
self.max_angle = float(LAUNCHER_MAX_ANGLE)
self.length = float(LAUNCHER_LENGTH)
self.tube_width = float(LAUNCHER_WIDTH)
self.rotation_speed = float(LAUNCHER_ROTATION_SPEED)
self.spawn_speed = float(LAUNCHER_BALL_SPAWN_SPEED)
self._time = 0.0
self.angle = 0.0 # degrees from straight down (0=down, negative=left, positive=right)
self._update_angle()
def update_screen_size(self, width, height):
self.screen_width = float(width)
self.screen_height = float(height)
self.pivot_x = self.screen_width / 2.0
def update(self, delta_time):
self._time += delta_time
self._update_angle()
def _update_angle(self):
mid_angle = (self.min_angle + self.max_angle) / 2.0
amplitude = (self.max_angle - self.min_angle) / 2.0
if amplitude <= 0:
self.angle = mid_angle
return
# Smooth harmonic oscillation within [min_angle, max_angle]
period = max(0.1, (4.0 * amplitude) / max(1.0, self.rotation_speed))
omega = (2.0 * math.pi) / period
sine_val = math.sin(omega * self._time)
subtle_wave = 0.85 * sine_val + 0.15 * math.sin(omega * 0.5 * self._time)
self.angle = mid_angle + amplitude * max(-1.0, min(1.0, subtle_wave))
def get_solid_params(self):
"""
Returns (cx, cy, rect_long, rect_short, angle) for creating a _Solid in physics.
"""
rad = math.radians(self.angle)
dir_x = math.sin(rad)
dir_y = math.cos(rad)
cx = self.pivot_x + dir_x * (self.length / 2.0)
cy = self.pivot_y + dir_y * (self.length / 2.0)
# OBB angle in geometry convention: 90 degrees minus launcher angle
obb_angle = 90.0 - self.angle
return cx, cy, self.length, self.tube_width, obb_angle
def get_spawn_transform(self):
"""
Returns (spawn_x, spawn_y, dir_x, dir_y) for spawning a ball outward
along the nozzle direction, positioned slightly outside the nozzle tip.
"""
rad = math.radians(self.angle)
dir_x = math.sin(rad)
dir_y = math.cos(rad)
max_ball_radius = BALL_RADIUS * (1.0 + BALL_RADIUS_VARIATION)
safe_dist = self.length + max_ball_radius + 3.0
spawn_x = self.pivot_x + dir_x * safe_dist
spawn_y = self.pivot_y + dir_y * safe_dist
return spawn_x, spawn_y, dir_x, dir_y
def get_points(self):
"""
Returns the 4 corner points of the launcher tube rectangle.
"""
rad = math.radians(self.angle)
u_x = math.sin(rad)
u_y = math.cos(rad)
v_x = -u_y
v_y = u_x
half_w = self.tube_width / 2.0
length = self.length
p_top_left = (
self.pivot_x - v_x * half_w,
self.pivot_y - v_y * half_w,
)
p_top_right = (
self.pivot_x + v_x * half_w,
self.pivot_y + v_y * half_w,
)
p_bot_right = (
self.pivot_x + u_x * length + v_x * half_w,
self.pivot_y + u_y * length + v_y * half_w,
)
p_bot_left = (
self.pivot_x + u_x * length - v_x * half_w,
self.pivot_y + u_y * length - v_y * half_w,
)
return [p_top_left, p_top_right, p_bot_right, p_bot_left]