Ajuste velocidad alas libelula

This commit is contained in:
luisgulo 2026-10-09 18:59:13 +02:00
parent 34177bf951
commit f4ca4e3b50
23 changed files with 0 additions and 19861 deletions

View File

@ -1,555 +0,0 @@
import sys
import math
import random
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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.2, 0.2) # Mosquitos (Rojo)
COLOR_FLY = (1.0, 0.9, 0.0) # Moscas (Amarillo/Dorado)
# Dimensiones del Mundo
POND_RADIUS = 1800.0 # Estanque de 1800u de superficie
STREAM_LEN = 900.0 # Longitud de cada arroyo
STREAM_MIN_Y = -(POND_RADIUS + STREAM_LEN) # -2700.0
STREAM_MAX_Y = (POND_RADIUS + STREAM_LEN) # 2700.0
WORLD_LEN = STREAM_MAX_Y - STREAM_MIN_Y
# Cantidades de insectos
MOSQUITO_MIN, MOSQUITO_MAX = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# ELEMENTOS DEL MAPA
# ==========================================
REEDS = [
# Juncos en islas del estanque principal
(-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),
# Juncos en el arroyo inferior
(-90, -1950, 0, 20), (-110, -2150, 0, 23), (-80, -2350, 0, 19), (-100, -2550, 0, 22),
# Juncos en el arroyo superior
(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):
"""Devuelve el centro X del arroyo fuera de los límites del estanque central."""
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):
"""Comprueba si unas coordenadas (x, y) están dentro del agua."""
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():
"""Genera una posición inicial aleatoria sobre el agua."""
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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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: # Fly
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":
if 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)
# Cuerpo
glVertex3f(0, s, 0)
glVertex3f(0, -s, 0)
# Alas
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()
# ==========================================
# RENDERIZADO DEL ESCENARIO
# ==========================================
def draw_water_surface(time_counter):
"""Dibuja una malla 3D animada para el agua."""
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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():
"""Dibuja el terreno del mapa."""
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)
# Orilla Izquierda
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()
# Orilla Derecha
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):
"""Dibuja todo el mapa."""
draw_terrain()
draw_water_surface(time_counter)
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
for rx, ry, rz, rh in REEDS:
glVertex3f(rx, ry, rz)
glVertex3f(rx, ry, rz + rh)
glVertex3f(rx, ry, rz)
glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7)
glVertex3f(rx, ry, rz)
glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8)
glEnd()
# ==========================================
# MODELO 3D DE LA LIBÉLULA
# ==========================================
def draw_dragonfly(wing_angle, caught_insect=None):
"""Dibuja la libélula y el insecto atrapado en sus patas si tiene uno."""
glColor3f(*COLOR_DRAGONFLY)
# Cuerpo
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)
# Patas (Visibles sujetando al insecto)
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()
# Cabeza
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()
# Alas
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()
# Si lleva un insecto atrapado, se dibuja sujeto bajo su tórax
if caught_insect:
glPushMatrix()
glTranslatef(0.0, 0.2, -0.6) # Posición bajo las patas
caught_insect.draw(attached=True)
glPopMatrix()
# ==========================================
# BUCLE PRINCIPAL Y LÓGICA
# ==========================================
def main():
pygame.init()
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
# Estado de Caza
caught_insect = None # Insecto sujeto actualmente en las patas
eaten_mosquitos = 0 # Contador de mosquitos comidos
eaten_flies = 0 # Contador de moscas comidas
def update_title():
status = " (Llevando presa a un junco...)" if caught_insect else ""
pygame.display.set_caption(
f"Libélula 3D | Mosquitos cazados: {eaten_mosquitos} | Moscas cazadas: {eaten_flies}{status}"
)
update_title()
# Inicializar Insectos
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
running = False
keys = pygame.key.get_pressed()
# CONTROLES Y MOVIMIENTO DE LA LIBÉLULA
if is_perched:
if keys[K_LEFT]:
yaw += 3.0
if keys[K_RIGHT]:
yaw -= 3.0
if keys[K_UP]:
is_perched = False
speed = 0.5
wing_angle = 0.0
pitch = 0.0
roll = 0.0
else:
if keys[K_UP]:
speed = min(speed + acceleration, max_speed)
elif keys[K_DOWN]:
speed = max(speed - deceleration, 0.0)
# Intentar posarse en la punta de un junco
if speed < 0.3:
for rx, ry, rz, rh in REEDS:
dist = math.hypot(px - rx, py - ry)
if dist < 8.0 and abs(pz - (rz + rh)) < 8.0:
px, py, pz = rx, ry, rz + rh
speed = 0.0
roll = 0.0
pitch = 0.0
is_perched = True
# Si lleva una presa al posarse, se la come
if caught_insect:
if caught_insect.type == "mosquito":
eaten_mosquitos += 1
else:
eaten_flies += 1
caught_insect = None
update_title()
break
target_pitch = 0.0
if keys[K_w]:
pz = min(pz + 0.8, max_altitude)
target_pitch = max_pitch
elif keys[K_s]:
pz = max(pz - 0.8, min_altitude)
target_pitch = -max_pitch
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 += 60.0 if speed > 0.1 else 30.0
wing_angle = math.sin(wing_timer) * 35.0
# ACTUALIZACIÓN DE INSECTOS
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
# Detección de contacto/choque con la libélula
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0: # Rozamiento o choque
caught_insect = insect
insects.remove(insect)
update_title()
# Mantener poblaciones dentro del rango
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
# RENDERIZADO
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
# Dibujar Insectos en el mapa
for insect in insects:
insect.draw()
# Dibujar Libélula (con el insecto en las patas si lo ha capturado)
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()
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()

View File

@ -1,702 +0,0 @@
import sys
import math
import random
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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.2, 0.2) # Mosquitos (Rojo)
COLOR_FLY = (1.0, 0.9, 0.0) # Moscas (Amarillo/Dorado)
COLOR_HUD = (1.0, 1.0, 1.0) # Retícula HUD (Blanco)
# Dimensiones del Mundo
POND_RADIUS = 1800.0 # Estanque de 1800u de superficie
STREAM_LEN = 900.0 # Longitud de cada arroyo
STREAM_MIN_Y = -(POND_RADIUS + STREAM_LEN) # -2700.0
STREAM_MAX_Y = (POND_RADIUS + STREAM_LEN) # 2700.0
WORLD_LEN = STREAM_MAX_Y - STREAM_MIN_Y
# Cantidades de insectos
MOSQUITO_MIN, MOSQUITO_MAX = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# ELEMENTOS DEL MAPA
# ==========================================
REEDS = [
# Juncos en islas del estanque principal
(-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),
# Juncos en el arroyo inferior
(-90, -1950, 0, 20), (-110, -2150, 0, 23), (-80, -2350, 0, 19), (-100, -2550, 0, 22),
# Juncos en el arroyo superior
(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):
"""Devuelve el centro X del arroyo fuera de los límites del estanque central."""
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):
"""Comprueba si unas coordenadas (x, y) están dentro del agua."""
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():
"""Genera una posición inicial aleatoria sobre el agua."""
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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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: # Fly
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":
if 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()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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)
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
for rx, ry, rz, rh in REEDS:
glVertex3f(rx, ry, rz)
glVertex3f(rx, ry, rz + rh)
glVertex3f(rx, ry, rz)
glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7)
glVertex3f(rx, ry, rz)
glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8)
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 / RADAR / HUD
# ==========================================
def draw_hud(player_pos, yaw, insects, eaten_mosquitos, eaten_flies, font):
"""Dibuja el Radar central y los contadores en 2D superpuestos."""
glDisable(GL_DEPTH_TEST)
# Cambiar a proyección 2D ortogonal
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
gluOrtho2D(0, WIDTH, 0, HEIGHT)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
px, py, pz = player_pos
# ----------------------------------
# 1. RADAR / MIRILLA (Centro Superior)
# ----------------------------------
cx, cy = WIDTH // 2, HEIGHT - 70
radius = 45
# Dibujar Círculo exterior
glColor3f(*COLOR_HUD)
glBegin(GL_LINE_LOOP)
for i in range(32):
angle = 2.0 * math.pi * i / 32
glVertex2f(cx + radius * math.cos(angle), cy + radius * math.sin(angle))
glEnd()
# Dibujar Cruz Central y Horizonte en línea discontinua
glBegin(GL_LINES)
glVertex2f(cx, cy + radius)
glVertex2f(cx, cy + radius - 10)
glVertex2f(cx, cy - radius)
glVertex2f(cx, cy - radius + 10)
glVertex2f(cx - radius, cy)
glVertex2f(cx - radius + 10, cy)
glVertex2f(cx + radius, cy)
glVertex2f(cx + radius - 10, cy)
glEnd()
# Cruz pequeña central
glBegin(GL_LINES)
glVertex2f(cx - 4, cy)
glVertex2f(cx + 4, cy)
glVertex2f(cx, cy - 4)
glVertex2f(cx, cy + 4)
glEnd()
# Buscar el insecto más cercano al frente
closest_insect = None
min_dist = 999999.0
relative_angle = 0.0
diff_z = 0.0
rad_yaw = math.radians(yaw)
# Vector dirección del jugador
dir_x = -math.sin(rad_yaw)
dir_y = math.cos(rad_yaw)
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:
# Calcular ángulo relativo respecto a hacia dónde mira la libélula
angle_to_insect = math.degrees(math.atan2(dy, dx))
# Ajuste de ángulo respecto al YAW
heading = (angle_to_insect - (90 - yaw)) % 360
if heading > 180:
heading -= 360
# Solo mostrar si está en un cono frontal de visión (120 grados)
if abs(heading) < 60:
min_dist = dist
closest_insect = insect
relative_angle = heading
diff_z = insect.z - pz # Por encima (+) o debajo (-)
# Si hay una presa cercana al frente, dibujarla en la mirilla del radar
if closest_insect:
# Mapear posición horizontal (-60° a +60° -> -radius a +radius)
radar_x = cx - (relative_angle / 60.0) * (radius - 8)
# Mapear altura (-20u a +20u de diferencia -> -radius a +radius)
radar_y = cy + (diff_z / 20.0) * (radius - 8)
# Clampear dentro del círculo del radar
dist_from_center = math.hypot(radar_x - cx, radar_y - cy)
if dist_from_center > (radius - 6):
factor = (radius - 6) / dist_from_center
radar_x = cx + (radar_x - cx) * factor
radar_y = cy + (radar_y - cy) * factor
# Color del punto según la especie
if closest_insect.type == "mosquito":
glColor3f(*COLOR_MOSQUITO)
else:
glColor3f(*COLOR_FLY)
# Dibujar punto e indicador de la presa
glBegin(GL_TRIANGLE_FAN)
for i in range(12):
ang = 2.0 * math.pi * i / 12
glVertex2f(radar_x + 4 * math.cos(ang), radar_y + 4 * math.sin(ang))
glEnd()
# Pequeñas alas en el radar
glBegin(GL_LINES)
glVertex2f(radar_x - 7, radar_y)
glVertex2f(radar_x + 7, radar_y)
glEnd()
# ----------------------------------
# 2. ICONOS Y CONTADORES HUD
# ----------------------------------
# Marcador Izquierdo: Mosquitos
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
# Dibujar pequeño ícono de mosquito
glVertex2f(25, HEIGHT - 25)
glVertex2f(25, HEIGHT - 45)
glVertex2f(25, HEIGHT - 35)
glVertex2f(10, HEIGHT - 35)
glVertex2f(25, HEIGHT - 35)
glVertex2f(40, HEIGHT - 35)
glEnd()
# Marcador Derecho: Moscas
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
# Dibujar pequeño ícono de mosca
glVertex2f(WIDTH - 45, HEIGHT - 25)
glVertex2f(WIDTH - 45, HEIGHT - 45)
glVertex2f(WIDTH - 45, HEIGHT - 35)
glVertex2f(WIDTH - 60, HEIGHT - 35)
glVertex2f(WIDTH - 45, HEIGHT - 35)
glVertex2f(WIDTH - 30, HEIGHT - 35)
glEnd()
# Restaurar matrices 3D
glPopMatrix()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glEnable(GL_DEPTH_TEST)
# Renderizar el texto de los contadores mediante imágenes PyGame
txt_mosq = font.render(f"x {eaten_mosquitos}", True, (255, 60, 60))
txt_flies = font.render(f"x {eaten_flies}", True, (255, 230, 0))
surface = pygame.display.get_surface()
# Crear textura rápida para el texto en pantalla
draw_text_pygame(txt_mosq, 50, 20)
draw_text_pygame(txt_flies, WIDTH - 110, 20)
def draw_text_pygame(text_surface, x, y):
"""Dibuja texto de PyGame en la pantalla de OpenGL."""
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)
glRasterPos2d(x, HEIGHT - y - h)
glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, text_data)
glDisable(GL_BLEND)
# ==========================================
# BUCLE PRINCIPAL Y LÓGICA
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 26, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
# Estado de Caza
caught_insect = None
eaten_mosquitos = 0
eaten_flies = 0
# Inicializar Insectos
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
running = False
keys = pygame.key.get_pressed()
# CONTROLES Y MOVIMIENTO DE LA LIBÉLULA
if is_perched:
if keys[K_LEFT]:
yaw += 3.0
if keys[K_RIGHT]:
yaw -= 3.0
if keys[K_UP]:
is_perched = False
speed = 0.5
wing_angle = 0.0
pitch = 0.0
roll = 0.0
else:
if keys[K_UP]:
speed = min(speed + acceleration, max_speed)
elif keys[K_DOWN]:
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 < 8.0 and abs(pz - (rz + rh)) < 8.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_w]:
pz = min(pz + 0.8, max_altitude)
target_pitch = max_pitch
elif keys[K_s]:
pz = max(pz - 0.8, min_altitude)
target_pitch = -max_pitch
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 += 60.0 if speed > 0.1 else 30.0
wing_angle = math.sin(wing_timer) * 35.0
# ACTUALIZACIÓN DE INSECTOS
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
# RENDERIZADO
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
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()
# DIBUJAR INTERFAZ (HUD) Y RADAR
draw_hud((px, py, pz), yaw, insects, eaten_mosquitos, eaten_flies, font)
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()

View File

@ -1,675 +0,0 @@
import sys
import math
import random
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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo)
COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón)
COLOR_HUD = (1.0, 1.0, 1.0) # Retícula HUD (Blanco)
# Dimensiones del Mundo
POND_RADIUS = 1800.0 # Estanque de 1800u de superficie
STREAM_LEN = 900.0 # Longitud de cada arroyo
STREAM_MIN_Y = -(POND_RADIUS + STREAM_LEN) # -2700.0
STREAM_MAX_Y = (POND_RADIUS + STREAM_LEN) # 2700.0
WORLD_LEN = STREAM_MAX_Y - STREAM_MIN_Y
# Cantidades de insectos
MOSQUITO_MIN, MOSQUITO_MAX = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# ELEMENTOS DEL MAPA
# ==========================================
REEDS = [
# Juncos en islas del estanque principal
(-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),
# Juncos en el arroyo inferior
(-90, -1950, 0, 20), (-110, -2150, 0, 23), (-80, -2350, 0, 19), (-100, -2550, 0, 22),
# Juncos en el arroyo superior
(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):
"""Devuelve el centro X del arroyo fuera de los límites del estanque central."""
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):
"""Comprueba si unas coordenadas (x, y) están dentro del agua."""
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():
"""Genera una posición inicial aleatoria sobre el agua."""
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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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: # Fly
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":
if 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()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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)
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
for rx, ry, rz, rh in REEDS:
glVertex3f(rx, ry, rz)
glVertex3f(rx, ry, rz + rh)
glVertex3f(rx, ry, rz)
glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7)
glVertex3f(rx, ry, rz)
glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8)
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 / RADAR REDUCIDO / HUD
# ==========================================
def draw_hud(player_pos, yaw, insects, eaten_mosquitos, eaten_flies, font):
"""Dibuja el Radar reducido y los contadores en 2D superpuestos fijados arriba."""
glDisable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
gluOrtho2D(0, WIDTH, 0, HEIGHT)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
px, py, pz = player_pos
# ----------------------------------
# 1. RADAR REDUCIDO A LA MITAD (Centro Superior)
# ----------------------------------
cx, cy = WIDTH // 2, HEIGHT - 35
radius = 22 # Tamaño reducido a la mitad (era 45)
# Dibujar Círculo exterior
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()
# Dibujar Cruz Central
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()
# Cruz pequeña central
glBegin(GL_LINES)
glVertex2f(cx - 2, cy)
glVertex2f(cx + 2, cy)
glVertex2f(cx, cy - 2)
glVertex2f(cx, cy + 2)
glEnd()
# Buscar el insecto más cercano al frente
closest_insect = None
min_dist = 999999.0
relative_angle = 0.0
diff_z = 0.0
rad_yaw = math.radians(yaw)
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
# Si hay una presa cercana al frente, se dibuja un pequeño círculo genérico blanco
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
# Círculo vacío blanco (genérico)
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()
# ----------------------------------
# 2. ICONOS Y CONTADORES HUD FIJOS
# ----------------------------------
# Marcador Izquierdo: Mosquitos (Amarillo)
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 20)
glVertex2f(25, HEIGHT - 40)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
# Marcador Derecho: Moscas (Rojo-Marrón)
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 45, HEIGHT - 20)
glVertex2f(WIDTH - 45, HEIGHT - 40)
glVertex2f(WIDTH - 45, HEIGHT - 30)
glVertex2f(WIDTH - 58, HEIGHT - 30)
glVertex2f(WIDTH - 45, HEIGHT - 30)
glVertex2f(WIDTH - 32, HEIGHT - 30)
glEnd()
glPopMatrix()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glEnable(GL_DEPTH_TEST)
# Textos fija de conteo
txt_mosq = font.render(f"x {eaten_mosquitos}", True, (255, 230, 0)) # Texto amarillo
txt_flies = font.render(f"x {eaten_flies}", True, (200, 65, 40)) # Texto rojo-marrón
draw_text_pygame(txt_mosq, 48, 18)
draw_text_pygame(txt_flies, WIDTH - 110, 18)
def draw_text_pygame(text_surface, x, y):
"""Dibuja texto de PyGame en la pantalla de OpenGL."""
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)
glRasterPos2d(x, HEIGHT - y - h)
glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, text_data)
glDisable(GL_BLEND)
# ==========================================
# BUCLE PRINCIPAL Y LÓGICA
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
# Estado de Caza
caught_insect = None
eaten_mosquitos = 0
eaten_flies = 0
# Inicializar Insectos
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
running = False
keys = pygame.key.get_pressed()
# CONTROLES Y MOVIMIENTO DE LA LIBÉLULA
if is_perched:
if keys[K_LEFT]:
yaw += 3.0
if keys[K_RIGHT]:
yaw -= 3.0
if keys[K_UP]:
is_perched = False
speed = 0.5
wing_angle = 0.0
pitch = 0.0
roll = 0.0
else:
if keys[K_UP]:
speed = min(speed + acceleration, max_speed)
elif keys[K_DOWN]:
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 < 8.0 and abs(pz - (rz + rh)) < 8.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_w]:
pz = min(pz + 0.8, max_altitude)
target_pitch = max_pitch
elif keys[K_s]:
pz = max(pz - 0.8, min_altitude)
target_pitch = -max_pitch
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 += 60.0 if speed > 0.1 else 30.0
wing_angle = math.sin(wing_timer) * 35.0
# ACTUALIZACIÓN DE INSECTOS
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
# RENDERIZADO
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
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()
# DIBUJAR INTERFAZ (HUD) Y RADAR
draw_hud((px, py, pz), yaw, insects, eaten_mosquitos, eaten_flies, font)
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()

View File

@ -1,668 +0,0 @@
import sys
import math
import random
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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo)
COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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":
if 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()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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)
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
for rx, ry, rz, rh in REEDS:
glVertex3f(rx, ry, rz)
glVertex3f(rx, ry, rz + rh)
glVertex3f(rx, ry, rz)
glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7)
glVertex3f(rx, ry, rz)
glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8)
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 / RADAR Y TEXTOS FIJOS EN PANTALLA
# ==========================================
def draw_text_2d(text_surface, x, y):
"""Renderiza texto de PyGame fijado en coordenadas 2D absolutas de pantalla."""
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)
#glRasterPos utiliza la matriz 2D activa
glRasterPos2i(x, y)
glDrawPixels(w, h, GL_RGBA, GL_UNSIGNED_BYTE, text_data)
glDisable(GL_BLEND)
def draw_hud(player_pos, yaw, insects, eaten_mosquitos, eaten_flies, font):
"""Dibuja todo el HUD fijado en el plano 2D de la pantalla."""
glDisable(GL_DEPTH_TEST)
# 1. Configurar proyección 2D ortogonal estricta
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
gluOrtho2D(0, WIDTH, 0, HEIGHT)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
px, py, pz = player_pos
# ----------------------------------
# A. RADAR REDUCIDO (Centro Superior)
# ----------------------------------
cx, cy = WIDTH // 2, HEIGHT - 35
radius = 22
# Círculo del radar
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()
# Cruz
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()
# Buscar presa cercana al frente
closest_insect = None
min_dist = 999999.0
relative_angle = 0.0
diff_z = 0.0
rad_yaw = math.radians(yaw)
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
# Círculo blanco genérico en radar
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()
# ----------------------------------
# B. ÍCONOS SUPERIORES FIJOS
# ----------------------------------
# Ícono Mosquito (Izquierda - Amarillo)
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 20)
glVertex2f(25, HEIGHT - 40)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
# Ícono Mosca (Derecha - Rojo/Marrón)
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 20)
glVertex2f(WIDTH - 85, HEIGHT - 40)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 72, HEIGHT - 30)
glEnd()
# ----------------------------------
# C. RENDERIZADO DE TEXTOS 2D FIJOS
# ----------------------------------
txt_mosq = font.render(f"x {eaten_mosquitos}", True, (255, 230, 0))
txt_flies = font.render(f"x {eaten_flies}", True, (200, 65, 40))
# Posicionar texto exacto en coordenadas de pantalla 2D
draw_text_2d(txt_mosq, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
# Restaurar matrices
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
# Estado de Caza
caught_insect = None
eaten_mosquitos = 0
eaten_flies = 0
# Inicializar Insectos
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
running = False
keys = pygame.key.get_pressed()
# CONTROLES Y MOVIMIENTO DE LA LIBÉLULA
if is_perched:
if keys[K_LEFT]:
yaw += 3.0
if keys[K_RIGHT]:
yaw -= 3.0
if keys[K_UP]:
is_perched = False
speed = 0.5
wing_angle = 0.0
pitch = 0.0
roll = 0.0
else:
if keys[K_UP]:
speed = min(speed + acceleration, max_speed)
elif keys[K_DOWN]:
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 < 8.0 and abs(pz - (rz + rh)) < 8.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_w]:
pz = min(pz + 0.8, max_altitude)
target_pitch = max_pitch
elif keys[K_s]:
pz = max(pz - 0.8, min_altitude)
target_pitch = -max_pitch
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 += 60.0 if speed > 0.1 else 30.0
wing_angle = math.sin(wing_timer) * 35.0
# ACTUALIZACIÓN DE INSECTOS
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
# RENDERIZADO
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
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()
# DIBUJAR INTERFAZ (HUD) Y TEXTOS FIJOS EN 2D
draw_hud((px, py, pz), yaw, insects, eaten_mosquitos, eaten_flies, font)
pygame.display.flip()
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()

View File

@ -1,649 +0,0 @@
import sys
import math
import random
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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo)
COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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: # Fly
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":
# VENTAJA TÁCTICA: Si la libélula vuela por encima de ellos (pz > self.z),
# los mosquitos NO la detectan y no intentan huir.
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":
# Las moscas sí detectan siempre a la libélula desde cualquier ángulo/altura
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()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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)
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
for rx, ry, rz, rh in REEDS:
glVertex3f(rx, ry, rz)
glVertex3f(rx, ry, rz + rh)
glVertex3f(rx, ry, rz)
glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7)
glVertex3f(rx, ry, rz)
glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8)
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 / RADAR Y TEXTOS FIJOS
# ==========================================
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_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
# RADAR REDUCIDO
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
rad_yaw = math.radians(yaw)
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()
# ÍCONOS SUPERIORES
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 20)
glVertex2f(25, HEIGHT - 40)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 20)
glVertex2f(WIDTH - 85, HEIGHT - 40)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 32, HEIGHT - 30)
glEnd()
# TEXTOS
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, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
caught_insect = None
eaten_mosquitos = 0
eaten_flies = 0
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
running = False
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_UP]:
is_perched = False
speed = 0.5
wing_angle = 0.0
pitch = 0.0
roll = 0.0
else:
if keys[K_UP]:
speed = min(speed + acceleration, max_speed)
elif keys[K_DOWN]:
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 < 8.0 and abs(pz - (rz + rh)) < 8.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_w]:
pz = min(pz + 0.8, max_altitude)
target_pitch = max_pitch
elif keys[K_s]:
pz = max(pz - 0.8, min_altitude)
target_pitch = -max_pitch
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 += 60.0 if speed > 0.1 else 30.0
wing_angle = math.sin(wing_timer) * 35.0
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
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()

View File

@ -1,676 +0,0 @@
import sys
import math
import random
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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo)
COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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: # Fly
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()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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)
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
for rx, ry, rz, rh in REEDS:
glVertex3f(rx, ry, rz)
glVertex3f(rx, ry, rz + rh)
glVertex3f(rx, ry, rz)
glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7)
glVertex3f(rx, ry, rz)
glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8)
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 / RADAR Y TEXTOS FIJOS
# ==========================================
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_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
# RADAR REDUCIDO
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
rad_yaw = math.radians(yaw)
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()
# ÍCONOS SUPERIORES
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 20)
glVertex2f(25, HEIGHT - 40)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 20)
glVertex2f(WIDTH - 85, HEIGHT - 40)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 32, HEIGHT - 30)
glEnd()
# TEXTOS
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, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
is_diving = False # Estado de picado automático
caught_insect = None
eaten_mosquitos = 0
eaten_flies = 0
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
running = False
# Activar movimiento automático de PICADO al pulsar la barra ESPACIO
elif event.type == KEYDOWN and event.key == K_SPACE and not is_perched:
is_diving = True
keys = pygame.key.get_pressed()
if is_perched:
# En el junco, las flechas izquierda/derecha giran
if keys[K_LEFT]:
yaw += 3.0
if keys[K_RIGHT]:
yaw -= 3.0
# Despegar con W (Acelerar)
if keys[K_w]:
is_perched = False
speed = 0.5
wing_angle = 0.0
pitch = 0.0
roll = 0.0
is_diving = False
else:
# ----------------------------------
# CONTROLES Y MOVIMIENTO REORDENADOS
# ----------------------------------
if is_diving:
# MODO PICADO AUTOMÁTICO (Caída a 45º a máxima velocidad)
speed = max_speed * 1.5
pz -= 1.2
target_pitch = -45.0
# Desactivar picado al tocar suelo/agua o soltar control
if pz <= min_altitude:
pz = min_altitude
is_diving = False
else:
# Controles estándar
# W: Acelerar / S: Frenar (hasta vuelo estático)
if keys[K_w]:
speed = min(speed + acceleration, max_speed)
elif keys[K_s]:
speed = max(speed - deceleration, 0.0)
# Si está casi detenida, intentar posarse en un junco cercano
if speed < 0.3:
for rx, ry, rz, rh in REEDS:
dist = math.hypot(px - rx, py - ry)
if dist < 8.0 and abs(pz - (rz + rh)) < 8.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
# Flecha Arriba: Subir altitud / Flecha Abajo: Bajar altitud
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
# Transición suave de cabeceo (Pitch)
pitch += (target_pitch - pitch) * 0.15
# Flecha Izquierda / Derecha: Giro lateral + Alabeo (Roll)
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
# Traslación hacia adelante según la dirección (Yaw)
rad_yaw = math.radians(yaw)
px += -math.sin(rad_yaw) * speed
py += math.cos(rad_yaw) * speed
# Muros invisibles
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))
# Bucle continuo en arroyos
if py > STREAM_MAX_Y:
py -= WORLD_LEN
elif py < STREAM_MIN_Y:
py += WORLD_LEN
wing_timer += 80.0 if is_diving else (60.0 if speed > 0.1 else 30.0)
wing_angle = math.sin(wing_timer) * 35.0
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
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()

View File

@ -1,694 +0,0 @@
import sys
import math
import random
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_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo)
COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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: # Fly
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()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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)
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
for rx, ry, rz, rh in REEDS:
glVertex3f(rx, ry, rz)
glVertex3f(rx, ry, rz + rh)
glVertex3f(rx, ry, rz)
glVertex3f(rx - 1.5, ry + 0.5, rz + rh * 0.7)
glVertex3f(rx, ry, rz)
glVertex3f(rx + 1.5, ry - 0.5, rz + rh * 0.8)
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 / RADAR Y TEXTOS FIJOS
# ==========================================
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_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
# RADAR REDUCIDO
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
rad_yaw = math.radians(yaw)
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()
# ÍCONOS SUPERIORES
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 20)
glVertex2f(25, HEIGHT - 40)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 20)
glVertex2f(WIDTH - 85, HEIGHT - 40)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 32, HEIGHT - 30)
glEnd()
# TEXTOS
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, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
# Estado de Picado y Recuperación
dive_phase = "none" # "none", "diving" o "recovering"
pre_dive_alt = 15.0 # Almacena la altitud previa al picado
pre_dive_speed = 0.0 # Almacena la velocidad previa al picado
caught_insect = None
eaten_mosquitos = 0
eaten_flies = 0
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
running = False
# Activar movimiento automático de PICADO al pulsar ESPACIO
elif event.type == KEYDOWN and event.key == K_SPACE and not is_perched and dive_phase == "none":
dive_phase = "diving"
pre_dive_alt = pz
pre_dive_speed = speed
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"
else:
# ----------------------------------
# GESTIÓN DE FASES DE PICADO Y RECUPERACIÓN
# ----------------------------------
if dive_phase == "diving":
# FASE 1: CAÍDA EN PICADO A 45º Y MÁXIMA VELOCIDAD
speed = max_speed * 1.5
pz -= 1.2
target_pitch = -45.0
# Pasa a recuperación si toca agua/suelo o alcanza el límite inferior
if pz <= min_altitude:
pz = min_altitude
dive_phase = "recovering"
elif dive_phase == "recovering":
# FASE 2: RECUPERACIÓN AUTOMÁTICA HASTA ALTITUD Y VELOCIDAD PREVIAS
target_pitch = 30.0 # Inclinación ascendente
pz += 0.8
# Ajustar suavemente la velocidad a la inicial antes del picado
speed += (pre_dive_speed - speed) * 0.1
# Al alcanzar la altitud original o superior, finaliza la maniobra
if pz >= pre_dive_alt:
pz = pre_dive_alt
speed = pre_dive_speed
dive_phase = "none"
else:
# VUELO ESTÁNDAR
if keys[K_w]:
speed = min(speed + acceleration, max_speed)
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 < 8.0 and abs(pz - (rz + rh)) < 8.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
# Transición suave del ángulo Pitch
pitch += (target_pitch - pitch) * 0.15
# Giros laterales con Flechas Izquierda / Derecha
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
# Traslación
rad_yaw = math.radians(yaw)
px += -math.sin(rad_yaw) * speed
py += math.cos(rad_yaw) * speed
# Muros invisibles
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" else (60.0 if speed > 0.1 else 30.0)
wing_angle = math.sin(wing_timer) * 35.0
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
# SI CAZA MIENTRAS ESTÁ EN PICADO, INICIA INMEDIATAMENTE LA RECUPERACIÓN DE ALTITUD
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
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()

View File

@ -1,696 +0,0 @@
import sys
import math
import random
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) # Espiga marrón oscuro de los juncos
COLOR_DRAGONFLY = (0.0, 1.0, 1.0) # Libélula alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo)
COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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)
# Juncos realistas con múltiples varas en distintas direcciones y espigas marrones (cattails)
for rx, ry, rz, rh in REEDS:
# Tallo principal vertical
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
glVertex3f(rx, ry, rz)
glVertex3f(rx, ry, rz + rh)
# Varas secundarias inclinadas en diferentes direcciones
offsets = [(-1.8, 0.8), (1.5, -0.7), (-0.8, -1.6), (1.2, 1.4)]
for ox, oy in offsets:
glVertex3f(rx, ry, rz)
glVertex3f(rx + ox, ry + oy, rz + rh * 0.8)
glEnd()
# Espigas / cilindros marrón oscuro en la punta de cada vara
glColor3f(*COLOR_CATTAIL)
glBegin(GL_LINES)
# Punta principal
glVertex3f(rx, ry, rz + rh - 3.5)
glVertex3f(rx, ry, rz + rh)
# Puntas secundarias
for ox, oy in offsets:
glVertex3f(rx + ox, ry + oy, (rz + rh * 0.8) - 3.0)
glVertex3f(rx + ox, ry + oy, rz + rh * 0.8)
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 / RADAR Y TEXTOS FIJOS (LIMPIA)
# ==========================================
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_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
# 1. RADAR REDUCIDO (Centro Superior)
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
rad_yaw = math.radians(yaw)
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()
# 2. ÍCONOS SUPERIORES (SIN CRUCES, MÁS LIMPIO)
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 72, HEIGHT - 30)
glEnd()
# 3. TEXTOS
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, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
caught_insect = None
eaten_mosquitos = 0
eaten_flies = 0
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
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 and event.key == K_SPACE and not is_perched and dive_phase == "none":
dive_phase = "diving"
pre_dive_alt = pz
pre_dive_speed = speed
keys = pygame.key.get_pressed()
if is_perched:
# Flecha Izquierda / Derecha para girar posada
if keys[K_LEFT]:
yaw += 3.0
if keys[K_RIGHT]:
yaw -= 3.0
# W para acelerar y despegar
if keys[K_w]:
is_perched = False
speed = 0.5
wing_angle = 0.0
pitch = 0.0
roll = 0.0
dive_phase = "none"
else:
if dive_phase == "diving":
speed = max_speed * 1.5
pz -= 1.2
target_pitch = -45.0
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:
# W: Acelerar / S: Frenar
if keys[K_w]:
speed = min(speed + acceleration, max_speed)
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)
# Radio de detección ampliado y ajustado a la altura del junco para facilitar el aterrizaje
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
# Flecha Arriba: Subir / Flecha Abajo: Bajar
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
pitch += (target_pitch - pitch) * 0.15
# Flecha Izquierda / Derecha: Moverse lateralmente o girar en vuelo
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" else (60.0 if speed > 0.1 else 30.0)
wing_angle = math.sin(wing_timer) * 35.0
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
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()

View File

@ -1,736 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
COLOR_MOSQUITO = (1.0, 0.9, 0.0) # Mosquitos (Amarillo)
COLOR_FLY = (0.8, 0.25, 0.15) # Moscas (Rojo-Marrón)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS
# ==========================================
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()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 30
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(POND_RADIUS), 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), 20):
if abs(y) < POND_RADIUS:
continue
cx = get_river_center(y)
glBegin(GL_LINE_STRIP)
for dx in range(-100, 110, 15):
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
# ==========================================
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_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
# RADAR REDUCIDO
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()
# ÍCONOS SUPERIORES
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 72, HEIGHT - 30)
glEnd()
# TEXTOS
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, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
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 and 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.type == KEYDOWN and 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
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 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)
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
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
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()

View File

@ -1,797 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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.1, 0.9, 0.3) # Rana verde decorativa
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 Frog:
def __init__(self):
# Seleccionar dos puntos aleatorios de inicio y destino entre islas y juncos
self.pick_new_destination()
self.x, self.y = self.target_x, self.target_y
self.z = 0.5
self.speed = 0.4
self.yaw = 0.0
def pick_new_destination(self):
# Destinos posibles: islas o posiciones de juncos
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(-20, 20), dest[1] + random.uniform(-20, 20)
def update(self):
dx = self.target_x - self.x
dy = self.target_y - self.y
dist = math.hypot(dx, dy)
if dist < 5.0:
self.pick_new_destination()
else:
self.yaw = math.degrees(math.atan2(dy, dx))
self.x += (dx / dist) * self.speed
self.y += (dy / dist) * self.speed
def draw(self):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
# Pequeño modelo alámbrico de rana
glBegin(GL_LINES)
# Cuerpo
glVertex3f(0, 2.0, 0)
glVertex3f(0, -2.0, 0)
glVertex3f(-1.5, 1.0, 0)
glVertex3f(1.5, 1.0, 0)
glVertex3f(-1.5, -1.0, 0)
glVertex3f(1.5, -1.0, 0)
# Ojos saltones
glVertex3f(-0.8, 1.8, 0.5)
glVertex3f(-0.8, 1.8, 0)
glVertex3f(0.8, 1.8, 0.5)
glVertex3f(0.8, 1.8, 0)
glEnd()
glPopMatrix()
# ==========================================
# RENDERIZADO DE ESCENARIO Y MODELOS
# ==========================================
def draw_water_surface(time_counter):
"""Malla de agua con mayor densidad de líneas para mayor realismo visual."""
glColor3f(*COLOR_WATER)
grid_size = 15 # Reducido de 30 a 15 para duplicar la densidad de líneas
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), 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
# ==========================================
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_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
# RADAR REDUCIDO
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()
# ÍCONOS SUPERIORES
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 72, HEIGHT - 30)
glEnd()
# TEXTOS
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, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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
# Instanciar elementos y animales
frog = Frog()
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
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 and 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.type == KEYDOWN and 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
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 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)
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
# Actualizar rana y ambiente
frog.update()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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)
# Dibujar rana sobre el agua
frog.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()

View File

@ -1,828 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 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
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):
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))
self.x += (dx / dist) * self.speed
self.y += (dy / dist) * self.speed
def draw(self):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
# Modelo 3D de rana ampliado y detallado (Anatomía reconocible)
glBegin(GL_LINES)
# Tronco / Cuerpo robusto y ancho
glVertex3f(0.0, 5.0, 0.5)
glVertex3f(0.0, -4.0, 0.5)
glVertex3f(-3.0, 2.0, 0.5)
glVertex3f(3.0, 2.0, 0.5)
glVertex3f(-3.5, -2.0, 0.5)
glVertex3f(3.5, -2.0, 0.5)
# Conexión lomo
glVertex3f(-3.0, 2.0, 0.5)
glVertex3f(0.0, 5.0, 0.5)
glVertex3f(3.0, 2.0, 0.5)
glVertex3f(0.0, 5.0, 0.5)
# Cabeza ancha
glVertex3f(-2.5, 5.0, 0.6)
glVertex3f(2.5, 5.0, 0.6)
glVertex3f(-2.5, 5.0, 0.6)
glVertex3f(0.0, 7.5, 0.6)
glVertex3f(2.5, 5.0, 0.6)
glVertex3f(0.0, 7.5, 0.6)
# Ojos saltones prominentes
glVertex3f(-2.0, 6.5, 1.8)
glVertex3f(-2.0, 6.5, 0.8)
glVertex3f(2.0, 6.5, 1.8)
glVertex3f(2.0, 6.5, 0.8)
# Patas delanteras flexionadas
glVertex3f(-3.0, 2.0, 0.5)
glVertex3f(-5.0, 3.5, 0.0)
glVertex3f(-5.0, 3.5, 0.0)
glVertex3f(-4.5, 4.5, 0.0)
glVertex3f(3.0, 2.0, 0.5)
glVertex3f(5.0, 3.5, 0.0)
glVertex3f(5.0, 3.5, 0.0)
glVertex3f(4.5, 4.5, 0.0)
# Patas traseras musculosas y plegadas
glVertex3f(-3.5, -2.0, 0.5)
glVertex3f(-6.5, -4.0, 0.0)
glVertex3f(-6.5, -4.0, 0.0)
glVertex3f(-4.0, -6.0, 0.0)
glVertex3f(3.5, -2.0, 0.5)
glVertex3f(6.5, -4.0, 0.0)
glVertex3f(6.5, -4.0, 0.0)
glVertex3f(4.0, -6.0, 0.0)
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), 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
# ==========================================
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_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
# RADAR REDUCIDO
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()
# ÍCONOS SUPERIORES
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 72, HEIGHT - 30)
glEnd()
# TEXTOS
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, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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()
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
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 and 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.type == KEYDOWN and 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
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 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)
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()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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 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()

View File

@ -1,834 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 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:
# Apuntar la cabeza hacia la dirección del movimiento (ajustado 90 grados para orientar el frente del modelo)
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):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
# Offset de animación de las patas traseras para simular el nado
leg_offset = math.sin(self.swim_timer) * 1.5
# Modelo 3D de rana orientado al frente y con patas traseras móviles
glBegin(GL_LINES)
# Tronco / Cuerpo robusto
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)
# Conexión lomo
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)
# Cabeza (apuntando hacia adelante / eje Y positivo)
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)
# Ojos saltones prominentes
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)
# Patas delanteras estáticas
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 móviles simulando brazada/patada de natación
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(-3.0 - leg_offset, -6.0, 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(3.0 + leg_offset, -6.0, 0.0)
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), 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
# ==========================================
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_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
# RADAR REDUCIDO
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()
# ÍCONOS SUPERIORES
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 30)
glVertex2f(12, HEIGHT - 30)
glVertex2f(25, HEIGHT - 30)
glVertex2f(38, HEIGHT - 30)
glEnd()
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 98, HEIGHT - 30)
glVertex2f(WIDTH - 85, HEIGHT - 30)
glVertex2f(WIDTH - 72, HEIGHT - 30)
glEnd()
# TEXTOS
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, 45, HEIGHT - 40)
draw_text_2d(txt_flies, WIDTH - 65, HEIGHT - 40)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glEnable(GL_DEPTH_TEST)
# ==========================================
# BUCLE PRINCIPAL
# ==========================================
def main():
pygame.init()
pygame.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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()
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
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 and 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.type == KEYDOWN and 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
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 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)
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()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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 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()

View File

@ -1,830 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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 = 10, 20
FLY_MIN, FLY_MAX = 2, 7
# ==========================================
# 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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 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):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
leg_offset = 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)
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(-3.0 - leg_offset, -6.0, 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(3.0 + leg_offset, -6.0, 0.0)
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), 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
# ==========================================
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_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
# RADAR REDUCIDO
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()
# DIBUJOS ESTÁTICOS 2D DE LOS INSECTOS EN EL HUD
# 1. Icono de Mosquito (Izquierda - Amarillo)
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(25, HEIGHT - 33)
glVertex2f(25, HEIGHT - 27)
glVertex2f(21, HEIGHT - 30)
glVertex2f(29, HEIGHT - 30)
glEnd()
# 2. Icono de Mosca (Derecha - Rojo/Marrón)
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
# Cuerpo y alas estáticas de la mosca
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()
# TEXTOS DE CONTADORES
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.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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()
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
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 and 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.type == KEYDOWN and 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
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 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)
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()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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 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()

View File

@ -1,827 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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 (Aumentadas en +7 el mínimo y máximo)
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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 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):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
leg_offset = 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)
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(-3.0 - leg_offset, -6.0, 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(3.0 + leg_offset, -6.0, 0.0)
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), 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
# ==========================================
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_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
# RADAR REDUCIDO
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()
# DIBUJOS ESTÁTICOS 2D DE LOS INSECTOS EN EL HUD
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()
# TEXTOS DE CONTADORES
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.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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()
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
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 and 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.type == KEYDOWN and 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
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 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)
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()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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 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()

View File

@ -1,827 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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 (Aumentadas en +7 el mínimo y máximo)
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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 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):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
leg_offset = 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)
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(-3.0 - leg_offset, -6.0, 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(3.0 + leg_offset, -6.0, 0.0)
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), 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
# ==========================================
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_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
# RADAR REDUCIDO
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()
# DIBUJOS ESTÁTICOS 2D DE LOS INSECTOS EN EL HUD
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()
# TEXTOS DE CONTADORES
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.font.init()
font = pygame.font.SysFont("Arial", 22, bold=True)
pygame.display.set_mode((WIDTH, HEIGHT), DOUBLEBUF | OPENGL)
pygame.display.set_caption("Simulador de Vuelo de Libélula 3D")
glEnable(GL_DEPTH_TEST)
glClearColor(*COLOR_BG, 1.0)
glMatrixMode(GL_PROJECTION)
gluPerspective(50, (WIDTH / HEIGHT), 0.1, 5000.0)
glMatrixMode(GL_MODELVIEW)
# Estado de la Libélula
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()
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()
running = True
while running:
dt = clock.tick(60) / 1000.0
water_timer += dt
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 and 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.type == KEYDOWN and 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
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 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)
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()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
if current_mosquitos < MOSQUITO_MIN:
insects.append(Insect("mosquito"))
if current_flies < FLY_MIN:
insects.append(Insect("fly"))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
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 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()

View File

@ -1,968 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 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):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
leg_offset = 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)
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(-3.0 - leg_offset, -6.0, 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(3.0 + leg_offset, -6.0, 0.0)
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), 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):
glDisable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
gluOrtho2D(0, WIDTH, 0, HEIGHT)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
# TÍTULO PRINCIPAL
txt_title = font_title.render("LIBÉLULA", True, (0, 220, 255))
draw_text_2d(txt_title, WIDTH // 2 - 130, HEIGHT - 100)
# DIBUJO ANIMADO DE LA LIBÉLULA EN LA PANTALLA INICIAL
glPushMatrix()
glTranslatef(WIDTH // 2 + 150, HEIGHT - 75, 0.0)
glScalef(12.0, 12.0, 1.0)
# Dibujar libélula estática en 2D para el menú
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()
# Alas aleteando
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()
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 caza",
"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 y Junco)
y_icon = 90
# Icono Mosquito
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(100, y_icon + 5)
glVertex2f(100, y_icon - 5)
glVertex2f(95, y_icon)
glVertex2f(105, y_icon)
glEnd()
txt_m = font_text.render("Mosquito", True, (255, 230, 0))
draw_text_2d(txt_m, 120, y_icon - 8)
# Icono Mosca
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(280, y_icon + 5)
glVertex2f(280, y_icon - 5)
glVertex2f(275, y_icon - 3)
glVertex2f(285, y_icon + 3)
glVertex2f(275, y_icon + 3)
glVertex2f(285, y_icon - 3)
glEnd()
txt_f = font_text.render("Mosca", True, (200, 65, 40))
draw_text_2d(txt_f, 300, y_icon - 8)
# Icono Junco
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
glVertex2f(450, y_icon - 10)
glVertex2f(450, y_icon + 15)
glEnd()
glColor3f(*COLOR_CATTAIL)
glBegin(GL_LINES)
glVertex2f(450, y_icon + 8)
glVertex2f(450, y_icon + 15)
glEnd()
txt_r = font_text.render("Junco", True, (100, 220, 100))
draw_text_2d(txt_r, 470, 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
# RADAR REDUCIDO
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()
# DIBUJOS ESTÁTICOS 2D DE LOS INSECTOS EN EL HUD[cite: 4]
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()
# TEXTOS DE CONTADORES[cite: 4]
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.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)
# Estado de la Libélula
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()
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
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 # Salir de la pantalla de inicio con cualquier tecla
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:
# Renderizar fondo del mundo de forma estática para ambientar el menú
cam_x, cam_y, cam_z = 0.0, -100.0, 30.0
gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1)
draw_map(water_timer)
frog.draw()
# Dibujar la pantalla de inicio 2D encima
wing_angle = math.sin(wing_timer * 0.5) * 35.0
draw_start_screen(font_title, font_subtitle, font_text, wing_angle)
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 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)
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()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
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 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()

View File

@ -1,982 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 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):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
leg_offset = 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)
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(-3.0 - leg_offset, -6.0, 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(3.0 + leg_offset, -6.0, 0.0)
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), 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
# Par de alas delanteras
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()
# Par de alas traseras (añadidas para completar las 4 alas)
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):
glDisable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
gluOrtho2D(0, WIDTH, 0, HEIGHT)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
# TÍTULO PRINCIPAL
txt_title = font_title.render("LIBÉLULA", True, (0, 220, 255))
draw_text_2d(txt_title, WIDTH // 2 - 130, HEIGHT - 100)
# DIBUJO ANIMADO DE LA LIBÉLULA DE 4 ALAS EN LA PANTALLA INICIAL
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()
# 4 alas aleteando en el menú
rad_w = math.radians(wing_angle)
w_off = math.sin(rad_w) * 1.5
# Alas delanteras
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()
# Alas traseras
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 caza",
"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 y Junco)
y_icon = 90
# Icono Mosquito
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(100, y_icon + 5)
glVertex2f(100, y_icon - 5)
glVertex2f(95, y_icon)
glVertex2f(105, y_icon)
glEnd()
txt_m = font_text.render("Mosquito", True, (255, 230, 0))
draw_text_2d(txt_m, 120, y_icon - 8)
# Icono Mosca
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(280, y_icon + 5)
glVertex2f(280, y_icon - 5)
glVertex2f(275, y_icon - 3)
glVertex2f(285, y_icon + 3)
glVertex2f(275, y_icon + 3)
glVertex2f(285, y_icon - 3)
glEnd()
txt_f = font_text.render("Mosca", True, (200, 65, 40))
draw_text_2d(txt_f, 300, y_icon - 8)
# Icono Junco
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
glVertex2f(450, y_icon - 10)
glVertex2f(450, y_icon + 15)
glEnd()
glColor3f(*COLOR_CATTAIL)
glBegin(GL_LINES)
glVertex2f(450, y_icon + 8)
glVertex2f(450, y_icon + 15)
glEnd()
txt_r = font_text.render("Junco", True, (100, 220, 100))
draw_text_2d(txt_r, 470, 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
# RADAR REDUCIDO
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()
# DIBUJOS ESTÁTICOS 2D DE LOS INSECTOS EN EL HUD
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()
# TEXTOS DE CONTADORES
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.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)
# Estado de la Libélula
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()
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
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 # Salir de la pantalla de inicio con cualquier tecla
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, -100.0, 30.0
gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1)
draw_map(water_timer)
frog.draw()
wing_angle = math.sin(wing_timer * 0.5) * 35.0
draw_start_screen(font_title, font_subtitle, font_text, wing_angle)
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 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)
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()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
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 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()

View File

@ -1,972 +0,0 @@
import sys
import math
import random
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 alámbrica (Cian)
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
# ==========================================
# CLASES DE INSECTOS Y RANA DECORATIVA
# ==========================================
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 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):
glColor3f(*COLOR_FROG)
glPushMatrix()
glTranslatef(self.x, self.y, self.z)
glRotatef(self.yaw, 0, 0, 1)
leg_offset = 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 mirando hacia el exterior (más realismo)[cite: 4]
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()
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), 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
# Par de alas delanteras
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()
# Par de alas traseras
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):
glDisable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
gluOrtho2D(0, WIDTH, 0, HEIGHT)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
# TÍTULO PRINCIPAL
txt_title = font_title.render("LIBÉLULA", True, (0, 220, 255))
draw_text_2d(txt_title, WIDTH // 2 - 130, HEIGHT - 100)
# DIBUJO ANIMADO DE LA LIBÉLULA DE 4 ALAS EN LA PANTALLA INICIAL
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 caza",
"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
y_icon = 90
glColor3f(*COLOR_MOSQUITO)
glBegin(GL_LINES)
glVertex2f(100, y_icon + 5)
glVertex2f(100, y_icon - 5)
glVertex2f(95, y_icon)
glVertex2f(105, y_icon)
glEnd()
txt_m = font_text.render("Mosquito", True, (255, 230, 0))
draw_text_2d(txt_m, 120, y_icon - 8)
glColor3f(*COLOR_FLY)
glBegin(GL_LINES)
glVertex2f(280, y_icon + 5)
glVertex2f(280, y_icon - 5)
glVertex2f(275, y_icon - 3)
glVertex2f(285, y_icon + 3)
glVertex2f(275, y_icon + 3)
glVertex2f(285, y_icon - 3)
glEnd()
txt_f = font_text.render("Mosca", True, (200, 65, 40))
draw_text_2d(txt_f, 300, y_icon - 8)
glColor3f(*COLOR_REED)
glBegin(GL_LINES)
glVertex2f(450, y_icon - 10)
glVertex2f(450, y_icon + 15)
glEnd()
glColor3f(*COLOR_CATTAIL)
glBegin(GL_LINES)
glVertex2f(450, y_icon + 8)
glVertex2f(450, y_icon + 15)
glEnd()
txt_r = font_text.render("Junco", True, (100, 220, 100))
draw_text_2d(txt_r, 470, y_icon - 8)
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.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)
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()
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
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, -100.0, 30.0
gluLookAt(cam_x, cam_y, cam_z, 0, 0, 15, 0, 0, 1)
draw_map(water_timer)
frog.draw()
wing_angle = math.sin(wing_timer * 0.5) * 35.0
draw_start_screen(font_title, font_subtitle, font_text, wing_angle)
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 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)
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()
current_mosquitos = 0
current_flies = 0
for insect in insects[:]:
insect.update((px, py, pz))
if insect.type == "mosquito":
current_mosquitos += 1
else:
current_flies += 1
if not caught_insect:
dist_to_dragonfly = math.sqrt((px - insect.x)**2 + (py - insect.y)**2 + (pz - insect.z)**2)
if dist_to_dragonfly < 6.0:
caught_insect = insect
insects.remove(insect)
if dive_phase == "diving":
dive_phase = "recovering"
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 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()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff