package linea; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; public class BouleBonus extends ObjetGraphique { private double rayon = 15; private double vitesse = 5.0; private boolean estVerte; // true = bonus (niveau +1), false = malus (niveau -1) private int frameCounter = 0; private double vitesseVerticale = 0.0; private double amplitudeOndulation = 0.2; private double vitesseHorizontaleLisse = 0.0; private double phaseOndulation = Math.random() * Math.PI * 2; // phase unique par boule private double sensEvitementLigne = 0.0; private static double clamp(double value, double min, double max) { return Math.max(min, Math.min(max, value)); } public BouleBonus(double x, double y, boolean estVerte) { this.x = x; this.y = y; this.estVerte = estVerte; if (estVerte) { this.couleur = new Color(0.0f, 0.8f, 0.0f); // Vert this.rayon = 15; this.amplitudeOndulation = 0.45; // onde visible plus ample this.vitesseHorizontaleLisse = 4.8; } else { this.couleur = new Color(0.8f, 0.0f, 0.0f); // Rouge this.rayon = 14; this.amplitudeOndulation = 0.14; this.vitesseHorizontaleLisse = 4.3; this.sensEvitementLigne = Math.random() < 0.5 ? -1.0 : 1.0; } } public double getRayon() { return rayon; } public boolean isVerte() { return estVerte; } public void garantirDistanceLigne(double yLigne, double margePx) { if (estVerte) { return; } double distanceMiniCentre = rayon + Math.max(0.0, margePx); double ecart = y - yLigne; double distance = Math.abs(ecart); if (distance >= distanceMiniCentre) { return; } double sens = (ecart == 0.0) ? sensEvitementLigne : Math.signum(ecart); y = yLigne + sens * distanceMiniCentre; vitesseVerticale = 0.0; if (y < 20) y = 20; if (y > 580) y = 580; } public void ajusterVitessePourDistanceLigne(double yLigne, double margePx) { if (estVerte) { return; } double distanceCibleCentre = rayon + Math.max(0.0, margePx); double ecart = y - yLigne; double distance = Math.abs(ecart); double progressionZone = clamp((560.0 - x) / 240.0, 0.0, 1.0); if (progressionZone <= 0.0) { return; } double sens = (ecart == 0.0) ? sensEvitementLigne : Math.signum(ecart); // vitesse variable if (distance > distanceCibleCentre) { double excedent = distance - distanceCibleCentre; double pas = clamp(excedent * (0.08 + progressionZone * 0.20), 0.0, 10.0 + progressionZone * 8.0); y -= sens * pas; } if (y < 20) y = 20; if (y > 580) y = 580; } @Override void Afficher(Graphics g) { Graphics2D g2D = (Graphics2D) g; double rayonAffiche = rayon; // Dessiner le cercle rempli g2D.setColor(couleur); g2D.fillOval((int)(x - rayonAffiche), (int)(y - rayonAffiche), (int)(2 * rayonAffiche), (int)(2 * rayonAffiche)); // Contour g2D.setStroke(new BasicStroke(2.0f)); g2D.setColor(estVerte ? new Color(0.0f, 1.0f, 0.0f) : new Color(1.0f, 0.0f, 0.0f)); g2D.drawOval((int)(x - rayonAffiche), (int)(y - rayonAffiche), (int)(2 * rayonAffiche), (int)(2 * rayonAffiche)); } @Override void Animer() { // Déplacement vers la gauche x -= vitesse; } public void animerAvecCible(double vitesseLigne, double cibleY) { animerAvecCible(vitesseLigne, cibleY, 0.0); } public void animerAvecCible(double vitesseLigne, double cibleY, double cibleVitesseY) { // Vertes: trajectoire fluide à intercepter. Rouges: poursuite plus nette avec fenêtre d'esquive. double facteurHorizontal = estVerte ? 0.88 : 0.64; double vitesseMax = estVerte ? 9.2 : 5.8; double vitessePoursuite = estVerte ? 0.075 : 0.050; // meilleure réactivité double limiteVerticale = estVerte ? 1.85 : 1.05; // accélération verticale double amortissement = estVerte ? 0.87 : 0.81; // moins de friction double zoneMorte = estVerte ? 18.0 : 16.0; // zone morte double vitesseVerticaleMax = estVerte ? 4.2 : 2.6; // vitesses verticales plus hautes double proximiteJoueur = clamp((540.0 - x) / 280.0, 0.0, 1.0); if (!estVerte) { // Plus rouge proche du joueur, plus on ouvre la fenêtre d'esquive. vitessePoursuite *= (1.0 - proximiteJoueur * 0.66); limiteVerticale *= (1.0 - proximiteJoueur * 0.52); vitesseVerticaleMax *= (1.0 - proximiteJoueur * 0.40); zoneMorte += proximiteJoueur * 22.0; } double vitesseLigneSecurisee = Math.max(0.0, vitesseLigne); double vitesseCible = Math.min(vitesseMax, vitesseLigneSecurisee * facteurHorizontal); if (!estVerte) { vitesseCible = vitesseCible * 0.68 + 4.5 * 0.32; } double deltaVitesse = clamp(vitesseCible - vitesseHorizontaleLisse, -0.25, 0.25); double vitesseMaxLocale = vitesseMax; if (!estVerte && x < 380) { double facteurApproche = clamp((380.0 - x) / 180.0, 0.0, 1.0); vitesseMaxLocale = vitesseMax - (0.55 * facteurApproche); } vitesseHorizontaleLisse = clamp(vitesseHorizontaleLisse + deltaVitesse, 4.0, vitesseMaxLocale); vitesse = vitesseHorizontaleLisse; x -= vitesse; double vitesseJoueurLimitee = clamp(cibleVitesseY, -6.0, 6.0); double anticipation = estVerte ? vitesseJoueurLimitee * 3.2 : 0.0; double cibleLisse = cibleY + anticipation + (estVerte ? Math.sin(frameCounter * 0.055 + phaseOndulation) * 45.0 : 0.0); double deltaY = cibleLisse - y; double ratioEcartY = clamp(Math.abs(deltaY) / 120.0, 0.50, 1.60); vitessePoursuite *= ratioEcartY; limiteVerticale *= Math.sqrt(ratioEcartY); if (Math.abs(deltaY) < zoneMorte) { deltaY = 0; } vitesseVerticale += clamp(deltaY * vitessePoursuite, -limiteVerticale, limiteVerticale); vitesseVerticale *= amortissement; vitesseVerticale = clamp(vitesseVerticale, -vitesseVerticaleMax, vitesseVerticaleMax); double ondulation = Math.sin(frameCounter * 0.085 + (estVerte ? 0.0 : 1.1)) * amplitudeOndulation; y += vitesseVerticale + ondulation; frameCounter++; if (y < 20) { y = 20; vitesseVerticale = 0; } else if (y > 580) { y = 580; vitesseVerticale = 0; } } // Vérifier collision avec le cercle public boolean collisionAvec(Cercle c) { double cx = c.getX(); double cy = c.getY(); double cRayon = c.getRayon(); double dist = Math.hypot(cx - x, cy - y); double seuil = estVerte ? (rayon + cRayon - 6.0) : (rayon + cRayon - 10.5); return dist <= seuil; } public void setVitesse(double vitesse) { this.vitesse = vitesse; } }