Ajuste velocidad alas libelula

This commit is contained in:
luisgulo 2026-10-09 18:58:05 +02:00
parent 222871e1da
commit 34177bf951
26 changed files with 19876 additions and 9 deletions

BIN
esquema-mapa.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -409,24 +409,30 @@ class Frog:
def draw_water_surface(time_counter):
glColor3f(*COLOR_WATER)
grid_size = 15
# Cuadrícula más densa y fluida para el estanque principal
grid_size = 8 # Reducido de 15 a 8 para mayor resolución en las ondas
for x in range(int(-POND_RADIUS), int(POND_RADIUS), grid_size * 2):
glBegin(GL_LINE_STRIP)
for y in range(int(-POND_RADIUS), int(-POND_RADIUS) + int(POND_RADIUS)*2, grid_size):
for y in range(int(-POND_RADIUS), int(-POND_RADIUS) + int(POND_RADIUS)*2, grid_size * 2):
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)
# Combinación de ondas complejas para dar un aspecto orgánico y natural
z = (1.2 * math.sin(dist * 0.015 - time_counter * 3.5) +
0.6 * math.cos(x * 0.02 + time_counter * 2.0) +
0.4 * math.sin(y * 0.025 - time_counter * 1.8))
glVertex3f(x, y, z)
glEnd()
for y in range(int(STREAM_MIN_Y), int(STREAM_MAX_Y), 15):
# Ondas mejoradas para los arroyos
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, 10):
for dx in range(-80, 90, 8):
x = cx + dx
z = 0.5 * math.sin(y * 0.03 + dx * 0.08 + time_counter * 4.0)
z = (0.8 * math.sin(y * 0.04 + dx * 0.1 + time_counter * 4.5) +
0.3 * math.cos(x * 0.05 - time_counter * 2.5))
glVertex3f(x, y, z)
glEnd()
@ -676,8 +682,8 @@ def draw_start_screen(font_title, font_subtitle, font_text, wing_angle, menu_win
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))
obj_line1 = font_text.render("Objetivo: Vuela libre por el estanque y sus arroyos capturando moscas y mosquitos.", True, (255, 255, 255))
obj_line2 = font_text.render("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)
@ -1165,7 +1171,7 @@ def main():
py += WORLD_LEN
# Aleteo rápido cuando está volando o maniobrando
wing_timer += 140.0 if (dive_phase != "none" or auto_landing) else (60.0 if speed > 0.1 else 100.0)
wing_timer += 180.0 if (dive_phase != "none" or auto_landing) else (40.0 if speed > 0.1 else 140.0)
wing_angle = math.sin(wing_timer) * 35.0
frog.update()

View File

@ -0,0 +1,555 @@
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

@ -0,0 +1,702 @@
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

@ -0,0 +1,675 @@
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

@ -0,0 +1,668 @@
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

@ -0,0 +1,649 @@
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

@ -0,0 +1,676 @@
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

@ -0,0 +1,694 @@
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

@ -0,0 +1,696 @@
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

@ -0,0 +1,736 @@
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

@ -0,0 +1,797 @@
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

@ -0,0 +1,828 @@
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

@ -0,0 +1,834 @@
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

@ -0,0 +1,830 @@
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

@ -0,0 +1,827 @@
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

@ -0,0 +1,827 @@
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()

968
previos/libelula_v8.2.py Normal file
View File

@ -0,0 +1,968 @@
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()

982
previos/libelula_v8.3.py Normal file
View File

@ -0,0 +1,982 @@
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()

972
previos/libelula_v8.4.py Normal file
View File

@ -0,0 +1,972 @@
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()

1139
previos/libelula_v9.0.py Normal file

File diff suppressed because it is too large Load Diff

1199
previos/libelula_v9.2.py Normal file

File diff suppressed because it is too large Load Diff

1199
previos/libelula_v9.3.py Normal file

File diff suppressed because it is too large Load Diff

1204
previos/libelula_v9.4.py Normal file

File diff suppressed because it is too large Load Diff

1204
previos/libelula_v9.5.py Normal file

File diff suppressed because it is too large Load Diff

BIN
radar-ayuda-captura.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB