
Slick2d, leçon 20 :: Les combats aux tour par tour (5/6)
17 May 2015 08:58 Slick2d - Leçons 20-29Aujourd'hui on va voir comment ajouter un hud à notre combat par l'intermédiaire de bouton cliquable et une fenêtre de texte qui contiendra les logs du combat.
Préparation :: un bouton.
Comme pour la leçon 13 j'ai utilisé la ressource Golden UI trouvée OpenGameArt pour faire un bouton que voici :
Code :: BattleHud
Comme pour la leçon 13 créons une classe dédiée à l'affichage du hud BattleHud.
// Classe : BattleHud
public class BattleHud {
public void init(GameContainer container) throws SlickException { }
public void render(GameContainer container, Graphics g) { }
}
Code :: Ajouter des boutons
Un bouton dans Slick c'est une zone clickable avec une image à l’intérieur, ces zones sont représentés avec la classe MouseOverArea. On va utiliser le constructeur avec les arguments :
- GameContainer : container du jeux
- Image : image du bouton
- int x, int y : coin supérieur gauche
- ComponentListener : écouteur pour les évènements
Construisons nos boutons, dans la méthode init() et comme d'habitude affichons les dans la méthode render(). Il y a 3 boutons qui correspondent au 3 actions possible dans notre combat.
// Classe : BattleHud
// espace entre les éléments de l'interface
private static final int SPACE = 5;
// pour le centrage du text dans les boutons
private static final int Y_PADDING = 3;
private static final int X_PADDING = 13;
private MouseOverArea attackButton;
private MouseOverArea fleeButton;
private MouseOverArea defendButton;
public void init(GameContainer container) throws SlickException {
Image buttonImage = new Image("hud/button.png");
attackButton = new MouseOverArea(container, buttonImage, SPACE, container.getHeight() - (buttonImage.getHeight() + SPACE) * 3, this);
defendButton = new MouseOverArea(container, buttonImage, SPACE, container.getHeight() - (buttonImage.getHeight() + SPACE) * 2, this);
fleeButton = new MouseOverArea(container, buttonImage, SPACE, container.getHeight() - (buttonImage.getHeight() + SPACE) * 1, this);
}
public void render(GameContainer container, Graphics g) {
g.setColor(Color.black);
attackButton.render(container, g);
g.drawString("Attaquer", attackButton.getX() + X_PADDING, attackButton.getY() + Y_PADDING);
defendButton.render(container, g);
g.drawString("Defendre", defendButton.getX() + X_PADDING, defendButton.getY() + Y_PADDING);
fleeButton.render(container, g);
g.drawString("Fuire", fleeButton.getX() + X_PADDING, fleeButton.getY() + Y_PADDING);
}
Les calculs sont fait pour que les trois boutons soit placés les uns en dessous des autres dans le coin inférieur gauche.
Code :: Cliquer sur les boutons
Pour que notre Hud puisse capter les événements de notre souris il doit implémenter l'interface ComponentListener.
// Classe : BattleHud
public class BattleHud implements ComponentListener {
// [...]
@Override
public void componentActivated(AbstractComponent source) {
}
}
La méthode reçois en argument la source, c'est à dire le bouton, qui est activé quand on clic dessus. Il faut encore compléter cette méthode en appelant notre BattleController pour activer les actions de combat qui correspondent à chaque bouton.
// Classe : BattleHud
// [...]
private BattleController controller;
// . il faut avoir une référence sur le contrôleur
public BattleHud(BattleController controller) {
this.controller = controller;
}
// [...]
@Override
public void componentActivated(AbstractComponent source) {
if (source == attackButton) {
controller.controlPressed(BattleCommand.ATTACK);
} else if (source == defendButton) {
controller.controlPressed(BattleCommand.DEFEND);
} else if (source == fleeButton) {
controller.controlPressed(BattleCommand.FLEE);
}
}
// [...]
Il faut intégrer tous cela dans notre la boucle de combat, cela commence à devenir routinier.
// Classe : BattleGameState
// [...]
private BattleHud hud;
@Override
public void init(GameContainer container, StateBasedGame game) throws SlickException {
// initialisation, vu dans les leçons précédentes
BattleController controller = new BattleController(player, ennemy, game);
provider.addListener(controller);
this.hud = new BattleHud(controller);
this.hud.init(container);
}
@Override
public void render(GameContainer container, StateBasedGame game, Graphics g) throws SlickException {
// affichage, vu dans les leçons précédentes
this.hud.render(container, g);
}
// [...]
Et voici le résultat :
Un log de combat
Je vous propose ensuite d'ajouter un log de combat, c'est à dire une fenêtre de texte ou est retranscrit par écrit tous les événements de combat.
Code :: TextArea
Slick ne propose pas solion clef en main pour écrire du texte en multi-ligne. Je vous passe les détails, je l'ai déjà fais pour vous. Il vous faut importer SlickUtils dans sa version 1.3. Ajoutons notre log à l'initialisation et au rendu :
// Classe : BattleHud
// [...]
private TextArea log;
public void init(GameContainer container) throws SlickException {
log = new TextArea(SPACE + attackButton.getWidth() + SPACE, attackButton.getY(), container.getWidth() - attackButton.getWidth() - SPACE * 3, buttonImage.getHeight() * 3 + SPACE * 2);
// affichage de bas en haut
log.setBottomUp(true);
log.setDefaultFont(container.getDefaultFont());
}
// [...]
public void render(GameContainer container, Graphics g) {
// [...]
g.drawString("Fuire", fleeButton.getX() + X_PADDING, fleeButton.getY() + Y_PADDING);
log.render();
}
// [...]
public void addLog(String text) {
log<.addFirstText(text, VerticalAlignement.LEFT);
}
// [...]
Maintenant il faut passer notre instance du hud au contrôleur pour que celui-ci puisse écrire dans le log.
Classe :: BattleHud
// [...]
public BattleHud(BattleController controller) {
this.controller = controller;
this.controller.setHud(this);
}
// [...]
Et dans le contrôleur :
// Classe :: BattleController
public class BattleController implements InputProviderListener {
// [...]
private BattleHud hud;
// [...]
public void setHud(BattleHud hud) {
this.hud = hud;
}
// [...]
Pour finir il reste à appeler la méthode addLog de notre hud depuis la classe BattleController, pour afficher les messages qu'on souhaite lors des différentes étape du combat. C'est trivial aussi je passe cette partie sous silence. Mais voici le résultat :
Pour aller plus loin
Le combat avance bien, la prochaine fois on verra comment changer la musique et comment ajouter une pause a la fin du combat avant de retourner à la carte.
Ressources
- Ressource : Golden UI sur OpenGameArt
- SlickUtils : sa page
- Repo git : GitHub
Super tes tuto comme d'habitude ;) , à quand la sauvegarde ? :P
J'ai commencé la sauvegarde sous forme de BD hors ligne avec jdbc lite , penses tu que c'est une bonne idée ?
Il y a t-il d'autres moyens ?
La sauvegarde viendra, mais de la manière dont je souhait le faire elle demande un peu de réorganisation de code. Il existe plein de manière de faire une sauvegarde. La plus évidente est de faire une sauvegarde du joueur ou de l’état du jeu dans un fichier en utilisant la sérialisation java.
Aurais tu un site à me conseiller pour apprendre et voir comment fonctionne la sérialisation ? :)
Tu fais de très bon tuto !
Penses tu continuer ?
Tu n'as pas mis le lien de la leçon 20 sur l'accueil on doit y accéder en modifiant l'url, ce qui ne facilite pas son accès :)
Oui je pense continuer mais j'ai beaucoup d'autre projet en ce moment qui ne me permette pas d'avancer sur les tutos. J’espérai pouvoir reprendre avec la rentrée mais je me rend compte que ce n'est pas possible. Actuellement je travail sur la bdd de magic qui reçoit une grosse mise à jour technique.
Merci je vais le rajouter prochainement.
Coucou Shionn ! la suite ! la suite ! moi j'ai un souci d'algo pour ramasser un objet ou alors voir l'ennemi sur la map ! faut le dire les choses carrément j'ai un mal de chien à utiliser la classe TiledMap du genre les méthodes map.getLayerIndex(), map.getLayerProperty() et surtout comment les manipuler entre elles !
Merci d'avance :D
Salut.
En fait ce sont des méthode pour la lecture pas vraiment pour la modification. Mais normalement avec l'article sur la collision tu devrais tous comprendre sur ces méthodes. Si tu veux faire tomber des objets (genre une pomme) au sol, il faut utilisé ta propre liste avec les objets (java) représentant ces objets (genre la pomme) et tu les affichent / gerent comme le personnage.
je vais aller voir ca :) parce que là j'essaye désespérément de mettre un objet sur tiled map editor genre une boite sauf que je sais pas est-ce que j'essaye de le mettre dans un calque object ou calque tile puis de le lire par les propriété tout en essayant de mettre un log en console pour implementer un truc qui ressemble à de la reflexion (Class.forName(name) ) ! bref ce vaseux et laborieux
voilà mon inspiration : https://github.com/Actimia/TDF-Platforms/blob/master/TDF%20Platforms/src/org/dyndns/tdfpro/platforms/Map.java
voilà mon code :
et voilà ma console et je sais pas pourquoi ! le code qui plante se trouve dans le commentaire !
Haaaa tu essayes d'utilisé les objets de Tiled ! Ouai en effet j'ai jamais essayé et je pense que cela marche pas toppissime.
Y a beaucoup de soucis avec les cartes de Slick2d je pense la refaire (un jour si je peu).
On ne peu pas mettre de xml dans mes commentaires wordpress enlève les caractères < et >
Bonjour, je suis totalement debutant avec slick et j'ai un peu de mal a faire certaines choses
Ma question est un peu HS avec cette leçon mais plus en rapport avec le tuto sur les deplacements des personnage :
Je souhaite deplacer mon personnage non pas avec les touches flechées mais automatiquement (avec une fonction moveTo(int x, int y) par exemple) cependant je n'arrive pas vraiment a implementer ce genre de fonction dans mon code...
C'est bon j'ai finalement réussi a le faire (sa m'apprendra a ne plus lire les tutos à moitié)...
Salut, sait tu comment laisser le jeu tourné quand on clique hors de la fenêtre ? Exemple : je lance un chrono, je quitte la fenêtre le chrono doit continuer de s'actualiser
merci d'avance
Techniquement le jeu ne s’arrête pas, l'affichage s’arrête mais la logique elle continue. Je ne sais pas comment tu as codé ton chrono mais si celui-ci dépend de l'affichage ce n'est pas bon. Car si la machine est trop puissante le chrono ira trop vite et inversement.
Sinon pour répondre à ta question, sur la classe AppGameContainer il y a une méthode setAlwaysRender().
Merci beaucoup ! :),
je n'ai pas fait de chrono c'était pour un exemple ^^ , le vrai problème est que j'ai commencer à ajouter du réseau au jeu, mais vue que je teste pour le moment sur une seul machine je ne pouvais pas voir l'actualisation
J'avais fait du jeu en réseau pendant un temps avec RedDwarf. C'est bien cool. Je crois que le projet est mort malheureusement.
Quel techno utilise tu pour le réseau ?
ah dommage :/. Pour le moment j'utilise de simple socket
J'ai encore besoin de ton aide ^^, peux tu m'expliquer comment fonctionne le textefield stp car je n'arrive absolument pas à écrire avec :/
Comme cela ?
Salut à toi ! J'aurais aimé savoir si tu penses faire la suite de tes tutos ?
Cordialement, Un petit bonhomme passant par là.
J'ai déjà répondu au moins 100 fois, oui, quand ? Je ne sais pas je n'ai pas de temps libre pour ça.
merci shionn pour le textfield
Salut,
Je tiens à te remercier pour ce super tuto qui m'aide beaucoup :)
J'ai tout de même une question ^^
Dans le tutoriel la méthode componentActivated est appelée lors du clic sur l'un des boutons, cependant cette méthode ne permet que de détecter "l'activation" d'un bouton (qui ne correspond pour les MouseOverArea qu'au clic gauche si j'ai bien compris) . Cependant j'aimerais pouvoir détecter un clic droit sur un bouton ou même séparer la pression du relachement (pour mettre en place un système de drag and drop).
La seule méthode que j'ai trouvée est de réimplémenter les méthodes telles que mouseClicked et d'y ajouter une condition afin de détecter si la souris est sur le bouton ou non. Je n'utilise donc plus la méthode componentActivated et donc m'éloigne de la fonction du MouseOverArea.
J'aimerais savoir si cette façon de faire est correcte ou si il existe une autre méthode plus proche du tutoriel.
Merci d'avance :)
Bonjour Saliwan.
Quand tu dis réimplémenter tu parles de surcharge par héritage de la classe MouseOverArea ? Je ne sais pas si c'est la meilleure manière de faire, mais c'est ce que je ferrai :
Oui c'est ce que j'ai fait :)
Le problème que j'avais était par rapport à la méthode componentActivated qui ne sert plus, mais qui doit être déclarée dans la classe implémentant le componentListener (nécessaire au constructeur de MouseOverArea).
Ce n'est pas grave je vais laisser une méthode vide ^^
Merci de ta réponse :)
Salut j'ai essayer de téléporter le personnage en cliquant sur la souris, mais je récupéré la position de l'écran et pas de la carte :/. Je ne sais pas comment convertir la position écran vers la position du monde. Pourrais tu me donner une piste s'il te plait ?
Salut désoler d'avoir mis du temps pour te répondre. Dans la leçon 4 se trouve une partie de la réponse. On utilise une transformation pour décaler l'affichage par rapport à la caméra. Il faut faire la fonction inverse pour transformer les coordonnées de la souris en coordonnée de carte. Quelque chose comme :
X = Xsouris - container.getWidth()/2 + Xcamera.
Enfin c'est dans cet esprits la.Merci, ça fonctionne super bien, mais je ne comprend pas tellement pourquoi :(
C'est assez simple.
La caméra ne modifie pas le monde c'est à dire ton joueur est dans des coordonnées bien en dehors de la taille de ton écran. L'astuce que j'ai utilisé c'est que la caméra "déplace" le monde pour que le joueur soit sous l'écran.
La souris elle n'est pas impactée par cela.
Bonjour Shionn! D'abord, Merci beaucoup pour votre tutoriel très complet! Je suis en ce moment entrain de tester la bibliothèque Slick et je voulais essayé de refaire dans un but pédagogique une classe bouton en utilisant le pattern observer. Je crée donc un projet test avec une classe BasicGame (la même que dans la leçon 1). Je crée une classe bouton à laquelle j'implémente MouseListener. Pour test je mets des system.out.print() dans les méthodes implémentés. J'instancie mon bouton dans la classe BasicGame et je rajoute dans init() container.getInput().addMouseListener(monBouton); mais lorsque je lance le projet et que je clique dans la fenêtre rien ne s'affiche dans mon terminal. Ai je oublié quelque chose?
finalement j'ai reessayé ce matin et bingo! Il fallait juste changer la méthode implémenté isAcceptingInput() et la faire retourné true :)
Bonjour max, ça fait plaisir que mes lecteurs trouvent parfois la solution avant que j'ai le temps de chercher :]
Bonjour! Encore trois petites questions! Y a-t-il un moyen d'éviter l'écran noir de la fenêtre pendant l’initialisation et d'y mettre à la place un écran de chargement? Et quel est la différence entre utiliser g.drawImage() et image.draw()? Et enfin, j'ai essayé d'utiliser la méthode public void texture(Shape shape,Image image) pour mapper une petite texture. La texture est bien splitter dans la zone mais un quadrillage noir se forme entre chaque "tile".Est-ce un bug de la librairie ou ai-je oublié quelque chose? Merci d'avance pour votre réponse!
Oui il est possible de faire un systreme de chargement intelligent, mais cela demande beaucoup de programmation multithreadeée...
Hum faudrait faire quelque essai. Mais je suppose g.drawimage se fait dans le context g et donc est impacté les transformations. Alors image.draw() doit permettre de dessiner dans un autre buffer.
Hum je n'ai fais joujou avec ces fonctions qu'une seul fois et je n'ai pas été confronté a ce problème.
ok, merci beaucoup pour toutes vos réponses!
Bonjour Shionn tu vas bien ? Je viens encore t'embêter :D.
Je rencontre actuellement un soucis, quand je clique sur l 'icone "réduire" et qu'après je retourne sur la fenêtre du jeu plus rien ne s'affiche, aurais tu une idée de comment régler le problème ?
Merci d'avance.
Ps: j'ai des classes pour générer des PNJ et gérer leur déplacement, pas encore optimiser et pas encore terminer.. mais si certain souhaite une base..
Salut Ultraine,
Malheureusement je n'ai jamais été confronté à ce problème, mais je n'ai jamais utilisé que Linux.
Bonjour,
J'ai créer un objet qui posséde des boutons.
Dans le main principal du jeu j'affiche les boutons de l'objet, mais impossible de l'activer au clique.... pourtant j'ai bien donnée à l'objet le container. Aurais tu une idée ?
Salut,
Comme ca la je n'ai aucune piste. Un oublie de listener ? Tu peu m'envoyer ton code par mail ?