#include "raylib.h" #include #include #include #include #include // ============================================================ // A4B1 0.0.2 // ============================================================ const int PANTALLA_ANCHO = 1280; const int PANTALLA_ALTO = 720; const float MUNDO_ANCHO = 2800.0f; const float MUNDO_ALTO = 1900.0f; const float BORDE = 45.0f; const float RADIO_JUGADOR = 19.0f; const float RADIO_IA = 20.0f; const float VELOCIDAD_JUGADOR = 310.0f; const float VELOCIDAD_IA = 180.0f; const float RADIO_VISION = 470.0f; const float TIEMPO_RECARGA = 1.15f; const int EQUIPO_AZUL = 0; const int EQUIPO_ROJO = 1; // ============================================================ // ARMAS // ============================================================ enum TipoArma { ARMA_RAPIDA = 0, ARMA_EQUILIBRADA, ARMA_PESADA, ARMA_PRECISA }; struct Arma { TipoArma tipo; int balas; int cartuchos; bool enSuelo; bool activa; Vector2 posicion; }; struct Bala { Vector2 posicion; Vector2 direccion; float velocidad; float dano; int equipo; int paredesAtraviesadas; int rebotes; bool activa; }; struct Botiquin { Vector2 posicion; bool activa; }; struct Municion { Vector2 posicion; bool activa; }; struct MarcaMuerte { Vector2 posicion; float tiempo; bool activa; }; struct Entidad { Vector2 posicion; Vector2 velocidad; Vector2 direccion; float radio; float vida; float vidaMaxima; int equipo; bool jugador; bool viva; int arma; bool recargando; float tiempoRecarga; float tiempoDisparo; float tiempoAtascado; Vector2 ultimaPosicion; }; struct Pared { Rectangle rectangulo; }; // ============================================================ // FUNCIONES BÁSICAS // ============================================================ float Distancia(Vector2 a, Vector2 b) { float dx = a.x - b.x; float dy = a.y - b.y; return std::sqrt(dx * dx + dy * dy); } Vector2 Normalizar(Vector2 v) { float longitud = std::sqrt(v.x * v.x + v.y * v.y); if (longitud <= 0.0001f) return {0, 0}; return { v.x / longitud, v.y / longitud }; } Vector2 Multiplicar(Vector2 v, float valor) { return { v.x * valor, v.y * valor }; } // ============================================================ // ARMAS // ============================================================ const char* NombreArma(TipoArma tipo) { switch (tipo) { case ARMA_RAPIDA: return "RAPIDA"; case ARMA_EQUILIBRADA: return "EQUILIBRADA"; case ARMA_PESADA: return "PESADA"; case ARMA_PRECISA: return "PRECISA"; } return "???"; } int CapacidadArma(TipoArma tipo) { switch (tipo) { case ARMA_RAPIDA: return 12; case ARMA_EQUILIBRADA: return 8; case ARMA_PESADA: return 4; case ARMA_PRECISA: return 3; } return 8; } float CadenciaArma(TipoArma tipo) { switch (tipo) { case ARMA_RAPIDA: return 0.10f; case ARMA_EQUILIBRADA: return 0.25f; case ARMA_PESADA: return 0.70f; case ARMA_PRECISA: return 1.05f; } return 0.25f; } float DanoArma(TipoArma tipo) { switch (tipo) { case ARMA_RAPIDA: return 10.0f; case ARMA_EQUILIBRADA: return 18.0f; case ARMA_PESADA: return 40.0f; case ARMA_PRECISA: return 70.0f; } return 10.0f; } float VelocidadBala(TipoArma tipo) { switch (tipo) { case ARMA_RAPIDA: return 820.0f; case ARMA_EQUILIBRADA: return 760.0f; case ARMA_PESADA: return 690.0f; case ARMA_PRECISA: return 1050.0f; } return 760.0f; } Color ColorArma(TipoArma tipo) { switch (tipo) { case ARMA_RAPIDA: return YELLOW; case ARMA_EQUILIBRADA: return SKYBLUE; case ARMA_PESADA: return ORANGE; case ARMA_PRECISA: return PURPLE; } return WHITE; } Arma CrearArma( TipoArma tipo, Vector2 posicion, bool enSuelo ) { Arma arma{}; arma.tipo = tipo; arma.balas = CapacidadArma(tipo); // Cartuchos infinitos. arma.cartuchos = 999999; arma.enSuelo = enSuelo; arma.activa = true; arma.posicion = posicion; return arma; } // ============================================================ // COLISIONES // ============================================================ bool CirculoChocaRectangulo( Vector2 centro, float radio, Rectangle rect ) { float cercanoX = std::clamp( centro.x, rect.x, rect.x + rect.width ); float cercanoY = std::clamp( centro.y, rect.y, rect.y + rect.height ); float dx = centro.x - cercanoX; float dy = centro.y - cercanoY; return dx * dx + dy * dy <= radio * radio; } bool EntidadChocaPared( Vector2 posicion, float radio, const std::vector& paredes ) { for (const auto& pared : paredes) { if ( CirculoChocaRectangulo( posicion, radio, pared.rectangulo ) ) { return true; } } return false; } bool PuntoDentroRectangulo( Vector2 punto, Rectangle rect ) { return CheckCollisionPointRec( punto, rect ); } bool LineaChocaRectangulo( Vector2 inicio, Vector2 fin, Rectangle rect ) { const int pasos = 50; for (int i = 0; i <= pasos; i++) { float t = static_cast(i) / static_cast(pasos); Vector2 punto = { inicio.x + (fin.x - inicio.x) * t, inicio.y + (fin.y - inicio.y) * t }; if ( PuntoDentroRectangulo( punto, rect ) ) { return true; } } return false; } bool HayParedEntre( Vector2 a, Vector2 b, const std::vector& paredes ) { for (const auto& pared : paredes) { if ( LineaChocaRectangulo( a, b, pared.rectangulo ) ) { return true; } } return false; } // ============================================================ // MAPA // ============================================================ std::vector CrearMapa() { std::vector paredes; // Bordes paredes.push_back({ {0, 0, MUNDO_ANCHO, BORDE} }); paredes.push_back({ { 0, MUNDO_ALTO - BORDE, MUNDO_ANCHO, BORDE } }); paredes.push_back({ {0, 0, BORDE, MUNDO_ALTO} }); paredes.push_back({ { MUNDO_ANCHO - BORDE, 0, BORDE, MUNDO_ALTO } }); // Izquierda paredes.push_back({ {500, 300, 400, 50} }); paredes.push_back({ {500, 1550, 400, 50} }); paredes.push_back({ {900, 300, 55, 500} }); paredes.push_back({ {900, 1100, 55, 500} }); // Derecha paredes.push_back({ {1900, 300, 400, 50} }); paredes.push_back({ {1900, 1550, 400, 50} }); paredes.push_back({ {1845, 300, 55, 500} }); paredes.push_back({ {1845, 1100, 55, 500} }); // Centro paredes.push_back({ {1220, 650, 360, 55} }); paredes.push_back({ {1220, 1195, 360, 55} }); // Coberturas paredes.push_back({ {650, 700, 180, 45} }); paredes.push_back({ {650, 1155, 180, 45} }); paredes.push_back({ {1970, 700, 180, 45} }); paredes.push_back({ {1970, 1155, 180, 45} }); return paredes; } // ============================================================ // MOVIMIENTO // ============================================================ void MoverEntidad( Entidad& entidad, Vector2 movimiento, const std::vector& paredes ) { Vector2 nuevaX = entidad.posicion; nuevaX.x += movimiento.x; if ( !EntidadChocaPared( nuevaX, entidad.radio, paredes ) ) { entidad.posicion.x = nuevaX.x; } Vector2 nuevaY = entidad.posicion; nuevaY.y += movimiento.y; if ( !EntidadChocaPared( nuevaY, entidad.radio, paredes ) ) { entidad.posicion.y = nuevaY.y; } } // ============================================================ // SEPARACIÓN DE COMPAÑEROS // ============================================================ Vector2 FuerzaSeparacion( int indice, const std::vector& entidades ) { Vector2 fuerza = {0, 0}; const Entidad& yo = entidades[indice]; for ( int i = 0; i < static_cast(entidades.size()); i++ ) { if (i == indice) continue; const Entidad& otra = entidades[i]; if (!otra.viva) continue; if (otra.equipo != yo.equipo) continue; float distancia = Distancia( yo.posicion, otra.posicion ); if ( distancia > 0 && distancia < 110 ) { Vector2 alejamiento = { yo.posicion.x - otra.posicion.x, yo.posicion.y - otra.posicion.y }; alejamiento = Normalizar( alejamiento ); float intensidad = (110.0f - distancia) / 110.0f; fuerza.x += alejamiento.x * intensidad; fuerza.y += alejamiento.y * intensidad; } } return fuerza; } // ============================================================ // DIRECCIÓN LIBRE // ============================================================ Vector2 BuscarDireccionLibre( const Entidad& entidad, const std::vector& paredes ) { const Vector2 direcciones[] = { {1, 0}, {-1, 0}, {0, 1}, {0, -1}, {0.707f, 0.707f}, {-0.707f, 0.707f}, {0.707f, -0.707f}, {-0.707f, -0.707f} }; for (const Vector2& direccion : direcciones) { Vector2 prueba = { entidad.posicion.x + direccion.x * 100.0f, entidad.posicion.y + direccion.y * 100.0f }; if ( !EntidadChocaPared( prueba, entidad.radio + 4, paredes ) ) { return direccion; } } return {1, 0}; } // ============================================================ // ENEMIGO VISIBLE // ============================================================ int BuscarEnemigo( int indice, const std::vector& entidades, const std::vector& paredes ) { const Entidad& yo = entidades[indice]; int mejor = -1; float mejorDistancia = 999999.0f; for ( int i = 0; i < static_cast(entidades.size()); i++ ) { if (i == indice) continue; const Entidad& enemigo = entidades[i]; if (!enemigo.viva) continue; if (enemigo.equipo == yo.equipo) continue; float distancia = Distancia( yo.posicion, enemigo.posicion ); if (distancia > 900) continue; if ( !HayParedEntre( yo.posicion, enemigo.posicion, paredes ) ) { if (distancia < mejorDistancia) { mejorDistancia = distancia; mejor = i; } } } return mejor; } // ============================================================ // ARMAS EN EL SUELO // ============================================================ int BuscarArmaCercana( const Entidad& entidad, const std::vector& armas ) { int mejor = -1; float mejorDistancia = 75.0f; for ( int i = 0; i < static_cast(armas.size()); i++ ) { const Arma& arma = armas[i]; if (!arma.activa) continue; if (!arma.enSuelo) continue; float distancia = Distancia( entidad.posicion, arma.posicion ); if (distancia < mejorDistancia) { mejorDistancia = distancia; mejor = i; } } return mejor; } void RecogerArma( Entidad& entidad, int indiceArma, std::vector& armas ) { if ( indiceArma < 0 || indiceArma >= static_cast(armas.size()) ) { return; } Arma& nueva = armas[indiceArma]; if ( !nueva.activa || !nueva.enSuelo ) { return; } // Si ya llevaba una, la tira. if (entidad.arma >= 0) { Arma& vieja = armas[entidad.arma]; vieja.enSuelo = true; vieja.activa = true; vieja.posicion = entidad.posicion; } nueva.enSuelo = false; entidad.arma = indiceArma; entidad.recargando = false; entidad.tiempoRecarga = 0; } // ============================================================ // DISPARO // ============================================================ void Disparar( Entidad& entidad, std::vector& armas, std::vector& balas ) { if (!entidad.viva) return; if (entidad.arma < 0) return; if ( entidad.arma >= static_cast(armas.size()) ) { entidad.arma = -1; return; } Arma& arma = armas[entidad.arma]; if (arma.balas <= 0) return; if (entidad.recargando) return; if (entidad.tiempoDisparo > 0) return; arma.balas--; Bala bala{}; bala.posicion = { entidad.posicion.x + entidad.direccion.x * (entidad.radio + 9), entidad.posicion.y + entidad.direccion.y * (entidad.radio + 9) }; bala.direccion = Normalizar( entidad.direccion ); bala.velocidad = VelocidadBala( arma.tipo ); bala.dano = DanoArma( arma.tipo ); bala.equipo = entidad.equipo; bala.paredesAtraviesadas = 0; bala.rebotes = 0; bala.activa = true; balas.push_back(bala); entidad.tiempoDisparo = CadenciaArma( arma.tipo ); } // ============================================================ // RECARGA // ============================================================ void IniciarRecarga( Entidad& entidad, std::vector& armas ) { if (!entidad.viva) return; if (entidad.arma < 0) return; if ( entidad.arma >= static_cast(armas.size()) ) { entidad.arma = -1; return; } Arma& arma = armas[entidad.arma]; if (entidad.recargando) return; if ( arma.balas >= CapacidadArma(arma.tipo) ) { return; } // Nunca se acaban. arma.cartuchos = 999999; entidad.recargando = true; entidad.tiempoRecarga = TIEMPO_RECARGA; } void ActualizarRecarga( Entidad& entidad, std::vector& armas, float dt ) { if (!entidad.recargando) return; entidad.tiempoRecarga -= dt; if (entidad.tiempoRecarga <= 0) { entidad.recargando = false; if ( entidad.arma >= 0 && entidad.arma < static_cast(armas.size()) ) { Arma& arma = armas[entidad.arma]; arma.balas = CapacidadArma( arma.tipo ); arma.cartuchos = 999999; } } } // ============================================================ // MARCAS DE MUERTE // ============================================================ void CrearMarcaMuerte( std::vector& marcas, Vector2 posicion ) { marcas.push_back({ posicion, 0.0f, true }); } // ============================================================ // BALAS // ============================================================ void MoverBalas( std::vector& balas, const std::vector& paredes, std::vector& entidades, std::vector& marcas, std::vector& armas, float dt ) { for (Bala& bala : balas) { if (!bala.activa) continue; Vector2 inicio = bala.posicion; Vector2 destino = { inicio.x + bala.direccion.x * bala.velocidad * dt, inicio.y + bala.direccion.y * bala.velocidad * dt }; // ---------------------------------------------------- // BORDES // ---------------------------------------------------- bool bordeX = destino.x < BORDE || destino.x > MUNDO_ANCHO - BORDE; bool bordeY = destino.y < BORDE || destino.y > MUNDO_ALTO - BORDE; if (bordeX) { if (bala.rebotes < 1) { bala.direccion.x *= -1.0f; bala.rebotes++; destino.x = inicio.x + bala.direccion.x * bala.velocidad * dt; } else { bala.activa = false; continue; } } if (bordeY) { if (bala.rebotes < 1) { bala.direccion.y *= -1.0f; bala.rebotes++; destino.y = inicio.y + bala.direccion.y * bala.velocidad * dt; } else { bala.activa = false; continue; } } // ---------------------------------------------------- // PAREDES // ---------------------------------------------------- bool chocoPared = false; Rectangle paredGolpeada{}; for (const auto& pared : paredes) { if ( LineaChocaRectangulo( inicio, destino, pared.rectangulo ) ) { chocoPared = true; paredGolpeada = pared.rectangulo; break; } } if (chocoPared) { // PRIMERA PARED: // atraviesa sin rebotar. if (bala.paredesAtraviesadas == 0) { bala.paredesAtraviesadas++; bala.posicion = destino; continue; } // SEGUNDA PARED: // rebota. if ( bala.paredesAtraviesadas == 1 && bala.rebotes == 0 ) { bala.paredesAtraviesadas++; bala.rebotes++; float izquierda = std::fabs( inicio.x - paredGolpeada.x ); float derecha = std::fabs( inicio.x - ( paredGolpeada.x + paredGolpeada.width ) ); float arriba = std::fabs( inicio.y - paredGolpeada.y ); float abajo = std::fabs( inicio.y - ( paredGolpeada.y + paredGolpeada.height ) ); float minimo = std::min( std::min( izquierda, derecha ), std::min( arriba, abajo ) ); if ( minimo == izquierda || minimo == derecha ) { bala.direccion.x *= -1.0f; } else { bala.direccion.y *= -1.0f; } bala.posicion = { inicio.x + bala.direccion.x * 8.0f, inicio.y + bala.direccion.y * 8.0f }; continue; } // TERCERA PARED: // desaparece. bala.activa = false; continue; } bala.posicion = destino; // ---------------------------------------------------- // GOLPEAR ENTIDADES // ---------------------------------------------------- for ( int i = 0; i < static_cast(entidades.size()); i++ ) { Entidad& entidad = entidades[i]; if (!entidad.viva) continue; if (entidad.equipo == bala.equipo) continue; if ( Distancia( bala.posicion, entidad.posicion ) < entidad.radio + 5.0f ) { entidad.vida -= bala.dano; bala.activa = false; if (entidad.vida <= 0) { entidad.vida = 0; entidad.viva = false; // Marca naranja si muere // un compañero del jugador. if ( entidad.equipo == EQUIPO_AZUL && !entidad.jugador ) { CrearMarcaMuerte( marcas, entidad.posicion ); } // Su arma cae al suelo // conservando las balas. if ( entidad.arma >= 0 && entidad.arma < static_cast( armas.size() ) ) { Arma& arma = armas[entidad.arma]; arma.enSuelo = true; arma.activa = true; arma.posicion = entidad.posicion; entidad.arma = -1; } } break; } } } balas.erase( std::remove_if( balas.begin(), balas.end(), [](const Bala& bala) { return !bala.activa; } ), balas.end() ); } // ============================================================ // PICKUPS // ============================================================ bool PosicionLibre( Vector2 posicion, const std::vector& paredes ) { for (const auto& pared : paredes) { if ( CirculoChocaRectangulo( posicion, 18.0f, pared.rectangulo ) ) { return false; } } return true; } void CrearBotiquin( std::vector& botiquines, const std::vector& paredes ) { for (int intento = 0; intento < 100; intento++) { Vector2 posicion = { static_cast( 120 + rand() % 2560 ), static_cast( 120 + rand() % 1660 ) }; if ( PosicionLibre( posicion, paredes ) ) { botiquines.push_back({ posicion, true }); return; } } } void CrearMunicion( std::vector& municiones, const std::vector& paredes ) { for (int intento = 0; intento < 100; intento++) { Vector2 posicion = { static_cast( 120 + rand() % 2560 ), static_cast( 120 + rand() % 1660 ) }; if ( PosicionLibre( posicion, paredes ) ) { municiones.push_back({ posicion, true }); return; } } } void ActualizarBotiquines( std::vector& botiquines, std::vector& entidades ) { for (auto& botiquin : botiquines) { if (!botiquin.activa) continue; for (Entidad& entidad : entidades) { if (!entidad.viva) continue; if ( Distancia( botiquin.posicion, entidad.posicion ) < entidad.radio + 18.0f ) { if ( entidad.vida < entidad.vidaMaxima ) { entidad.vida = std::min( entidad.vidaMaxima, entidad.vida + 35.0f ); botiquin.activa = false; break; } } } } } void ActualizarMunicion( std::vector& municiones, std::vector& entidades, std::vector& armas ) { for (auto& municion : municiones) { if (!municion.activa) continue; for (Entidad& entidad : entidades) { if (!entidad.viva) continue; if (entidad.arma < 0) continue; if ( entidad.arma >= static_cast(armas.size()) ) { entidad.arma = -1; continue; } if ( Distancia( municion.posicion, entidad.posicion ) < entidad.radio + 18.0f ) { Arma& arma = armas[entidad.arma]; arma.balas = CapacidadArma( arma.tipo ); arma.cartuchos = 999999; municion.activa = false; break; } } } } // ============================================================ // IA // ============================================================ void ActualizarIA( int indice, std::vector& entidades, std::vector& armas, std::vector& balas, const std::vector& paredes, float dt, float tiempoPartida ) { Entidad& ia = entidades[indice]; if (!ia.viva) return; ia.tiempoDisparo = std::max( 0.0f, ia.tiempoDisparo - dt ); ActualizarRecarga( ia, armas, dt ); // -------------------------------------------------------- // BUSCAR ARMA // -------------------------------------------------------- if (ia.arma < 0) { int armaCercana = BuscarArmaCercana( ia, armas ); if (armaCercana >= 0) { RecogerArma( ia, armaCercana, armas ); } } if (ia.arma < 0) { Vector2 direccion = { ia.equipo == EQUIPO_AZUL ? 1.0f : -1.0f, 0 }; MoverEntidad( ia, Multiplicar( direccion, VELOCIDAD_IA * dt ), paredes ); return; } if ( ia.arma >= static_cast(armas.size()) ) { ia.arma = -1; return; } Arma& arma = armas[ia.arma]; if (arma.balas <= 0) { IniciarRecarga( ia, armas ); return; } int enemigo = BuscarEnemigo( indice, entidades, paredes ); Vector2 direccionMovimiento = { 0, 0 }; if (enemigo >= 0) { Entidad& objetivo = entidades[enemigo]; Vector2 haciaObjetivo = { objetivo.posicion.x - ia.posicion.x, objetivo.posicion.y - ia.posicion.y }; float distancia = Distancia( ia.posicion, objetivo.posicion ); haciaObjetivo = Normalizar( haciaObjetivo ); ia.direccion = haciaObjetivo; if (distancia > 480.0f) { direccionMovimiento = haciaObjetivo; } else if (distancia < 250.0f) { direccionMovimiento = Multiplicar( haciaObjetivo, -1.0f ); } else { direccionMovimiento = { -haciaObjetivo.y, haciaObjetivo.x }; float signo = std::sin( GetTime() * 1.5 + indice * 3 ); if (signo < 0) { direccionMovimiento.x *= -1; direccionMovimiento.y *= -1; } } // Protección inicial: // durante 3 segundos no disparan. if ( tiempoPartida > 3.0f && distancia < 900.0f && arma.balas > 0 ) { Disparar( ia, armas, balas ); } } else { float tiempo = GetTime() + indice * 5.0f; direccionMovimiento = { std::cos( tiempo * 0.45f ), std::sin( tiempo * 0.37f ) }; direccionMovimiento = Normalizar( direccionMovimiento ); } // Separación entre compañeros. Vector2 separacion = FuerzaSeparacion( indice, entidades ); direccionMovimiento.x += separacion.x * 2.0f; direccionMovimiento.y += separacion.y * 2.0f; direccionMovimiento = Normalizar( direccionMovimiento ); // Evitar paredes. Vector2 prueba = { ia.posicion.x + direccionMovimiento.x * 80.0f, ia.posicion.y + direccionMovimiento.y * 80.0f }; if ( EntidadChocaPared( prueba, ia.radio, paredes ) ) { direccionMovimiento = BuscarDireccionLibre( ia, paredes ); } Vector2 antes = ia.posicion; MoverEntidad( ia, Multiplicar( direccionMovimiento, VELOCIDAD_IA * dt ), paredes ); float movimiento = Distancia( antes, ia.posicion ); if (movimiento < 1.5f) ia.tiempoAtascado += dt; else ia.tiempoAtascado = 0; // Desatascar. if (ia.tiempoAtascado > 0.45f) { Vector2 libre = BuscarDireccionLibre( ia, paredes ); MoverEntidad( ia, Multiplicar( libre, VELOCIDAD_IA * dt * 3.0f ), paredes ); ia.tiempoAtascado = 0; } ia.ultimaPosicion = ia.posicion; } // ============================================================ // DIBUJAR MAPA // ============================================================ void DibujarMapa( const std::vector& paredes ) { DrawRectangle( 0, 0, static_cast(MUNDO_ANCHO), static_cast(MUNDO_ALTO), Color{ 47, 49, 54, 255 } ); for ( int x = 0; x < static_cast(MUNDO_ANCHO); x += 100 ) { DrawLine( x, 0, x, static_cast(MUNDO_ALTO), Color{ 55, 57, 62, 255 } ); } for ( int y = 0; y < static_cast(MUNDO_ALTO); y += 100 ) { DrawLine( 0, y, static_cast(MUNDO_ANCHO), y, Color{ 55, 57, 62, 255 } ); } for (const auto& pared : paredes) { DrawRectangleRec( pared.rectangulo, Color{ 25, 27, 31, 255 } ); DrawRectangleLinesEx( pared.rectangulo, 2, Color{ 90, 94, 100, 255 } ); } } // ============================================================ // DIBUJAR ARMAS // ============================================================ void DibujarArmas( const std::vector& armas ) { for (const Arma& arma : armas) { if (!arma.activa) continue; if (!arma.enSuelo) continue; DrawCircleV( arma.posicion, 13, ColorArma(arma.tipo) ); DrawCircleLines( static_cast(arma.posicion.x), static_cast(arma.posicion.y), 15, BLACK ); DrawText( NombreArma(arma.tipo), static_cast(arma.posicion.x - 35), static_cast(arma.posicion.y + 18), 10, WHITE ); } } // ============================================================ // BOTIQUINES // ============================================================ void DibujarBotiquines( const std::vector& botiquines ) { for (const auto& botiquin : botiquines) { if (!botiquin.activa) continue; DrawCircleV( botiquin.posicion, 15, GREEN ); DrawRectangle( static_cast(botiquin.posicion.x - 4), static_cast(botiquin.posicion.y - 10), 8, 20, WHITE ); DrawRectangle( static_cast(botiquin.posicion.x - 10), static_cast(botiquin.posicion.y - 4), 20, 8, WHITE ); } } // ============================================================ // MUNICIÓN // ============================================================ void DibujarMunicion( const std::vector& municiones ) { for (const auto& municion : municiones) { if (!municion.activa) continue; DrawRectangle( static_cast(municion.posicion.x - 13), static_cast(municion.posicion.y - 10), 26, 20, GOLD ); DrawRectangleLines( static_cast(municion.posicion.x - 13), static_cast(municion.posicion.y - 10), 26, 20, BLACK ); DrawText( "AM", static_cast(municion.posicion.x - 8), static_cast(municion.posicion.y - 7), 12, BLACK ); } } // ============================================================ // ENTIDADES // ============================================================ void DibujarEntidades( const std::vector& entidades ) { for (const auto& entidad : entidades) { if (!entidad.viva) continue; Color color = entidad.equipo == EQUIPO_AZUL ? BLUE : RED; DrawCircleV( entidad.posicion, entidad.radio, color ); DrawCircleLines( static_cast(entidad.posicion.x), static_cast(entidad.posicion.y), entidad.radio, WHITE ); DrawLineEx( entidad.posicion, { entidad.posicion.x + entidad.direccion.x * (entidad.radio + 10), entidad.posicion.y + entidad.direccion.y * (entidad.radio + 10) }, 4, WHITE ); float porcentaje = entidad.vida / entidad.vidaMaxima; DrawRectangle( static_cast(entidad.posicion.x - 25), static_cast(entidad.posicion.y - 34), 50, 6, DARKGRAY ); DrawRectangle( static_cast(entidad.posicion.x - 25), static_cast(entidad.posicion.y - 34), static_cast(50 * porcentaje), 6, GREEN ); if (entidad.jugador) { DrawCircleLines( static_cast(entidad.posicion.x), static_cast(entidad.posicion.y), entidad.radio + 5, WHITE ); } } } // ============================================================ // MINIMAPA // ============================================================ void DibujarMinimapa( const std::vector& paredes, const std::vector& entidades, const std::vector& marcas ) { const int mapaX = 820; const int mapaY = 8; const int mapaAncho = 440; const int mapaAlto = 58; DrawRectangle( mapaX, mapaY, mapaAncho, mapaAlto, Color{ 10, 12, 16, 245 } ); DrawRectangleLines( mapaX, mapaY, mapaAncho, mapaAlto, LIGHTGRAY ); float escalaX = static_cast(mapaAncho) / MUNDO_ANCHO; float escalaY = static_cast(mapaAlto) / MUNDO_ALTO; // Paredes for (const auto& pared : paredes) { int x = mapaX + static_cast( pared.rectangulo.x * escalaX ); int y = mapaY + static_cast( pared.rectangulo.y * escalaY ); int w = std::max( 1, static_cast( pared.rectangulo.width * escalaX ) ); int h = std::max( 1, static_cast( pared.rectangulo.height * escalaY ) ); DrawRectangle( x, y, w, h, Color{ 80, 83, 88, 255 } ); } // Muertes de compañeros. for (const auto& marca : marcas) { if (!marca.activa) continue; int x = mapaX + static_cast( marca.posicion.x * escalaX ); int y = mapaY + static_cast( marca.posicion.y * escalaY ); DrawCircle( x, y, 5, ORANGE ); DrawCircleLines( x, y, 8, GOLD ); } // Solo equipo azul. for (const auto& entidad : entidades) { if (!entidad.viva) continue; if (entidad.equipo != EQUIPO_AZUL) continue; int x = mapaX + static_cast( entidad.posicion.x * escalaX ); int y = mapaY + static_cast( entidad.posicion.y * escalaY ); if (entidad.jugador) { DrawCircle( x, y, 5, WHITE ); DrawCircleLines( x, y, 8, BLUE ); } else { DrawCircle( x, y, 4, SKYBLUE ); } } DrawText( "MAPA", mapaX + 6, mapaY + 4, 9, LIGHTGRAY ); } // ============================================================ // HUD // ============================================================ void DibujarHUD( const Entidad& jugador, const std::vector& armas ) { DrawRectangle( 0, 0, PANTALLA_ANCHO, 74, Color{ 10, 12, 16, 245 } ); DrawText( TextFormat( "VIDA: %.0f / %.0f", jugador.vida, jugador.vidaMaxima ), 15, 10, 18, WHITE ); if ( jugador.arma >= 0 && jugador.arma < static_cast(armas.size()) ) { const Arma& arma = armas[jugador.arma]; DrawText( TextFormat( "ARMA: %s", NombreArma(arma.tipo) ), 15, 38, 15, ColorArma(arma.tipo) ); DrawText( TextFormat( "BALAS: %d / %d", arma.balas, CapacidadArma(arma.tipo) ), 220, 14, 18, WHITE ); DrawText( "CARTUCHOS: INFINITOS", 220, 40, 15, GREEN ); } else { DrawText( "SIN ARMA", 220, 20, 18, RED ); } DrawText( TextFormat( "FPS: %d", GetFPS() ), 700, 12, 18, GREEN ); } // ============================================================ // MAIN // ============================================================ int main() { srand( static_cast( time(nullptr) ) ); InitWindow( PANTALLA_ANCHO, PANTALLA_ALTO, "A4B1 0.0.2" ); SetTargetFPS(60); // ======================================================== // MAPA // ======================================================== std::vector paredes = CrearMapa(); // ======================================================== // ARMAS DEL SUELO // ======================================================== std::vector armas; armas.push_back( CrearArma( ARMA_RAPIDA, {650, 450}, true ) ); armas.push_back( CrearArma( ARMA_EQUILIBRADA, {2150, 450}, true ) ); armas.push_back( CrearArma( ARMA_PESADA, {650, 1400}, true ) ); armas.push_back( CrearArma( ARMA_PRECISA, {2150, 1400}, true ) ); // ======================================================== // 3 VS 3 // // AZUL: lado izquierdo // ROJO: lado derecho // // MUY SEPARADOS. // ======================================================== std::vector entidades; // Jugador azul entidades.push_back({ {250, 500}, {0, 0}, {1, 0}, RADIO_JUGADOR, 100, 100, EQUIPO_AZUL, true, true, -1, false, 0, 0, 0, {250, 500} }); // Compañero azul entidades.push_back({ {250, 850}, {0, 0}, {1, 0}, RADIO_IA, 100, 100, EQUIPO_AZUL, false, true, -1, false, 0, 0, 0, {250, 850} }); // Compañero azul entidades.push_back({ {250, 1200}, {0, 0}, {1, 0}, RADIO_IA, 100, 100, EQUIPO_AZUL, false, true, -1, false, 0, 0, 0, {250, 1200} }); // Enemigo rojo entidades.push_back({ {2550, 500}, {0, 0}, {-1, 0}, RADIO_IA, 100, 100, EQUIPO_ROJO, false, true, -1, false, 0, 0, 0, {2550, 500} }); // Enemigo rojo entidades.push_back({ {2550, 850}, {0, 0}, {-1, 0}, RADIO_IA, 100, 100, EQUIPO_ROJO, false, true, -1, false, 0, 0, 0, {2550, 850} }); // Enemigo rojo entidades.push_back({ {2550, 1200}, {0, 0}, {-1, 0}, RADIO_IA, 100, 100, EQUIPO_ROJO, false, true, -1, false, 0, 0, 0, {2550, 1200} }); // ======================================================== // UN ARMA EXCLUSIVA PARA CADA JUGADOR // ======================================================== armas.push_back( CrearArma( ARMA_EQUILIBRADA, {0, 0}, false ) ); entidades[0].arma = static_cast(armas.size()) - 1; armas.push_back( CrearArma( ARMA_RAPIDA, {0, 0}, false ) ); entidades[1].arma = static_cast(armas.size()) - 1; armas.push_back( CrearArma( ARMA_PESADA, {0, 0}, false ) ); entidades[2].arma = static_cast(armas.size()) - 1; armas.push_back( CrearArma( ARMA_PRECISA, {0, 0}, false ) ); entidades[3].arma = static_cast(armas.size()) - 1; armas.push_back( CrearArma( ARMA_EQUILIBRADA, {0, 0}, false ) ); entidades[4].arma = static_cast(armas.size()) - 1; armas.push_back( CrearArma( ARMA_PESADA, {0, 0}, false ) ); entidades[5].arma = static_cast(armas.size()) - 1; // ======================================================== // BALAS // ======================================================== std::vector balas; // ======================================================== // BOTIQUINES // ======================================================== std::vector botiquines; for (int i = 0; i < 5; i++) { CrearBotiquin( botiquines, paredes ); } // ======================================================== // MUNICIÓN // ======================================================== std::vector municiones; for (int i = 0; i < 5; i++) { CrearMunicion( municiones, paredes ); } // ======================================================== // MARCAS DE MUERTE // ======================================================== std::vector marcas; // ======================================================== // CÁMARA // ======================================================== Camera2D camara{}; camara.offset = { PANTALLA_ANCHO / 2.0f, PANTALLA_ALTO / 2.0f }; camara.target = entidades[0].posicion; camara.rotation = 0; camara.zoom = 1.0f; // ======================================================== // TIEMPO // ======================================================== float tiempoPartida = 0.0f; // ======================================================== // LOOP PRINCIPAL // ======================================================== while (!WindowShouldClose()) { float dt = GetFrameTime(); tiempoPartida += dt; // ==================================================== // JUGADOR // ==================================================== Entidad& jugador = entidades[0]; if (jugador.viva) { jugador.tiempoDisparo = std::max( 0.0f, jugador.tiempoDisparo - dt ); ActualizarRecarga( jugador, armas, dt ); Vector2 movimiento = { 0, 0 }; if (IsKeyDown(KEY_W)) movimiento.y -= 1; if (IsKeyDown(KEY_S)) movimiento.y += 1; if (IsKeyDown(KEY_A)) movimiento.x -= 1; if (IsKeyDown(KEY_D)) movimiento.x += 1; movimiento = Normalizar( movimiento ); MoverEntidad( jugador, Multiplicar( movimiento, VELOCIDAD_JUGADOR * dt ), paredes ); if ( movimiento.x != 0 || movimiento.y != 0 ) { jugador.direccion = movimiento; } if ( IsKeyDown(KEY_SPACE) && tiempoPartida > 3.0f ) { Disparar( jugador, armas, balas ); } if (IsKeyPressed(KEY_R)) { IniciarRecarga( jugador, armas ); } if (IsKeyPressed(KEY_E)) { int armaCercana = BuscarArmaCercana( jugador, armas ); if (armaCercana >= 0) { RecogerArma( jugador, armaCercana, armas ); } } } // ==================================================== // IA // ==================================================== for ( int i = 1; i < static_cast(entidades.size()); i++ ) { ActualizarIA( i, entidades, armas, balas, paredes, dt, tiempoPartida ); } // ==================================================== // BALAS // ==================================================== MoverBalas( balas, paredes, entidades, marcas, armas, dt ); // ==================================================== // BOTIQUINES // ==================================================== ActualizarBotiquines( botiquines, entidades ); int botiquinesActivos = 0; for (const auto& botiquin : botiquines) { if (botiquin.activa) botiquinesActivos++; } while (botiquinesActivos < 5) { CrearBotiquin( botiquines, paredes ); botiquinesActivos = 0; for (const auto& botiquin : botiquines) { if (botiquin.activa) botiquinesActivos++; } if (botiquines.size() > 30) break; } // ==================================================== // MUNICIÓN // ==================================================== ActualizarMunicion( municiones, entidades, armas ); int municionActiva = 0; for (const auto& municion : municiones) { if (municion.activa) municionActiva++; } while (municionActiva < 5) { CrearMunicion( municiones, paredes ); municionActiva = 0; for (const auto& municion : municiones) { if (municion.activa) municionActiva++; } if (municiones.size() > 30) break; } // ==================================================== // CÁMARA // ==================================================== camara.target = jugador.posicion; // ==================================================== // ZOOM // ==================================================== if (IsKeyDown(KEY_MINUS)) { camara.zoom -= 0.5f * dt; } if (IsKeyDown(KEY_EQUAL)) { camara.zoom += 0.5f * dt; } camara.zoom = std::clamp( camara.zoom, 0.65f, 1.4f ); // ==================================================== // FULLSCREEN // ==================================================== if (IsKeyPressed(KEY_F11)) { ToggleFullscreen(); } // ==================================================== // DIBUJAR // ==================================================== BeginDrawing(); ClearBackground(BLACK); BeginMode2D(camara); DibujarMapa( paredes ); DibujarBotiquines( botiquines ); DibujarMunicion( municiones ); DibujarArmas( armas ); // Balas for (const Bala& bala : balas) { if (!bala.activa) continue; Color color = bala.equipo == EQUIPO_AZUL ? SKYBLUE : ORANGE; DrawCircleV( bala.posicion, 5, color ); } DibujarEntidades( entidades ); EndMode2D(); // ==================================================== // OSCURIDAD // ==================================================== DrawRectangle( 0, 74, PANTALLA_ANCHO, PANTALLA_ALTO - 74, Color{ 0, 0, 0, 115 } ); // ==================================================== // HUD // ==================================================== DibujarHUD( jugador, armas ); // ==================================================== // MINIMAPA // ==================================================== DibujarMinimapa( paredes, entidades, marcas ); // ==================================================== // CONTROLES // ==================================================== DrawText( "WASD: mover", 15, 685, 15, LIGHTGRAY ); DrawText( "SPACE: disparar", 130, 685, 15, LIGHTGRAY ); DrawText( "R: recargar", 275, 685, 15, LIGHTGRAY ); DrawText( "E: recoger arma", 375, 685, 15, LIGHTGRAY ); // ==================================================== // RECARGANDO // ==================================================== if ( jugador.viva && jugador.recargando ) { DrawText( "RECARGANDO...", PANTALLA_ANCHO / 2 - 90, PANTALLA_ALTO - 55, 22, YELLOW ); } // ==================================================== // MUERTO // ==================================================== if (!jugador.viva) { DrawRectangle( 0, 0, PANTALLA_ANCHO, PANTALLA_ALTO, Color{ 0, 0, 0, 180 } ); DrawText( "HAS MUERTO", PANTALLA_ANCHO / 2 - 145, PANTALLA_ALTO / 2 - 30, 45, RED ); } // ==================================================== // MARCADOR // ==================================================== int azulesVivos = 0; int rojosVivos = 0; for (const auto& entidad : entidades) { if (!entidad.viva) continue; if (entidad.equipo == EQUIPO_AZUL) azulesVivos++; else rojosVivos++; } DrawText( TextFormat( "AZUL %d - ROJO %d", azulesVivos, rojosVivos ), 1000, 685, 16, WHITE ); EndDrawing(); } CloseWindow(); return 0; }