Implémentation d'un MOBA en Java avec AWT et Swing

Structure du projet

Gestion du décor

package moteur;

import java.awt.*;

public class Decor extends EntiteJeu {
    private Image fond;

    public Decor(FenetreJeu fenetre) {
        super(fenetre);
        this.fond = Toolkit.getDefaultToolkit().getImage("ressources/carte_principale.jpg");
    }

    @Override
    public void dessiner(Graphics2D g) {
        g.drawImage(fond, 0, 0, null);
    }

    @Override
    public Rectangle limites() {
        return null;
    }
}

Projectiles et système de combat

package moteur;

import java.awt.*;

public class Projectile extends EntiteJeu {
    private EntiteJeu lanceur;
    private EntiteJeu cible;
    private int degats;
    private int velocite;

    public Projectile(FenetreJeu fenetre, EntiteJeu lanceur, EntiteJeu cible, 
                      int degats, int vitesse, String cheminImage) {
        super(lanceur.posX, lanceur.posY, fenetre);
        this.lanceur = lanceur;
        this.cible = cible;
        this.degats = degats;
        this.velocite = vitesse;
        if (cheminImage != null) {
            definirImage(cheminImage);
        }
    }

    public Projectile() {
        super();
    }

    private void deplacer() {
        if (collisionRectangles(limites(), cible.limites())) {
            cible.modifierSante(-degats);
            fenetre.entitesASupprimer.add(this);
            return;
        }

        double distance = calculerDistance(posX, posY, cible.posX, cible.posY);
        if (distance > 0) {
            int vx = (velocite * (cible.posX - posX)) / (int) distance;
            int vy = (velocite * (cible.posY - posY)) / (int) distance;
            posX += vx;
            posY += vy;
        }
    }

    @Override
    public void dessiner(Graphics2D g) {
        g.drawImage(obtenirImage(), posX - 16, posY - 16, null);
        g.setColor(Color.BLACK);
        g.fillOval(posX - 5, posY - 5, 10, 10);
        deplacer();
    }

    @Override
    public Rectangle limites() {
        return new Rectangle(posX - 5, posY - 5, 10, 10);
    }
}

Classe abstraite des héros jouables

package moteur;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public abstract class Heros extends EntiteJeu {
    protected boolean haut, bas, gauche, droite;
    protected static String[] texturesDeplacement = new String[8];
    protected int indiceTexture = 1;
    
    protected Image capaciteQ;
    protected Image capaciteW;
    protected Image capaciteE;
    
    protected int delaiRechargeQ;
    protected int delaiRechargeW;
    protected int delaiRechargeE;
    
    protected boolean disponibleQ = true;
    protected boolean disponibleW = true;
    protected boolean disponibleE = true;

    static {
        for (int i = 1; i < 8; i++) {
            texturesDeplacement[i] = "ressources/heros/marche/" + i + ".png";
        }
    }

    public Heros(FenetreJeu fenetre) {
        super(fenetre);
        definirImage("ressources/heros/immobile.png");
        posX = 700;
        posY = 3800;
        vitesse = 75;
        santeMax = 24000;
        porteeAttaque = 250;
        delaiAttaque = 100;
        santeActuelle = santeMax;
    }

    public void touchePressee(KeyEvent evenement) {
        switch (evenement.getKeyCode()) {
            case KeyEvent.VK_D -> droite = true;
            case KeyEvent.VK_A -> gauche = true;
            case KeyEvent.VK_W -> haut = true;
            case KeyEvent.VK_S -> bas = true;
        }
    }

    public void toucheRelachee(KeyEvent evenement) {
        switch (evenement.getKeyCode()) {
            case KeyEvent.VK_D -> droite = false;
            case KeyEvent.VK_A -> gauche = false;
            case KeyEvent.VK_W -> haut = false;
            case KeyEvent.VK_S -> bas = false;
        }
    }

    protected void mouvement() {
        if (haut) posY -= vitesse;
        if (bas) posY += vitesse;
        if (gauche) posX -= vitesse;
        if (droite) posX += vitesse;
        
        if (haut || bas || gauche || droite) {
            definirImage(texturesDeplacement[indiceTexture]);
            indiceTexture = (indiceTexture % 7) + 1;
        } else {
            definirImage("ressources/heros/immobile.png");
        }
    }

    protected void ajouterBoutonsCompetences() {
        JButton boutonQ = new JButton();
        boutonQ.setBounds(1056, 513, 100, 100);
        boutonQ.addActionListener(e -> utiliserCompetenceQ());
        
        JButton boutonW = new JButton();
        boutonW.setBounds(1090, 370, 100, 100);
        boutonW.addActionListener(e -> utiliserCompetenceW());
        
        JButton boutonE = new JButton();
        boutonE.setBounds(1220, 300, 100, 100);
        boutonE.addActionListener(e -> utiliserCompetenceE());
        
        fenetre.add(boutonQ);
        fenetre.add(boutonW);
        fenetre.add(boutonE);
    }

    public abstract void utiliserCompetenceQ();
    public abstract void utiliserCompetenceW();
    public abstract void utiliserCompetenceE();
    public abstract void effetCompetence(Graphics2D g);

    @Override
    public void dessiner(Graphics2D g) {
        if (santeActuelle <= 0) {
            vivant = false;
            fenetre.entitesASupprimer.add(this);
            return;
        }

        afficherBarreSante(g, 30, 80, 80, 20, Color.GREEN);
        
        g.drawImage(capaciteQ, posX + 360, posY + 180, null);
        g.drawImage(capaciteW, posX + 400, posY + 40, null);
        g.drawImage(capaciteE, posX + 520, posY - 30, null);
        
        g.drawImage(obtenirImage(), posX - 33, posY - 50, null);
        g.setColor(Color.GREEN);
        g.fillOval(posX, posY, 10, 10);
        g.drawRect(posX - 23, posY - 50, 60, 120);
        
        mouvement();
        effetCompetence(g);
    }

    @Override
    public Rectangle limites() {
        return new Rectangle(posX - 30, posY - 60, 60, 120);
    }
}

Implémentation d'un héros spécifique : l'Enchanteresse

package moteur;

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

public class Enchanteresse extends Heros {
    private boolean competenceQActive = false;
    private boolean competenceWActive = false;
    
    private EcouteurSouris ecouteur;
    private PolygoneMagique zoneCompetenceQ;
    private double sinus, cosinus;
    private ArrayList<EntiteJeu> ciblesTouchées;
    private int etapesCompetenceQ = 0;
    
    private EntiteJeu cibleCompetenceW;
    private Projectile projectileW;
    
    private Projectile[] salveProjectiles = new Projectile[5];

    public Enchanteresse(FenetreJeu fenetre) {
        super(fenetre);
        capaciteQ = Toolkit.getDefaultToolkit().getImage("ressources/enchanteresse/sort_q.jpg");
        capaciteW = Toolkit.getDefaultToolkit().getImage("ressources/enchanteresse/sort_w.jpg");
        capaciteE = Toolkit.getDefaultToolkit().getImage("ressources/enchanteresse/sort_e.jpg");
        
        delaiRechargeQ = 3000;
        delaiRechargeW = 5000;
        delaiRechargeE = 8000;
    }

    private void deplacerCompetenceQ() {
        zoneCompetenceQ.decaler((int) (50 * cosinus), -(int) (50 * sinus));
        
        for (EntiteJeu ennemi : fenetre.listeRouge) {
            if (ennemi instanceof SbireRouge && 
                zoneCompetenceQ.intersecte(ennemi.limites()) && 
                !ciblesTouchées.contains(ennemi)) {
                ennemi.modifierSante(-400);
                ciblesTouchées.add(ennemi);
            }
        }
    }

    @Override
    public void utiliserCompetenceQ() {
        if (disponibleQ) {
            ecouteur = new EcouteurSouris();
            zoneCompetenceQ = new PolygoneMagique();
            fenetre.addMouseListener(ecouteur);
            ciblesTouchées = new ArrayList<>();
        }
    }

    @Override
    public void utiliserCompetenceW() {
        if (!disponibleW) return;
        
        for (EntiteJeu ennemi : fenetre.entitesJeu) {
            if (ennemi instanceof SbireRouge && 
                collisionCercle(ennemi.limites(), posX, posY, 250) && 
                ennemi.estVivant()) {
                
                projectileW = new Projectile(fenetre, this, ennemi, 250, 60, 
                    "ressources/enchanteresse/projectile_w.png");
                fenetre.entitesJeu.add(projectileW);
                cibleCompetenceW = ennemi;
                competenceWActive = true;
                
                new ThreadRechargeW().start();
                break;
            }
        }
    }

    @Override
    public void utiliserCompetenceE() {
        if (!disponibleE) return;
        
        ArrayList<EntiteJeu> ciblesPotentielles = new ArrayList<>();
        
        for (EntiteJeu cible : fenetre.entitesJeu) {
            if (cible instanceof SbireRouge && 
                collisionCercle(cible.limites(), posX, posY, 250) && 
                cible.estVivant()) {
                ciblesPotentielles.add(cible);
            }
        }
        
        if (ciblesPotentielles.isEmpty()) return;
        
        java.util.Random aleatoire = new java.util.Random();
        int compteur = 0;
        
        while (compteur < 5) {
            int index = aleatoire.nextInt(ciblesPotentielles.size());
            EntiteJeu cible = ciblesPotentielles.get(index);
            
            if (!cible.estVivant()) {
                cible = new Substitut(cible.posX, cible.posY);
            }
            
            salveProjectiles[compteur] = new Projectile(fenetre, this, cible, 150, 60,
                "ressources/enchanteresse/projectile_w.png");
            compteur++;
        }
        
        new ThreadSalveProjectiles().start();
        new ThreadRechargeE().start();
    }

    @Override
    public void effetCompetence(Graphics2D g) {
        if (competenceQActive) {
            g.setColor(Color.RED);
            g.fill(zoneCompetenceQ);
            deplacerCompetenceQ();
            etapesCompetenceQ++;
            if (etapesCompetenceQ >= 10) {
                etapesCompetenceQ = 0;
                competenceQActive = false;
            }
        }
        
        if (competenceWActive && projectileW.limites().intersects(cibleCompetenceW.limites())) {
            new ThreadControle().start();
            competenceWActive = false;
        }
    }

    private class ThreadRechargeQ extends Thread {
        public void run() {
            disponibleQ = false;
            try {
                Thread.sleep(delaiRechargeQ);
            } catch (Exception e) {
                e.printStackTrace();
            }
            disponibleQ = true;
        }
    }

    private class ThreadRechargeW extends Thread {
        public void run() {
            disponibleW = false;
            try {
                Thread.sleep(delaiRechargeW);
            } catch (Exception e) {
                e.printStackTrace();
            }
            disponibleW = true;
        }
    }

    private class ThreadRechargeE extends Thread {
        public void run() {
            disponibleE = false;
            int tempsRestant = delaiRechargeE;
            try {
                while (tempsRestant > 0) {
                    Thread.sleep(1000);
                    tempsRestant -= 1000;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            disponibleE = true;
        }
    }

    private class ThreadControle extends Thread {
        public void run() {
            cibleCompetenceW.estControle = true;
            try {
                Thread.sleep(20000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            cibleCompetenceW.estControle = false;
        }
    }

    private class ThreadSalveProjectiles extends Thread {
        public void run() {
            try {
                for (int i = 0; i < 5; i++) {
                    fenetre.entitesJeu.add(salveProjectiles[i]);
                    Thread.sleep(100);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private class EcouteurSouris extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent evenement) {
            int sourisX = evenement.getX(), sourisY = evenement.getY();
            int herosX = 700, herosY = 350;
            double distance = calculerDistance(sourisX, sourisY, herosX, herosY);
            
            cosinus = (sourisX - herosX) / distance;
            sinus = -(sourisY - herosY) / distance;
            
            int decalX = (int) (60 * sinus);
            int decalY = (int) (60 * cosinus);
            
            zoneCompetenceQ.ajouterPoint(posX - decalX, posY - decalY);
            zoneCompetenceQ.ajouterPoint(posX + decalX, posY + decalY);
            zoneCompetenceQ.ajouterPoint(posX + decalX + (int) (20 * cosinus), 
                                         posY + decalY - (int) (20 * sinus));
            zoneCompetenceQ.ajouterPoint(posX - decalX + (int) (20 * cosinus), 
                                         posY - decalY - (int) (20 * sinus));
            
            fenetre.removeMouseListener(this);
            new ThreadRechargeQ().start();
            competenceQActive = true;
        }
    }
}

Fenêtre principale et boucle de jeu

package moteur;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

public class FenetreJeu extends JFrame {
    private int etatPartie = 0; // 0: en cours, 1: victoire, 2: défaite
    
    private final int largeurFenetre = 1400;
    private final int hauteurFenetre = 700;
    private Image imageTampon;
    
    private Image iconeAttaque;
    private Image ecranVictoire;
    private Image ecranDefaite;
    
    public Decor decor = new Decor(this);
    public Heros joueur = new Enchanteresse(this);
    public Sbire sbireBleu = new SbireBleu(this);
    public Sbire sbireRouge = new SbireRouge(this);
    public Bestiaire bestiaire = new Bestiaire(this);
    public Tourelle tourelles = new Tourelle(this);
    
    public ArrayList<EntiteJeu> entitesJeu = new ArrayList<>();
    public ArrayList<EntiteJeu> listeRouge = new ArrayList<>();
    public ArrayList<EntiteJeu> listeBleue = new ArrayList<>();
    public ArrayList<EntiteJeu> entitesASupprimer = new ArrayList<>();

    public void initialiser() {
        setSize(largeurFenetre, hauteurFenetre);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setResizable(false);
        setTitle("Arène des Légendes");
        setVisible(true);
        
        addKeyListener(new EcouteurClavier());
        
        entitesJeu.add(decor);
        entitesJeu.add(joueur);
        entitesJeu.addAll(bestiaire.creatures);
        entitesJeu.addAll(tourelles.listeTourelles);
        
        for (int i = 0; i < 4; i++) {
            listeBleue.add(tourelles.listeTourelles.get(i));
        }
        for (int i = 4; i < 8; i++) {
            listeRouge.add(tourelles.listeTourelles.get(i));
        }
        
        JButton boutonAttaque = new JButton();
        boutonAttaque.setBounds(1150, 430, 130, 132);
        boutonAttaque.addActionListener(e -> joueur.attaquer(listeRouge));
        add(boutonAttaque);
        
        joueur.ajouterBoutonsCompetences();
        
        while (true) {
            sbireBleu.generer(this, listeBleue);
            sbireRouge.generer(this, listeRouge);
            repaint();
            try {
                Thread.sleep(25);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void paint(Graphics g) {
        if (imageTampon == null) {
            imageTampon = createImage(5984, 4452);
        }
        
        Graphics2D g2d = (Graphics2D) imageTampon.getGraphics();
        
        if (etatPartie == 0) {
            tourelles.verifierEtat();
            
            for (EntiteJeu entite : entitesJeu) {
                entite.dessiner(g2d);
            }
            
            g2d.drawImage(iconeAttaque, joueur.posX + 500, joueur.posY + 100, null);
            entitesJeu.removeAll(entitesASupprimer);
        } else if (etatPartie == 1) {
            g2d.drawImage(ecranVictoire, joueur.posX - 700, joueur.posY - 300, null);
        } else {
            g2d.drawImage(ecranDefaite, joueur.posX - 700, joueur.posY - 300, null);
        }
        
        g.drawImage(imageTampon, -joueur.posX + 700, -joueur.posY + 350, null);
        requestFocus();
    }

    private class EcouteurClavier extends KeyAdapter {
        @Override
        public void keyPressed(KeyEvent e) {
            joueur.touchePressee(e);
        }

        @Override
        public void keyReleased(KeyEvent e) {
            joueur.toucheRelachee(e);
        }
    }

    public static void main(String[] args) {
        new FenetreJeu().initialiser();
    }
}

Classe de base pour toutes les entités

package moteur;

import java.awt.*;
import java.util.ArrayList;

public abstract class EntiteJeu {
    protected int posX;
    protected int posY;
    private Image sprite;
    protected FenetreJeu fenetre;
    protected int vitesse;
    private int santeMax;
    private int santeActuelle;
    private EntiteJeu cible;
    private boolean possedeCible = false;
    private int porteeAttaque;
    private int delaiAttaque;
    private boolean attaqueDisponible = true;
    protected boolean vivant = true;
    protected boolean estControle = false;

    public EntiteJeu(FenetreJeu fenetre) {
        this.fenetre = fenetre;
    }

    public EntiteJeu(int x, int y, FenetreJeu fenetre) {
        this.posX = x;
        this.posY = y;
        this.fenetre = fenetre;
    }

    public EntiteJeu() {}

    protected void afficherBarreSante(Graphics2D g, int decalX, int decalY, 
                                     int largeur, int hauteur, Color couleur) {
        g.setColor(Color.BLACK);
        g.drawRect(posX - decalX, posY - decalY, largeur, hauteur);
        g.setColor(couleur);
        int largeurSante = (int) (largeur * ((double) santeActuelle / santeMax));
        g.fillRect(posX - decalX, posY - decalY, largeurSante, hauteur);
    }

    protected double calculerDistance(int x1, int y1, int x2, int y2) {
        return Math.hypot(x1 - x2, y1 - y2);
    }

    protected boolean collisionRectangles(Rectangle r1, Rectangle r2) {
        return r1.intersects(r2);
    }

    protected boolean collisionCercle(Rectangle rect, int cx, int cy, int rayon) {
        return calculerDistance(cx, cy, rect.x, rect.y) < rayon ||
               calculerDistance(cx, cy, rect.x, rect.y + rect.height) < rayon ||
               calculerDistance(cx, cy, rect.x + rect.width, rect.y) < rayon ||
               calculerDistance(cx, cy, rect.x + rect.width, rect.y + rect.height) < rayon;
    }

    public void attaquer(ArrayList<EntiteJeu> ciblesPotentielles) {
        if (possedeCible) {
            if (!collisionCercle(cible.limites(), posX, posY, porteeAttaque)) {
                possedeCible = false;
            } else if (!cible.estVivant()) {
                possedeCible = false;
            } else if (attaqueDisponible && vivant) {
                Projectile p = null;
                
                if (this instanceof Tourelle) {
                    p = new Projectile(fenetre, this, cible, 500, 50, null);
                } else if (this instanceof Sbire) {
                    p = new Projectile(fenetre, this, cible, 50, 30, null);
                } else if (this instanceof Heros) {
                    p = new Projectile(fenetre, this, cible, 500, 50, null);
                }
                
                if (p != null) {
                    fenetre.entitesJeu.add(p);
                    new ThreadAttaque().start();
                }
            }
        } else {
            for (EntiteJeu candidat : ciblesPotentielles) {
                if (collisionCercle(candidat.limites(), posX, posY, porteeAttaque)) {
                    cible = candidat;
                    possedeCible = true;
                    break;
                }
            }
        }
    }

    private class ThreadAttaque extends Thread {
        public void run() {
            attaqueDisponible = false;
            try {
                Thread.sleep(delaiAttaque);
            } catch (Exception e) {
                e.printStackTrace();
            }
            attaqueDisponible = true;
        }
    }

    public abstract void dessiner(Graphics2D g);
    public abstract Rectangle limites();

    public void modifierSante(int variation) {
        santeActuelle = Math.max(0, Math.min(santeMax, santeActuelle + variation));
    }

    public boolean estVivant() { return vivant; }
    public void setAlive(boolean v) { vivant = v; }
    public int getX() { return posX; }
    public void setX(int x) { posX = x; }
    public int getY() { return posY; }
    public void setY(int y) { posY = y; }
    protected Image obtenirImage() { return sprite; }
    protected void definirImage(String chemin) { 
        sprite = Toolkit.getDefaultToolkit().getImage(chemin); 
    }
    protected void definirImage(Image img) { sprite = img; }
    public int getSanteActuelle() { return santeActuelle; }
    public void setCurrentHp(int val) { santeActuelle = val; }
    public int getSanteMax() { return santeMax; }
    public void setHp(int val) { santeMax = val; }
    public int getVitesse() { return vitesse; }
    public void setSpd(int val) { vitesse = val; }
    public int getPorteeAttaque() { return porteeAttaque; }
    public void setDis(int val) { porteeAttaque = val; }
    public int getDelaiAttaque() { return delaiAttaque; }
    public void setAttackCoolDownTime(int val) { delaiAttaque = val; }
}

Gestion des sbires et IA de déplacement

package moteur;

import java.awt.*;
import java.util.ArrayList;

public abstract class Sbire extends EntiteJeu {
    private boolean prochainSbire = true;
    private boolean prochaineVague = true;
    private int compteSbires = 0;
    private boolean cibleDetectee = false;

    public Sbire(FenetreJeu fenetre) {
        super(fenetre);
        setHp(800);
        santeActuelle = getSanteMax();
        setDis(100);
        delaiAttaque = 2000;
    }

    public abstract void deplacer(ArrayList<EntiteJeu> cibles);

    protected void detecterCible(ArrayList<EntiteJeu> cibles) {
        for (EntiteJeu cible : cibles) {
            if (collisionCercle(cible.limites(), posX, posY, 200)) {
                this.cible = cible;
                cibleDetectee = true;
            }
        }
    }

    protected void avancerVersCible() {
        double distance = calculerDistance(posX, posY, cible.posX, cible.posY);
        if (distance > 0) {
            int vx = (vitesse * (cible.posX - posX)) / (int) distance;
            int vy = (vitesse * (cible.posY - posY)) / (int) distance;
            posX += vx;
            posY += vy;
        }
    }

    public void generer(FenetreJeu fenetre, ArrayList<EntiteJeu> listeSbires) {
        if (!prochaineVague) return;
        
        if (prochainSbire) {
            Sbire nouveau;
            if (listeSbires == this.fenetre.listeBleue) {
                nouveau = new SbireBleu(fenetre);
            } else {
                nouveau = new SbireRouge(fenetre);
            }
            fenetre.entitesJeu.add(nouveau);
            listeSbires.add(nouveau);
            compteSbires++;
            
            new ThreadDelaiSbire().start();
            
            if (compteSbires >= 3) {
                compteSbires = 0;
                new ThreadDelaiVague().start();
            }
        }
    }

    private class ThreadDelaiSbire extends Thread {
        public void run() {
            prochainSbire = false;
            try { Thread.sleep(1500); } catch (Exception e) {}
            prochainSbire = true;
        }
    }

    private class ThreadDelaiVague extends Thread {
        public void run() {
            prochaineVague = false;
            try { Thread.sleep(15000); } catch (Exception e) {}
            prochaineVague = true;
        }
    }

    @Override
    public void dessiner(Graphics2D g) {
        if (santeActuelle <= 0) {
            vivant = false;
            fenetre.entitesASupprimer.add(this);
            fenetre.listeBleue.remove(this);
            fenetre.listeRouge.remove(this);
            return;
        }

        Color couleurSante = (this instanceof SbireBleu) ? Color.GREEN : Color.RED;
        afficherBarreSante(g, 17, 28, 45, 10, couleurSante);
        
        g.drawImage(obtenirImage(), posX - 16, posY - 16, null);
        g.setColor(Color.RED);
        g.fillOval(posX, posY, 10, 10);
        g.drawRect(posX - 16, posY - 16, 45, 45);
        g.drawOval(posX - 200, posY - 200, 400, 400);

        if (!estControle) {
            if (this instanceof SbireBleu) {
                deplacer(fenetre.listeRouge);
            } else {
                deplacer(fenetre.listeBleue);
            }
        }
    }

    @Override
    public Rectangle limites() {
        return new Rectangle(posX - 16, posY - 16, 45, 45);
    }
}

Variantes de sbires avec trajectoires prédéfinies

package moteur;

import java.util.ArrayList;

public class SbireBleu extends Sbire {
    public SbireBleu(FenetreJeu fenetre) {
        super(fenetre);
        definirImage("ressources/sbires/bleu.jpg");
        posX = 1325;
        posY = 3750;
    }

    @Override
    public void deplacer(ArrayList<EntiteJeu> cibles) {
        if (cibleDetectee) {
            if (!collisionCercle(cible.limites(), posX, posY, 200)) {
                cibleDetectee = false;
            } else if (!possedeCible) {
                avancerVersCible();
            }
            attaquer(cibles);
        } else {
            detecterCible(cibles);
            
            if (posX < 4425) {
                vitesse = 5;
                posX += vitesse;
            } else if (posX < 5100) {
                vitesse = 20;
                posX += vitesse;
                posY -= vitesse;
            } else {
                vitesse = 18;
                posY -= vitesse;
            }
        }
    }
}

Système de tourelles défensives

package moteur;

import java.awt.*;
import java.util.ArrayList;

public class Tourelle extends EntiteJeu {
    ArrayList<Tourelle> listeTourelles = new ArrayList<>();
    
    public TourelleBleue tourelleB1, tourelleB2, tourelleB3, baseBleue;
    public TourelleRouge tourelleR1, tourelleR2, tourelleR3, baseRouge;

    public Tourelle(FenetreJeu fenetre) {
        super(fenetre);
        definirImage("ressources/tourelles/tourelle.png");
        
        listeTourelles.add(tourelleB1 = new TourelleBleue(1860, 3790, fenetre));
        listeTourelles.add(tourelleB2 = new TourelleBleue(2650, 3820, fenetre));
        listeTourelles.add(tourelleB3 = new TourelleBleue(3995, 3830, fenetre));
        listeTourelles.add(baseBleue = new TourelleBleue(1130, 3650, fenetre));
        listeTourelles.add(tourelleR1 = new TourelleRouge(5100, 3030, fenetre));
        listeTourelles.add(tourelleR2 = new TourelleRouge(5120, 2100, fenetre));
        listeTourelles.add(tourelleR3 = new TourelleRouge(5060, 1570, fenetre));
        listeTourelles.add(baseRouge = new TourelleRouge(4850, 1100, fenetre));
    }

    public Tourelle(int x, int y, FenetreJeu fenetre) {
        super(x, y, fenetre);
        definirImage("ressources/tourelles/tourelle.png");
        setHp(6000);
        santeActuelle = getSanteMax();
        delaiAttaque = 1000;
        porteeAttaque = 300;
    }

    public void verifierEtat() {
        if (baseBleue.santeActuelle <= 0) fenetre.etatPartie = 2;
        if (baseRouge.santeActuelle <= 0) fenetre.etatPartie = 1;
    }

    @Override
    public void dessiner(Graphics2D g) {
        if (santeActuelle <= 0) {
            vivant = false;
            fenetre.entitesASupprimer.add(this);
            return;
        }

        Color couleur = (this instanceof TourelleBleue) ? Color.GREEN : Color.RED;
        afficherBarreSante(g, 50, 130, 100, 20, couleur);
        
        if (this instanceof TourelleBleue) {
            attaquer(fenetre.listeRouge);
        } else {
            attaquer(fenetre.listeBleue);
        }
        
        g.drawImage(obtenirImage(), posX - 50, posY - 100, null);
        g.fillOval(posX, posY, 10, 10);
        g.drawRect(posX - 50, posY - 100, 100, 180);
        g.drawOval(posX - 300, posY - 300, 600, 600);
    }

    @Override
    public Rectangle limites() {
        return new Rectangle(posX - 50, posY - 100, 100, 180);
    }
}

Créatures neutres du bestiaire

package moteur;

import java.awt.*;
import java.util.ArrayList;

public class Bestiaire extends EntiteJeu {
    public ArrayList<Bestiaire> creatures = new ArrayList<>();
    private int largeur;
    private int hauteur;
    private Bestiaire reference;

    public Bestiaire(FenetreJeu fenetre) {
        super(fenetre);
        creatures.add(new BuffRouge(3045, 3170, fenetre));
        creatures.add(new Ours(2800, 2855, fenetre));
        creatures.add(new Rapace(3570, 3380, fenetre));
        creatures.add(new Reptile(4585, 2365, fenetre));
        creatures.add(new BuffBleu(4025, 2295, fenetre));
        creatures.add(new Loup(4235, 1945, fenetre));
    }

    public Bestiaire(int x, int y, FenetreJeu fenetre) {
        super(x, y, fenetre);
        setHp(1000);
        santeActuelle = getSanteMax();
        reference = this;
    }

    @Override
    public void dessiner(Graphics2D g) {
        if (santeActuelle <= 0) {
            vivant = false;
            fenetre.entitesASupprimer.add(this);
            fenetre.bestiaire.creatures.remove(this);
            new ThreadResurrection().start();
        } else {
            afficherBarreSante(g, largeur / 2, 80, largeur, 20, Color.GREEN);
            g.drawImage(obtenirImage(), posX - largeur / 2, posY - hauteur / 2, null);
            g.setColor(Color.RED);
            g.fillOval(posX, posY, 10, 10);
            g.drawOval(posX - porteeAttaque, posY - porteeAttaque, 
                      2 * porteeAttaque, 2 * porteeAttaque);
        }
    }

    @Override
    public Rectangle limites() {
        return new Rectangle(posX - largeur / 2, posY - hauteur / 2, largeur, hauteur);
    }

    private class ThreadResurrection extends Thread {
        public void run() {
            try { Thread.sleep(5000); } catch (Exception e) {}
            
            Bestiaire ressuscite;
            if (reference instanceof BuffRouge) {
                ressuscite = new BuffRouge(3045, 3170, fenetre);
            } else if (reference instanceof Ours) {
                ressuscite = new Ours(2800, 2855, fenetre);
            } else if (reference instanceof Rapace) {
                ressuscite = new Rapace(3570, 3380, fenetre);
            } else if (reference instanceof Reptile) {
                ressuscite = new Reptile(4585, 2365, fenetre);
            } else if (reference instanceof BuffBleu) {
                ressuscite = new BuffBleu(4025, 2295, fenetre);
            } else {
                ressuscite = new Loup(4235, 1945, fenetre);
            }
            
            fenetre.entitesJeu.add(ressuscite);
            fenetre.bestiaire.creatures.add(ressuscite);
        }
    }
}

L'architecture présentée démontre plsuieurs patrons de conception essentiels : le patron de méthode template pour les comportements d'entités, l'observateur pour les entrées utilisateur, et le gestionnaire d'état pour la boucle de jeu. La gestion des collisions repose sur des calculs géométriques simples optimisés pour un rendu à 40 images par seconde.

Les threads internes gèrent les temps de recharge et la logique asynchrone, bien qu'une implémentation moderne préférerait un planificateur centralisé ou des ScheduledExecutorService pour éviter la prolifération de threads.

Étiquettes: Java AWT Swing Game Development MOBA

Publié le 29 août à 23h13