1200 lines
38 KiB
Python
1200 lines
38 KiB
Python
import sys
|
|
import math
|
|
import random
|
|
import struct
|
|
import pygame
|
|
from pygame.locals import *
|
|
from OpenGL.GL import *
|
|
from OpenGL.GLU import *
|
|
|
|
# ==========================================
|
|
# CONFIGURACIÓN Y CONSTANTES
|
|
# ==========================================
|
|
WIDTH, HEIGHT = 800, 600
|
|
|
|
# Colores (R, G, B)
|
|
COLOR_BG = (0.12, 0.15, 0.12) # Fondo ambiente
|
|
COLOR_WATER = (0.0, 0.5, 0.85) # Agua animada (Cian/Azul)
|
|
COLOR_LAND = (0.45, 0.32, 0.15) # Tierra / Relieve
|
|
COLOR_LAND_RIVER = (0.40, 0.28, 0.12) # Tierra en riachuelos
|
|
COLOR_ISLAND = (0.5, 0.38, 0.18) # Islas
|
|
COLOR_REED = (0.15, 0.85, 0.25) # Juncos verdes
|
|
COLOR_CATTAIL = (0.35, 0.18, 0.08) # Espigas marrones densas
|
|
COLOR_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula del jugador (Cian)
|
|
COLOR_PURPLE_DRAGONFLY = (0.7, 0.2, 0.8) # Libélulas decorativas (Morado)
|
|
COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo)
|
|
COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón)
|
|
COLOR_FROG = (0.15, 0.85, 0.35) # Rana verde detallada
|
|
COLOR_HUD = (1.0, 1.0, 1.0) # Retícula HUD (Blanco)
|
|
|
|
# Dimensiones del Mundo
|
|
POND_RADIUS = 1800.0
|
|
STREAM_LEN = 900.0
|
|
STREAM_MIN_Y = -(POND_RADIUS + STREAM_LEN)
|
|
STREAM_MAX_Y = (POND_RADIUS + STREAM_LEN)
|
|
WORLD_LEN = STREAM_MAX_Y - STREAM_MIN_Y
|
|
|
|
# Cantidades de insectos
|
|
MOSQUITO_MIN, MOSQUITO_MAX = 17, 27
|
|
FLY_MIN, FLY_MAX = 9, 14
|
|
|
|
# ==========================================
|
|
# ELEMENTOS DEL MAPA
|
|
# ==========================================
|
|
REEDS = [
|
|
(-250, 350, 0, 20), (-160, -500, 0, 22), (500, 200, 0, 24), (-700, 200, 0, 21), (200, -800, 0, 23),
|
|
(-900, -300, 0, 25), (760, -600, 0, 22), (900, 500, 0, 26), (-500, 900, 0, 23), (600, -1000, 0, 22),
|
|
(-1200, 400, 0, 24), (1300, -300, 0, 20), (-1400, -700, 0, 25), (1100, 1200, 0, 23), (-360, -1300, 0, 22),
|
|
(-90, -1950, 0, 20), (-110, -2150, 0, 23), (-80, -2350, 0, 19), (-100, -2550, 0, 22),
|
|
(90, 1950, 0, 22), (110, 2150, 0, 20), (85, 2350, 0, 24), (105, 2550, 0, 21)
|
|
]
|
|
|
|
ISLANDS = [
|
|
(-250, 350, 90), (-160, -500, 85), (500, 200, 100), (-700, 200, 110), (200, -800, 95),
|
|
(-900, -300, 120), (760, -600, 90), (900, 500, 105), (-500, 900, 115), (600, -1000, 100)
|
|
]
|
|
|
|
def get_river_center(y):
|
|
if y < -POND_RADIUS:
|
|
dy = y + POND_RADIUS
|
|
return -50 + 100 * math.sin(dy * 0.008)
|
|
elif y > POND_RADIUS:
|
|
dy = y - POND_RADIUS
|
|
return 50 + 100 * math.sin(dy * 0.008)
|
|
else:
|
|
return (y / POND_RADIUS) * 50.0
|
|
|
|
def is_over_water(x, y):
|
|
dist_center = math.hypot(x, y)
|
|
if dist_center < POND_RADIUS - 50:
|
|
return True
|
|
if abs(y) >= POND_RADIUS:
|
|
cx = get_river_center(y)
|
|
if abs(x - cx) < 80:
|
|
return True
|
|
return False
|
|
|
|
def get_random_water_position():
|
|
while True:
|
|
if random.random() < 0.7:
|
|
rad = random.uniform(0, math.pi * 2)
|
|
r = random.uniform(100, POND_RADIUS - 150)
|
|
x = r * math.cos(rad)
|
|
y = r * math.sin(rad)
|
|
else:
|
|
y = random.choice([random.uniform(POND_RADIUS, STREAM_MAX_Y), random.uniform(STREAM_MIN_Y, -POND_RADIUS)])
|
|
cx = get_river_center(y)
|
|
x = cx + random.uniform(-60, 60)
|
|
|
|
if is_over_water(x, y):
|
|
z = random.uniform(5.0, 35.0)
|
|
return x, y, z
|
|
|
|
# ==========================================
|
|
# GENERADOR DE AUDIO PROCEDURAL (SIN ARCHIVOS)
|
|
# ==========================================
|
|
def generate_buzz_sound():
|
|
try:
|
|
sample_rate = 22050
|
|
duration = 0.3
|
|
num_samples = int(sample_rate * duration)
|
|
buffer = bytearray()
|
|
for i in range(num_samples):
|
|
t = i / sample_rate
|
|
val = int(32767 * 0.25 * math.sin(2 * math.pi * 180 * t + math.sin(2 * math.pi * 25 * t) * 3))
|
|
buffer.extend(struct.pack('<h', val))
|
|
return pygame.mixer.Sound(buffer=bytes(buffer))
|
|
except Exception:
|
|
return None
|
|
|
|
def generate_wind_sound():
|
|
try:
|
|
sample_rate = 22050
|
|
duration = 0.5
|
|
num_samples = int(sample_rate * duration)
|
|
buffer = bytearray()
|
|
for i in range(num_samples):
|
|
val = int(32767 * 0.15 * (random.random() * 2 - 1))
|
|
buffer.extend(struct.pack('<h', val))
|
|
return pygame.mixer.Sound(buffer=bytes(buffer))
|
|
except Exception:
|
|
return None
|
|
|
|
# ==========================================
|
|
# CLASES DE INSECTOS, LIBÉLULAS DECORATIVAS Y RANA
|
|
# ==========================================
|
|
class Insect:
|
|
def __init__(self, insect_type):
|
|
self.type = insect_type
|
|
self.x, self.y, self.z = get_random_water_position()
|
|
self.yaw = random.uniform(0, 360)
|
|
self.timer = random.uniform(0, 100)
|
|
|
|
if self.type == "mosquito":
|
|
self.speed = 1.8
|
|
self.size = 1.2
|
|
else:
|
|
self.speed = 0.9
|
|
self.size = 1.6
|
|
|
|
def update(self, player_pos):
|
|
self.timer += 0.05
|
|
px, py, pz = player_pos
|
|
dist_to_player = math.hypot(self.x - px, self.y - py)
|
|
|
|
if self.type == "mosquito":
|
|
can_detect = (pz <= self.z)
|
|
if can_detect and dist_to_player < 100:
|
|
escape_angle = math.degrees(math.atan2(self.y - py, self.x - px))
|
|
self.yaw += (escape_angle - self.yaw) * 0.05
|
|
|
|
self.yaw += math.sin(self.timer * 3) * 1.5
|
|
self.z += math.sin(self.timer * 1.5) * 0.2
|
|
|
|
elif self.type == "fly":
|
|
if dist_to_player < 120:
|
|
escape_angle = math.degrees(math.atan2(self.y - py, self.x - px))
|
|
self.yaw = escape_angle + random.uniform(-45, 45)
|
|
self.z += random.choice([-1.5, 1.5])
|
|
else:
|
|
if random.random() < 0.08:
|
|
self.yaw += random.uniform(-60, 60)
|
|
|
|
self.yaw += math.sin(self.timer * 6) * 4.0
|
|
self.z += math.cos(self.timer * 4) * 0.6
|
|
|
|
self.z = max(3.0, min(50.0, self.z))
|
|
|
|
rad = math.radians(self.yaw)
|
|
next_x = self.x - math.sin(rad) * self.speed
|
|
next_y = self.y + math.cos(rad) * self.speed
|
|
|
|
if is_over_water(next_x, next_y):
|
|
self.x, self.y = next_x, next_y
|
|
else:
|
|
self.yaw += 140.0 + random.uniform(-20, 20)
|
|
|
|
if self.y > STREAM_MAX_Y:
|
|
self.y -= WORLD_LEN
|
|
elif self.y < STREAM_MIN_Y:
|
|
self.y += WORLD_LEN
|
|
|
|
def draw(self, attached=False):
|
|
if self.type == "mosquito":
|
|
glColor3f(*COLOR_MOSQUITO)
|
|
else:
|
|
glColor3f(*COLOR_FLY)
|
|
|
|
glPushMatrix()
|
|
if not attached:
|
|
glTranslatef(self.x, self.y, self.z)
|
|
glRotatef(self.yaw, 0, 0, 1)
|
|
|
|
s = self.size
|
|
glBegin(GL_LINES)
|
|
glVertex3f(0, s, 0)
|
|
glVertex3f(0, -s, 0)
|
|
w_offset = 0.0 if attached else math.sin(self.timer * 30) * (s * 0.8)
|
|
glVertex3f(0, 0, 0)
|
|
glVertex3f(-s * 1.5, 0, w_offset)
|
|
glVertex3f(0, 0, 0)
|
|
glVertex3f(s * 1.5, 0, w_offset)
|
|
glEnd()
|
|
|
|
glPopMatrix()
|
|
|
|
class PurpleDragonfly:
|
|
def __init__(self, behavior_type):
|
|
self.behavior = behavior_type
|
|
self.x, self.y, self.z = get_random_water_position()
|
|
self.yaw = random.uniform(0, 360)
|
|
self.timer = random.uniform(0, 100)
|
|
self.wing_timer = random.uniform(0, 100)
|
|
self.is_perched = False
|
|
self.perch_timer = 0
|
|
|
|
def update(self):
|
|
self.wing_timer += 60.0
|
|
if self.is_perched:
|
|
self.perch_timer -= 1
|
|
if self.perch_timer <= 0:
|
|
self.is_perched = False
|
|
return
|
|
|
|
self.timer += 0.05
|
|
if self.behavior == 1:
|
|
self.yaw += 1.2
|
|
rad = math.radians(self.yaw)
|
|
self.x += -math.sin(rad) * 1.1
|
|
self.y += math.cos(rad) * 1.1
|
|
elif self.behavior == 2:
|
|
if random.random() < 0.02:
|
|
self.yaw += random.uniform(-40, 40)
|
|
rad = math.radians(self.yaw)
|
|
self.x += -math.sin(rad) * 1.3
|
|
self.y += math.cos(rad) * 1.3
|
|
else:
|
|
self.yaw += math.sin(self.timer * 3) * 3.5
|
|
rad = math.radians(self.yaw)
|
|
self.x += -math.sin(rad) * 1.4
|
|
self.y += math.cos(rad) * 1.4
|
|
|
|
self.z = 15.0 + math.sin(self.timer * 2) * 6.0
|
|
|
|
if random.random() < 0.0015 and REEDS:
|
|
reed = random.choice(REEDS)
|
|
self.x, self.y, self.z = reed[0], reed[1], reed[2] + reed[3]
|
|
self.is_perched = True
|
|
self.perch_timer = random.randint(300, 600)
|
|
|
|
if not is_over_water(self.x, self.y):
|
|
self.yaw += 150.0
|
|
|
|
if self.y > STREAM_MAX_Y:
|
|
self.y -= WORLD_LEN
|
|
elif self.y < STREAM_MIN_Y:
|
|
self.y += WORLD_LEN
|
|
|
|
def draw(self):
|
|
glColor3f(*COLOR_PURPLE_DRAGONFLY)
|
|
glPushMatrix()
|
|
glTranslatef(self.x, self.y, self.z)
|
|
glRotatef(self.yaw, 0, 0, 1)
|
|
|
|
glBegin(GL_LINES)
|
|
glVertex3f(0, 1.5, 0)
|
|
glVertex3f(0, -3.0, 0)
|
|
glVertex3f(-0.4, 0.6, 0)
|
|
glVertex3f(0.4, 0.6, 0)
|
|
glVertex3f(-0.3, -0.6, 0)
|
|
glVertex3f(0.3, -0.6, 0)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
for i in range(8):
|
|
angle = 2.0 * math.pi * i / 8
|
|
glVertex3f(0.3 * math.cos(angle), 1.4 + 0.3 * math.sin(angle), 0)
|
|
glEnd()
|
|
|
|
wing_angle = 0.0 if self.is_perched else math.sin(self.wing_timer) * 35.0
|
|
rad_wing = math.radians(wing_angle)
|
|
w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0.6, 0)
|
|
glVertex3f(-4.5, 1.0, w_z)
|
|
glVertex3f(-3.8, 0.2, w_z)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0.6, 0)
|
|
glVertex3f(4.5, 1.0, w_z)
|
|
glVertex3f(3.8, 0.2, w_z)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, -0.1, 0)
|
|
glVertex3f(-3.8, -0.2, w_z)
|
|
glVertex3f(-3.0, -0.8, w_z)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, -0.1, 0)
|
|
glVertex3f(3.8, -0.2, w_z)
|
|
glVertex3f(3.0, -0.8, w_z)
|
|
glEnd()
|
|
|
|
glPopMatrix()
|
|
|
|
class Frog:
|
|
def __init__(self):
|
|
self.pick_new_destination()
|
|
self.x, self.y = self.target_x, self.target_y
|
|
self.z = 0.6
|
|
self.speed = 0.35
|
|
self.yaw = 0.0
|
|
self.swim_timer = 0.0
|
|
|
|
def pick_new_destination(self):
|
|
destinations = [(rx, ry) for rx, ry, _, _ in REEDS] + [(ix, iy) for ix, iy, _ in ISLANDS]
|
|
dest = random.choice(destinations)
|
|
self.target_x, self.target_y = dest[0] + random.uniform(-30, 30), dest[1] + random.uniform(-30, 30)
|
|
|
|
def update(self):
|
|
self.swim_timer += 0.15
|
|
dx = self.target_x - self.x
|
|
dy = self.target_y - self.y
|
|
dist = math.hypot(dx, dy)
|
|
|
|
if dist < 6.0:
|
|
self.pick_new_destination()
|
|
else:
|
|
self.yaw = math.degrees(math.atan2(dy, dx)) - 90.0
|
|
self.x += (dx / dist) * self.speed
|
|
self.y += (dy / dist) * self.speed
|
|
|
|
def draw(self, custom_x=None, custom_y=None, custom_z=None, scale=1.0, menu_icon=False):
|
|
glColor3f(*COLOR_FROG)
|
|
glPushMatrix()
|
|
if custom_x is not None and custom_y is not None and custom_z is not None:
|
|
glTranslatef(custom_x, custom_y, custom_z)
|
|
glScalef(scale, scale, scale)
|
|
else:
|
|
glTranslatef(self.x, self.y, self.z)
|
|
glRotatef(self.yaw, 0, 0, 1)
|
|
|
|
leg_offset = 0.0 if menu_icon else math.sin(self.swim_timer) * 1.5
|
|
|
|
glBegin(GL_LINES)
|
|
glVertex3f(0.0, 0.0, 0.5)
|
|
glVertex3f(0.0, -5.0, 0.5)
|
|
glVertex3f(-2.0, 2.0, 0.5)
|
|
glVertex3f(2.0, 2.0, 0.5)
|
|
glVertex3f(-2.5, -2.0, 0.5)
|
|
glVertex3f(2.5, -2.0, 0.5)
|
|
|
|
glVertex3f(-2.0, 2.0, 0.5)
|
|
glVertex3f(0.0, 0.0, 0.5)
|
|
glVertex3f(2.0, 2.0, 0.5)
|
|
glVertex3f(0.0, 0.0, 0.5)
|
|
|
|
glVertex3f(-2.0, 2.0, 0.6)
|
|
glVertex3f(2.0, 2.0, 0.6)
|
|
glVertex3f(-2.0, 2.0, 0.6)
|
|
glVertex3f(0.0, 4.5, 0.6)
|
|
glVertex3f(2.0, 2.0, 0.6)
|
|
glVertex3f(0.0, 4.5, 0.6)
|
|
|
|
glVertex3f(-1.5, 3.5, 1.5)
|
|
glVertex3f(-1.5, 3.5, 0.6)
|
|
glVertex3f(1.5, 3.5, 1.5)
|
|
glVertex3f(1.5, 3.5, 0.6)
|
|
|
|
glVertex3f(-2.0, 2.0, 0.5)
|
|
glVertex3f(-4.0, 3.0, 0.0)
|
|
glVertex3f(-4.0, 3.0, 0.0)
|
|
glVertex3f(-3.5, 4.0, 0.0)
|
|
|
|
glVertex3f(2.0, 2.0, 0.5)
|
|
glVertex3f(4.0, 3.0, 0.0)
|
|
glVertex3f(4.0, 3.0, 0.0)
|
|
glVertex3f(3.5, 4.0, 0.0)
|
|
|
|
# Patas traseras con los pies hacia el exterior
|
|
glVertex3f(-2.5, -2.0, 0.5)
|
|
glVertex3f(-5.0 - leg_offset, -4.0, 0.0)
|
|
glVertex3f(-5.0 - leg_offset, -4.0, 0.0)
|
|
glVertex3f(-6.5 - leg_offset, -3.2, 0.0)
|
|
|
|
glVertex3f(2.5, -2.0, 0.5)
|
|
glVertex3f(5.0 + leg_offset, -4.0, 0.0)
|
|
glVertex3f(5.0 + leg_offset, -4.0, 0.0)
|
|
glVertex3f(6.5 + leg_offset, -3.2, 0.0)
|
|
glEnd()
|
|
|
|
# Pequeño cuadrado/rectángulo central para representar el cuerpo sólido en el icono del menú
|
|
if menu_icon:
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(-1.5, -1.5, 0.4)
|
|
glVertex3f(1.5, -1.5, 0.4)
|
|
glVertex3f(1.5, 1.5, 0.4)
|
|
glVertex3f(-1.5, 1.5, 0.4)
|
|
glEnd()
|
|
|
|
glPopMatrix()
|
|
|
|
# ==========================================
|
|
# RENDERIZADO DE ESCENARIO Y MODELOS
|
|
# ==========================================
|
|
def draw_water_surface(time_counter):
|
|
glColor3f(*COLOR_WATER)
|
|
|
|
grid_size = 15
|
|
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size * 2):
|
|
glBegin(GL_LINE_STRIP)
|
|
for y in range(int(-POND_RADIUS), int(-POND_RADIUS) + int(POND_RADIUS)*2, grid_size):
|
|
dist = math.hypot(x, y)
|
|
if dist < POND_RADIUS - 30:
|
|
z = 1.0 * math.sin(dist * 0.015 - time_counter * 3.0) + 0.5 * math.cos(x * 0.01 + time_counter)
|
|
glVertex3f(x, y, z)
|
|
glEnd()
|
|
|
|
for y in range(int(STREAM_MIN_Y), int(STREAM_MAX_Y), 15):
|
|
if abs(y) < POND_RADIUS:
|
|
continue
|
|
cx = get_river_center(y)
|
|
glBegin(GL_LINE_STRIP)
|
|
for dx in range(-100, 110, 10):
|
|
x = cx + dx
|
|
z = 0.5 * math.sin(y * 0.03 + dx * 0.08 + time_counter * 4.0)
|
|
glVertex3f(x, y, z)
|
|
glEnd()
|
|
|
|
def draw_terrain():
|
|
glColor3f(*COLOR_LAND)
|
|
|
|
grid_step = 120
|
|
for y in range(int(STREAM_MIN_Y - 300), int(STREAM_MAX_Y + 300), grid_step):
|
|
glBegin(GL_LINE_STRIP)
|
|
for x in range(-2500, 2500, grid_step):
|
|
z = 1.5 * math.sin(x * 0.005) + 1.5 * math.cos(y * 0.005)
|
|
glVertex3f(x, y, z)
|
|
glEnd()
|
|
|
|
width_river = 100.0
|
|
margin_land = 70.0
|
|
glColor3f(*COLOR_LAND_RIVER)
|
|
|
|
for y in range(int(STREAM_MIN_Y), int(STREAM_MAX_Y), 30):
|
|
if abs(y) < POND_RADIUS:
|
|
continue
|
|
cx = get_river_center(y)
|
|
|
|
glBegin(GL_LINE_STRIP)
|
|
for dx in range(int(-width_river - margin_land), int(-width_river), 15):
|
|
x = cx + dx
|
|
z = 0.6 * math.sin(y * 0.02 + x * 0.02) + 0.3
|
|
glVertex3f(x, y, z)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_STRIP)
|
|
for dx in range(int(width_river), int(width_river + margin_land), 15):
|
|
x = cx + dx
|
|
z = 0.6 * math.sin(y * 0.02 + x * 0.02) + 0.3
|
|
glVertex3f(x, y, z)
|
|
glEnd()
|
|
|
|
for ix, iy, ir in ISLANDS:
|
|
glColor3f(*COLOR_ISLAND)
|
|
for layer in range(1, 4):
|
|
rad_layer = ir * (1 - layer * 0.25)
|
|
height = layer * 1.2
|
|
glBegin(GL_LINE_LOOP)
|
|
for i in range(16):
|
|
angle = 2.0 * math.pi * i / 16
|
|
dx = rad_layer * math.cos(angle)
|
|
dy = rad_layer * math.sin(angle)
|
|
glVertex3f(ix + dx, iy + dy, height)
|
|
glEnd()
|
|
|
|
def draw_map(time_counter):
|
|
draw_terrain()
|
|
draw_water_surface(time_counter)
|
|
|
|
offsets = [(-1.5, 0.6), (1.3, -0.6), (-0.7, -1.4), (1.1, 1.2), (0.0, 1.6), (-1.2, 0.0)]
|
|
|
|
for rx, ry, rz, rh in REEDS:
|
|
glColor3f(*COLOR_REED)
|
|
glBegin(GL_LINES)
|
|
glVertex3f(rx, ry, rz)
|
|
glVertex3f(rx, ry, rz + rh)
|
|
for ox, oy in offsets:
|
|
glVertex3f(rx, ry, rz)
|
|
glVertex3f(rx + ox, ry + oy, rz + rh * 0.85)
|
|
glEnd()
|
|
|
|
glColor3f(*COLOR_CATTAIL)
|
|
glBegin(GL_LINES)
|
|
for d in [-0.2, 0.0, 0.2]:
|
|
glVertex3f(rx + d, ry, rz + rh - 4.5)
|
|
glVertex3f(rx + d, ry, rz + rh)
|
|
for ox, oy in offsets:
|
|
for d in [-0.15, 0.15]:
|
|
glVertex3f(rx + ox + d, ry + oy, (rz + rh * 0.85) - 3.8)
|
|
glVertex3f(rx + ox + d, ry + oy, rz + rh * 0.85)
|
|
glEnd()
|
|
|
|
def draw_dragonfly(wing_angle, caught_insect=None):
|
|
glColor3f(*COLOR_DRAGONFLY)
|
|
|
|
glBegin(GL_LINES)
|
|
glVertex3f(0, 1.5, 0)
|
|
glVertex3f(0, -3.0, 0)
|
|
glVertex3f(-0.4, 0.6, 0)
|
|
glVertex3f(0.4, 0.6, 0)
|
|
glVertex3f(-0.3, -0.6, 0)
|
|
glVertex3f(0.3, -0.6, 0)
|
|
|
|
glVertex3f(-0.3, 0.5, 0)
|
|
glVertex3f(-0.2, 0.2, -0.6)
|
|
glVertex3f(0.3, 0.5, 0)
|
|
glVertex3f(0.2, 0.2, -0.6)
|
|
glVertex3f(-0.2, -0.2, 0)
|
|
glVertex3f(-0.1, -0.3, -0.6)
|
|
glVertex3f(0.2, -0.2, 0)
|
|
glVertex3f(0.1, -0.3, -0.6)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
for i in range(8):
|
|
angle = 2.0 * math.pi * i / 8
|
|
glVertex3f(0.3 * math.cos(angle), 1.4 + 0.3 * math.sin(angle), 0)
|
|
glEnd()
|
|
|
|
rad_wing = math.radians(wing_angle)
|
|
w_z = math.sin(rad_wing) * 1.2 if wing_angle != 0 else 0.0
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0.6, 0)
|
|
glVertex3f(-4.5, 1.0, w_z)
|
|
glVertex3f(-3.8, 0.2, w_z)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0.6, 0)
|
|
glVertex3f(4.5, 1.0, w_z)
|
|
glVertex3f(3.8, 0.2, w_z)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, -0.1, 0)
|
|
glVertex3f(-3.8, -0.2, w_z)
|
|
glVertex3f(-3.0, -0.8, w_z)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, -0.1, 0)
|
|
glVertex3f(3.8, -0.2, w_z)
|
|
glVertex3f(3.0, -0.8, w_z)
|
|
glEnd()
|
|
|
|
if caught_insect:
|
|
glPushMatrix()
|
|
glTranslatef(0.0, 0.2, -0.6)
|
|
caught_insect.draw(attached=True)
|
|
glPopMatrix()
|
|
|
|
# ==========================================
|
|
# INTERFAZ 2D / HUD Y PANTALLA INICIAL
|
|
# ==========================================
|
|
def draw_text_2d(text_surface, x, y):
|
|
text_data = pygame.image.tostring(text_surface, "RGBA", True)
|
|
w, h = text_surface.get_size()
|
|
|
|
glEnable(GL_BLEND)
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
|
|
glRasterPos2i(x, y)
|
|
glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, text_data)
|
|
glDisable(GL_BLEND)
|
|
|
|
def draw_start_screen(font_title, font_subtitle, font_text, wing_angle, frog_instance):
|
|
glDisable(GL_DEPTH_TEST)
|
|
|
|
glMatrixMode(GL_PROJECTION)
|
|
glPushMatrix()
|
|
glLoadIdentity()
|
|
gluOrtho2D(0, WIDTH, 0, HEIGHT)
|
|
|
|
glMatrixMode(GL_MODELVIEW)
|
|
glPushMatrix()
|
|
glLoadIdentity()
|
|
|
|
# TÍTULO PRINCIPAL Y LIBÉLULA SUPERIOR
|
|
txt_title = font_title.render("LIBÉLULA", True, (0, 220, 255))
|
|
draw_text_2d(txt_title, WIDTH // 2 - 130, HEIGHT - 100)
|
|
|
|
glPushMatrix()
|
|
glTranslatef(WIDTH // 2 + 150, HEIGHT - 75, 0.0)
|
|
glScalef(12.0, 12.0, 1.0)
|
|
|
|
glColor3f(*COLOR_DRAGONFLY)
|
|
glBegin(GL_LINES)
|
|
glVertex3f(0, 1.5, 0)
|
|
glVertex3f(0, -3.0, 0)
|
|
glVertex3f(-0.4, 0.6, 0)
|
|
glVertex3f(0.4, 0.6, 0)
|
|
glEnd()
|
|
|
|
rad_w = math.radians(wing_angle)
|
|
w_off = math.sin(rad_w) * 1.5
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0.6, 0)
|
|
glVertex3f(-4.5, 1.0, w_off)
|
|
glVertex3f(-3.8, 0.2, w_off)
|
|
glEnd()
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0.6, 0)
|
|
glVertex3f(4.5, 1.0, w_off)
|
|
glVertex3f(3.8, 0.2, w_off)
|
|
glEnd()
|
|
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, -0.1, 0)
|
|
glVertex3f(-3.8, -0.2, w_off)
|
|
glVertex3f(-3.0, -0.8, w_off)
|
|
glEnd()
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, -0.1, 0)
|
|
glVertex3f(3.8, -0.2, w_off)
|
|
glVertex3f(3.0, -0.8, w_off)
|
|
glEnd()
|
|
|
|
glPopMatrix()
|
|
|
|
# OBJETIVO DEL JUEGO
|
|
obj_line1 = font_text.render("Objetivo: Vuela libre por el estanque y sus arroyos capturando moscas", True, (255, 255, 255))
|
|
obj_line2 = font_text.render("y mosquitos. Una vez cazados debes devorarlos posado en un junco.", True, (255, 255, 255))
|
|
draw_text_2d(obj_line1, 70, HEIGHT - 170)
|
|
draw_text_2d(obj_line2, 70, HEIGHT - 195)
|
|
|
|
# CONTROLES Y TECLAS
|
|
controls = [
|
|
"CONTROLES:",
|
|
"W / S : Acelerar / Frenar",
|
|
"Flecha Arriba / Abajo : Cambiar altitud",
|
|
"Flecha Izquierda / Derecha : Girar / Virar",
|
|
"ESPACIO : Picado rápido para cazar",
|
|
"P : Aterrizaje automático en junco cercano",
|
|
"ESC : Salir del juego"
|
|
]
|
|
|
|
y_pos = HEIGHT - 250
|
|
for ctrl in controls:
|
|
is_title = ctrl.endswith(":")
|
|
col = (255, 220, 50) if is_title else (200, 200, 200)
|
|
t_surf = font_subtitle.render(ctrl, True, col) if is_title else font_text.render(ctrl, True, col)
|
|
draw_text_2d(t_surf, 70, y_pos)
|
|
y_pos -= 25
|
|
|
|
# LEYENDA VISUAL INFERIOR (Mosquito, Mosca, Junco, Rana y Libélula)
|
|
y_icon = 90
|
|
|
|
# 1. Mosquito
|
|
glColor3f(*COLOR_MOSQUITO)
|
|
glBegin(GL_LINES)
|
|
glVertex2f(60, y_icon + 5)
|
|
glVertex2f(60, y_icon - 5)
|
|
glVertex2f(55, y_icon)
|
|
glVertex2f(65, y_icon)
|
|
glEnd()
|
|
txt_m = font_text.render("Mosquito", True, (255, 230, 0))
|
|
draw_text_2d(txt_m, 75, y_icon - 8)
|
|
|
|
# 2. Mosca
|
|
glColor3f(*COLOR_FLY)
|
|
glBegin(GL_LINES)
|
|
glVertex2f(210, y_icon + 5)
|
|
glVertex2f(210, y_icon - 5)
|
|
glVertex2f(205, y_icon - 3)
|
|
glVertex2f(215, y_icon + 3)
|
|
glVertex2f(205, y_icon + 3)
|
|
glVertex2f(215, y_icon - 3)
|
|
glEnd()
|
|
txt_f = font_text.render("Mosca", True, (200, 65, 40))
|
|
draw_text_2d(txt_f, 225, y_icon - 8)
|
|
|
|
# 3. Junco
|
|
glColor3f(*COLOR_REED)
|
|
glBegin(GL_LINES)
|
|
glVertex2f(340, y_icon - 10)
|
|
glVertex2f(340, y_icon + 15)
|
|
glEnd()
|
|
glColor3f(*COLOR_CATTAIL)
|
|
glBegin(GL_LINES)
|
|
glVertex2f(340, y_icon + 8)
|
|
glVertex2f(340, y_icon + 15)
|
|
glEnd()
|
|
txt_r = font_text.render("Junco", True, (100, 220, 100))
|
|
draw_text_2d(txt_r, 355, y_icon - 8)
|
|
|
|
# 4. Rana (Con su cuadradito de cuerpo sólido incorporado)
|
|
frog_instance.draw(custom_x=475, custom_y=y_icon, custom_z=0.0, scale=3.5, menu_icon=True)
|
|
txt_frog = font_text.render("Rana", True, (100, 255, 100))
|
|
draw_text_2d(txt_frog, 505, y_icon - 8)
|
|
|
|
# 5. Libélula (Con alas animadas)
|
|
glPushMatrix()
|
|
glTranslatef(625, y_icon, 0.0)
|
|
glScalef(6.0, 6.0, 1.0)
|
|
glColor3f(*COLOR_PURPLE_DRAGONFLY)
|
|
glBegin(GL_LINES)
|
|
glVertex3f(0, 1.5, 0)
|
|
glVertex3f(0, -3.0, 0)
|
|
glVertex3f(-0.4, 0.6, 0)
|
|
glVertex3f(0.4, 0.6, 0)
|
|
glEnd()
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0.6, 0)
|
|
glVertex3f(-4.5, 1.0, w_off)
|
|
glVertex3f(-3.8, 0.2, w_off)
|
|
glEnd()
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, 0.6, 0)
|
|
glVertex3f(4.5, 1.0, w_off)
|
|
glVertex3f(3.8, 0.2, w_off)
|
|
glEnd()
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, -0.1, 0)
|
|
glVertex3f(-3.8, -0.2, w_off)
|
|
glVertex3f(-3.0, -0.8, w_off)
|
|
glEnd()
|
|
glBegin(GL_LINE_LOOP)
|
|
glVertex3f(0, -0.1, 0)
|
|
glVertex3f(3.8, -0.2, w_off)
|
|
glVertex3f(3.0, -0.8, w_off)
|
|
glEnd()
|
|
glPopMatrix()
|
|
|
|
txt_df = font_text.render("Libélula", True, (0, 220, 255))
|
|
draw_text_2d(txt_df, 654, y_icon - 8)
|
|
|
|
# MENSAJE PARA COMENZAR
|
|
start_msg = font_subtitle.render("Pulsa INTRO o cualquier tecla para comenzar...", True, (100, 255, 100))
|
|
draw_text_2d(start_msg, WIDTH // 2 - 200, 30)
|
|
|
|
glMatrixMode(GL_PROJECTION)
|
|
glPopMatrix()
|
|
glMatrixMode(GL_MODELVIEW)
|
|
glPopMatrix()
|
|
glEnable(GL_DEPTH_TEST)
|
|
|
|
def draw_hud(player_pos, yaw, insects, eaten_mosquitos, eaten_flies, font):
|
|
glDisable(GL_DEPTH_TEST)
|
|
|
|
glMatrixMode(GL_PROJECTION)
|
|
glPushMatrix()
|
|
glLoadIdentity()
|
|
gluOrtho2D(0, WIDTH, 0, HEIGHT)
|
|
|
|
glMatrixMode(GL_MODELVIEW)
|
|
glPushMatrix()
|
|
glLoadIdentity()
|
|
|
|
px, py, pz = player_pos
|
|
|
|
cx, cy = WIDTH // 2, HEIGHT - 35
|
|
radius = 22
|
|
|
|
glColor3f(*COLOR_HUD)
|
|
glBegin(GL_LINE_LOOP)
|
|
for i in range(24):
|
|
angle = 2.0 * math.pi * i / 24
|
|
glVertex2f(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
|
|
glEnd()
|
|
|
|
glBegin(GL_LINES)
|
|
glVertex2f(cx, cy + radius)
|
|
glVertex2f(cx, cy + radius - 5)
|
|
glVertex2f(cx, cy - radius)
|
|
glVertex2f(cx, cy - radius + 5)
|
|
glVertex2f(cx - radius, cy)
|
|
glVertex2f(cx - radius + 5, cy)
|
|
glVertex2f(cx + radius, cy)
|
|
glVertex2f(cx + radius - 5, cy)
|
|
glEnd()
|
|
|
|
closest_insect = None
|
|
min_dist = 999999.0
|
|
relative_angle = 0.0
|
|
diff_z = 0.0
|
|
|
|
for insect in insects:
|
|
dx = insect.x - px
|
|
dy = insect.y - py
|
|
dist = math.hypot(dx, dy)
|
|
|
|
if dist < min_dist and dist > 0.1:
|
|
angle_to_insect = math.degrees(math.atan2(dy, dx))
|
|
heading = (angle_to_insect - (90 - yaw)) % 360
|
|
if heading > 180:
|
|
heading -= 360
|
|
|
|
if abs(heading) < 60:
|
|
min_dist = dist
|
|
closest_insect = insect
|
|
relative_angle = heading
|
|
diff_z = insect.z - pz
|
|
|
|
if closest_insect:
|
|
radar_x = cx - (relative_angle / 60.0) * (radius - 4)
|
|
radar_y = cy + (diff_z / 20.0) * (radius - 4)
|
|
|
|
dist_from_center = math.hypot(radar_x - cx, radar_y - cy)
|
|
if dist_from_center > (radius - 3):
|
|
factor = (radius - 3) / dist_from_center
|
|
radar_x = cx + (radar_x - cx) * factor
|
|
radar_y = cy + (radar_y - cy) * factor
|
|
|
|
glColor3f(1.0, 1.0, 1.0)
|
|
glBegin(GL_LINE_LOOP)
|
|
for i in range(8):
|
|
ang = 2.0 * math.pi * i / 8
|
|
glVertex2f(radar_x + 3 * math.cos(ang), radar_y + 3 * math.sin(ang))
|
|
glEnd()
|
|
|
|
glColor3f(*COLOR_MOSQUITO)
|
|
glBegin(GL_LINES)
|
|
glVertex2f(25, HEIGHT - 33)
|
|
glVertex2f(25, HEIGHT - 27)
|
|
glVertex2f(21, HEIGHT - 30)
|
|
glVertex2f(29, HEIGHT - 30)
|
|
glEnd()
|
|
|
|
glColor3f(*COLOR_FLY)
|
|
glBegin(GL_LINES)
|
|
glVertex2f(WIDTH - 82, HEIGHT - 33)
|
|
glVertex2f(WIDTH - 82, HEIGHT - 27)
|
|
glVertex2f(WIDTH - 86, HEIGHT - 28)
|
|
glVertex2f(WIDTH - 78, HEIGHT - 32)
|
|
glVertex2f(WIDTH - 86, HEIGHT - 32)
|
|
glVertex2f(WIDTH - 78, HEIGHT - 28)
|
|
glEnd()
|
|
|
|
txt_mosq = font.render(f"x {eaten_mosquitos}", True, (255, 230, 0))
|
|
txt_flies = font.render(f"x {eaten_flies}", True, (200, 65, 40))
|
|
|
|
draw_text_2d(txt_mosq, 38, HEIGHT - 40)
|
|
draw_text_2d(txt_flies, WIDTH - 68, HEIGHT - 40)
|
|
|
|
glMatrixMode(GL_PROJECTION)
|
|
glPopMatrix()
|
|
glMatrixMode(GL_MODELVIEW)
|
|
glPopMatrix()
|
|
glEnable(GL_DEPTH_TEST)
|
|
|
|
# ==========================================
|
|
# BUCLE PRINCIPAL
|
|
# ==========================================
|
|
def main():
|
|
pygame.init()
|
|
pygame.mixer.init(frequency=22050, size=-16, channels=1)
|
|
pygame.font.init()
|
|
|
|
font = pygame.font.SysFont("Arial", 22, bold=True)
|
|
font_title = pygame.font.SysFont("Arial", 48, bold=True)
|
|
font_subtitle = pygame.font.SysFont("Arial", 20, bold=True)
|
|
font_text = pygame.font.SysFont("Arial", 16, bold=False)
|
|
|
|
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
|
|
pygame.display.set_caption("Libélula")
|
|
|
|
glEnable(GL_DEPTH_TEST)
|
|
glClearColor(*COLOR_BG, 1.0)
|
|
|
|
glMatrixMode(GL_PROJECTION)
|
|
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
|
|
glMatrixMode(GL_MODELVIEW)
|
|
|
|
buzz_sound = generate_buzz_sound()
|
|
wind_sound = generate_wind_sound()
|
|
|
|
px, py, pz = 0.0, 0.0, 15.0
|
|
yaw = 0.0
|
|
roll = 0.0
|
|
pitch = 0.0
|
|
max_roll = 30.0
|
|
max_pitch = 20.0
|
|
|
|
speed = 0.0
|
|
max_speed = 3.0
|
|
acceleration = 0.06
|
|
deceleration = 0.03
|
|
|
|
min_altitude = 1.0
|
|
max_altitude = 120.0
|
|
|
|
wing_timer = 0.0
|
|
water_timer = 0.0
|
|
is_perched = False
|
|
|
|
dive_phase = "none"
|
|
pre_dive_alt = 15.0
|
|
pre_dive_speed = 0.0
|
|
|
|
auto_landing = False
|
|
target_perch = None
|
|
|
|
caught_insect = None
|
|
eaten_mosquitos = 0
|
|
eaten_flies = 0
|
|
|
|
frog = Frog()
|
|
|
|
purple_dragonflies = [
|
|
PurpleDragonfly(1),
|
|
PurpleDragonfly(2),
|
|
PurpleDragonfly(3)
|
|
]
|
|
|
|
insects = []
|
|
num_mosquitos = random.randint(MOSQUITO_MIN, MOSQUITO_MAX)
|
|
num_flies = random.randint(FLY_MIN, FLY_MAX)
|
|
|
|
for _ in range(num_mosquitos):
|
|
insects.append(Insect("mosquito"))
|
|
for _ in range(num_flies):
|
|
insects.append(Insect("fly"))
|
|
|
|
clock = pygame.time.Clock()
|
|
|
|
in_start_screen = True
|
|
running = True
|
|
|
|
buzz_timer = 0
|
|
|
|
while running:
|
|
dt = clock.tick(60) / 1000.0
|
|
water_timer += dt
|
|
wing_timer += 60.0
|
|
|
|
for event in pygame.event.get():
|
|
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
|
|
running = False
|
|
elif event.type == KEYDOWN:
|
|
if in_start_screen:
|
|
in_start_screen = False
|
|
else:
|
|
if event.key == K_SPACE:
|
|
if not is_perched and dive_phase == "none":
|
|
dive_phase = "diving"
|
|
pre_dive_alt = pz
|
|
pre_dive_speed = speed
|
|
auto_landing = False
|
|
elif event.key == K_p:
|
|
if not is_perched and dive_phase == "none":
|
|
best_reed = None
|
|
min_d = 999.0
|
|
for rx, ry, rz, rh in REEDS:
|
|
dist = math.hypot(px - rx, py - ry)
|
|
if dist < 35.0:
|
|
angle_to_reed = math.degrees(math.atan2(ry - py, rx - px))
|
|
heading = (angle_to_reed - (90 - yaw)) % 360
|
|
if heading > 180:
|
|
heading -= 360
|
|
if abs(heading) < 45 and dist < min_d:
|
|
min_d = dist
|
|
best_reed = (rx, ry, rz + rh)
|
|
|
|
if best_reed:
|
|
target_perch = best_reed
|
|
auto_landing = True
|
|
|
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
|
glLoadIdentity()
|
|
|
|
if in_start_screen:
|
|
cam_x, cam_y, cam_z = 0.0, -200.0, 50.0
|
|
gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1)
|
|
|
|
draw_map(water_timer)
|
|
frog.update()
|
|
frog.draw()
|
|
for p_df in purple_dragonflies:
|
|
p_df.update()
|
|
p_df.draw()
|
|
|
|
wing_angle = math.sin(wing_timer * 0.5) * 35.0
|
|
draw_start_screen(font_title, font_subtitle, font_text, wing_angle, frog)
|
|
else:
|
|
keys = pygame.key.get_pressed()
|
|
|
|
if is_perched:
|
|
if keys[K_LEFT]:
|
|
yaw += 3.0
|
|
if keys[K_RIGHT]:
|
|
yaw -= 3.0
|
|
|
|
if keys[K_w]:
|
|
is_perched = False
|
|
speed = 0.5
|
|
|
|
wing_angle = 0.0
|
|
pitch = 0.0
|
|
roll = 0.0
|
|
dive_phase = "none"
|
|
auto_landing = False
|
|
else:
|
|
if auto_landing and target_perch:
|
|
tx, ty, tz = target_perch
|
|
dx = tx - px
|
|
dy = ty - py
|
|
dist_to_target = math.hypot(dx, dy)
|
|
|
|
speed = 1.2
|
|
px += (dx / dist_to_target) * speed if dist_to_target > 0.1 else 0
|
|
py += (dy / dist_to_target) * speed if dist_to_target > 0.1 else 0
|
|
pz += (tz - pz) * 0.1
|
|
|
|
if dist_to_target < 2.5 and abs(pz - tz) < 2.5:
|
|
px, py, pz = target_perch
|
|
speed = 0.0
|
|
roll = 0.0
|
|
pitch = 0.0
|
|
is_perched = True
|
|
auto_landing = False
|
|
target_perch = None
|
|
|
|
if caught_insect:
|
|
if caught_insect.type == "mosquito":
|
|
eaten_mosquitos += 1
|
|
else:
|
|
eaten_flies += 1
|
|
caught_insect = None
|
|
|
|
elif dive_phase == "diving":
|
|
speed = max_speed * 1.5
|
|
pz -= 1.2
|
|
target_pitch = -45.0
|
|
if wind_sound and not pygame.mixer.get_busy():
|
|
wind_sound.play()
|
|
|
|
if pz <= min_altitude:
|
|
pz = min_altitude
|
|
dive_phase = "recovering"
|
|
|
|
elif dive_phase == "recovering":
|
|
target_pitch = 30.0
|
|
pz += 0.8
|
|
speed += (pre_dive_speed - speed) * 0.1
|
|
|
|
if pz >= pre_dive_alt:
|
|
pz = pre_dive_alt
|
|
speed = pre_dive_speed
|
|
dive_phase = "none"
|
|
else:
|
|
if keys[K_w]:
|
|
speed = min(speed + acceleration, max_speed)
|
|
if speed > 2.0 and wind_sound and random.random() < 0.1 and not pygame.mixer.get_busy():
|
|
wind_sound.play()
|
|
elif keys[K_s]:
|
|
speed = max(speed - deceleration, 0.0)
|
|
|
|
if speed < 0.3:
|
|
for rx, ry, rz, rh in REEDS:
|
|
dist = math.hypot(px - rx, py - ry)
|
|
if dist < 10.0 and abs(pz - (rz + rh)) < 10.0:
|
|
px, py, pz = rx, ry, rz + rh
|
|
speed = 0.0
|
|
roll = 0.0
|
|
pitch = 0.0
|
|
is_perched = True
|
|
|
|
if caught_insect:
|
|
if caught_insect.type == "mosquito":
|
|
eaten_mosquitos += 1
|
|
else:
|
|
eaten_flies += 1
|
|
caught_insect = None
|
|
break
|
|
|
|
target_pitch = 0.0
|
|
if keys[K_UP]:
|
|
pz = min(pz + 0.8, max_altitude)
|
|
target_pitch = max_pitch
|
|
elif keys[K_DOWN]:
|
|
pz = max(pz - 0.8, min_altitude)
|
|
target_pitch = -max_pitch
|
|
|
|
if not auto_landing:
|
|
pitch += (target_pitch - pitch) * 0.15
|
|
|
|
target_roll = 0.0
|
|
if keys[K_LEFT]:
|
|
yaw += 2.8
|
|
target_roll = -max_roll
|
|
elif keys[K_RIGHT]:
|
|
yaw -= 2.8
|
|
target_roll = max_roll
|
|
|
|
roll += (target_roll - roll) * 0.15
|
|
|
|
rad_yaw = math.radians(yaw)
|
|
px += -math.sin(rad_yaw) * speed
|
|
py += math.cos(rad_yaw) * speed
|
|
|
|
if abs(py) < POND_RADIUS:
|
|
max_limit_x = POND_RADIUS + 200.0
|
|
else:
|
|
cx = get_river_center(py)
|
|
max_limit_x = abs(cx) + 200.0
|
|
|
|
px = max(-max_limit_x, min(max_limit_x, px))
|
|
|
|
if py > STREAM_MAX_Y:
|
|
py -= WORLD_LEN
|
|
elif py < STREAM_MIN_Y:
|
|
py += WORLD_LEN
|
|
|
|
wing_timer += 80.0 if (dive_phase != "none" or auto_landing) else (60.0 if speed > 0.1 else 30.0)
|
|
wing_angle = math.sin(wing_timer) * 35.0
|
|
|
|
frog.update()
|
|
|
|
for p_df in purple_dragonflies:
|
|
p_df.update()
|
|
|
|
current_mosquitos = 0
|
|
current_flies = 0
|
|
near_insect = False
|
|
|
|
for insect in insects[:]:
|
|
insect.update((px, py, pz))
|
|
|
|
if insect.type == "mosquito":
|
|
current_mosquitos += 1
|
|
else:
|
|
current_flies += 1
|
|
|
|
dist_to_df = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
|
|
if dist_to_df < 40.0:
|
|
near_insect = True
|
|
|
|
if not caught_insect:
|
|
if dist_to_df < 6.0:
|
|
caught_insect = insect
|
|
insects.remove(insect)
|
|
if dive_phase == "diving":
|
|
dive_phase = "recovering"
|
|
|
|
if near_insect and buzz_sound:
|
|
buzz_timer += 1
|
|
if buzz_timer > 25 and not pygame.mixer.get_busy():
|
|
buzz_sound.play()
|
|
buzz_timer = 0
|
|
|
|
if current_mosquitos < MOSQUITO_MIN:
|
|
insects.append(Insect("mosquito"))
|
|
if current_flies < FLY_MIN:
|
|
insects.append(Insect("fly"))
|
|
|
|
cam_dist = 18.0
|
|
cam_height = 8.0
|
|
rad_yaw = math.radians(yaw)
|
|
|
|
cam_x = px + math.sin(rad_yaw) * cam_dist
|
|
cam_y = py - math.cos(rad_yaw) * cam_dist
|
|
cam_z = pz + cam_height
|
|
|
|
gluLookAt(cam_x, cam_y, cam_z,
|
|
px, py, pz + 0.5,
|
|
0, 0, 1)
|
|
|
|
draw_map(water_timer)
|
|
frog.draw()
|
|
|
|
for p_df in purple_dragonflies:
|
|
p_df.draw()
|
|
|
|
for insect in insects:
|
|
insect.draw()
|
|
|
|
glPushMatrix()
|
|
glTranslatef(px, py, pz)
|
|
glRotatef(yaw, 0, 0, 1)
|
|
glRotatef(pitch, 1, 0, 0)
|
|
glRotatef(roll, 0, 1, 0)
|
|
draw_dragonfly(wing_angle, caught_insect)
|
|
glPopMatrix()
|
|
|
|
draw_hud((px, py, pz), yaw, insects, eaten_mosquitos, eaten_flies, font)
|
|
|
|
pygame.display.flip()
|
|
|
|
pygame.quit()
|
|
sys.exit()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|