Slick2d, leçon 3 :: Animer et Déplacer un Personnage

12 January 2014 08:45 Slick2d - Leçons 20-29 Java, Jeux, Slick2d, Tutorial

Suite de ma série de tutoriels sur Slick2D. Au menu du jour animer et déplacer un personnage sur la carte que nous avons réalisé dans la leçon 2.

Théorie.

Dans un jeu 2D, un personnage ou tout autre objet graphique se déplaçant à l’écran animé ou non est appelé un sprite. Un sprite est une image que l'on peu afficher ou l'on souhaite sur l'écran avec une gestion de la transparence.

Pour animer un sprite on utilise une succession d'image assez proche qu'on affiche l'une après l'autre pour donner l’illusion du mouvement. C'est la même technique qu'un dessin animé ou qu'un film. Voici un exemple de la marche de Simon Balmon dans Super Castelvania IV.

tuto-slick2d-023-simon-balmon-exemple

Sur une telle image on se rend bien compte de la succession des sprites qui composent l'animation.  Une telle image est un tableau de sprite, SpriteSheet en anglais.

Préparation.

Vous m’excuserez pour ma fainéantise, mais je n'ai pas eu le courage de créer un nouveau personnage pour ce tutoriel, j'ai réutilisé celui de la présentation Human Talk. J'ai créé à l'origine ce personnage en cumulant plusieurs calques de cette ressource trouvée sur OpenGameArt.

tuto-slick2d-024-character-exemple

En observant ce spritesheet on remarque que chaque ligne correspond à une direction. De ces 4 lignes on va définir les directions (de 0 à 3). En ne prenant que la première colonne nous avons les images permettant d'afficher le personnage immobile. On utilisera judicieusement les animations de 0 à 3 pour cela.

Les autres cellules de chaque ligne servent quand à elles aux animations de déplacement, qu'on numérota elle de 4 à 7. Ainsi si  le numéro X correspond à une image immobile dans une direction, le numéro X+4 correspondra à l'animation de marche dans la même direction.

Si vous n'avez pas compris, cette image vous aidera à vous y retrouver.

tuto-slick2d-025-animations

Code :: Initialisation

Avant toute chose, les codes ici présentés sont des versions simplifiées de ce que vous verrez dans les screenshots de résultat. C'est à dire que je passe volontairement sous silence l'affichage de la carte qu'on a vu dans le précédent article.

Commençons par définir les variables représentants notre personnage. En premier lieu, on va définir la position de notre personnage avec des coordonnées (x, y) que je définis arbitrairement à (300, 300). Comme je l'ai dis précédemment la direction est représenté par un entier dont le domaine de valeur va de 0 à 3. Pour indiquer si le personnage se déplace ou pas on va simplement ajouter un booléen. Afin comme on l'a vu tout à l'heure nous avons 8 animations, représentées ici par un tableau de la classe org.newdawn.slick.Animation.

private float x = 300, y = 300;
private int direction = 0;
private boolean moving = false;
private Animation[] animations = new Animation[8];
// Les objets sont crées, il nous faut encore les initialiser, et pour cela on va compléter la méthode «  init() ». Un tableau de sprite est représenté par la classe org.newdawn.slick.SpriteSheet, il suffit d'instancier cette classe en lui donnant en argument le nom du fichier et les dimensions des cellules soit 64x64 dans mon cas.
@Override
public void init(GameContainer container) throws SlickException {
    SpriteSheet spriteSheet = new SpriteSheet(("sprites/character.png", 64, 64);
}

Ensuite on initialise les animations en les composants sprite par sprite à l'aide du tableau de sprite et en indiquant le temps d'affichage en milliseconde de chaque sprite. Ici au hasard j'ai mis 100ms. Voici l'exemple du code pour l'animation de marche vers l'ouest (animation n° 5).

@Override
public void init(GameContainer container) throws SlickException {
    SpriteSheet spriteSheet = new SpriteSheet(("sprites/character.png", 64, 64);
    Animation animation = new Animation();
    animation.addFrame(spriteSheet.getSprite(1, 1), 100);
    animation.addFrame(spriteSheet.getSprite(2, 1), 100);
    animation.addFrame(spriteSheet.getSprite(3, 1), 100);
    animation.addFrame(spriteSheet.getSprite(4, 1), 100);
    animation.addFrame(spriteSheet.getSprite(5, 1), 100);
    animation.addFrame(spriteSheet.getSprite(6, 1), 100);
    animation.addFrame(spriteSheet.getSprite(7, 1), 100);
    animation.addFrame(spriteSheet.getSprite(8, 1), 100);
    this.animations[5] = animation;
}

On fait cela pour chaque animation, on factorise le code et voila ce que cela devient :

@Override
public void init(GameContainer container) throws SlickException {
    SpriteSheet spriteSheet = new SpriteSheet("sprite/character.png", 64, 64);
    this.animations[0] = loadAnimation(spriteSheet, 0, 1, 0);
    this.animations[1] = loadAnimation(spriteSheet, 0, 1, 1);
    this.animations[2] = loadAnimation(spriteSheet, 0, 1, 2);
    this.animations[3] = loadAnimation(spriteSheet, 0, 1, 3);
    this.animations[4] = loadAnimation(spriteSheet, 1, 9, 0);
    this.animations[5] = loadAnimation(spriteSheet, 1, 9, 1);
    this.animations[6] = loadAnimation(spriteSheet, 1, 9, 2);
    this.animations[7] = loadAnimation(spriteSheet, 1, 9, 3);
}

private Animation loadAnimation(SpriteSheet spriteSheet, int startX, int endX, int y) {
    Animation animation = new Animation();
    for (int x = startX; x < endX; x++) {
        animation.addFrame(spriteSheet.getSprite(x, y), 100);
    }
    return animation;
}

Code :: Affichage

Pour l'affichage c'est plus simple. On utilise la méthode drawAnimation() de l'objet _ g_ de la méthode render(). Il faut déterminer l'animation à afficher, revenons pour cela à ma définition du tableau d'animation. J'ai mis de 0 à 3 les postures d’arrêts, et de 4 à 7 les animations de déplacement dans le même ordre que la direction. Donc il me suffit d'ajouter 4 à la direction si le personnage se déplace. Voici le code correspondants et le résultat.

@Override
public void render(GameContainer container, Graphics g) throws SlickException {
    g.drawAnimation(animations[direction + (moving ? 4 : 0)], x, y);
}

tuto-slick2d-026-character-display

Mais ce n'est pas parfait en effet les coordonnées d'affichage qu'on donne à la méthode drawAnimation()_ correspondent au coin supérieur gauche du sprite, mais dans notre esprit les coordonnées correspondent aux pieds du personnage. Il faut donc décaler l'affichage. Et pour parfaire l'illusion on va afficher une ombre sous le personnage à l'aide d'une élipse.

@Override
public void render(GameContainer container, Graphics g) throws SlickException {
    g.setColor(new Color(0, 0, 0, .5f));
    g.fillOval(x - 16, y - 8, 32, 16);
    g.drawAnimation(animations[direction + (moving ? 4 : 0)], x-32, y-60);
}

tuto-slick2d-027-character-display-with-shadow

Code :: Déplacement

Il faut ajouter de nouvelle fonction au clavier pour permettre le déplacement du personnage. A l'appui des touches [HAUT],[BAS],[GAUCHE],[DROITE] du clavier on va enregistrer la nouvelle direction et indiquer grâce au booléen que le personnage se déplace. Pour cela on va redéfinir la méthode keyPressed() de la classe abstraite BasicGame.

@Override
public void keyPressed(int key, char c) {
    switch (key) {
        case Input.KEY_UP:    this.direction = 0; this.moving = true; break;
        case Input.KEY_LEFT:  this.direction = 1; this.moving = true; break;
        case Input.KEY_DOWN:  this.direction = 2; this.moving = true; break;
        case Input.KEY_RIGHT: this.direction = 3; this.moving = true; break;
    }
}

Quand on relâchera une de ces touches on arrêtera le déplacement en repassant le booléen à faux. Je simplifie volontairement l'exemple de code suivant :

@Override
public void keyReleased(int key, char c) {
    this.moving = false;
}

A ce moment si vous lancez l'application et que vous essayer de déplacer le personnage vous le verrez marcher dans le vide immobile. Et c'est bien normal. Il nous reste encore à traiter le déplacement. Et pour cela on va utiliser la méthode update(). Cette méthode est pour que vous puissiez mettre à jour l'état du jeu en fonction de votre logique de jeux et du temps écoulé. Il est très important de prendre en compte le temps écoulé pour adapter votre jeu à la vitesse de l'ordinateur de l'utilisateur. Cette information vous est donnée à l'aide de la variable delta. Ici j'ai fixé arbitrairement la vitesse à 0.1f.

@Override
public void update(GameContainer container, int delta) throws SlickException {
    if (this.moving) {
        switch (this.direction) {
            case 0: this.y -= .1f * delta; break;
            case 1: this.x -= .1f * delta; break;
            case 2: this.y += .1f * delta; break;
            case 3: this.x += .1f * delta; break;
        }
    }
}

Et voici le résultat, votre personnage bouge mais. Il traverse les murs, il passe dessus les arbres, il marche sur l'eau et en plus il sort de l'écran et la caméra ne le suis pas ! Mais malheureusement on verra cela dans un prochain article.

Ressource.

Anecdote.

Si vous avez beaucoup joué à de vieux jeux sur de veille console, vous peu être vu parfois que les personnages clignotais ou étais mal affiché. En fait c'est simplement que le jeu en demandais trop à la console. On parle alors de sprite limite. Ce défaut se vois particulièrement dans la série des mégaman sur Nes. La nes n’était d’ailleurs pas capable d'afficher des trop grand, toujours dans méga man, certain boss de moitié de niveau qui était très gros, était en fait simplement une image fixe dans le background, et les quelques parties animés du boss était des sprites.

Dans les vieux jeux la transparence était gérée avec une couleur unique, généralement le fushia, aujourd'hui on utilise le canal alpha qui permet en plus une transparence partielle.

par Shionn, dernière modification le 29 June 2017 11:21
50 réflexions au sujet de « Slick2d, leçon 3 :: Animer et Déplacer un Personnage »
  • dotista 03 November 2014 20:32

    Rehello:

    Le HUD est un tuto qui tombe à pique pour nous. Nous ne pouvions pas rêver mieux. Ce site devient très complet. Nous sommes face à un problème récurent, qui je pense a déjà été rencontré par eux qui on un minimum trifouillé Slick: L'incapacité à gérer un héritage entre 2 classes qui ne sont même pas BasicGame ou StateBasedGame. Bien entendu, elles sont utilisées dans ces classes.

    Une solution Shionn ?

  • Shionn 04 November 2014 06:53

    L'héritage multiple est un non sens. Je ne vois aucune application ou il est nécessaire. Peut tu m'en dire plus sur ton cas ?

  • SkyNov 08 February 2015 20:40

    Bonjour et félicitation pour vos tuto très bien constitué.

    Je voulais juste savoir si toutes ces lignes de programmations sont bien à mettre dans la classe WindowGame du début.

    Désolé si la question peut parraître de mauvaise qualitée mais je ne suis que simple débutant.

    Cordialement.

  • Shionn 09 February 2015 05:53

    Pour cette lecon oui. C'est qu'à partir de la lecon 10 qu'on separe en plusieurs classe.

  • SkyNov 11 February 2015 19:56

    Je te remercie pour cette réponse très rapide.

    Si je peut me permettre de vous posez une question, ou même par la même occasion de vous donnez deux trois idées que je trouvent intéressantes,

    J'aimerais de part un appui sur une touche ( par exemple 0 ) faire courir le personnage, ou tout du moins le faire accélérer. Cela serait il envisageable avec Slick ?

    De plus une gestion de chat avec une petite interface en jeu, pourrais être pas mal à expliquer en tutoriel qu'en pensez-vous ? :)

    Je vous souhaite une bonne soirée et vous remercie d'avance pour toutes réponses que vous m'apporterez.

    Cordialement.

  • Shionn 12 February 2015 13:46

    Pour pouvoir courrir il te suffit de modifier le facteur de vitesse comme indiquer dans le tuto sur le deplacement analogique ;)

    Je vais aborder l'affichage de grand text sur plusieur ligne, mais je n'aborderai pas le chat, en effet cela implique du jeux en reseau et je n'ai pas envie, bien que j'adire la techno, de faire des tuto sur reddwarf. En revanche il y a déjà un tuto sur l'affichage d'un début d'interface. La base est présente il suffit d'ajouter ce qu'on veux.

    Toutes les remarques sont les bienvenues.

  • SkyNov 12 February 2015 20:17

    Bonsoir, je comprend ce que tu veux dire, mais je ne veux pas faire varier la vitesse du personnage grâce a un joyg, car je voudrais gérer les déplacements seulement avec les 4 flèches directionnelles.

    De ce fait, il faudrait donc un appui sur une autre touche en parallèle, ou double click sur la flèche afin de le faire avancer plus vite.

    Merci pour toutes tes aides.

  • Shionn 13 February 2015 06:58

    Dans la methode update tu as une constante 0.1, c'est en fait la vitesse du perso. Essaye de remplacer par 0.2 tu verra alors qu'il se deplace deux fois plus vite ;)

    Ajoute un booleen running = false apres le booleen moving. A l'appui de la touche souhaite tu le passe a true et au relachement tu le passe a false.

    En fonction de running il te reste plus qu'as utiliser 0.1 ou 0.2 ;)

    Apres idealement il faudrait ajouter 4 autre animation de course apres celle de deplacement et utilisé celle ci quant le perso cours.

  • SkyNov 13 February 2015 11:22

    Ok, je te remercie et je regarde cela des que je peux, je te tiendrai au courant :)

  • Darkrzien 15 March 2015 00:27

    Merci pour ce tuto mais j'ai un soucis quand j'appuis sur les touches le personnage fais l'animation du déplacement mais ne bouge pas Je ne comprend pas pourquoi?

  • Cliuura 15 March 2015 00:55

    Bonjour Darkrzien, je me permet de te répondre car j'ai vu ton commentaire en cherchant certaines informations sur ce blog.

    As tu bien rentrer les informations dans la méthode « update() » ?

    La ou tu à les

    switch (this.direction) {
    case 0: futurY = this.y - .1f * delta; break; 
    ....
    ....
    case 3: futurX = this.x + .1f * delta; break; "
    

    C'est avec ceci que tu va gérer le déplacement comme l'a expliqué Shionn.

    J'espere t'avoir éclairé, si ce n'est pas le cas, dit le.

    Bonne soirée.

  • Shionn 15 March 2015 09:09

    @Darkzien, Je ne peu pas deviner ou tu as fais une erreur, à mon avis les variables d'instance x et y sont mal mise à jour lors du déplacement. Si tu trouve pas n'hésite à m'envoyer ton code par email. (dans le mail, en PJ, ou git/svn au choix)

    @Cliuura, futurX et futurY servent pour les calcul de collision, on y est pas encore. :]

  • Cliuura 20 March 2015 21:01

    Bonjour Shionn, après avoir essayé de voir l'éclipse aujourd'hui, je me pose une question ^^

    J'ai essayé de faire un code pour faire courir mon personnage a l'appui de "A" :

    case Input.KEY_RIGHT + Input.KEY_A:     this.direction = 3; this.running = true; break;
    

    J'ai ensuite private boolean moving = false; Puis this.moving = false; dans la méthode de relachement des boutons, mais j'ai certainement du rater une étape, vois tu ce que je veux dire ?

  • Shionn 21 March 2015 11:41

    Oui en effet.

    Tu ne t'en rend pas compte mais les Input.KEY_XXX sont des entiers, il s'agit des codes ascii des touches. C'est à dire 205 pour RIGHT et 30 pour A. Quand tu écris case Input.KEY_RIGHT + Input.KEY_A. C'est comme si tu mettais case 205+30 ou case 235, c'est à dire une touche dont je n'ai pas idée à l'instant (faudrait sue je cherche et j'ai la flemme).

    Pour ce que tu veux faire tu n'as pas a touché ce qui est fait. tu doit juste ajouter :

    case Input.<var class="static">KEY_A : this.running = true; break;
    

    Et ensuite dans le update tu dois adapter le code pour utilisé 0.2 à la place de 0.1 quand ton perso cours :

    if (this.moving) {  
      float speed = this.running ? .2f : .1f;
      switch (this.direction) {
        case 0: this.y -= speed * delta; break;
        // ...
    
  • ???? 28 May 2015 15:57

    Konnichiwa!

    Tout d'abord je tiens a vous remercier de votre travail qui aide beaucoup les debutant(e)s comme moi sur le developpement de jeu sur java. Pourriez vous m'expliquer ce que vous vouliez dire par "factoriser"? et les parametres de la methode : loadAnimation(spriteSheet, 0, 1, 0);

    Merci d'avance et bonne continuation

  • Shionn 31 May 2015 09:17

    Salut,

    Factoriser, à la même signification en informatique qu'en math. par exemple quand tu dois faire plusieurs fois la même chose, il est utile de le factoriser pour simplifié l'écriture. Ici ce qu'on fait 8 fois c'est charger une animation.

    Il faut lire loadAnimation(spriteSheet, startX, endX, Y); comme suit : ChargerL'animation depuis spritesheet de la colonne startX à endX et de la ligne Y

  • ユキリン 01 June 2015 15:53

    Ok ! Si j'ai bien compris, c'est pour le cote pratique. Je risque de te poser des tas de questions (^^). Au faite, même si je ne fais que commencer, j'ai parcouru les grandes ligne et une question se pose : c'est quand la suite ? En tout cas, merci pour l'explication et bonne continuation.

  • Shionn 02 June 2015 09:56

    J'écris les article a mon rythme j'en poste environ 1 par mois, plus je tiens pas le rythme.

  • progmen 10 June 2015 12:42

    esque que le sprite et le presonnage que vous utilisez dans son tuto son copiright ou puise je les utiliser pour mon jeux (qui seras commercialiser).

  • progmen 10 June 2015 12:43

    désoler, pour le double poste, et veuillez m'escuser pour les erreures de frappe dans mon poste precedent.

  • Shionn 12 June 2015 21:24

    Les sprites que j'utilise provienne tous d'OpenGameArt. A la fin de mes article tu as un lien vers le post de l'auteur des ressources.

    Le sprite du joueur par exemple est en GPL, (Gnu Public Licence) cela signifie que si tu fait un jeux, il y a un risque que ton jeux doivent être en GPL, et donc tu sera dans l'obligation de donner le code source gratuitement a quiconque te le demandera et donc il pourra compiler son jeux tous seul.

    C'est aussi le cas de mes tutos, il sont en GPL, si tu copie/colle mes tuto (même en les modifiant a 99.9%) ton code deviendra GPL.

  • ninokiri 11 July 2015 14:06

    Salut!
    Ça va être(un peu) long mais je ne sais plus trop ou demander de l'aide donc je me lance :

    En fait, je voudrai mettre en place dans le jeu que je souhaite créer un système de gestion du temps (jour/nuit, jour de la semaine...) la totale quoi, mais je ne sais pas comment m'y prendre.

    Cote rendu graphique, j'ai lu sur le net qu'il faut changer le ton d'affichage en fonction de l'heure de la semaine mais comment faire?

    Cote code, pour rendre tout ça "propre" je pense tout mettre dans une seule classe "gestionTemps" es-ce une bonne idée?

    Enfin, dans mon jeu, je voudrai recréer le mouvement de la mer pour la rendre plus "vivante". comment faire? on m'a suggéré des sprites dynamiques mais j'y comprend rien alors si tu pouvais m'aider , je te serai infiniment reconnaissant car je bloque dessus depuis des semaines.

    Merci d'avance.

  • Shionn 15 July 2015 15:48

    bonjour ninokiri.

    Pour ton cycle jour-nuit, ce n'est déjà pas évident. Voici une première piste : Il de faut faire la carte en double, une jour et une nuit. Lors de la transition de l'une à l'autre au crépuscule et a l'aurore, tu mélange l'affichage des deux en jouant sur la transparence des deux cartes, mais attention l'effet obtenu risque d'être aléatoire. Ce genre d'effet n'est vraiment pas évident en 2D.

    Pour la mer c'est presque plus simple. Dans ta carte, avec Tiled, tu fais 3 layer pour la mer. (eau basse, eau moyenne, eau haute) et à l'affichage tu n'en affiche qu'un seul en fonction d'un compteur d'animation. voili voilou.

  • […] Animations et déplcaements : http://www.shionn.org/slick2d-lesson-3-animate-and-move […]

  • Rémi 16 March 2016 16:57

    Bonjour, pour commencer merci pour le tuto qui m'aide vraiment pour mon projet.

    J'ai une question, en faite j'aimerai déplacer la camera sur la map en "drag" de ma souris, comme warcraft3 si tu connais, donc j'ai créé une classe, je fais implements MouseListener, j'ai bien les différentes méthodes, dragged, pressed etc..

    mais je n'arrive pas à m'en servir, par exemple même des println ne s'affiche pas alors qu'ils sont dans souris clicked ou autre Pourrais tu m'expliquer, si tu sais, pourquoi, voire comment faire? :)

    Rémi

  • Shionn 17 March 2016 17:57

    Bonjour Rémi. Je ne l'ai pas fait avec le listener mais y a pas de raison que cela marche pas je pense que tu l'as simplement mal enregistré dans le gestionnaire d'input comme cela :

    container.getInput().addMouseListener(listener);
    

    Sinon on peu aussi faire comme pour le clavier si tu surcharge la méthode public void mouseDragged(int oldx, int oldy, int newx, int newy) de ta classe Game tu catcherais bien les évents. Je préfère utiliser cette méthode pour les tutos qui est plus simple à comprendre.

    J'ai fais une mise à jour de la leçon 4 que tu peu trouver ici Attention cependant des qu'on relâche la souris c'est l'ancienne camera qui prend le relais :p.

    Si ça ne marche pas, est-ce que le curseur de ta souris apparaît quand ta souris passe par dessus de ta fenêtre ?

  • Marius 22 July 2016 16:39

    Bonjour, Après avoir testé le code , je constate qu'après avoir appuyé sur une touche mon personnage continue à se déplacer. Je souhaiterai qu'il s'arrête simultanément à l'arrêt de la pression de la touche ( J'sais pas si c'est très clair ..)

    Merci d'avance :)

  • Shionn 26 July 2016 09:23

    Bonjour Marius, je pense que tu as sauté le paragraphe sur le keyReleased().

  • Impa 12 November 2016 00:09

    Salut Shionn ! J'ai bien lu les commentaires mais j'ai tout de même un soucis concernant la vitesse de mon personnage que je souhaite faire marcher (1f) et courir (2f)

    j'ai deux booleans = false; se nommant "moving" et "running"

    dans la méthode keyPressed, j'ai bien un cas ou l'input de la KEY_SPACE fait passer "running" à True.

    Cependant dans ma méthode update, j'ai tenté diverses approches au niveau du "switch" qui n'ont pas fonctionné. Le premier bout de code fonctionne parfaitement (il provient de tes tutos, hué)

    if ( this.moving ) {
      float speed = this.running ? .2f : .1f;
      switch ( this.direction ) {
        case 0: futurY = this.y - .1f * delta; break;
        case 1: futurX = this.x - .1f * delta; break; 
        case 2: futurY = this.y + .1f * delta; break;
        case 3: futurX = this.x + .1f * delta; break;
      }
    }
    

    Bien evidemment, modifier le case 0,1,2 ou 3 par "this.x -.2f * delta; break;" fait augmenter la vitesse de mon personnage. Cependant, je souhaite l'augmenter en appuyant sur la touche Espace de mon clavier

    j'ai tenté divers implémentations comme

      case 4: this.y -= speed * delta; break;
      case 5: this.x -= speed * delta; break;
      case 6: this.y += speed * delta; break;
      case 7: this.x += speed * delta; break;
    

    ou encore modifier les cas 0->3

      case 0: futurX = this.y -= speed * delta; break;
      case 1: futurY = this.x -= speed * delta; break;
      case 2: futurX = this.y += speed * delta; break;
      case 3: futurY = this.x += speed * delta; break;
    

    qui, cette fois-ci, me procure une vitesse CONSTANTE de 2f, correspondant à 'running'

    Desole pour ce petit pavé, mais qu'ai je manqué ? Encore merci pour ce tutoriel

  • Shionn 12 November 2016 08:36

    Salut Impa, je m’excuse je me suis permis d'éditer ton commentaire pour rendre tes bous de code plus lisible.

    Je vois que tu as des variables "futurX" et "futurY" je pense que tu as déjà commencé les tuto sur la collision, franchement t'es pas loin de la solution. Il suffit de calculer speed comme ceci au debut du calcul de déplacement :

    float speed = this.running ? .2f : .1f;
    

    Puis de remplacé la constante de vitesse, c'est à dire .1f par speed.

    Hors collision la méthode update devrais être comme dans mon commentaires 21/03/2015 :

    if (this.moving) {  
      float speed = this.running ? .2f : .1f;
      switch (this.direction) {
        case 0: this.y -= speed * delta; break;
        // ...
    

    Si ton personnage ne cours jamais c'est tu ne détecte pas l'appui sur la touche espace et si il s’arrête pas de courir c'est que tu détecte pas le relâchement de la touche espace pour remettre le boolean "running" à false.

    Avec la collision cela devrait être comme ca :

    @Override
    public void update(GameContainer container, int delta) throws SlickException {
      if (this.moving) {
        float futurX = this.x;
        float futurY = this.y;
        float speed = this.running ? .2f : .1f;
        switch (this.direction) {
          case 0: futurY = this.y - speed * delta; break;
          //les autres cases
        }
        // calul des colisions
    
  • Clement 14 December 2016 10:59

    Salut Shionn, merci pour ce tutoriel j'apprend beaucoup grace à toi :) J'ai une petite question je voudrais effectuer une animation d'attaque avec mon personnage, un peu à la zelda-like : un petit coup d'épée ou un coup de lance et le sprite revient à la normale.

    J'ai deux Spritesheet : le premier est celui que tu utilises pour te déplacer Le deuxième est comme le premier sauf que mon héros porte une arme. Les deux sheets font bien sur 4 hauteur et 9 longueur comme dans ton tuto

    J'ai regardé un peu la doc Animation http://slick.ninjacave.com/javadoc/org/newdawn/slick/Animation.html et les méthodes "stopAt" puis "restart" me plaisent bien

    Mais je sais pas comment l'implémenter.. Aurais tu des pistes ?

  • Shionn 14 December 2016 11:09

    Bonjour clément,

    Tu n'es pas obligé d'avoir la même taille des spritesheet, pour faire ce que tu veux. Dans ce tuto je propose des animation "basique", en fait l'animation est composé de 8 sprite et ces 8 sprites tourne en boucle. C'est à dire que quand slick en a fini avec le 8ieme il recommence à la première. C'est à dire que l'animation est en setLooping(true), par defaut.

    Pour faire une animation d'attaque que je vais appelé atk il faut faire un setLooping(false) sur celle ci, celle-ci étant composé uniquement des frames d'attaque. Ensuite quand ton joueur appuye sur la touche d'attaque tu remplace l'animation courante par l'animation d'attaque et la lancer en appeant la méthode start() (ou restart) sur celle-ci. Cette animation se lancera une seul fois et l'image de fin restera afficher à l’infini.

    Pour cela, dans le update tu peu faire un isStop() dessus pour savoir si l'animation est fini ou non :] et retourné sur l'animation précédente (de mouvement par exemple).

  • Clement 14 December 2016 14:33

    Merci Shionn, ce serait quelque chose comme ca : ?

    // Class Player :
    // Tutoriel
    private Animation[] animations = new Animation[8];
    // Mon animation PUBLIC pour pouvoir etre recup dans la class Map
    public Animation[] atkAnimations = new Animation[6];
    
    // Get + set pour la recuperation dans Map
    public Animation[] getAtkAnim(){ return atkAnimations; }
    public void setAtkAnim(){ this.atkAnimations = atkAnimations; }
    
    function init() :
    SpriteSheet spriteAtk = new SpriteSheet("src/main/resources/sprites/mageAttack.png", 64, 64);
    this.atkAnimations[0] = loadAnimation(spriteAtk, 0, 1, 0);
    /* ... */
    
    // Class Map :
    private boolean atkBoolean;
    
    // Render :
    if(atkBoolean == true)
    ==> ???
    
    // Update :
    Input input = container.getInput();
    if(input.isKeyPressed(Input.KEY_1)
    atkBoolean = true;
    
    if(atkBoolean == true)
    if(input.isKeyPressed(Input.KEY_1)
    atkBoolean = false;
    
    // Est il nécessaire de déclarer une fonction dans la class Player :
    private Animation loadAtkAnimation(){
    } 
    

    qui reprend la syntaxe de loadAnimation de ton tutoriel en ajoutant les conseils que tu m'as donné ? start/restart/isStop etc

  • Shionn 14 December 2016 14:56

    Je m’excuse je me suis permis de mettre en forme ton commentaire.

    Alors oui et non. Tous d’abord on ne met JAMAIS de champs de champs public dans une classe (sinon à a quoi servent les getter ?) . Les sprites tu les charges au même endroit que la ou tu charge ceux d'animation. Je te conseil de les mettres dans le même tableau que les animations de marche comme cela à l'affichage du fait +8 au lieu de +4 pour la marche (si tu as pas compris relis le tuto). Du coup au lieu d'avoir un tableau de 8 animation tu en a 12 (4 de plus pour l'attaque dans chaque direction).

    Dans le le contrôle du clavier c'est pas bon. Ca devrais etre comme ca :

    if (!atk && la touche appuyé) { // le test sur atk evite de spammer la touche
      animation[8].start(); // idem pour 9-10-11 (ou a la limite seulement la direction en cours mais dans se cas faut ajouter un truc pour empêcher de changer de direction pendant l'attaque
      atk = true;
    } 
    

    Il n'est pas nécessaire de faire un test ou on relâche la touche car l'animation s’arrêtera d'elle même. c'est bien sur une autre histoire si on par sur un game play comme à la "link to the past" ou on peu maintenir l’épée dégainée.

    Dans le update cela devrait etre comme ca :

    if (atk && animation[direction+8].isStop() ) { // direction + 8 devrait être l'animation d'attaque
      atk = false;
    }
    

    Dans le render un truc du genre :

    if (atk) {
      g.drawAnimation(animations[direction + 8], x, y);
    } else {
      g.drawAnimation(animations[direction + (moving ? 4 : 0)], x, y);
    }
    

    Voila voila, je pense que c'est assez détaillé et je vais te laissé chercher un peu maintenant :] Je n’écris jamais de code vraiment fonctionnel dans mes commentaire mais plutôt du pseudo code que tu devra adapter. Si je devais le faire j'y passerai trop de temps et au pire je peu toujours le faire sur le github.

  • Clement 14 December 2016 16:01

    Merci beaucoup Shionn, ca parait carrément simple. Je t'embete une derniere fois car j'ai encore du mal à comprendre la pertinence des get, set pour passer les variables entre les classes.

    J'ai donc :

    // Class Player :
    private boolean atk = false;
    
    public boolean getAtk(){ return atk; }
    public void setAtk(){ this.atk = atk; }
    public Animation[] getAnim(){ return animations; }
    public void setAnim(){ this.animations = animations; }
    
    // Init :
    SpriteSheet spriteSheet = new SpriteSheet("src/main/resources/sprites/mage.png", 64, 64);
    this.animations[0] ==> this.animations[7];
    SpriteSheet spriteAtk = new SpriteSheet("src/main/resources/sprites/mageAttack.png", 64, 64);
    this.animations[8] => [11]
    
    // Render et Update sont comme tu viens de m'expliquer
    
    // Et donc dans la class Map :
    public void keyPressed(int key, char c){
    switch case()
      case Input B :
        boolean atak = this.player.getAtk();
        Animation anim[] = this.player.getAnim();
        anim[8].start();
        atak = true;
      break;
    }
    

    J'ai l'impression que c'est très très moche

  • Shionn 14 December 2016 16:11

    Ajoute plutot une methode "startAttack" à ton player dans le genre :

    public void startAttack() {
      if (!this.atk) {
        this.atk = true;
        this.animation[this.direction+8].start(); // faudrat peut être mettre restart.
      }
    }
    

    Et dans ton contrôleur quand t’appuie sur la touche t'as juste à faire un player.startAttack(), cela te parait plus propre ?

  • Clement 14 December 2016 16:27

    Fouaah j'ai d'énormes oeillères, j'avais à peine pensé à une méthode publique, tellement j'étais focalisé sur mes pauvres get, set

    Ca marche nickel merci Shionn :D

  • Shionn 14 December 2016 16:46

    Au plaisir :]

  • Impa 18 December 2016 14:54

    Salut Shionn, je me permets de poser une petite question concernant l'affichage des spritesheet : Quand tu refactor

    this.animations[0] = loadAnimation(spriteSheet, 0, 1, 0);
    this.animations[1] etc ..
    

    On a respectivement 3 chiffres entre les parenthèses qui correspondent à

    • numéro de colonne (la premiere colonne commence à 0 et pas 1)
    • le 'count' du nombre d'élements de la ligne
    • numéro de ligne (premiere ligne commence à 0)

    Mais comment faire si les éléments des sprites sont assez éloignés les uns des autres ? Par exemple http://image.noelshack.com/fichiers/2016/50/1482068083-capture.png

    // personnage immobile
    this.animations[0] // correspond à (0,1,0)
    this.animations[1] // correpond à  (0,1,1)
    this.animations[2] // correpond à  (0,1,2)
    this.animations[3] // correpond à  (0,1,3)
    
    // personnage mobile
    this.animations[4], [5], [6] et [7] ne sont pas visibles sur la capture d'écran
    

    // personnage qui attaque C'est la que ca coince : Sachant qu'il y a des espaces, des trous blancs entre les lignes et colonnes.

    On a quand meme :

    this.animations[8] = (1, 16, 6)
    this.animations[9] = (1,16, 9)
    this.animations[10] = (1,16,12)
    this.animations[11] = (1,16,15)
    

    L'animation d'attaque commence à la premiere colonne il y a 16 emplacements Ca commence à la 6ème ligne car la 4ème est l'animation du perso qui meurt et la 5ème est un trou blanc Ca fait 9 -> 12 -> 15 car il y a des trous de '2' entre les lignes

    Bref l'animation fonctionne pour les 4 directions Mais comme tu t'en doutes , il y a des moments de 'clignotements' entre les étapes d'attaque. L'animation n'est pas fluide à cause des trous blancs

    Aurais tu une idée pour palier à ca ? J'espère ne pas t'emmerder excessivement et avoir été assez explicite. En esperant que tu reprennes vite tes tutos , finis vite ton moteur de blog !! :p

  • Shionn 21 December 2016 16:22

    Bonjour impa,

    Honnêtement le plus simple c'est de retravailler ton spritesheet pour qu'il n'y ai plus de blanc. Cela par contre impose que tes animations de combat rentre dans la même surface que celle de marche.

    Sinon il ne faut plus utiliser la méthode loadAnimation que je donne en exemple, mais d'en faire une autre qui est capable de "sauté" les blancs lors du chargement. Un genre de loadAnimationBattle qui aurait une boucle légèrement différente :

    // j'ai mis 3 un peu au pifomètre en voyant ton image.
    for (int x = startX; x < endX; x+=3) {
    

    Et concernant le moteur de blog cela n'avance pas aussi vite que je le voudrais, il faudrait ajouter une journée par semaine, genre 7 cela m'aiderai ... c'est déjà le cas ? merde alors.

  • vincent 14 April 2017 18:02

    bonjour, je voudrait savoir comment afficher une animation 1 seule fois car j'ai mis un battiment dans mon jeu et a un moment donné il s'ecroulle mais si je demande au code de charger l'animation de destruction il l'affiche plusieurs fois. y a t il un moyen de demander au code de charger l'animation une seule fois ?

  • Shionn 14 April 2017 23:04

    Bonjour vincent,

    Oui, j'en parle dans un tuto, je sais plus lequel, mais il est possible de lire une animation une seul fois avec la méthode setLoop() (ou un truc du genre) sur l'objet animation.

  • vincent 18 April 2017 16:17

    problème résolut grâce aux méthode setlooping() et isStoped() ! merci!

  • Eegan 10 May 2017 17:03

    Bonjour,

    Merci pour ces tutos, ca me plait beaucoup de les suivre! =)

    J'ai une petite question: Dans les ressources que tu donnes, on peut trouver le personnage "Humain" et "Squelette" totalement formés, mais pour les autres (par exemple garde) la tête est séparée du corps, des jambes etc etc, du coup je me demande comment je peux faire pour les reconstituer?

    Merci d'avance!

    PS: Pour avoir le format .gzip (perso je suis sur la version 0.18 je crois, la dernière il me semble) : Carte => Propriété de la carte => Format de calque de tiles => Choisir compressé .gzpi. Voila voila =)

  • Shionn 11 May 2017 21:43

    Bonjour Eegan !

    Merci pour l'astuce du règlage de la compression ce n'est plus au même endroit qu'avant en effet ! Me reste plus qu'a faire des screen pour les ajouter au tuto.

    Pour les sprites, c'est des calcques, normalement suffit de les supperposer dans un outil comme gimp et ca se passe normalement plutot bien, des fois faut déplacer un peu les chose pour que ca tombe bien.

  • Eegan 12 May 2017 23:11

    Merci pour votre réponse rapide, j'avais en effet fini par comprendre par moi même, je me suis donc muni de mon meilleur Paint et fait quelques personnages =) Et merci encore pour vos tutos, ils sont géniaux =) En espérant avoir bientot le tuto sur la sauvegarde, même si je pense trouver une solution moi même en l'écrivant dans un fichier avec RandomAccesFile, qu'en pensez-vous?

  • Shionn 14 May 2017 07:47

    C'est possible,

    En effet il suffit d'ecrire les données et de les lire dans un fichier. le plus difficile etant de savoir quoi sauvegarder.

  • DanielKumok 29 June 2017 09:37

    Je ne sais pas si tu réponds encore aux commentaires mais je tente quand même. J'ai un problème horriblement gênant avec le pour de rien du tout [ for (int x = startX; x return animation; ]. Au lancement j'ai une erreur sur cette ligne qui indique [ insert " ) Statement"... Type mismatch : cannot convert int to boolean ]. Alors j'ai fais la boucle que je connaissais, j'ai modifié la ligne de long en large : rien n'y fait ! Donc voilà. Très bon blog et très bons tutos sur Slick, Merci d'avance ;) !

  • Shionn 29 June 2017 11:13

    Bonjour Daniel.

    Bien sur que je répond toujours au commentaire, j'ai même fait un outil pour être prévenu des commentaires. Des lignes ont disparu lors de la migration de mon tuto en common-mark. Je viens de corriger le tuto.

    Si je peu me permettre un conseil, il ne faut pas que tu te contentes de copier/coller, il faut que tu comprennes ce que tu fais.

  • DanielKumok 29 June 2017 15:21

    Pour ma défense je dirai que vu mon piètre niveau en Java je faisais aveuglément confiance xD Mais je repars potasser, Merci pour ton aide !

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.