Slick2d, leçon 17 :: Les combats aux tour par tour, (2/6)

22 February 2015 09:08 Slick2d - Leçons 10-19 Java, Jeux, Slick2d, Tutorial

Dans l’article précédent nous avons vus comment lancer de manière aléatoire des combats. Mais ces combats étaient vide. Nous allons voir comment les remplir.

Préparation.

Dans notre précédent article nous avons simplement fait un affichage mais nous n'avons pas cherché à séparer le code en plusieurs classes. Il faut le faire, qu'avons nous dans notre écran de combat ?

  • un joueur
  • un monstre

Nous devons donc ajouter  deux classes, Player et Monster. Mais nous avons déjà une classe Player qui existe, celle qui est utilisé dans la carte, nous l'avons vu, nous n'affichons pas le joueur de la même manière dans les combat que dans la carte, ce n'est donc pas les mêmes classes.

Pour simplifier le code je vais préfixé par Map toute les classes utilisées dans le MapGameState et par Battle toutes les classes utilisées dans BattleGameState. Il serait bien sur mieux d'utiliser des packages mais cela compliquerai les explications dans le présent article.

Code :: Séparation en objet

Classes :: Map*

Renommons simplement toutes les classes utilisé dans la classe MapGameState, c'est à dire Camera, HudPlayerController, TriggerController et Player en les préfixant par Map.

Classe :: BattlePlayer

Nous pouvons extraire la classe BattlePlayer de notre classe BattleGameState, comme nous l'avons fait dans les articles sur la séparation en objet, je ne vais donc pas réexpliquer la procédure.

public class BattlePlayer {
  private Image hero;

  public void init() throws SlickException {
    this.hero = new Image("battle/hero.png").getScaledCopy(2);
  }

  public void render(GameContainer container) {
    this.hero.drawCentered(container.getWidth() * 1 / 4, container.getHeight() / 2);
  }
}

Classe :: BattleEnnemy

De la même manière nous pouvons extraire la classe _BattleEnnemy _de notre classe BattleGameState.

public class BattleEnnemy {
  private Image ennemy;

  public void init() throws SlickException {
    this.ennemy = new Image("battle/gobelin.png").getScaledCopy(2);
  }

  public void render(GameContainer container) {
    this.ennemy.drawCentered(container.getWidth() * 3 / 4, container.getHeight() / 2);
  }
}

Classe :: BattleGameState

Enfin il nous reste à faire la mise à jour de la classe BattleGameState.

Classe :: BattleEnnemypublic class BattleGameState extends BasicGameState {
  // déclaration des autres variables (leçon précédente)
  private BattleEnnemy ennemy = new BattleEnnemy();
  private BattlePlayer player = new BattlePlayer();

  @Override
  public void init(GameContainer container, StateBasedGame game) throws SlickException {
    // initialisation du fond (leçon précédente)
    this.ennemy.init();
    this.player.init();
  }

  @Override
  public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
    // affichage du fond (leçon précédente)
    this.player.render(container);
    this.ennemy.render(container);
  }
}

Voila pour la partie refactoring, nous pouvons enfin attaquer le gros du boulot, c'est à dire le combat !

Préparation :: Combat

Nous allons regrouper toutes les mécaniques de combat, c'est a dire ce qui calcul les dégâts, les assignes, détermine la priorité entre le joueur et l'ennemi, etc, dans une seule classe BattleController.

Il faudrait pour cela qu'on commence à parler de mécanique de combat, introduire plein de caractéristiques et définir les règles de calcul. Je vous propose ici ces règles :

  • Le joueur commence avec 50 PV et l'ennemi lui à 12 PV.
  • Si le joueur attaque :
    1. Il inflige entre 7 et 10 points dégâts avec 10% de chance de faire un critique (+50% de dégât)
    2. L'ennemi contre attaque en infligeant entre 5 et 9 dégâts.
  • Si le joueur défend :
    1. L'ennemi attaque et inflige entre 5 et 9 dégâts. Mais la moitié des dégâts sont prévenus.
    2. Le joueur contre attaque en infligeant entre 7 et 10 points de dégâts sans possibilité de faire des critiques.
  • Si le joueur fuit :
    1. L'ennemi attaque et inflige entre 5 et 9 dégâts.
    2. Le joueur quitte le combat.

Code :: Les points de vies.

Commençons par définir et afficher les points de vie de notre joueur et de notre monstre respectivement dans les classes BattlePlayer et BattleEnnemy.

public class BattlePlayer {
  private int pv = 50;
  private Image hero;

  // méthode init() vu précédement

  // pour cette méthode nous ajoutons l'attribut g
  public void render(GameContainer container, Graphics g) {
    hero.drawCentered(container.getWidth() * 1 / 4, container.getHeight() / 2);
    Font font = g.getFont();
    String text = Integer.toString(pv);
    font.drawString(container.getWidth() * 1 / 4 - font.getWidth(text) / 2, container.getHeight() / 2 - hero.getHeight() / 2 - font.getLineHeight(), text, Color.white);
  }
  
  public void setPv(int pv) { this.pv = pv; }
  public int getPv() { return pv; }
}

Il suffit de faire la même chose pour la classe BattleEnnemy, et voici le résultat :

tuto-slick2d-094-battle-screen-pvs

Code :: Les Commandes.

Il est temps d'écrire la mécanique de combat évoqué plus haut. Contrairement à ce que nous avons fait jusqu'ici, nous allons utiliser la mécanique des commandes plutôt que de la gestion des évènements classique. Bref c'est l’occasion de voir un nouveau concept du développement du jeux vidéo. L'avantage des commandes c'est de pouvoir dissocier l'action qu'on exécute que la chose qui la d’éclanche (souris, clavier etc...). Dans slick une commande est un object implémentant l'interface Command. Je choisi de passer par un enum, ce n'ai pas la solution la plus judicieuse, mais la plus simple à comprendre.

public enum BattleCommand implements Command {
  ATTACK, DEFEND, FLEE
}

Dans notre classe BattleGameState nous pouvons maintenant associer les commande aux touche souhaitée grâce à un InputProvider. J'associe également le contrôleur que je décris dans le paragraphe suivant.

public class BattleGameState extends BasicGameState {
  @Override
  public void init(GameContainer container, StateBasedGame game) throws SlickException {
    // [...]
    InputProvider provider = new InputProvider(container.getInput());
    provider.bindCommand(new KeyControl(Input.KEY_A), BattleCommand.ATTACK);
    provider.bindCommand(new KeyControl(Input.KEY_D), BattleCommand.DEFEND);
    provider.bindCommand(new KeyControl(Input.KEY_F), BattleCommand.FLEE);
    provider.addListener(new BattleController(player, ennemy, game));
}

Code :: BattleController

Notre classe BattleController va capter les commandes pour agir sur le combat, pour cela nous allons implémenté l'interface InputProviderListener.  A l'activation de notre commande il suffit d'éxécuter le bon code.


public class BattleController implements InputProviderListener {
  private BattlePlayer player;
  private BattleEnnemy ennemy;
  private StateBasedGame game;
  private Random random = new Random();

  public BattleController(BattlePlayer player, BattleEnnemy ennemy, StateBasedGame game) {
    this.player = player;
    this.ennemy = ennemy;
    this.game = game;
  }

  @Override
  public void controlPressed(Command command) {
    switch ((BattleCommand) command) {
      case ATTACK: attack(); break;
      case DEFEND: defend(); break;
      case FLEE:   flee();   break;
      default: break;
    }
  }

  @Override
  public void controlReleased(Command command) { }

  private void attack() {
    // le joueur inflige entre 7 et 10 dégats
    int playerAttack = 7 + random.nextInt(4); 
    if (random.nextDouble() < .1) { // 10%
      playerAttack += playerAttack / 2; // +50% de dégats
    }
    ennemy.setPv(ennemy.getPv() - playerAttack);
    if (ennemy.getPv() <= 0) { // ennemi mort ?
      game.enterState(MapGameState.ID); // retour à la carte
    } else { 
      // l'ennemi inflige entre 5 et 9 dégats
      int ennemyAttack = 5 + random.nextInt(5);
      player.setPv(player.getPv() - ennemyAttack);
      if (player.getPv() <= 0) { // joueur mort ?
        game.enterState(MainScreenGameState.ID); // retour titre
      }
    }
  }

  private void defend() {
    // l'ennemi inflige entre 5 et 9 dégâts, la moitié est absorbée
    int ennemyAttack = (5 + random.nextInt(5)) / 2;
    player.setPv(player.getPv() - ennemyAttack);
    if (player.getPv() <= 0) { // joueur mort ?
      game.enterState(MainScreenGameState.ID); // retour titre
    } else {
      // le joueur inflige entre 7 et 10 dégats sans critique
      int playerAttack = 7 + random.nextInt(4);
      ennemy.setPv(ennemy.getPv() - playerAttack);
      if (ennemy.getPv() <= 0) { // ennemi mort ?
        game.enterState(MapGameState.ID); // retour à la carte
      }
    }
  }

  private void flee() {
    // l'ennemi inflige entre 5 et 9 dégats
    int ennemyAttack = 5 + random.nextInt(5);
    player.setPv(player.getPv() - ennemyAttack);
    if (player.getPv() <= 0) { // joueur mort ?
      game.enterState(MainScreenGameState.ID); // retour titre
    } else {
      game.enterState(MapGameState.ID); // retour à la carte
    }
  }

}

Et donc on peu enfin combattre et voir les points de vie évoluer. tuto-slick2d-095-battle-screen-pvs-lost

Pour aller plus loin

Les tutos sur les combats ne sont pas encore fini, il en reste encore au moins 2 voir 3. D'ici la on peu imaginer corriger plein de chose :

  • Ajouter un contrôle avec une manette comme dans la leçon 12.
  • Créer un écran de game over plutôt que de retourner à l'écran titre.
  • Remettre des points de vie à l'ennemi au deuxième combat.
  • Ajouter des boutons pour contrôler le combat à la souris.
  • Changer de musique lors du combat.
  • Ajouter des animations pour mieux visualiser les combats.

Nous verrons certain de ces points dans les prochains tutos.

Ressources

par Shionn, dernière modification le 09 April 2017 18:07
9 réflexions au sujet de « Slick2d, leçon 17 :: Les combats aux tour par tour, (2/6) »
  • le hegaret 24 March 2015 18:18

    lut shionn !!! Hein ?? : "Mais la moitié des dégâts sont prévenu."

  • Shionn 25 March 2015 08:50

    Quand le joueur défend il se met en position défensive, du coup quand il attaque il ne peu pas faire de critique, mais en revanche il encaisse mieux les attaques et la moitié des dégâts sont prévenus.

    Ce sont des propositions de règle de combat simpliste, tu peu faire ce que tu veux, il va de soit.

  • le hegaret 25 March 2015 17:37

    ahhh ouaiii là ce plus clair :D ps : tu vas tout nous expliquer sur les moteurs de jeux : dongeon combat, mise à jour dans le hub ce hub magasin inventaire PNJ ??? soit dit en passant j'abuse un peu : ce possible de creer un jeux facebook avec slick ??? :D j'ai vu que facebook a integré OpenGL et comme slick est basé sur opengl je me demande :)

  • Shionn 26 March 2015 06:49

    Je ne sais pas si je vais tous faire, cela sera en fonction de ce qu'on me demandera. Les combats on me l'as beaucoup demandé. Je ferai aussi une série de tuto qui n'est pas lié au RPG, peut-être un shmup ? Mais pas avant 1 ans voir plus.

    J'ai de toute manière l'impression que mes tutos deviennent trop complexe depuis la leçon 10, j'ai moins de retour sur ceux la.

    Non slick ne te permet pas de faire un jeu facebook. Je pense que cela doit être possible avec libgdx, c'est en tous cas possible de faire un jeu pour navigateur. Slick reste assez simple dans son approche pour faire un jeux, c'est pour cela que je l'ai choisi pour les tutos.

  • le hegaret 26 March 2015 22:18

    trop complexe pff disons qu'il vaut mieux avoir de solide base POO pour tes tutos d’où ma question sur le pattern Observable d'ailleurs !!!

    jeux facebook ben je me dis créer un truc marrant ki peut etre rapportera du fric !! ya pire pour gagner sa vie ;) et au pire des cas ce toujours formateur !! j'ai vu libglx est integré dans facebook et ce pour ca que je demande je me suis dit integrer la slick en jar interne !! comme des fichiers sources !! ce clair ca grossit le projet !!

    au fait parallèlement à tes cours je me suis mis à créer un utilitaire pour créer mes propres perso RPG un peu comme Fusion Maker !!

    moi j'aurais besoin d'un tuto sur les Marchand et les inventaires ( recherche vente d'objets au joueur par exemple comme ca : http://cityvillegoals.com/cityville-new-build-menu/)

    ce koi shmup?? ben d'ailleurs c'etait plutot l'origine de mon post tu sais ou on peut trouver la signifcation de tout ce jargon ??

  • Shionn 27 March 2015 12:41

    Je pense franchement qu'intégrer Slick pour Facebook, n'est pas la solution. Il vaut mieux prendre libgdx et ajouter les deux/trois fonctionnalités manquante, comme la gestion des gamestate.

    Par rapport à ton projet, cela me rappel mon projet de recherche, tu devrais t'appuyer sur un moteur de scripting, Lua ou ECMA par exemple, cela permet une édition plus simple des comportements du jeu.

    Je ne connais pas cityvillegoals. Je vais faire un tuto sur l'ajout de pnj, en plusieurs étape, chargement, déplacement, interaction, marchant, qui mènera à un sac avec des items etc. Mais bien sur cela va être une grosse série, avec probablement scripting pour le comportement et pathfinding pour les déplacement aléatoire.

    Un shmup, c'est un shoot them up ou shoot'em up et enfin shmup dans sa version la plus courte. Je ne sais pas ou trouver un descriptif de tout ce jargon, c'est ma culture vidéo ludique :p

  • le hegaret 27 March 2015 16:11

    ahhh bon pour facebook à voir !! ECMA ou Lua !! tu sais qu'openttd est en Lua et civilisation 4 en python tu sais que tu me proposes d'apprendre 1 enieme langage mdr !!! je commence à le savoir le geek est un champolion sur pattes !! bon cityville ce ca une fenetre interne qui donne les caracteristiques du perso ou acheter un truc ou equiper son perso !!! ouai bon pour les termes techniques faut etre un geek donc jouer miam miam ;)

  • Shionn 28 March 2015 17:39

    Au pire avec ECMAScript c'est intégrée avec la jdk depuis la 6 je crois. C'est pas trop mal. t'écrit du javascript mais cela appel du java. Donc pas grand chose à apprendre. J'aborderai ce point bientôt.

    Par exemple pour les triggers, dansTtiled, sur l'attribut de l'objet, on peu faire un truc du genre "player.teleport(x,y) à la place de l’utilisation des types de trigger qui ne sont pas super générique.

  • le hegaret 28 March 2015 18:34

    si ce du javascript ce cool !! bon ben la suite ;)

Laissez un commentaire

Vous pouvez utilisez du markdown pour la mise en forme

Votre adresse de messagerie ne sera pas publiée.

Temporairement, pour lutter contre les bots, il n'est pas permis de mettre http:// dans le commentaire.