139 lines
4.5 KiB
Python
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]
|