Batch Forum Index
RegisterSearchFAQMemberlistUsergroupsLog in
Reply to topic Page 1 of 1
[Javascript] Jeu avec la mécanique de combat d'Undertale
Author Message
Reply with quote
Post [Javascript] Jeu avec la mécanique de combat d'Undertale 
Salut à tous !!

Je reviens un peu sur le forum qui m'a fait découvrir (et aimer) la programmation. J'essaie de progresser tant bien que mal dans des languages autres que le batch, et récemment je me suis lancer dans la création d'un jeu en pur javascript.

C'est un jeu qui reprend la mécanique de combat d'Undertale. Le but ici est de vaincre Totoro (oui le personnage du studio Ghibli).

Voilà le lien : https://sam4af.github.io/Totorotale/





(le combat est un peu compliqué, c'est normal si vous n'y arrivez pas du premier coup)
(je me demande si quelqu'un réussira à le battre en perfect sans perdre aucun pdv)


Je sais que dans ce forum il y a des vrais pro du javascript, ducoup j'aimerais bien avoir votre avis sur mon code pour savoir s'il y a des points à améliorer.


Code:

//canvas
let canvas = document.getElementById('c1');
let ctx = canvas.getContext('2d');
window.onload = create;

var img = new Image();
img.src = 'Image/Interface/sprite.png';

var img0 = new Image();
img0.src = 'Image/Sprite/heart.png';

var img1 = new Image();
img1.src = 'Image/Sprite/atk.png';

var imgt = new Image();
imgt.src = 'Image/Sprite/test.png';

var sound = {
  mus : document.querySelector('#s0'),

  atk : document.querySelector('#s1'),
  deg : document.querySelector('#s2'),
  aie : document.querySelector('#s3'),
  sel : document.querySelector('#s4'),
  val : document.querySelector('#s5'),
  brk : document.querySelector('#s6'),
  bkk : document.querySelector('#s7'),

  vo1 : document.querySelector('#s8'),
  sil : document.querySelector('#s9'),
  vo2 : document.querySelector('#s10'),

  end : document.querySelector('#s11'),
};


// -----------------------------------------------------------------------------
// sprite ennemis

// totoro
var sprite1 = {
  img : new Image(),
  ded : new Image(),
  h : 450,
  w : 345,
  ww : 138,
  hh : 180,
  max : 2,
  tick : 800,
}
sprite1.img.src = 'Image/Sprite/Totoro.png';
sprite1.ded.src = 'Image/Sprite/TotoroM.png';

var sprite = [sprite1];

// -------------
// boule
var spriteA1 = {
  img : new Image(),
  h : 15,
  w : 15,
}
spriteA1.img.src = 'Image/Sprite/atk1.png';

// éclaire
var spriteA2 = {
  img : new Image(),
  w : 7.64,
  h : 10,
}
spriteA2.img.src = 'Image/Sprite/atk2.png';

// flèche
var spriteA3 = {
  img : new Image(),
  w : 15,
  h : 15,
}
spriteA3.img.src = 'Image/Sprite/atk3.png';

var spriteA4 = {
  img : new Image(),
  w : 50,
  h : 42,
}
spriteA4.img.src = 'Image/Sprite/atk4.png';

var spAtk = [spriteA1,spriteA2,spriteA3,spriteA4];

// -----------------------------------------------------------------------------

var aff = 1;
var xmax = 600;
var ymax = 450;
var e = window.innerHeight/ymax;


function create(){

  iniFight();
  iniMap();
  iniGo();

  // ----

   update();

}

function changeAff(a){
  iniFight();
  iniMap();
  iniGo();

  aff = a;
}

function update(){

  window.requestAnimationFrame(update);

  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;

  ctx.fillStyle = 'rgb(0,0,0)';
   ctx.fillRect(0, 0, window.innerWidth,window.innerHeight);

  ctx.translate(window.innerWidth/2-((xmax/2)*e), 0)
  ctx.scale(e, e);

  switch(aff){

    case 1 :
      uptdateMap();
    break;

    case 2 :
      uptdateFight();
    break;

    case 3 :
      uptdateGo();
    break;

  }

  ctx.setTransform(1, 0, 0, 1, 0, 0);

}

document.onkeydown = function(event) {

  switch(aff){

    case 1 :
      keyMap();
    break;

    case 2 :
      keyFight();
    break;

    case 3 :
      keyGo();
    break;
  }


}

// -----------------------------------------------------------------------------

var heart;
var zone;
var joueur;
var monstre;
var interface;
var Attaque;

function iniFight() {

  Attaque = [];
  debuge();

  // id actuel enemy
  monstre = Enemy[0];

  interface = {

    text : {
      t : '',
      c : 0,
      d : 0,
      time : 0,
      tick : 50,
    },

    fight : {
      pos : 0,
      v : 6,
      miss : false,
      tick : 1000,
      time : 0,
    },

    atk : {
      img : img1,
      go : 0,
      c : 0,
      time : 0,
      tick : 125,
    },

    choice : {
      max : 4,
      current : 0,
    },

    cbt : {
      tick : 24,
      time : 0,
    },

    reset : function(){
      this.text.t = '';
      this.text.c = 0;
      this.text.d = 0;
      this.text.time = 0;

      this.fight.pos = 0;
      this.fight.miss = false;
      this.atk.go = 0;
      this.atk.c = 0;
      this.atk.time = 0;
    },

  }

  joueur = {
    name : 'Frisk',
    lvl : 1,
    vie : 42,
    viemax : 42,
    atk : 15,
  }

  zone = {
    x : 0,
    y : 0,
    w : 520,
    h : 130,

    xm : 0,
    ym : 0,
    wm : 0,
    hm : 0,

    isDraw : false,

    draw : function(w,h,hh) {
      y = hh || 0;
      zone.x = 300-zone.w/2;
      zone.y = 300-zone.h/2 + y;
      zone.xm = 300-w/2;
      zone.ym = 300-h/2 + y;
      zone.wm = w;
      zone.hm = h;
      v = 10;

      for (var i = 0; i < v; i++) {
        if (zone.xm < zone.x) zone.x -= 1;
        if (zone.xm > zone.x) zone.x += 1;
        if (zone.ym < zone.y) zone.y -= 1;
        if (zone.ym > zone.y) zone.y += 1;
        if (zone.wm < zone.w) zone.w -= 1;
        if (zone.wm > zone.w) zone.w += 1;
        if (zone.hm < zone.h) zone.h -= 1;
        if (zone.hm > zone.h) zone.h += 1;
      }

      ctx.strokeStyle = 'rgb(255,255,255)';
      ctx.lineWidth = 2;
      ctx.strokeRect(zone.x, zone.y, zone.w, zone.h);

      if (zone.xm == zone.x && zone.ym == zone.y && zone.wm == zone.w && zone.hm == zone.h) { zone.isDraw = true; } else { zone.isDraw = false; }
    }
  }

  heart = {
    x : 0,
    y : 0,
    w : 13,
    h : 13,

    v : 2,
    e : 0,
    img : img0,

    update : function() {

      if (this.e > 0) {
        this.e -= 1;
        ctx.drawImage(imgt, this.x, this.y, this.w, this.h);
      } else {
        ctx.drawImage(this.img, this.x, this.y, this.w, this.h);
      }

        if ((keyState[37] || keyState[81])){
            this.gauche();
        }

        if ((keyState[39] || keyState[68])){
            this.droite();
        }

        if ((keyState[38] || keyState[90])) {
            this.haut();
        }

        if ((keyState[40] || keyState[83])) {
            this.bas();
        }

        if (joueur.vie <= 0) {
          sound.mus.pause();
          sound.mus.currentTime = 0;
          ani.time = Date.now();
          aff = 3;
        }

    },

    gauche : function() { this.test(-this.v,0); },

    droite : function() { this.test(this.v,0); },

    bas : function() { this.test(0,this.v); },

    haut : function() { this.test(0,-this.v); },

    test : function(x,y){
      if (this.x+x > zone.x && this.x+x+this.w < zone.x+zone.w && this.y+y > zone.y && this.y+y+this.h < zone.y+zone.h) {
        this.x += x;
        this.y += y;
      }
    },

    pos : function(){
      this.x = zone.xm + zone.wm/2 - this.w/2;
      this.y = zone.ym + zone.hm/2 - this.h/2;
    },

    detect : function(){
      if (this.e == 0) {

        pixel = ctx.getImageData(rposX(this.x+1), rposY(this.y+1), this.w-2, this.h-2);

        t = 0;
        for (var i = 0; i < pixel.data.length; i++) {
          t++;
          if (t == 4) { t = 0 } else {
            if (pixel.data[i] > 200) {
              joueur.vie -= monstre.atk;
              sound.aie.play();
              this.e = 40;
              break;
            }
          }
        }

      }
    },

  }

}

//-----------------------------------------------------


function uptdateFight() {

  if (sound.mus.currentTime == sound.mus.duration || sound.mus.currentTime == 0) sound.mus.play();
  sound.mus.volume = 0.6;

  //iu
  a = '#FF7E24';
  b = '#FF7E24';
  c = '#FF7E24';
  d = '#FF7E24';
  if (interface.choice.current == 1) a = '#FFFF00';
  if (interface.choice.current == 2) b = '#FFFF00';
  if (interface.choice.current == 3) c = '#FFFF00';
  if (interface.choice.current == 4) d = '#FFFF00';

  ctx.lineWidth = 1;
  ctx.strokeStyle = a;
  ctx.strokeRect(40, 400, 100, 40);
  ctx.strokeStyle = b;
  ctx.strokeRect(180, 400, 100, 40);
  ctx.strokeStyle = c;
  ctx.strokeRect(320, 400, 100, 40);
  ctx.strokeStyle = d;
  ctx.strokeRect(460, 400, 100, 40);

  ctx.font = '23px Verdana';
  ctx.textAlign = 'right';
  ctx.fillStyle = a;
  ctx.fillText('FIGHT', 136, 429);
  ctx.fillStyle = b;
  ctx.fillText('ACT', 262, 429);
  ctx.fillStyle = c;
  ctx.fillText('ITEM', 410, 429);
  ctx.font = '22px Verdana';
  ctx.fillStyle = d;
  ctx.fillText('MERCY', 557, 429);

  if (interface.choice.current == 1) { ctx.drawImage(heart.img, 46, 413, heart.w, heart.h); } else { ctx.drawImage(img, 0, 0, 15, 28, 45, 405, 15, 28); }
  if (interface.choice.current == 2) { ctx.drawImage(heart.img, 192, 413, heart.w, heart.h); } else { ctx.drawImage(img, 0, 39, 15, 28, 192, 412, 15, 28); }
  if (interface.choice.current == 3) { ctx.drawImage(heart.img, 330, 413, heart.w, heart.h); } else { ctx.drawImage(img, 0, 67, 15, 28, 330, 408, 15, 28); }
  if (interface.choice.current == 4) { ctx.drawImage(heart.img, 464, 413, heart.w, heart.h); } else { ctx.drawImage(img, 0, 98, 15, 28, 463, 410, 15, 28); }

  // info joueur
  ctx.font = '12px KulminoituvaRegular';
  ctx.fillStyle = 'rgb(255,255,255)';
  ctx.textAlign = 'left';
  ctx.fillText(joueur.name, 40, 388);
  ctx.fillText('LV ' + joueur.lvl, 150, 388);
  ctx.fillText(joueur.vie + ' / ' + joueur.viemax, 280 + joueur.viemax, 388);

  ctx.font = '10px KulminoituvaRegular';
  ctx.fillText('HP', 238, 387);
  ctx.fillStyle = 'rgb(255,0,0)';
  ctx.fillRect(258,376,joueur.viemax,13);
  ctx.fillStyle = 'rgb(255,255,0)';
  ctx.fillRect(258,376,joueur.vie,13);

  //carré vert
  if (monstre.cadre == 1) {
    ctx.strokeStyle = '#3A7748';
    ctx.lineWidth = 1;
    ctx.strokeRect(10, 10, 580, 215);

    ctx.strokeStyle = '#3A7748';
    ctx.lineWidth = 1;
    ctx.strokeRect(10, 10, 580, 215);
    for (var i=1; i<6; i++) {
      ctx.strokeRect(10+96*i, 10, 0.1, 215);
    }
    ctx.strokeRect(10, 112.5, 580, 0.1);
  }

  monstre.draw();

  // A quel étape sommes-nous ?
  switch (monstre.step[monstre.currentstep][0]) {
    // phase de choix libre
    case 0:
      zone.draw(520, 130);
      if (interface.choice.current == 0) interface.choice.current = 1;
      ctx.font = '14px KulminoituvaRegular';
      ctx.fillStyle = 'rgb(255,255,255)';
      ctx.textAlign = 'left';
      if (zone.isDraw) { ctx.fillText('* ' + monstre.text, 50, 265); }
    break;
    // phase de dialogue
    case 1:
      zone.draw(520, 130);

      ctx.fillStyle = 'rgb(255,255,255)';
      ctx.beginPath();
      ctx.moveTo(416, 85);
      ctx.lineTo(380, 95);
      ctx.lineTo(416, 105);
      ctx.fill();

      ctx.fillStyle = 'rgb(255,255,255)';
      roundRect(415,50,155,80,13);

      ctx.font = '14px KulminoituvaRegular';
      ctx.fillStyle = 'rgb(255,255,255)';
      ctx.textAlign = 'left';
      if (zone.isDraw) { ctx.fillText('* ' + monstre.text, 50, 265); }

      if (Date.now() - interface.text.time > interface.text.tick) {
        interface.text.time = Date.now();
        if (interface.text.c < monstre.step[monstre.currentstep][1].length) {
          if (monstre.step[monstre.currentstep][1][interface.text.c] == ' ' || monstre.step[monstre.currentstep][1][interface.text.c] == '/') { sound.sil.play(); } else { sound.vo1.play(); }
          interface.text.t = interface.text.t + monstre.step[monstre.currentstep][1][interface.text.c];
          interface.text.c += 1;
        }
      }

      ctx.font = '9px KulminoituvaRegular';
      ctx.fillStyle = 'rgb(0,0,0)';
      ctx.textAlign = 'left';
      fillTextM(interface.text.t, 423, 69);
    break;
    // phase d'attaque
    case 2:
      zone.draw(520, 130);
      if (zone.isDraw) {

      ctx.drawImage(img, 0, 215, 565, 130, 41, 236, 518, 128);

      n = 'rgb(0,0,0)';
      nn = 'rgb(255,255,255)';
      // -- calcul dégats
      a = (250 - interface.fight.pos)/5;
      if (a < 0) a = (a*-1);
      a = 1 - (a*0.02);
      // -- deplacement du curseur
      if (interface.atk.go == 0) {
        if (interface.fight.pos <= 490) {
          interface.fight.pos += interface.fight.v;
         } else {
          interface.fight.miss = true;
          interface.fight.time = Date.now();
          monstre.step[monstre.currentstep][0] = 4;
        }
      }
      // -- animation d'attaque
      if (interface.atk.go == 1) {
        if (Date.now() - interface.atk.time > interface.atk.tick) {
          interface.atk.time = Date.now();
          interface.atk.c += 1;
          if (n == 'rgb(0,0,0)') { n = 'rgb(255,255,255)'; } else { n = 'rgb(0,0,0)'; }
          if (nn == 'rgb(0,0,0)') { nn = 'rgb(255,255,255)'; } else { nn = 'rgb(0,0,0)'; }
        }

        ctx.drawImage(interface.atk.img,interface.atk.c*25,0,25,120,287.5,50,25,120);

        if (interface.atk.c*25 == interface.atk.img.width) {
          interface.atk.go = 2;
          sound.deg.play();
          if (monstre.vul == 0) { monstre.vie = Math.round(monstre.vie - joueur.atk*a); } else {
            monstre.vul = monstre.vie;
            monstre.vie -= monstre.vie;
          }
          if (monstre.vie < 0) monstre.vie = 0;
        }
      }
      // -- animation de degat recu
      if (interface.atk.go == 2) {

        ctx.font = '20px KulminoituvaRegular';
        ctx.fillStyle = 'rgb(255,0,0)';
        ctx.textAlign = 'center';
        if (monstre.vul == 0) { ctx.fillText(Math.round(joueur.atk*a), 380 + monstre.viemax/2, 99); } else { ctx.fillText(monstre.vul, 380 + monstre.viemax/2, 99); }

        monstre.degat();

      }

      ctx.fillStyle = n;
      ctx.fillRect(40+interface.fight.pos,236,14,128);
      ctx.fillStyle = nn;
      ctx.fillRect(44+interface.fight.pos,239,6,122);
      }
    break;
    // change le texte affiché
    case 3:
      monstre.text = monstre.step[monstre.currentstep][1];
      monstre.vul = monstre.step[monstre.currentstep][2];
      monstre.newstep();
    break;
    // phase de combat
    case 4:
      if (interface.fight.miss == true) {
          ctx.font = '22px KulminoituvaRegular';
          ctx.fillStyle = 'rgb(255,255,255)';
          ctx.textAlign = 'left';
          ctx.fillText('MISS', 360, 70);

         if (Date.now() - interface.fight.time > interface.fight.tick) {
           interface.fight.miss = false;
         }
      }

      zone.draw(zoneX, zoneY, zoneYY);

      if (zone.isDraw) {

        for (i=0; i<Attaque.length; i++){
          Attaque[i].affichage();
          Attaque[i].deplacement();
        }
        for (i=0; i<Attaque.length; i++){
          Attaque[i].end(i);
        }
        if (Attaque.length == 0) monstre.newstep();

        heart.detect();
        heart.update();

      } else {
        heart.pos();
      }
    break;
    // change fin de cbt
    case 5:
      monstre.end = [];
      for (var i = 1; i < monstre.step[monstre.currentstep].length; i++) {
        monstre.end.push(monstre.step[monstre.currentstep][i]);
      }
      monstre.newstep();
    break;
    // mort du monstre
    case 6:
      sound.mus.pause();
      zone.draw(520, 130);
      ctx.font = '14px KulminoituvaRegular';
      ctx.fillStyle = 'rgb(255,255,255)';
      ctx.textAlign = 'left';
      if (zone.isDraw) { ctx.fillText('* ' + monstre.name + ' a perdu.', 50, 265); }

      ctx.fillStyle = 'rgb(255,255,255)';
      ctx.beginPath();
      ctx.moveTo(416, 85);
      ctx.lineTo(380, 95);
      ctx.lineTo(416, 105);
      ctx.fill();
      ctx.fillStyle = 'rgb(255,255,255)';
      roundRect(415,50,155,80,13);

      if (Date.now() - interface.text.time > interface.text.tick) {
        interface.text.time = Date.now();
        if (interface.text.c < monstre.end[interface.text.d].length) {
          if (monstre.end[interface.text.d][interface.text.c] == ' ' || monstre.end[interface.text.d][interface.text.c] == '/') { sound.sil.play(); } else { sound.vo1.play(); }
          interface.text.t = interface.text.t + monstre.end[interface.text.d][interface.text.c];
          interface.text.c += 1;
        }
      }

      ctx.font = '9px KulminoituvaRegular';
      ctx.fillStyle = 'rgb(0,0,0)';
      ctx.textAlign = 'left';
      fillTextM(interface.text.t, 423, 69);
    break;
    // fin du cbt
    case 7:
      zone.draw(520, 130);
      ctx.font = '14px KulminoituvaRegular';
      ctx.fillStyle = 'rgb(255,255,255)';
      ctx.textAlign = 'left';
      if (zone.isDraw) { fillTextM('* Appuyez sur une touche pour revenir au menu /  principal', 50, 265); }

      if (monstre.d > 0) { monstre.d -= 0.05; }
      if (monstre.d <= 0) { monstre.d = 0; }
      monstre.spriteF = 'opacity(' + monstre.d + ')';
    break;
  }


}

//-----------------------------------------------------

function keyFight(){

  if (event.keyCode == 77) {
    if (sound.mus.paused) {
      sound.mus.play();
    } else {
      sound.mus.pause();
    }
  }

  switch (monstre.step[monstre.currentstep][0]) {

    // phase de choix
    case 0:
      if (event.keyCode == 37) {
        interface.choice.current -= 1;
        if (interface.choice.current == 0) interface.choice.current = interface.choice.max;
        sound.sel.play();
      }

      if (event.keyCode == 39) {
        interface.choice.current += 1;
        if (interface.choice.current == interface.choice.max+1) interface.choice.current = 1;
        sound.sel.play();
      }

      if (event.keyCode == 90) {

        if (interface.choice.current == 1) {
           monstre.step[monstre.currentstep][0] = 2;
           interface.choice.current = 0;
           sound.val.play();
        }

      }
    break;
    // phase de dialogue
    case 1:
      if (event.keyCode == 90) {

        if (interface.text.c < monstre.step[monstre.currentstep][1].length) {
          interface.text.t = monstre.step[monstre.currentstep][1];
          interface.text.c = monstre.step[monstre.currentstep][1].length;
        } else {
          monstre.newstep();
        }

      }
    break;
    // phase d'attaque
    case 2:
      if (event.keyCode == 90) {
        if (interface.atk.go == 0) {
          if (interface.fight.pos > 1) {
            sound.atk.play();
            interface.atk.go = 1;
          }
        }

      }

    break;
    // phase de cbt
    case 4:
      if (event.keyCode == 220) {
        Attaque = [];
      }
    break;
    // phase de cbt
    case 6:
      if (event.keyCode == 90) {

        if (interface.text.c < monstre.end[interface.text.d].length) {
          interface.text.t = monstre.end[interface.text.d];
          interface.text.c = monstre.end[interface.text.d].length;
        } else {
          if (interface.text.d < monstre.end.length-1) {
            interface.text.t = '';
            interface.text.c = 0;
            interface.text.d += 1;
          } else {
            sound.end.play();
            monstre.d = 1;
            monstre.step[monstre.currentstep][0] = 7;
          }
        }

      }
    break;
    case 7:
      if (monstre.d <= 0) {
        changeAff(1);
      }
    break;
  }

}

document.onmousedown = function(event) {
  a = Math.round(window.innerWidth/2-((xmax/2)*e));
  xs = Math.round((event.clientX-a)/e);
  ys = Math.round(event.clientY/e);
  xm = event.clientX;
  ym = event.clientY;


  xx = rposX(xs);
}

//-----------------------------------------------------

var Enemy = [];
function AllEnemy(name,sprite,v,c,s,a){
  this.name = name;
  this.sprite = sprite;

  this.atk = a || 4;
  this.spriteX = (xmax-sprite.ww)/2;
  this.spriteY = 10 + 205/2 - sprite.hh/2;
  this.spriteW = sprite.ww;
  this.spriteH = sprite.hh;
  this.spriteF = 'none';

  this.tick = sprite.tick;
  this.time = Date.now();
  this.s = 0;
  this.d = 0;

  this.vie = v;
  this.viemax = v;
  this.v = v;

  this.cadre = c;
  this.text = '...',
  this.currentstep = 0;
  this.step = s;
  this.end = [];
  this.vul = 0;

  this.draw = function(x) {
    if (Date.now() - this.time > this.tick) {
      this.time = Date.now();
      if (this.s == this.sprite.max-1) { this.s = 0; } else { this.s += 1; }
    }
    if (monstre.step[monstre.currentstep][0] < 6) {
      ctx.filter = this.spriteF;
      ctx.drawImage(this.sprite.img, this.s*this.sprite.w, 0, this.sprite.w, this.sprite.h, this.spriteX, this.spriteY, this.spriteW, this.spriteH);
      ctx.filter = 'none';
      this.spriteF = 'none';
    } else {
      ctx.filter = this.spriteF;
      ctx.drawImage(this.sprite.ded, 0, 0, this.sprite.w, this.sprite.h, this.spriteX, this.spriteY, this.spriteW, this.spriteH);
      ctx.filter = 'none';
      this.spriteF = 'none';
    }

  };

  this.degat = function(a){
    this.spriteF = 'opacity(0.8)';

    if (Date.now() - this.time > 50) {
      this.time = Date.now();
      if (this.d == 0) {
        this.d = 1;
        this.spriteX -= 10;
      } else {
        this.d = 0;
        this.spriteX += 10;
      }
    }

    ctx.fillStyle = 'rgb(255,0,0)';
    ctx.fillRect(380,60,this.viemax,10);
    ctx.fillStyle = 'rgb(0,255,0)';
    ctx.fillRect(380,60,this.v,10);
    if (this.v > this.vie) { this.v -= 1; }

    if (Date.now() - interface.atk.time > 1000) {
      if (this.v == this.vie) {
        interface.atk.go = 0;
        monstre.spriteX = (xmax-monstre.sprite.ww)/2;
        if (monstre.vie > 0) { monstre.step[monstre.currentstep][0] = 4; } else { monstre.step[monstre.currentstep][0] = 6; }
      }
    }

  };

  this.newstep = function() {
    interface.reset();

    monstre.currentstep += 1;

    if (monstre.currentstep > monstre.step.length-1) {

      m = [];
      for (var i = 0; i < monstre.step.length; i++) {
        if (monstre.step[i][0] == 4) m.push(monstre.step[i][1])
      }
      m = cleanArray(m);
      m = m[Math.floor(Math.random()*m.length)];
      m = parseInt(m, 10);
      monstre.step.push([0,m]);

    }

    if (monstre.step[monstre.currentstep][0] == 0) combo(monstre.step[monstre.currentstep][1]);

  }

  Enemy.push(this);
}

// ---------
function debuge(){
  Enemy = [];

  var Etape = [
    [3,'Totoro veut se battre.',0],
    [5,'Bon..','Je retourne faire /ma sieste.','Adieu.'],

    [1,'Comment oses-tu me /réveiller ?/Pauvre insolent.'],
    [1,"Prépare-toi à passer /un sale quart /d'heure !"],
    [0,1],
    [1,'Ne pense pas pouvoir /échapper à mes /griffes !'],
    [0,4],
    [1,'Tu vas bientôt avoir /le tournis à bouger /dans tous les sens /comme tu le fais.'],
    [0,3],
    [1,"Essaie d'esquiver ça !"],
    [0,5],
    [1,"Tu commences à /me mettre vraiment /en colère !"],
    [0,2],
    [1,"Tu es bien résistant.."],
    [0,8],
    [1,"Voici une de mes /attaques préférées. /Je l'ai appelée : /La griffe en colère"],
    [0,6],
    [1,"Il est temps d'en /finir."],
    [1,"Personne ne peut /survivre au roi des /Hamsters !"],
    [0,7],
    [3,"Totoro t'épargne.",1],
    [1,"Je ne peux pas /lutter. Tu as gagné."],
  ];
  // nom | sprite | viemax | cadre(1 oui, 0 non) | étapes
  new AllEnemy('Totoro',sprite[0],124,1,Etape);
  // ---------
}
//-----------------------------------------------------

var zoneX;
var zoneY;
var zoneYY;

function combo(x){

  switch (x) {
    case 1:
      v = 2;
      a = 2;
      for(var y=235; y>-2335;y-=100) {
        if (a == 1) a = Random(1,3);
        else if (a == 2) a = Random(1,3);
        else if (a == 3) a = Random(2,4);
        else if (a == 4) a = Random(2,4);

        if (a !== 1) new dAtk(spAtk[0],225,y,v,225,350);
        if (a !== 1) new dAtk(spAtk[0],242,y,v,242,350);
        if (a !== 2) new dAtk(spAtk[0],259,y,v,259,350);
        if (a !== 2) new dAtk(spAtk[0],276,y,v,276,350);
        if (a !== 3) new dAtk(spAtk[0],293,y,v,293,350);
        if (a !== 3) new dAtk(spAtk[0],310,y,v,310,350);
        if (a !== 4) new dAtk(spAtk[0],327,y,v,327,350);
        if (a !== 4) new dAtk(spAtk[0],344,y,v,344,350);
        if (a !== 4) new dAtk(spAtk[0],361,y,v,361,350);

        v += 0.007;
      }

      zoneX = 150;
      zoneY = 130;
      zoneYY = 0;
    break;
    case 2:
      for(var y=235; y>-1700;y-=9) {
        x = Random(225,365);
        new dAtk(spAtk[1],x,y,1.5,x,355);
      }

      zoneX = 150;
      zoneY = 130;
      zoneYY = 0;
    break;
    case 3:
      new torAtk(100, 301, 50, 1.5, 225);
      new torAtk(500, 301, 12, 1.5, 375);

      zoneX = 150;
      zoneY = 60;
    break;
    case 4:
      p = 46;
      v = 3;
      for(var a=0 ; a<p*22;a+=p) {
        x = Random(1,3);

        if (x !== 1) new dAtk(spAtk[3],150,235,v,325,235,a,a+p/2);
        if (x !== 2) new dAtk(spAtk[3],400,277,v,225,277,a,a+p/2);
        if (x !== 3) new dAtk(spAtk[3],150,319,v,325,319,a,a+p/2);
      }

      zoneX = 150;
      zoneY = 130;
      zoneYY = 0;
    break;
    case 5:
      v = 1.4;
      for(var a=0 ; a<1350;a+=16) {
        c = Random(1,3);

        switch (c) {
          case 1:
            y = 218 + Random(1,13)*10;
            new dAtk(spAtk[2],200,y,v,360,y,a);
          break;
          case 2:
            y = 218 + Random(1,13)*10;
            new dAtk(spAtk[2],400,y,v,225,y,a);
          break;
          case 3:
            x = 218 + Random(1,14)*10;
            new dAtk(spAtk[2],x,200,v,x,350,a,0,1);
          break;
        }
      }

      zoneX = 150;
      zoneY = 130;
      zoneYY = 0;
    break;
    case 6:
      p = 150;
      v = 3;
      for(var a=0 ; a<p*9;a+=p) {
        x = Random(1,3);

        if (x !== 1) new dAtk(spAtk[3],150,235,v,325,235,a,a+p/2);
        if (x !== 2) new dAtk(spAtk[3],400,277,v,225,277,a,a+p/2);
        if (x !== 3) new dAtk(spAtk[3],150,319,v,325,319,a,a+p/2);
      }

      for(var y=235; y>-1700;y-=30) {
        x = Random(225,365);
        new dAtk(spAtk[1],x,y,1.5,x,355);
      }

      zoneX = 150;
      zoneY = 130;
      zoneYY = 0;
    break;
    case 7:
      new torAtk(100, 300, 50, 1, 300,300,90,2000,5);

      v = 0.8;
      for(var a=0 ; a<1800;a+=60) {
          x = 218 + Random(1,14)*10;
          new dAtk(spAtk[2],x,150,v,x,350,a,0,1);
      }

      zoneX = 150;
      zoneY = 130;
      zoneYY = 0;
    break;
    case 8:
      v = 2.1;
      a = 3;
      for(var y=235; y>-2735;y-=60) {
        if (a == 1) a = Random(1,2);
        else if (a == 2) a = Random(1,3);
        else if (a == 3) a = Random(2,4);
        else if (a == 4) a = Random(3,4);

        if (a !== 1) new dAtk(spAtk[0],225,y,v,225,343);
        if (a !== 1) new dAtk(spAtk[0],242,y,v,242,343);
        if (a !== 2) new dAtk(spAtk[0],259,y,v,259,343);
        if (a !== 2) new dAtk(spAtk[0],276,y,v,276,343);
        if (a !== 3) new dAtk(spAtk[0],293,y,v,293,343);
        if (a !== 3) new dAtk(spAtk[0],310,y,v,310,343);
        if (a !== 4) new dAtk(spAtk[0],327,y,v,327,343);
        if (a !== 4) new dAtk(spAtk[0],344,y,v,344,343);
        if (a !== 4) new dAtk(spAtk[0],361,y,v,361,343);
      }

      zoneX = 150;
      zoneY = 15;
      zoneYY = 50;
    break;
  }

}

// attaque directionnel
// sprite - pos x - pos y - vitesse x - vitesse y - but x - but y - depart - pause - suivre
function dAtk(sprite, x, y, v, bx, by, dep, pau, hid) {
  this.sprite = sprite;

  this.x = x;
  this.y = y;
  this.vx = x <= bx ? v : -v;
  this.vy = y <= by ? v : -v;

  this.bx = bx || x;
  this.by = by || y;

  this.dep = dep || 0;
  this.pau = pau || 0;
  this.hid = hid || 234;

  this.deplacement = function() {

    if (this.dep == 0 && this.pau == 0) {

      if (this.x !== this.bx) this.x += this.vx;

      if (this.y !== this.by) this.y += this.vy;

    }

    if (this.dep > 0) this.dep -= 1;
    if (this.pau > 0) this.pau -= 1;

  };

  this.affichage = function() {

    if (this.dep == 0 && this.y > this.hid) ctx.drawImage(this.sprite.img,this.x,this.y,this.sprite.w,this.sprite.h);

  }

  this.end = function(i){

    if (this.vx > 0 && this.x >= this.bx && this.y >= this.by) Attaque.splice(i,1);
    if (this.vx < 0 && this.x <= this.bx && this.y >= this.by) Attaque.splice(i,1);

  }

  Attaque.push(this);
}


// attaque qui tourne
function torAtk(x, y, av, v, fx, fy, l, m, ep) {
  this.sprite = sprite;
  this.x = x;
  this.y = y;
  this.fx = fx || x;
  this.fy = fy || y;
  this.v = v;
  this.av = av;
  this.angle = av;
  this.l = l || 75;
  this.m = m || 1500;
  this.ep = ep || 3;

  this.deplacement = function() {
    if (this.x > this.fx) this.x -= 1;
    if (this.x < this.fx) this.x += 1;

    this.angle += this.v;
  };

  this.affichage = function(i) {
    r = this.angle * Math.PI / 180;
    ctx.translate(this.x,this.y)
    ctx.rotate(r);
    ctx.fillStyle = 'rgb(255,255,255)';
    ctx.fillRect(0,0,this.l,this.ep);
    ctx.fillRect(0,0,this.ep,this.l);
    ctx.fillRect(0,0,-this.l,this.ep);
    ctx.fillRect(0,0,this.ep,-this.l);
    ctx.rotate(-r);
    ctx.translate(-this.x,-this.y);
  };

  this.end = function(i){

    if (this.angle - this.av > this.m) { Attaque.splice(i,1); }

  }

  Attaque.push(this);
}

// -----------------------------------------------------------------------------

function iniMap() {



}

function uptdateMap() {
  ctx.strokeStyle = 'rgb(220,220,220)';
  ctx.strokeRect(0,30,600,380);

  ctx.translate(0,10);
  ctx.font = '12px KulminoituvaRegular';
  ctx.fillStyle = 'rgb(220,220,220)';
  ctx.textAlign = 'left';

  fillTextM('* Vous devez appuyer sur [z] pour passer les dialogues.', 9, 60);
  fillTextM('* Vous devez appuyer sur [m] pour couper la musique.', 9, 90);
  fillTextM("* Lorsque vous attaquez, vous devez viser le centre de l'image /  pour infliger un maximum de dégats à votre adversaire.", 9, 120);

  ctx.drawImage(heart.img, 300, 170, heart.w, heart.h);

  fillTextM("* Il s'agit d'un fangame, reprenant la mécanique de combat du jeu/  Undertale.", 9, 215);
  fillTextM("* La musique et les sons utilisés ne m'appartienne pas.", 9, 265);
  fillTextM("* Ce fangame a été réalisé par Sam4AF.", 9, 295);

  ctx.drawImage(heart.img, 300, 315, heart.w, heart.h);

  fillTextM("* Appuyez sur n'importe quelles touches pour commencer.", 9, 360);
  ctx.translate(0,-10);
}

function keyMap() {

  changeAff(2);

}

// -----------------------------------------------------------------------------
var ani;

function iniGo() {

  ani = {
    time : 0,
    tick : 1000,
    s : 0,
    a : 0,

    c : 0,
    t : '',
    text : "N'abandonne pas !/Garde ta détermination !!"
  }

}

function uptdateGo() {

  if (ani.s == 0) ctx.drawImage(heart.img, heart.x, heart.y, heart.w, heart.h);
  if (ani.s == 1) ctx.drawImage(img,19,0,19,16,heart.x-1,heart.y,15,13);

  if (ani.s == 2) {

    if (ani.a < 1) ani.a += 0.01;

    a = 'rgba(255,255,255,' + ani.a + ')';
    ctx.fillStyle = a;
    ctx.font = '90px KulminoituvaRegular';
    ctx.textAlign = 'center';
    ctx.fillText('Game',300,100);
    ctx.fillText('Over',300,190);

    ctx.font = '14px KulminoituvaRegular';
    ctx.fillStyle = 'rgb(220,220,220)';
    ctx.textAlign = 'center';
    fillTextM(ani.t, 300, 290);

    if (ani.c >= ani.text.length) {
      ctx.font = '11px KulminoituvaRegular';
      ctx.fillStyle = 'rgb(255,255,255)';
      ctx.textAlign = 'center';
      ctx.fillText('Appuie sur [z] pour recommencer', 500, 400);
    }

  }

  if (Date.now() - ani.time > ani.tick) {
    if (ani.s == 0) {
      ani.s = 1;
      sound.brk.play();
      ani.time = Date.now();
    }
    else if (ani.s == 1) {
      ani.s = 2;
      sound.bkk.play();
      ani.tick = 50;
      ani.time = Date.now();
    }
    else if (ani.s == 2 && ani.a >= 1) {
      ani.time = Date.now();
      if (ani.c < ani.text.length) {
        ani.t = ani.t + ani.text[ani.c];
        ani.c += 1;
        if (ani.text[ani.c] == ' ' || ani.text[ani.c] == '/') { sound.sil.play(); } else { sound.vo2.play(); }
      }
    }

  }


}

function keyGo() {

  if (event.keyCode == 90) {

    if (ani.c >= ani.text.length) {
      changeAff(2);
    }

  }

}

// -----------------------------------------------------------------------------

function rposX(x){
  a = Math.round(window.innerWidth/2-((xmax/2)*e));
  b = Math.round(x*e);

  return Math.round(b+a);
}

function rposY(y){
  return Math.round(y*e);
}

// -----------------------------------------------------------------------------

function Random(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min +1)) + min;
}

function roundDecimal(nombre, precision){
    var precision = precision || 2;
    var tmp = Math.pow(10, precision);
    return Math.round( nombre*tmp )/tmp;
}

// key controle
let keyState = [];
document.addEventListener(
    'keydown',
    (event)=>{
        keyState[event.keyCode || event.which] = true;
    }
);

document.addEventListener(
    'keyup',
    (event)=>{
        keyState[event.keyCode || event.which] = false;
    }
);

function fillTextM(text, x, y) {
  var lineHeight = ctx.measureText("M").width * 1.2;
  var lines = text.split("/");
  for (var i = 0; i < lines.length; ++i) {
    ctx.fillText(lines[i], x, y); y += lineHeight*1.8;
  }
}

function cleanArray(array) {
  var i, j, len = array.length, out = [], obj = {};
  for (i = 0; i < len; i++) {
    obj[array[i]] = 0;
  }
  for (j in obj) {
    out.push(j);
  }
  return out;
}

function roundRect(x, y, ww, hh, r) {
    if (ww < 2 * r) r = ww / 2;
    if (hh < 2 * r) r = hh / 2;
    ctx.beginPath();
    ctx.moveTo(x+r, y);
    ctx.arcTo(x+ww, y, x+ww, y+hh, r);
    ctx.arcTo(x+ww, y+hh, x, y+hh, r);
    ctx.arcTo(x, y+hh, x, y, r);
    ctx.arcTo(x, y, x+ww, y, r);
    ctx.closePath();
    ctx.fill();
  }


+ le lien github : https://github.com/Sam4AF/Totoro



Last edited by Bad-Code on Thu 7 May 2020 - 00:46; edited 1 time in total

______________________________________________________
We're just an accident. We're just bad code. - Root
Post Publicité 
PublicitéSupprimer les publicités ?


Reply with quote
Post [Javascript] Jeu avec la mécanique de combat d'Undertale 
Franchement gg Mr. Green
T'as gérer de fou Okay




Bad-Code wrote:
(le combat est un peu compliqué, c'est normal si vous n'y arrivez pas du premier coup)

Effectivement, je n'ai pas réussi du 1er coup, ni du 2ème, ni 3ème.... mais j'ai fini par réussir Mort de Rire



Bad-Code wrote:
(je me demande si quelqu'un réussira à le battre en perfect sans perdre aucun pdv)

J'ai un peu try hard j'avoue, mais j'ai aussi réussi ce petit défi, et d'ailleurs voici la preuve en vidéo :


(le fait que la vidéo ne soit pas très fluide c'est normale, j'utilise hypercam2)




Bad-Code wrote:
Je sais que dans ce forum il y a des vrais pro du javascript, ducoup j'aimerais bien avoir votre avis sur mon code pour savoir s'il y a des points à améliorer.

Pas de souci Very Happy


T'as hyper bien gérer les collisions, t'as aussi créé un système qui permet de gérer les différents états du jeu.
Les animations des sprites sont très bien gérer aussi. Tu n'utilise ni le html, ni le css pour faire tourner le jeu et c'est ce qu'il faut faire. La majorité des jeux en javascript utilisent le canvas à 100%.

D'un point de vue algorithmique, ton code est parfait, mais il est assez mal structuré : ce qui le rend assez compliqué à lire et à modifier. Tu n'utilises pratiquement pas la programmation orientée objet, ce qui aurait permis de mieux de structurer le code.

Ensuite, il y a un léger problème quand tu charges les images, tu n'attends pas qu'elles soient chargés pour les dessiner (ce qui peut provoquer des erreurs pour ceux ayant une faible connexion, en local il ne devrait pas trop avoir de problème à moins que le pc soit très lent)




Pour que tu comprennes pourquoi je fais ceci ou pourquoi je fais comme ça, voici le résultat recherché au niveau de la structure :
Code:
[lang=javascript][scroll]var game = new Game();




// Image
game.addAsset('heart', ASSET_TYPE.IMAGE, 'Image/Sprite/heart.png');
game.addAsset('attack-1', ASSET_TYPE.IMAGE, 'Image/Sprite/atk1.png');
game.addAsset('attack-2', ASSET_TYPE.IMAGE, 'Image/Sprite/atk2.png');
game.addAsset('attack-3', ASSET_TYPE.IMAGE, 'Image/Sprite/atk3.png');
game.addAsset('attack-4', ASSET_TYPE.IMAGE, 'Image/Sprite/atk4.png');
game.addAsset('TotoroM', ASSET_TYPE.IMAGE, 'Image/Sprite/TotoroM.png');





// Sprite
game.addAsset('interface.sprite', ASSET_TYPE.SPRITE, 'Image/Interface/sprite.png',{
  sprites: [{
    name: 'fight',
    x:0, y:0, w:15, h:28
  },{
    name: 'act',
    x:0, y:39, w:15, h:28
  },{
    name: 'item',
    x:0, y:67, w:15, h:28
  },{
    name: 'mercy',
    x:0, y:98, w:15, h:28
  },{
    name: 'attackImage',
    x:0, y:215, w:565, h:130
  },{
    name: 'breakHeart',
    x:19, y:0, w:19, h:16
  }]
});
game.addAsset('sprite.atk', ASSET_TYPE.SPRITE, 'Image/Sprite/atk.png',{
  width: 25,
  height: 120,
  tick: 125
});
game.addAsset('Totoro', ASSET_TYPE.SPRITE, 'Image/Sprite/Totoro.png',{
  width: 345,
  height: 450,
  tick: 800
});
game.addAsset('attack.boule', ASSET_TYPE.SPRITE, 'Image/Sprite/atk1.png',{
  width: 15,
  height: 15
});
game.addAsset('attack.éclaire', ASSET_TYPE.SPRITE, 'Image/Sprite/atk2.png',{
  width: 7.64,
  height: 10
});
game.addAsset('attack.flèche', ASSET_TYPE.SPRITE, 'Image/Sprite/atk3.png',{
  width: 15,
  height: 15
});
game.addAsset('attack.special', ASSET_TYPE.SPRITE, 'Image/Sprite/atk4.png',{
  width: 50,
  height: 42
});





// Audio
game.addAsset('musique', ASSET_TYPE.AUDIO, 'son/musique.mp3');
game.addAsset('laz', ASSET_TYPE.AUDIO, 'son/snd_laz.wav');
game.addAsset('damage', ASSET_TYPE.AUDIO, 'son/snd_damage.wav');
game.addAsset('hurt1', ASSET_TYPE.AUDIO, 'son/snd_hurt1.wav');
game.addAsset('squeak', ASSET_TYPE.AUDIO, 'son/snd_squeak.wav');
game.addAsset('select', ASSET_TYPE.AUDIO, 'son/snd_select.wav');
game.addAsset('break1', ASSET_TYPE.AUDIO, 'son/snd_break1.wav');
game.addAsset('break2', ASSET_TYPE.AUDIO, 'son/snd_break2.wav');
game.addAsset('voice1', ASSET_TYPE.AUDIO, 'son/voice1.mp3');
game.addAsset('silence', ASSET_TYPE.AUDIO, 'son/silence.mp3');
game.addAsset('voice2', ASSET_TYPE.AUDIO, 'son/voice2.mp3');





// Game data
game.registerData('aff',1);
game.registerData('xmax',600);
game.registerData('ymax',450);
game.registerData('e',window.innerHeight/game.retrieveData('ymax'));



// Our first Ennemy
game.addEnnemy('Totoro', new Entity({
  spriteSheets: [
    'Totoro',
    'TotoroM'
  ],
  health: 124,
  oncreate: function() {
    this.data.speechIndex = 0;
    this.data.speechData = [
      [
        'Comment oses-tu me \nréveiller ?\nPauvre insolent.',
        "Prépare-toi à passer \nun sale quart \nd'heure !"
      ],
      [
        'Ne pense pas pouvoir \néchapper à mes \ngriffes !'
      ],
      [
        'Tu vas bientôt avoir \nle tournis à bouger \ndans tous les sens \ncomme tu le fais.'
      ],
      [
        "Essaie d'esquiver ça !"
      ],
      [
        "Tu commences à \nme mettre vraiment \nen colère !"
      ],
      [
        "Tu es bien résistant.."
      ],
      [
        "Voici une de mes \nattaques préférées. \nJe l'ai appelée : \nLa griffe en colère"
      ],
      [
        "Il est temps d'en \nfinir."
      ],
      [
        "Personne ne peut \nsurvivre au roi des \nHamsters !"
      ]
    ];

    this.speechPlayerWin = [
      'Bon..','Je retourne faire \nma sieste.','Adieu.'
    ];

    this.speechMercy = 'Je ne peux pas \nlutter. Tu as gagné.';
    this.showOnceSpeechMercy = false; // used to show speech mercy only once time

    this.statusMessageData = {
      fight: this.getName() + ' veut se battre.',
      mercy: this.getName() + ' t\'épargne.',
      dead: this.getName() + ' a perdu.'
    };

    this.show

  },
  onupdate() {
    if (this.isAlive()) {
      var w = 138;
      var h = 180;
      this.x = (this.game.retrieveData('xmax')-138)/2;
      this.y = 10 + 205/2 - 180/2;
      this.w = 138;
      this.h = 180;
    } else {
      var w = 138;
      var h = 180;
      this.x = (this.game.retrieveData('xmax')-w)/2;
      this.y = 10 + 205/2 - h/2;
      this.w = w;
      this.h = h;
    }
  },
  ondraw: function(){
    if (this.lifeState == LIFE_STATE.DIE) {
      this.opacity -= 0.02;
    }
    if (this.opacity < 0) this.opacity = 0;
    var s = this.game.ctx.globalAlpha;
    this.game.ctx.globalAlpha = this.opacity;
    if (this.isAlive()) {
      this.spriteSheets['Totoro'].draw(SPRITE_DRAW.AUTO_INDEX,this.game.ctx,this.x+this.damageX,this.y,this.w,this.h);
    } else {
      this.game.ctx.drawImage(this.spriteSheets['TotoroM'].image,this.x+this.damageX,this.y,this.w,this.h);
    }
    this.game.ctx.globalAlpha = s;
  },
  onchangestate(state) {
    var _this = this;
    var keyCodeSkip = [90];
    switch (state) {
      case GAME_STATE.DIALOG:

        if (this.isAlive()) {
          this.game.area.changeStatusMessage('* '+this.statusMessageData.fight);
        } else {
          this.game.area.changeStatusMessage('* '+this.statusMessageData.dead);
        }

        // monster has nothing to say, and at this moment of the fight, we change status message and show the last dialog
        if (_this.data.speechIndex == _this.data.speechData.length) {

          this.game.area.changeStatusMessage('* '+this.statusMessageData.mercy);

          if (!this.showOnceSpeechMercy) {

            this.showOnceSpeechMercy = true;

            this.displayDialog(new Text(this.speechMercy,50,keyCodeSkip),function(){

              // register a local event and wait for keydown
              _this.waitKeyDown(keyCodeSkip).then(function(){

                _this.destroyDialog(); // destroy Dialog box

                // change the game state (this function propagate the event everywhere on each onchangestate function)
                _this.game.changeState(GAME_STATE.CHOICE);

              });

            });

          } else {

            // change the game state (this function propagate the event everywhere on each onchangestate function)
            this.game.changeState(GAME_STATE.CHOICE);

          }

          return;
        }



        var next = function(){

          // console.log('DIALOG ENDEDD!');

          if (_this.isAlive()) {

            _this.data.speechIndex++;

            // change the game state (this function propagate the event everywhere on each onchangestate function)
            _this.game.changeState(GAME_STATE.CHOICE);

          } else {

            // change the game state (this function propagate the event everywhere on each onchangestate function)
            _this.game.changeState(GAME_STATE.WIN);

            // change state life
            _this.lifeState = LIFE_STATE.DIE;

          }



        };

        var showDialog;
        showDialog = function(i) {

          var speech = _this.isAlive() ? _this.data.speechData[_this.data.speechIndex] : _this.speechPlayerWin;

          _this.displayDialog(new Text(speech[i],50,keyCodeSkip),function(){

            // register a local event and wait for keydown
            _this.waitKeyDown(keyCodeSkip).then(function(){
              _this.destroyDialog(); // destroy Dialog box
              if (i >= speech.length-1) {
                next();
              } else {
                showDialog(i+1);
              }
            });

          });
        }

        showDialog(0);



        break;
      default:

    }
  }
}));


// Our first Attack
game.addAttack('simple-attack', new Attack({

  // when the attack is create, this function will be call once one time in the start of the game
  oncreate: function() {

    // normal bind, access to self object with this  (access game with : this.game)

    var _this = this;

    var ctx = this.game.ctx;

    // register an asset
    this.data.assetAttack = this.assetManager.getAsset('attack-1');

    // number of item
    this.data.itemcount = 9;

    this.data.wave = {};

    // number of wave
    this.data.wavecount = 5/*26*/;
    this.data.waveindex = 0;
    this.data.waveid = 0;

    // add wave function
    this.data.addWave = function(r) {

      _this.data.wave['wave_'+_this.data.waveid] = {};

      // create data we need
      for (var i = 0; i < _this.data.itemcount; i++) {
        _this.data.wave['wave_'+_this.data.waveid]['item_'+i] = {
          x: 17*i,
          y: 0
        };
      }

      // random index
      _this.data.wave['wave_'+_this.data.waveid].randomindex = r;

      _this.data.waveid++;
    };

    // move wave function
    this.data.moveWave = function(id) {

      var x = _this.game.area.x;
      var y = _this.game.area.y;

      for (var i = 0; i < _this.data.itemcount; i++) {
        if (i == _this.data.wave[id].randomindex || i == _this.data.wave[id].randomindex+1) continue;
        ctx.drawImage(_this.data.assetAttack.image,x+_this.data.wave[id]['item_'+i].x,y+_this.data.wave[id]['item_'+i].y,15,15);
        if (!_this.data.wave[id].addedNext && _this.data.wave[id]['item_'+i].y > _this.area.h - 30) {
          if (_this.data.waveindex+1 < _this.data.wavecount) {
            _this.data.wave[id].addedNext = true;
            var s = _this.data.wave[id].randomindex;
            var r = Utils.getRandomIntInclusive(0,_this.data.itemcount-2);
            while (s == r || Math.abs(r-s) > 3) {
              r = Utils.getRandomIntInclusive(0,_this.data.itemcount-2);
            }
            _this.data.addWave(r);
          }
        }
        if (_this.data.wave[id]['item_'+i].y > _this.area.h - 15) {
          _this.data.wave[id] = null;
          delete _this.data.wave[id];
          _this.data.waveindex++;
          break;
        } else {
          _this.data.wave[id]['item_'+i].y+=2;
        }
      }

    };

    // add first wave
    this.data.addWave(Utils.getRandomIntInclusive(0,this.data.itemcount-2));

  },

  // first step, we need to call next to go to the "ondraw" function
  onstart: function(next) {

    // this function is bind to {parent:this,...}  (access game with : this.parent.game)

    var _this = this;
    this.parent.area.resize(150,130,function(){

      // enable player heart control in the area
      _this.parent.area.enablePlayer();

      next(_this);

    }); // when resize finish, that will call next function

  },

  // second step, we need to call next again to go to the "onend" function
  ondraw: function(next) {

    // this function is bind to {parent:this,...}  (access game with : this.parent.game)


    var _this = this;
    var __this = this.parent;

    // move and draw wave
    for (var id in __this.data.wave) {
      if (__this.data.wave.hasOwnProperty(id)) {
        __this.data.moveWave(id);
      }
    }


    // if player hit something, he take dmaage of attack
    __this.player.checkHitArea(__this);

    // end the attack
    if (__this.data.waveindex == __this.data.wavecount) {
      next(this);
    }

  },

  // the attack ended here, so change the state of game
  onend: function(next) {

    // this function is bind to {parent:this,...}

    // disable player heart control in the area
    this.parent.area.disablePlayer();

    this.parent.game.changeState(GAME_STATE.DIALOG);

    // reset attack
    next(this);

  }

}));



// add attack to our ennemy
game.getEnnemy('Totoro').addAttack('simple-attack');




// When area is draw
game.area.ondraw = function() {

  var _this = this;
  var state = this.game.state;
  var ctx = this.game.ctx;

  switch (state) {

    case GAME_STATE.ATTACK_PLAYER:
      this.game.UI.spriteSheet.drawSprite(ctx, 'attackImage', this.x, this.y, this.w, this.h);

      var keyCodeAttack = [90];
      var n = 'rgb(0,0,0)';
      var nn = 'rgb(255,255,255)';


      switch (this.fightBar.state) {
        case FIGHT_STATE.FIGHTBAR_MOVE:

          if (this.game.isKeyDown(keyCodeAttack)) {

            this.fightBar.hit = true;
            this.fightBar.state = FIGHT_STATE.ANIMATION_ATTACK;


          } else if (!this.fightBar.hit) {

            this.fightBar.x += this.fightBar.speed;

            if (this.fightBar.x > this.w) {

              this.fightBar.miss = true;
              this.fightBar.state = FIGHT_STATE.ANIMATION_ATTACK;

              // change game state
              if (this.game.getCurrentEnnemy().isAlive()) {
                this.game.changeState(GAME_STATE.ATTACK_ENTITY);
              } else {
                this.game.changeState(GAME_STATE.DIALOG); // listen the last word of monster
              }

            }

          }

          ctx.fillStyle = n;
          ctx.fillRect(40+this.fightBar.x,this.y,14,this.h);
          ctx.fillStyle = nn;
          ctx.fillRect(44+this.fightBar.x,this.y+4,6,this.h-8);

          break;
        case FIGHT_STATE.ANIMATION_ATTACK:

          if (this.fightBar.hit) {

            // blink fight bar
            if (Date.now() - this.fightBar.time > this.fightBar.tick) {
              this.fightBar.time = Date.now();
              n = n == 'rgb(0,0,0)' ? 'rgb(255,255,255)' : 'rgb(0,0,0)';
              nn = nn == 'rgb(0,0,0)' ? 'rgb(255,255,255)' : 'rgb(0,0,0)';
            }

            // play attack animation and wait the end of animation
            var attackAssetName = this.game.player.getRandomAttack();
            this.game.player.drawAttack(attackAssetName,SPRITE_DRAW.AUTO_INDEX,function(){

              //console.log('ANIMATION ENDED!');
              _this.fightBar.state = FIGHT_STATE.SHOW_DAMAGE;

            },287.5,50,25,120);
          }

          ctx.fillStyle = n;
          ctx.fillRect(40+this.fightBar.x,this.y,14,this.h);
          ctx.fillStyle = nn;
          ctx.fillRect(44+this.fightBar.x,this.y+4,6,this.h-8);

          break;
        case FIGHT_STATE.SHOW_DAMAGE:

          var calculDamage = Math.ceil(this.game.player.attack*(1-Math.pow(Math.abs(0.5-(this.fightBar.x/this.w)),0.8)*Math.pow(2,0.8)));

          this.game.getCurrentEnnemy().takeDamage(calculDamage,function(){

            //console.log('TakeDamage Ended!');
            _this.fightBar.state = null;

            if (_this.game.getCurrentEnnemy().isAlive()) {
              _this.game.changeState(GAME_STATE.ATTACK_ENTITY);
            } else {
              _this.game.changeState(GAME_STATE.DIALOG); // listen the last word of monster
            }


          });

          break;
        default:

      }

      break;

    case GAME_STATE.ATTACK_ENTITY:

      // try to get an attack of the current ennemy (that will propagate the event to the "onselectattack" function)
      // by default, a random attack is selected
      var attack = this.game.getCurrentEnnemy().selectAttack();

      // run the attack, this attack will change the game state by it self, so just run
      attack.run();


      break;

    default:

  }

};


// When Game State is changed
game.onchangestate = function(state) {
  var _this = this;
  var keyCodeToSelect = [37,39];
  var keyCodeToValidate = [90];

  this.area.hideStatusMessage(); // disable by default

  switch (state) {
    case GAME_STATE.WIN:

      // enable status message
      this.area.showStatusMessage();

      this.area.changeStatusMessage('* Appuyez sur une touche pour revenir au menu \n  principal');

      // wait any key (empty array for any key)
      this.waitKeyDown([]).then(function(){
        _this.changeState(GAME_STATE.SETUP);
      });

      break;
    case GAME_STATE.SETUP:

      // reset the whole game
      this.reset();

      // wait any key (empty array for any key)
      this.waitKeyDown([]).then(function(){
        _this.changeState(GAME_STATE.DIALOG);
      });

      break;
    case GAME_STATE.GAME_OVER:

      this.UI.gameOverPrepare(); // prepare to display game over screen

      break;
    case GAME_STATE.DIALOG:

      // resize area
      this.area.resize(520, 130, function(){

        // enable status message
        _this.area.showStatusMessage();

      });



      break;
    case GAME_STATE.CHOICE:

      // resize area
      this.area.resize(520, 130, function(){

        // enable status message
        _this.area.showStatusMessage();

      });

      // Menu Selection : Fight, Action, Item, Mercy
      this.UI.enable(keyCodeToSelect,keyCodeToValidate,function(buttonIndexSelected){

        // console.log('User Selected the button: ',buttonIndexSelected);

        if (buttonIndexSelected == UI_BUTTON.FIGHT) {
          _this.UI.disable(); // unselect button
          _this.area.resetFightBar(); // reset Bar Fight and variable
          _this.getCurrentEnnemy().resetDamageVariable(); // reset variable ennemy
          _this.changeState(GAME_STATE.ATTACK_PLAYER);
          return true; // cancel local event
        }

        // we ignore other button for now (it will be implemented soon)
        return false; // we return false to ignore and not cancel local event

      }); // enable arrow move
    default:
  }
};



// Game Loop
game.onUpdate = function() {

  var _this = this;

  var canvas = this.canvas;
  var ctx = this.ctx;

  var e = this.retrieveData('e');
  var xmax = this.retrieveData('xmax');


  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;

  ctx.fillStyle = 'rgb(0,0,0)';
  ctx.fillRect(0, 0, window.innerWidth,window.innerHeight);


  this.pointTranslate = {
    x: window.innerWidth/2-((xmax/2)*e),
    y: 0
  };
  this.pointScale = {
    x: e,
    y: e
  };

  this.translate();
  this.scale();


  switch (this.state) {

    case GAME_STATE.SETUP:

      ctx.strokeStyle = 'rgb(220,220,220)';
      ctx.strokeRect(0,30,600,380);

      ctx.translate(0,10);
      ctx.font = '12px KulminoituvaRegular';
      ctx.fillStyle = 'rgb(220,220,220)';
      ctx.textAlign = 'left';

      ctx.fillTextM('* Vous devez appuyer sur [z] pour passer les dialogues.', 9, 60);
      ctx.fillTextM('* Vous devez appuyer sur [m] pour couper la musique.', 9, 90);
      ctx.fillTextM("* Lorsque vous attaquez, vous devez viser le centre de l'image \n  pour infliger un maximum de dégats à votre adversaire.", 9, 120);

      this.player.drawHeart(300, 170);

      ctx.fillTextM("* Il s'agit d'un fangame, reprenant la mécanique de combat du jeu\n  Undertale.", 9, 215);
      ctx.fillTextM("* La musique et les sons utilisés ne m'appartienne pas.", 9, 265);
      ctx.fillTextM("* Ce fangame a été réalisé par Sam4AF.", 9, 295);

      this.player.drawHeart(300, 315);

      ctx.fillTextM("* Appuyez sur n'importe quelles touches pour commencer.", 9, 360);

      ctx.translate(0,-10);

      break;
    case GAME_STATE.GAME_OVER:

      switch (this.UI.gameOverState) {
        case 0x1:
          this.player.drawHeart(this.player.x+this.area.x,this.player.y+this.area.y);
          if (Date.now() - this.UI.gameOverKeepHeartTime > this.UI.gameOverKeepHeartDuration) {
            this.UI.gameOverState = 0x2;
            this.UI.gameOverKeepBreakHeartTime = Date.now();
          }
          break;
        case 0x2:
          this.UI.spriteSheet.drawSprite(ctx, 'breakHeart', this.area.x+this.player.x-this.player.w/2, this.area.y+this.player.y-this.player.h/2, 15, 13);
          if (Date.now() - this.UI.gameOverKeepBreakHeartTime > this.UI.gameOverKeepBreakHeartDuration) {
            this.UI.gameOverState = 0x3;
          }
          break;
        case 0x3:


          this.transition('game-over-transition',{
            from: 0,
            to: 1,
            step: 0.02
          },function(a){

            // draw
            var c = 'rgba(255,255,255,' + a + ')';
            ctx.fillStyle = c;
            ctx.font = '90px KulminoituvaRegular';
            ctx.textAlign = 'center';
            ctx.fillText('Game',300,100);
            ctx.fillText('Over',300,190);

          },function(){

            // when transition is finish
            _this.drawTextTransition('game-over-text-transition',{
              text: "N'abandonne pas !\nGarde ta détermination !!",
              tick: 80
            },function(text){

              // draw
              ctx.font = '14px KulminoituvaRegular';
              ctx.fillStyle = 'rgb(220,220,220)';
              ctx.textAlign = 'center';
              ctx.fillTextM(text, 300, 290);

            },function(){

              // when  text transitionis finish
              ctx.font = '11px KulminoituvaRegular';
              ctx.fillStyle = 'rgb(255,255,255)';
              ctx.textAlign = 'center';
              ctx.fillText('Appuie sur [z] pour recommencer', 500, 400);

              if (_this.isKeyDown(90)) {
                _this.changeState(GAME_STATE.SETUP);
              }

            })

          });


          break;
        default:
      }

      break;
    default:

      // update area
      this.area.update();

      // update ennemy
      this.getCurrentEnnemy().update();


      // draw UI:
      this.UI.draw();

      // draw player info
      this.player.drawState();


      // draw grid green
      ctx.strokeStyle = '#3A7748';
      ctx.lineWidth = 1;
      ctx.strokeRect(10, 10, 580, 215);

      ctx.strokeStyle = '#3A7748';
      ctx.lineWidth = 1;
      ctx.strokeRect(10, 10, 580, 215);
      for (var i=1; i<6; i++) {
        ctx.strokeRect(10+96*i, 10, 0.1, 215);
      }
      ctx.strokeRect(10, 112.5, 580, 0.1);


      // draw current Ennemy
      this.getCurrentEnnemy().draw();

      // draw area
      this.area.draw();

  }


  ctx.setTransform(1, 0, 0, 1, 0, 0);
};




// Load Assets Game
game.load(function(){

  console.log('GAME LOADED!');
  console.log(game.assetManager.countLoadedSuccess+'/'+game.assetManager.totalToLoaded+' Assets Loaded with Success!');
  console.log(game);

  // start event
  game.startEvents();

  // set the current Ennemy
  game.setCurrentEnnemy('Totoro');

  // Finally, run and init the game
  game.run();

  // Add Some Attack to Player
  game.player.addAttack('sprite.atk');

  // And set a state
  game.changeState(GAME_STATE.SETUP);

});



N'hésite pas à aller faire des aller-retour pour voir et comprendre comment j'utilise les classes
Parce qu'elles sont bien belles, compréhensibles (car bien commenté) mais pour les utiliser et les "mixer" ensemble pour créer le jeu n'est pas aussi facile



Il n'y a que deux fichiers, index.html et script.js (ce sont les seuls fichiers que j'ai modifié, j'ai gardé intacte tous les autres fichiers)
Donc si tu veux tester les codes, remplaces ces fichiers par mes fichiers et ça devrait marcher sans soucis Okay
(et c'est normal s'il n'y aucun son, je n'ai pas implémenté l'audio et je te laisserai le faire, pour que tu comprennes un peu mieux la structure du code)

CODE COMPET :






Juste avant, on va rajouter quelques prototypes à la classe CanvasRenderingContext2D et et on va aussi créer un objet Utils pour venir y placer des fonctions pratiques.

Code:
[lang=javascript]CanvasRenderingContext2D.prototype.fillTextM = function(text, x, y) {
  var lineHeight = this.measureText("M").width * 1.2;
  var lines = text.split('\n');
  for (var i = 0; i < lines.length; ++i) {
    this.fillText(lines[i], x, y); y += lineHeight*1.8;
  }
}
CanvasRenderingContext2D.prototype.roundRect = function(x, y, ww, hh, r) {
  if (ww < 2 * r) r = ww / 2;
  if (hh < 2 * r) r = hh / 2;
  this.beginPath();
  this.moveTo(x+r, y);
  this.arcTo(x+ww, y, x+ww, y+hh, r);
  this.arcTo(x+ww, y+hh, x, y+hh, r);
  this.arcTo(x, y+hh, x, y, r);
  this.arcTo(x, y, x+ww, y, r);
  this.closePath();
  this.fill();
}


Je n'ai pas forcément de commentaires à mettre car 99% du code vient de toi Mr. Green Okay
En faisant ça, on pourra utiliser ces méthodes directement depuis le contexte comme ceci :

Code:
[lang=javascript]var canvas = /* le canvas */;
var ctx = canvas.getContext('2d');

ctx.roundRect(10,20,50,50,5);
ctx.fillTextM('salut\nà\ntous',100,50);


Et voici les fonctions pratiques qu'on va mettre dans l'objet Utils :

Code:
[lang=javascript]var Utils = {
  getRandomIntInclusive: function(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min +1)) + min;
  }
};





Au lieu de mettre des nombres, ou des chaînes de caractères pour par exemple définir un état ou autre, il est pratique de faire ce qu'on appelle une "énumération"
On ne peut pas créer d'énumération au sens stricte du terme (en javascript) avec un mot clé "enum" à la place de "var", mais on peut faire comme ceci :

Code:
[lang=javascript]const AVIS = {
  TRES_SATISFAISANT : 0x1,
  SATISFAISANT      : 0x2,
  PEU_SATISFAISANT  : 0x3
}

(c'est qu'un exemple pour te montrer un peu ce que j'entends par "énumération")

Voici un autre exemple :

Code:
[lang=javascript]const MOVIE_TYPE = {
  COMIC    : 0x1,
  HORROR   : 0x2,
  THRILLER : 0x3,
  ROMANCE  : 0x4,
  ACTION   : 0x5,
  FANTASY  : 0x6,
  SCI_FI   : 0x7
}



Et donc, voici les énumérations que j'utilise :

Code:
[lang=javascript]// 3 types of Assets
const ASSET_TYPE = {
  IMAGE  : 0x000001,
  SPRITE : 0x000002,
  AUDIO  : 0x000003
};

// 7 types of game state
const GAME_STATE = {
  SETUP         : 0x000000,
  WIN           : 0x000001,
  GAME_OVER     : 0x000002,
  CHOICE        : 0x000003,
  DIALOG        : 0x000004,
  ATTACK_PLAYER : 0x000005,
  ATTACK_ENTITY : 0x000006,
};

// 2 types of draw mode
const SPRITE_DRAW = {
  AUTO_INDEX : 0x1, // draw sprite automatically with index table (spriteSheet where sprite are all same dimension, need width and height opt)
  AUTO_NAME  : 0x2  // draw sprite automatically with name table (spriteSheet where sprite are NOT all same dimension, need sprites opt)
};


// 4 types of UI Buttons
const UI_BUTTON = {
  FIGHT  : 0x1,
  ACTION : 0x2,
  ITEM   : 0x3,
  MERCY  : 0x4
};


// 3 State when Fight
const FIGHT_STATE = {
  FIGHTBAR_MOVE    : 0x1,
  ANIMATION_ATTACK : 0x2,
  SHOW_DAMAGE      : 0x3
};

// 3 STATE OF LIFE
const LIFE_STATE = {
  ALIVE : 0x1,
  DYING : 0x2,
  DIE   : 0x3
};


Mais ne t'inquiètes pas, j'en parlerai tout le long, histoire que tu vois un peu comment ça peut être pratique et rendre le code plus simple à lire Mr. Green Okay




Donc, je vais te montrer comment créer tout un système (bien structuré) permettant de charger des assets, comme on parle d'assets, on va devoir créer une classe Asset :

Code:
[lang=javascript]class Asset {

  constructor(type,path) {

    // le type de l'asset : image, son, ...etc...
    this.type = type;

    // le chemin vers le fichier (exemple: son/exemple.wav ou image/sprite.png ...etc..)
    this.path = path;

    // permet de savoir si notre asset est chargé ou pas (de base l'asset n'est pas chargé)
    this.loaded = false;

  }


  /**
  * isLoad: renvoie true si l'asset est chargé, sinon renvoie false
  */
  isLoad() {
    return this.loaded;
  }

  /**
  * load: on charge l'asset
  */
  load() {

    // Cette classe est une classe de base, on laisse les classes enfants définir cette méthode (elles vont overwrite cette méthode)
    return new Promise(function(resolve, reject) {resolve();});

  }

  /**
  * destroy: on détruit l'asset (ce qui peut soulager la mémoire)
  */
  destroy() {} // même remarque que pour la méthode load

}

(tu comprendras un peu plus tard pourquoi on implémente pas les méthodes load et destroy)
(les Promise sont comme des functions callback mais en mieux, j'expliquerai comment les utiliser en détail un peu plus bas)


Si tu as bien regardé, je parle de "type d'asset", comme on structure bien le code, on ne va pas mettre "image" ou "son" en chaîne de caractères, mais on va créer une énumération comme ceci :

Code:
[lang=javascript]// 3 types of Assets
const ASSET_TYPE = {
  IMAGE  : 0x000001,
  SPRITE : 0x000002,
  AUDIO  : 0x000003
};


tu pourras noter que je différencie les sprites des images (car en vérité, un sprite (ou spritesheet, je confonds un peu là), contient plein de "sous-images"), donc je n'implémenterai pas les même méthodes pour les images et pour les sprites

On va maintenant créer notre 1ère classe enfant de le classe Asset :

Code:
[lang=javascript]class AudioAsset extends Asset {

  constructor(...args) {

    // nouvelle façon (avec le ES6) d'envoyer tout les arguments vers le constructeur de la classe parent (ici la classe Asset)
    // on est obligé d'appeler le constructeur avec super (sinon ça renvoie une erreur)
    super(...args);

    // on créé notre élément audio (on ne l'ajoute pas au dom, ce n'est pas nécessaire)
    this.audio = document.createElement('audio');

  }


  /**
  * load: on charge le son
  */
  load() {

    // on stocke le scope actuelle (le this correspond à l'objet qui a été créé avec new)
    var _this = this;

    // j'expliquerai plus tard comme l'utiliser, mais en gros, ça ce comporte comme un callback
    return new Promise(function(resolve, reject) {

      // on load le son
      _this.audio.src = _this.path;
      _this.loaded = true;
      _this.audio.load();

      // on appelle le "callback"
      resolve();

    });

  }

  /**
  * destroy: on détruit l'asset (ce qui peut soulager la mémoire)
  */
  destroy() {

    // on stop correctement le son
    this.audio.pause();
    this.audio.currentTime = 0;

    // on détruit l'objet
    this.audio = null;

    // du coup, l'asset n'est plus charger
    this.loaded = false;
   
  }

}

(je pense que j'ai plutôt bien commenter, j'expliquerai les promise bientôt promis Mr. Green )

Tu peux juste noter une chose, pour l'instant, on a rien fait de compliquer, on a créé des fonctions toutes simples, et on fera que ça d'ailleurs, on créera des fonctions hyper basiques,
mais une fois le tout mis ensemble donnera quelque chose de plus complexe mais de de plus simple à lire et à modifier car toutes les fonctions prisent individuellement sont simples


Sans plus tarder, voici la classe enfant ImageAsset :
Code:
[lang=javascript]class ImageAsset extends Asset {

  constructor(...args) {
    super(...args);

    // on créé simplement un objet Image
    this.image = new Image();

  }

  /**
  * load: on charge l'image
  */
  load() {
    var _this = this;
    return new Promise(function(resolve, reject) {

      // on utilise l'event onload pour savoir quand l'image a fini de se charger
      _this.image.onload = function() {

        _this.loaded = true; // l'image a finit de se charger

        // on appele le "callback"
        resolve();

      };

      // s'il y a une erreur lors du chargement (l'image n'existe plus, ...etc...)
      _this.image.onerror = function() {

        _this.loaded = false; // on rédfinit loaded à false pour être sûr qu'elle soit bien à false
        // (imagine que c'est la 2ème fois qu'on la charge et que la 1ère fois, il n'y avais pas d'erreur)

        // on appelle la fonction qui gère les erreurs
        reject();

      };

      // on commence a charger l'image
      _this.image.src = _this.path;


    });
  }

  /**
  * destroy: on détruit l'asset (ce qui peut soulager la mémoire)
  */
  destroy() {

    // on détruit simplement l'image
    this.image = null;

    // du coup, l'asset n'est plus charger
    this.loaded = false;

  }

}


Et maintenant, la dernière classe enfant SpriteAsset :
Code:
[lang=javascript]class SpriteAsset extends ImageAsset {

  constructor(...args) {
    super(...args);

    // on essai de récupérer les options qui sont dans le dernier argument (on vérifie bien que le dernier argument est bien un objet)
    var opt = args.length > 0 ? typeof args[args.length-1] === 'object' ? args[args.length-1] : {} : {};

    // ces paramètres sont uniquement pour les spritesheets avec tous les sprites à la même taille
    this.width = typeof opt.width === 'number' ? opt.width : 1; // on définit la largeur d'une "sous-image"
    this.height = typeof opt.height === 'number' ? opt.height : 1; // on définit la hauteur d'une "sous-image"

    // on définit le temps minimal avant de passer à la prochaine image dans le cas d'une animation
    this.tick = typeof opt.tick === 'number' ? opt.tick :  1000/30; // par défaut 30FPS

    // ces paramètres sont uniquement pour les spritesheets avec des sprites de tailles différentes
    // ils sont stockés dans un tableau comme ceci :
    // [{
    //   name: "sous-image-1"
    //   x: 0, y: 0, w: 50, h: 50
    // },{....
    // x, y, w, h : c'est pour localiser la "sous-image" dans l'image
    this.sprites = typeof opt.sprites !== 'undefined' ? opt.sprites : [];
    this.spritesobj = {};
    for (var i = 0; i < this.sprites.length; i++) {
      this.spritesobj[this.sprites[i].name] = this.sprites[i]; // on stocke ça dans un objet pour faciliter l'accès
    }

    // contient des index (dans le cas où les "sous-images" sont de même taille),
    // ou les noms des "sous-images" (dans le cas où les "sous-images" sont de tailles différentes)
    this.animation = typeof opt.animation === 'undefined' ? [] : opt.animation;

    this.animationIndex = 0; // index qui nous servira de repère dans le tableau animation

    // variable "temporaire" qui rend possible l'animation
    this.time = Date.now();

  }

  /**
  * getPosition: on récupère les positions x et y de la "sous-image" seulement avec son index
  * (uniquement à utiliser dans le cas où tous les sprites sont de même tailles, ce qui est le cas la pluspart du temps)
  * @param index {int} : l'indice de la "sous-image"
  * @return {object} : on retourne la position en x et y de la "sous-image"
  */
  getPosition(index) {
    return {
      x:(index%(Math.ceil(this.image.width/this.width)))*this.width,
      y:Math.floor(index/(Math.ceil(this.image.width/this.width)))*this.height
    }
  }

  /**
  * getMaxIndex: on récupère l'indice maximale à partir de la taille des "sous-images" et de l'image
  * (uniquement à utiliser dans le cas où tous les sprites sont de même tailles, ce qui est le cas la pluspart du temps)
  * @return {int} : on retourne l'indice maximale
  */
  getMaxIndex() {
    return Math.ceil(this.image.width/this.width) * Math.ceil(this.image.height/this.height) - 1;
  }

  /**
  * drawSpriteByIndex: on dessine la n-ème "sous-image" seulement avec l'indice
  * (uniquement à utiliser dans le cas où tous les sprites sont de même tailles, ce qui est le cas la pluspart du temps)
  * (ce qui automatise l'opération, on est quitte de chercher et de mettre nous-même les coordonnées)
  * @param ctx {CanvasRenderingContext2D} : le context 2D du canvas
  * @param index {int} : l'indice de la "sous-image"
  * @params ...args {array} : x, y, width, height : endroit où on dessine l'image
  */
  drawSpriteByIndex(ctx,index,...args) {
    var point = this.getPosition(index); // on récupère les coordonées à partir de l'indice
    ctx.drawImage(this.image, point.x, point.y, this.width, this.height, ...args); // on dessine la "sous-image"
  }

  /**
  * getSprite: on récupère simplement la "sous-image" à parti de son nom
  * (uniquement à utiliser dans le cas où tous les sprites sont de tailles différentes)
  * @param name {string} : nom de la "sous-image"
  * @return {object} : on retourne l'objet associé
  */
  getSprite(name) {
    return this.spritesobj[name];
  }

  /**
  * drawSprite: on dessine la "sous-image" définit par son nom
  * (uniquement à utiliser dans le cas où tous les sprites sont de tailles différentes)
  * (ce qui automatise l'opération, on est quitte de chercher et de mettre nous-même les coordonnées)
  * @param ctx {CanvasRenderingContext2D} : le context 2D du canvas
  * @param name {string} : le nom de la "sous-image"
  * @params ...args {array} : x, y, width, height : endroit où on dessine l'image
  */
  drawSprite(ctx,name,...args) {
    ctx.drawImage(this.image, this.spritesobj[name].x, this.spritesobj[name].y, this.spritesobj[name].w, this.spritesobj[name].h, ...args);
  }

  /**
  * draw: on dessine et on anime la "sous-image" en précisant le mode avec les indices ou avec les noms
  * cette fonction permet d'animer automatiquement un spritesheet
  * @param ctx {CanvasRenderingContext2D} : le context 2D du canvas
  * @param mode {SPRITE_DRAW} : le mode utilisé (avec les indices ou avec les noms)
  * @params ...args {array} : x, y, width, height : endroit où on dessine l'image
  */
  draw(mode,ctx,...args) {

    // this.animation est censé contenir les indices ou les noms pour pouvoir animer
    // (on dessine la "sous-image" 'image-1' 'image-2' ...etc...., or si c'est vide, j'ai décidé arbitrairement qu'on ne dessine rien)
    if (this.animation.length == 0 && mode == SPRITE_DRAW.AUTO_NAME) return;

    // si on est là, c'est que le tableau this.animation contient les noms des "sous-images" à dessiner à la suite
    // sinon elle est vide et on est dans le mode SPRITE_DRAW.AUTO_INDEX
    // donc si le tableau est vide et qu'on est dans le mode SPRITE_DRAW.AUTO_INDEX, je dessine les "sous-images"
    // dans l'ordre croissant 0,1,2,3....


    // si le tableau est vide, j'utilise les indices dans l'odre croissant
    // sinon je suis les indices (ou les noms) du tableau et this.animationIndex devient un indice pour itérer dans le tableau
    var index = this.animation.length == 0 ? this.animationIndex : this.animation[this.animationIndex];

    // suivant le mode, on utilise la fonction adapté
    switch (mode) {
      case SPRITE_DRAW.AUTO_INDEX:
        this.drawSpriteByIndex(ctx,index,...args);
        break;
      case SPRITE_DRAW.AUTO_NAME:
        this.drawSprite(ctx,index,...args);
        break;
      default:

    }

    // on anime
    if (Date.now() - this.time > this.tick) {

      this.time = Date.now(); // tu connais déjà ce principe :)

      // on incrémente l'indice pour dessiner la prochaine "sous-image"
      this.animationIndex++;

      // si le tableau est vide, on est dans le mode SPRITE_DRAW.AUTO_INDEX, donc l'animation est fini quand on arrive au dernier indice (this.getMaxIndex())
      if (this.animation.length == 0 && (this.getMaxIndex() == this.animationIndex - 1)) {

        this.animationIndex = 0; // on recommence l'animation

      // on regarde si notre indice correspond à la longueur du tableau, au quel cas, on recommence l'animation
      } else if (this.animationIndex == this.animation.length) {

        this.animationIndex = 0; // on recommence l'animation

      }
    }

  }

  /**
  * isAnimationEnded: permet de savoir si l'animation est à la dernière "sous-image"
  * @return {boolean} : on renvoie true si l'animation est terminé, sinon en renvoie false
  */
  isAnimationEnded() {
    return (this.animation.length == 0 && (this.getMaxIndex() - 1 == this.animationIndex)) || (this.animationIndex == this.animation.length - 1);
  }

}

(je pense avoir bien commenté aussi, dis-moi s'il y a un truc que tu comprends pas)


Si tu as bien regardé, je propose 2 façons différents d'animer un srpitesheet, et j'utilise donc cette énumération pour les différents mode de dessin :
Code:
[lang=javascript]// 2 types of draw mode
const SPRITE_DRAW = {
  AUTO_INDEX : 0x1, // draw sprite automatically with index table (spriteSheet where sprite are all same dimension, need width and height opt)
  AUTO_NAME  : 0x2  // draw sprite automatically with name table (spriteSheet where sprite are NOT all same dimension, need sprites opt)
};



Et voilà, on a créé des classes qui nous permettrons de mieux gérer nos assets, il nous faut maintenant créer une classe AssetManager pour automatiser le chargement de nos assets.
Avant de rentrer dans le code pure et dure, voici un peu "la structure de la classe" :
Code:
[lang=javascript]class AssetManager {

  constructor(game) {}

  loadAll(next) {}

  addAsset(name,type,...args) {}

  removeAsset(name) {}

  getAsset(name) {}

}


Tu peux voir que dans le constructeur, il y a le paramètre game, c'est tout simplement un instancier de la classe Game (que l'on créera juste après)
Je vais décortiquer cette classe méthode par méthode,

  • constructeur: Pas grand chose de spécial :
    Code:
    [lang=javascript]constructor(game) {

      this.game = game; // l'objet qui contiendra tout
      this.assets = {}; // toutes les assets seront stockés ici

      this.countLoadedSuccess = 0; // le nombre d'assets chargés avec succès
      this.countLoadedError = 0; // le nombre d'assets non-chargés
      this.countLoadedTotal = 0; // le nombre total d'assets chargés
      this.totalToLoaded = 0; // le nombre total d'assets à charger

    }


  • loadAll: on charge ici toutes les assets et on appelle le callback next une fois le chargement de toutes les assets terminés
    Code:
    [lang=javascript]/**
    * loadAll: permet de charger toutes les assets
    * @param next {function} : le callback qui sera appelé une fois le chargement de toutes les assets terminés
    */
    loadAll(next) {
      var _this = this;

        // on réinitialise les variables
        this.countLoadedSuccess = 0; // le nombre d'assets chargés avec succès
        this.countLoadedError = 0; // le nombre d'assets non-chargés
        this.countLoadedTotal = 0; // le nombre total d'assets chargés

      // on itère sur toutes les assets
      for (var name in this.assets) {
        if (this.assets.hasOwnProperty(name)) {

          // on charge l'asset
          this.assets[name].load()

          // l'interêt de la promise est ici, si le chargement est un succès,
          // alors on appelle la fonction resolve (de la promise) qui nous envoie dans le "then"
          .then(function(){
            // load success
            _this.countLoadedSuccess++;
          })

          // sinon, c'est la fonction reject (de la promise) qui est appelé, et on arrive dans le catch
          .catch(function(e){
            // load fail
            _this.countLoadedError++;
            console.warn(e);
          })

          // dans tout les cas, que ce soit un succès ou pas, on exécute le finally
          .finally(function(){
            // finally
            _this.countLoadedTotal++;
            if (_this.countLoadedTotal==_this.totalToLoaded) {
              next(); // si tout est chargé on appelle le callback
            }
          });


        }

      }
    }


    Comme tu peux le voir, une Promise, c'est la fusion entre le "try..catch..finally" et le "callback", en gros, c'est un try catch asynchrone Mr. Green
    Plus de détails ici : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globa…


  • addAsset: on ajoute un asset et selon le type de l'asset, on utilise la classe adéquat
    on utilise la classe parent Asset par défaut
    Code:
    [lang=javascript]/**
    * addAsset: permet d'ajouter un asset
    * @param name {string} : le nom de l'asset
    * @param type {ASSET_TYPE} : le type de l'asset
    * @params ...args {array} : le reste des arguments
    */
    addAsset(name,type,...args) {

      // on créé automatiquement l'asset adéquat selon le type
      switch (type) {
        case ASSET_TYPE.IMAGE:
            this.assets[name] = new ImageAsset(type,...args);
          break;
        case ASSET_TYPE.AUDIO:
            this.assets[name] = new AudioAsset(type,...args);
          break;
        case ASSET_TYPE.SPRITE:
            this.assets[name] = new SpriteAsset(type,...args);
          break;
        default:
            this.assets[name] = new Asset(type,...args);
      }

      // on incrémente le nombre d'assets
      this.totalToLoaded++;

    }




  • removeAsset: on supprime l'asset de la mémoire (on libère la ressource)
    Code:
    [lang=javascript]/**
    * removeAsset: permet de supprimer un asset
    * @param name {string} : le nom de l'asset
    */
    removeAsset(name) {

      // on désincrémente le nombre d'assets
      this.totalToLoaded--;

      // si l'asset a bien été chargé, on désincrémente this.countLoadedSuccess sinon this.countLoadedError
      if (this.assets[name].isLoad()) {
        this.countLoadedSuccess--;
      } else {
        this.countLoadedError--;
      }

      // on détruit l'asset proprement
      this.assets[name].destroy();

      // on l'enlève de l'objet
      this.assets[name] = null;
      delete this.assets[name];
    }


    tu pourras noter que je ne vérifie pas si l'asset existe avant de la supprimer, si l'asset n'existe pas, le script renverra une erreur de lui-même, ça permet d'éviter des bugs "invisibles"


  • getAsset: on récupère simplement l'asset
    Code:
    [lang=javascript]/**
    * getAsset: permet de récupérer un asset
    * @param name {string} : le nom de l'asset
    */
    getAsset(name) {
      return this.assets[name];
    }




Au final, voici ce qu'on obtient (CODE COMPLET) : https://pastebin.com/JK3x1JnG
Je te montrerais comment l'utiliser après, là on va commencer à créer la classe Game


Voici sa structure finale (on implémentera les méthodes à la fin, donc je te conseil d'aller jeter un coup d'oeil de temps en temps, si tu vois qu'on utilise une méthode de la classe Game) :
Code:
[lang=javascript]class Game {

  constructor() {}

  reset() {}

  isKeyDown(code) {}

  startEvents() {}

  addAttack(name,attack) {}

  removeAttack(name) {}

  getAttack(name) {}

  addEnnemy(name,entity) {}

  removeEnnemy(name) {}

  getEnnemy(name) {}

  getCurrentEnnemy() {}

  setCurrentEnnemy(name) {}

  transition(name,object,draw,next) {}

  drawTextTransition(name,object,draw,next) {}

  translate() {}

  scale() {}

  changeState(state) {}

  run() {}

  waitKeyDown(keyCodeSkip) {}

  registerData(name, value) {}

  retrieveData(name) {}

  addAsset(...args) {}

  removeAsset(...args) {}

  load(next) {}

  update() {}

}


Et voici le constructeur :
Code:
[lang=javascript]constructor() {

  // on créé le canvas
  this.canvas = document.createElement('canvas');

  // on met le background en noir
  this.canvas.style.background = '#000000';

  // on récupère le contexte 2D
  this.ctx = this.canvas.getContext('2d');

  // contiendra toutes sortes de données
  this.data = {};

  // contiendra l'état des touches
  this.keyState = {};

  // contiendra tous les ennemies
  this.ennemies = {};

  // contiendra toutes les attaques
  this.attacks = {};

  // variable temporaire pour mieux gérer les transitons (j'expliquerai ça un peu plus tard)
  this.transitions = {};
  this.transitionstext = {};

  // on stock simplement le translate et le scale
  this.pointTranslate = {
    x: 0,
    y: 0
  };
  this.pointScale = {
    x: 1,
    y: 1
  };

  // on stocke ici l'ennemy courant
  this.currentEnnemy = null;

  // l'état global du jeu
  this.state = null;

  // l'asset manager
  this.assetManager = new AssetManager(this);

  // la zone où seront dessinés les attaques et le coeur du joueur
  this.area = new Area(this);

  // le joueur
  this.player = new Player(this);

  // l'UI qui gérera les boutons fight, act, item et mercy
  this.UI = new UI(this);

  // on ajoute enfin le canvas dans le DOM
  document.body.appendChild(this.canvas);

}





On va créer une classe qui nous permettra de créer des attaques de manières efficaces et rapides Cool Mr. Green
Donc, avant toutes choses voici ça structure :

Code:
[lang=javascript]class Attack {

  constructor(data) {}

  reset() {}

  init(game) {}

  run() {}

  draw() {}

  update() {}

}


Juste avant de commencer quoique ce soit, je vais t'expliquer ce qu'on va faire, voici un peu la structure de l'objet data passé dans le constructeur :

Code:
[lang=javascript]{
  oncreate: function() {},
  onstart: function(next) {},
  ondraw: function(next) {},
  onend: function(next) {}
}


  • oncreate: cette fonction est appelée juste avant que l’ennemi lance l'attaque (ça initialise l'attaque)
  • onstart: cette fonction est appelée au moment de l'attaque
  • ondraw: cette fonction est appelée pendant l'attaque (donc on va y dessiner l'attaque, c'est pourquoi je l'ai appelé "ondraw"
  • onend: cette fonction est appelée une fois l'attaque fini


Alors, tu as du remarquer que les fonctions "onstart" "ondraw" et "onend" : on comme paramètre next,
en gros, on appelle la fonction next pour passer à "l'étape suivante", je m'explique :

l'attaque est initialisé avec "oncreate", ensuite la fonction "onstart" est appelé, il faut savoir que la fonction "oncreate" est appelée 1 fois,
tandis que les autres fonctions sont appelées en continue à 60FPS (environ)
l'idée, c'est que lorsqu'on appelle la fonction next, on passe à la fonction suivante,
donc au début, seul la fonction "onstart" est appelé en continue, et dès qu'on appelle la fonction next, la fonction "onstart" n'est plus appelé mais
c'est la fonction "ondraw" qui est maintenant appelée en continue, et de même si on appelle la fonction next, la fonction "ondraw" n'est plus appelé mais
c'est la fonction "onend" qui est maintenant appelée en continue, et on appelle enfin, la fonction next pour arrêter l'attaque



    • Tu te doute qu'il y un poil de complexité derrière, mais je vais tenter de t'expliquer un tel système, tout d'abord, voici le constructeur :

      Code:
      [lang=javascript]constructor(data) {

        // si aucun argument n'est passé, data sera indéfiné, donc si on fait data['quelque_chose'] le script renverra une erreur,
        // pour empêcher cette erreur, on remplace data par {} donc le cas où data n'est pas défini
        data = data || {};

        // data.spriteSheets est un tableau qui contient le nom des assets
        this.spriteSheetsData = data.spriteSheets || [];

        // on organise ça dans un objet pour faciliter l'accès
        this.spriteSheets = {};
        for (var i = 0; i < this.spriteSheetsData.length; i++) {
          this.spriteSheets[this.spriteSheetsData[i]] = null;
        }

        // les dégats de l'attaque
        this.damage = typeof data.damage !== 'undefined' ? data.damage : 4;

        // tous les "events"
        this.oncreate = typeof data.oncreate === 'function' ? data.oncreate : function(){};
        this.onstart = typeof data.onstart === 'function' ? data.onstart.bind({parent:this,caller:'onstart'}) : function(){};
        this.onend = typeof data.onend === 'function' ? data.onend.bind({parent:this,caller:'onend'}) : function(){};
        this.ondraw = typeof data.ondraw === 'function' ? data.ondraw.bind({parent:this,caller:'ondraw'}) : function(){};
        this.onupdate = typeof data.onupdate === 'function' ? data.onupdate : function(){};

        // l'état local de l'attaque
        this.state = 'onstart';

        // on stocke les données temporaires de l'état ici
        this.stateData = {};

        // on propose à l'utilisateur de mettre ces variables dedans (ce qui est conseillé pour éviter de réécrire des variables existantes)
        this.data = {};

      }


      Juste avant de continuer quoique ce soit, tu dois probablement être surpris par le ".bind({parent:this," , en gros, on définit nous-même le scope de la fonction,
      plus d'info ici : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globa…
      je t’expliquerai un peu plus bas pourquoi on fait ça Okay



    • Cette méthode permet de reset complètement l'attaque

      Code:
      [lang=javascript]/**
      * reset: permet de reset l'attaque
      */
      reset() {
        this.state = 'onstart'; // on revient à l'état initiale de l'attaque
        this.stateData = {};
        this.oncreate();
      }

      (rien de spécial je pense, on reset juste)



    • Cette méthode est appelée lorsque le jeu s'initialise (cette méthode est appelée une seule fois au tout début)

      Code:
      [lang=javascript]/**
      * init: permet d'initialiser l'attaque
      * @param game {Game} : un instancier de la classe Game
      */
      init(game) {

        // on stocke les variables dont on aura besoin
        this.game = game;
        this.assetManager = this.game.assetManager;
        this.area = this.game.area;
        this.player = this.game.player;

        // on "reset" l'attaque
        this.oncreate();
       
      }




    • Cette méthode fait "tourner l'attaque", cette méthode est exécuté à 60FPS, et je pense que c'est l'une des méthodes les plus compliqués

      Code:
      [lang=javascript]/**
      * run: permet de faire "tourner l'attaque", cette fonction est exécuté à 60FPS
      */
      run() {

        // on récupère le scope actuel, le "this" correspond à notre objet attaque (construit avec new Attack())
        var _this = this;

        // on ecécute la fonction "onstart", "ondraw", ou "onend" (à 60FPS)
        this[this.state](function(__this){

          // ici le "__this", correspond au scope lorsque qu'on ".bind" les 3 fonctions,
          // donc le "__this" contient ceci : {parent:_this,caller:...} (le "_this" est le même que celui juste au-dessus)
          // ainsi, comme leur variable "caller" porte leur nom, on est en mesure de savoir quelle fonction est appelée

          // l'objectif ici, c'est de faire en sorte que cette fonction ne soit appelé seulement une fois
          // (je rappele qu'on est dans une fonction qui est exécuté une centaine de fois par seconde)

          // si "_this.stateData[__this.caller]" est défini, c'est que la fonction a déjà été appelé à l'état "__this.caller"
          if (typeof _this.stateData[__this.caller] !== 'undefined') return;

          // si on arrive là, c'est la fonction next n'a jamais été appelé à cet état de l'attaque, donc on fais en sorte,
          // que ce soit le 1er appelle mais aussi surtout le dernier appelle
          _this.stateData[__this.caller] = true;

          // on passe à la fonction suivante
          if (_this.state == 'onstart') {
            _this.state = 'ondraw';
          } else if (_this.state == 'ondraw') {
            _this.state = 'onend';
          } else if (_this.state == 'onend') {
            _this.reset(); // reset attack
          }

        });
      }

      Sincèrement, c'est normale si tu l'as comprends pas (mais je pense avoir bien expliqué le principe derrière)



    • Et enfin pour terminer cette belle classe, on "propage les events" ondraw et onupdate :

      Code:
      [lang=javascript]draw() {
        if (typeof this.ondraw === 'function') this.ondraw();
      }

      update() {
        if (typeof this.onupdate === 'function') this.onupdate();
      }




    Voici le CODE COMPLET de la classe : https://pastebin.com/mLh6X0CB





C'est l'une des classes les plus soft, la plus part des fonctions de cette classe, c'est toi qui les a codé (j'ai juste fais des copier/coller et j'ai modifier/rajouter quelques trucs histoire que la classe soit pratique à utiliser)

Voici ça structure :
Code:
[lang=javascript]class Area {

  constructor(game) {}

  reset() {}

  resize(w,h,next) {}

  changeStatusMessage(text) {}

  showStatusMessage() {}

  hideStatusMessage() {}

  draw() {}

  resetFightBar() {}

  enablePlayer() {}

  disablePlayer() {}

  update() {}

}




    • On commence par son constructeur :
      Code:
      [lang=javascript]constructor(game) {

        this.game = game;

        this.canvas = this.game.canvas;
        this.ctx = this.game.ctx;

        this.reset();

      }

      (rien de particulier)



    • Cette méthode permet reset la zone, cette méthode est appelée lorsque le jeu est reset

      Code:
      [lang=javascript]/**
      * reset: on reset la zone
      */
      reset() {

        this.x = 0;
        this.y = 0;
        this.w = 520;
        this.h = 130;

        this.xm = 0;
        this.ym = 0;
        this.wm = 0;
        this.hm = 0;

        this.rw = 0;
        this.rh = 0;

        // on simplifie l'utilisation d'affichage du message de status
        this.statusMessageText = '';
        this.statusMessageDisplay = true;

        this.playerHeartControl = false; // si c'est true, on dessine le coeur du joueur et on active les controls clavier

        this.resizenext = null; // callback appelé une fois que la zone a fini de se redimensionner

        this.resetFightBar(); // on reset la "fight bar"
      }




    • On reset simplement la barre de fight

      Code:
      [lang=javascript]/**
      * resetFightBar: permet de reset la barre de fight
      */
      resetFightBar() {
        this.fightBar = {
          speed: 6,
          x: 0,
          y: 0,
          hit: false,
          miss: false,
          state: FIGHT_STATE.FIGHTBAR_MOVE, // l'état de la barre
          time: Date.now(),
          tick: 125
        };
      }


      Comme tu peux le voir, j'utilise cette énumération :
      Code:
      [lang=javascript]// 3 State when Fight
      const FIGHT_STATE = {
        FIGHTBAR_MOVE    : 0x1,
        ANIMATION_ATTACK : 0x2,
        SHOW_DAMAGE      : 0x3
      };




    • [On notifie la zone qu'on veut changer ça taille

      Code:
      [lang=javascript]/**
      * resize: permet de redimensionner la zone
      * @param w {int} : la nouvelle longueur
      * @param h {int} : la nouvelle hauteur
      * @param next {function} : cette fonction sera appelée une fois que la zone aura fini de se redimensionner
      */
      resize(w,h,next) {
        this.rw = w;
        this.rh = h;
        this.resizenext = next;
      }




    • Des simples méthodes pour gérer le message status

      Code:
      [lang=javascript]/**
      * changeStatusMessage: permet de changer le message status
      * @param text {string} : le nouveau status
      */
      changeStatusMessage(text) {
        this.statusMessageText = text;
      }

      /**
      * showStatusMessage: on affiche le message status
      */
      showStatusMessage() {
        this.statusMessageDisplay = true;
      }

      /**
      * hideStatusMessage: on cache le message status
      */
      hideStatusMessage() {
        this.statusMessageDisplay = false;
      }




    • Des simples méthodes pour activer/désactiver les contrôles clavier du joueur

      Code:
      [lang=javascript]/**
      * enablePlayer: permet d'activer les contrôles du cœur du joueur
      */
      enablePlayer() {
        this.playerHeartControl = true;

        // on replace le coeur du joueur au centre
        this.game.player.x = this.w/2;
        this.game.player.y = this.h/2;
      }

      /**
      * disablePlayer: permet de désactiver les contrôles du cœur du joueur
      */
      disablePlayer() {
        this.playerHeartControl = false;
      }




    • On dessine la zone

      Code:
      [lang=javascript]/**
      * draw: on dessine la zone
      */
      draw() {

        // on récupère le contexte 2D du canvas
        var ctx = this.ctx;

        // on dessine le rectangle
        ctx.strokeStyle = 'rgb(255,255,255)';
        ctx.lineWidth = 2;
        ctx.strokeRect(this.x, this.y, this.w, this.h);

        // si le message status est "activé", on le dessine
        if (this.statusMessageDisplay) {
          ctx.font = '14px KulminoituvaRegular';
          ctx.fillStyle = 'rgb(255,255,255)';
          ctx.textAlign = 'left';
          ctx.fillTextM(this.statusMessageText, 50, 265);
        }

        // on propage l'event
        if (typeof this.ondraw === 'function') this.ondraw();

        // si on aactivé le control du coeur du joueur
        if (this.playerHeartControl) {

          var s = ctx.globalAlpha; // on sauvegarde cette valeur

          // si le joueur est invulnerable, c'est qu'il s'est pris des dégats, on le rend transparent temporairement
          if (!this.game.player.isVulnerable()) {
            ctx.globalAlpha = 0.4;
          }

          // on dessine le coeur du joueur
          this.game.player.drawHeart(this.game.player.x+this.x,this.game.player.y+this.y);

          // on réinitialise le globalAlpha
          if (!this.game.player.isVulnerable()) {
            ctx.globalAlpha = s;
          }

          // on fais bouger le coeur du joueur
          this.game.player.update();

        }

      }




    • On update la zone

      Code:
      [lang=javascript]/**
      * update: permet de mettre à jour la zone
      */
      update() {

        // comme c'est toi qui à fait ce code, je n'ai pas besoin de l'expliquer :)
        // pour éviter de rendre le tout trop compliquer d'un coup, j'ai pas cherché à l'optimiser
        var y = 0;
        this.x = 300-this.w/2;
        this.y = 300-this.h/2 + y;
        this.xm = 300-this.rw/2;
        this.ym = 300-this.rh/2 + y;
        this.wm = this.rw;
        this.hm = this.rh;
        var v = 10;
        for (var i = 0; i < v; i++) {
          if (this.xm < this.x) this.x -= 1;
          if (this.xm > this.x) this.x += 1;
          if (this.ym < this.y) this.y -= 1;
          if (this.ym > this.y) this.y += 1;
          if (this.wm < this.w) this.w -= 1;
          if (this.wm > this.w) this.w += 1;
          if (this.hm < this.h) this.h -= 1;
          if (this.hm > this.h) this.h += 1;
        }

        // si la zone est redimensionner correctement, on appelle le callback this.resizenext s'il existe
        if (this.xm == this.x && this.ym == this.y && this.wm == this.w && this.hm == this.h) {
          if (typeof this.resizenext === 'function') {
            this.resizenext();
            this.resizenext = null;
          }
        }
      }




    Voilà le CODE COMPLET de la classe : https://pastebin.com/qbYhW1Re





Bien évidemment, on va aussi automatiser ça, donc on va d'abord créer une classe Text comme ceci :
Code:
[lang=javascript]class Text {

  constructor(text,tick,keyCodeSkip) {
    this.text = text;
    this.tick = tick;
    this.keyCodeSkip = keyCodeSkip || [];
  }

}


Et enfin, la classe Dialog :
Code:
[lang=javascript][scroll]class Dialog {

  /**
  * constructor
  * @param textObject {Text} : l'objet Text créer avec new Text()
  * @param next {function} : callback qui sera appelé une fois que le dialogue sera fini
  */
  constructor(textObject,next) {

    // notre objet Text
    this.textObject = textObject;

    // le texte qui sera affiché
    this.displayText = '';

    // on stocke ici l'indice
    this.index = 0;

    // on stocke le callback
    this.next = next || function(){};

    // tu sais à quoi sert cette variable :)
    this.time = Date.now();

    // une variable qui indiquera si le dialogue est fini ou pas
    this.ended = false;

  }

  /**
  * end: permet de terminer le dialogue (par exemple, à l'appui d'une touche, on appelle cette fonction et ça termine le dialogue)
  */
  end() {
    this.displayText = this.textObject.text; // on affiche le texte finale
    this.ended = true; // le dialogue est fini
    if (typeof this.next === 'function') this.next(); // on appelle le callback
  }

  /**
  * update: permet de mettre à jour le dialogue
  * @param game {Game} : un instancier de la classe Game
  */
  update(game) {

    // si le dialogue est fini, on a plus rien a mettre à jour
    if (this.ended) return;

    // à l'appui d'une touche, on termine le dialogue
    if (this.textObject.keyCodeSkip.length > 0) {
      if (game.isKeyDown(this.textObject.keyCodeSkip)) {
        this.end(); // force the dialog to end
      }
    }

    // on "anime" l'écriture du dialogue tous les "this.textObject.tick" millisecondes
    if (Date.now() - this.time > this.textObject.tick) {
      this.time = Date.now();
      this.displayText += this.textObject.text[this.index];
      this.index++;
      if (this.index == this.textObject.text.length) {
        this.ended = true;
        if (typeof this.next === 'function') this.next();
      }
    }
  }

  /**
  * draw: permet de dessiner le dialogue
  * @param ctx {CanvasRenderingContext2D} : le Contexte 2D du canvas
  */
  draw(ctx) {
    ctx.fillStyle = 'rgb(255,255,255)';
    ctx.beginPath();
    ctx.moveTo(416, 85);
    ctx.lineTo(380, 95);
    ctx.lineTo(416, 105);
    ctx.fill();
    ctx.fillStyle = 'rgb(255,255,255)';
    ctx.roundRect(415,50,155,80,13);
    ctx.font = '9px KulminoituvaRegular';
    ctx.fillStyle = 'rgb(0,0,0)';
    ctx.textAlign = 'left';
    ctx.fillTextM(this.displayText, 423, 69);
  }

}

Comme elle est relativement simple et assez bien commenté, je n'ai pas besoin d'ajouter d'explication supplémentaire Okay

Avec ça, on va pouvoir afficher nos dialogues beaucoup plus facilement



On va créer une petite classe UI pour pouvoir gérer l'interface du joueur (on va en particulier venir dessiner le menu)

Voici ça structure :
Code:
[lang=javascript]class UI {

  constructor(game) {}

  reset() {}

  gameOverPrepare() {}

  init() {}

  draw() {}

  disable() {}

  enable(keyCodeToSelect,keyCodeToValidate,next) {}

  drawChoiceButton() {}

}




    • Voici le constructeur :
      Code:
      [lang=javascript]/**
      * constructor
      * @param game {Game} : un instancier de la classe Game
      * @return {UI}
      */
      constructor(game) {

        this.game = game;

        this.assetManager = this.game.assetManager;

        this.canvas = this.game.canvas;
        this.ctx = this.game.ctx;

        // on stockera ici l'asset de l'UI
        this.spriteSheet = null;

        // l'indice
        this.currentChoice = 0;

        // le nombre de boutons
        this.countButtons = 4;

        // le temps que dure le moment où le coeur du joueur se retrouve tout seul sur le fond noir (sans être briser)
        this.gameOverKeepHeartDuration = 1200;
        this.gameOverKeepHeartTime = Date.now();

        // le temps que dure le moment où le coeur se brise
        this.gameOverKeepBreakHeartDuration = 500;
        this.gameOverKeepBreakHeartTime = Date.now();

        // on stockera ici les différentes étapes de l'affiche du game over
        this.gameOverState = 0x0;


      }




    • Cette méthode est appelée lorsque le jeu est reset
      Code:
      [lang=javascript]/**
      * reset: permet de reset l'UI
      */
      reset() {

        this.gameOverState = 0x0;
        this.currentChoice = 0;

      }




    • On prépare l'affichage du game over
      Code:
      [lang=javascript]/**
      * gameOverPrepare: permet de préparer le game over
      */
      gameOverPrepare() {
        // on passe à l'état 0x1 (j'aurais pu utiliser une énumération pour ça)
        this.gameOverKeepHeartTime = Date.now();
        this.gameOverKeepBreakHeartTime = Date.now();
        this.gameOverState = 0x1;
      }




    • Cette méthode est appelée une fois à l'initialisation du jeu

      Code:
      [lang=javascript]/**
      * init: permet d'initialiser l'UI
      */
      init() {
        // on récupère simplement l'asset
        this.spriteSheet = this.assetManager.getAsset('interface.sprite');
      }




    • On créé une méthode pour dessiner l'UI

      Code:
      [lang=javascript]/**
      * draw: permet de dessiner l'UI
      */
      draw() {

        // pour l'instant, on dessine les boutons
        this.drawChoiceButton();

      }


      Je pense qu'on peut améliorer cette partie du code, cette partie est la moins organisé
      Je te laisserai améliorer le code comme tu veux Mr. Green



    • Une petite méthode pour désactiver le choix du menu

      Code:
      [lang=javascript]/**
      * disable: permet de désactiver le choix du menu
      */
      disable() {
        this.currentChoice = 0;
      }




    • Je pense sincèrement que cette fonction fais partie des plus compliqués (comme tu n'as pas l'habitude des events) , mais je pense qu'avec les commentaires ça devrait aller Okay

      Code:
      [lang=javascript]/**
      * enable: permet d'activer le choix du menu
      * @param keyCodeToSelect {array} : contient la touche qui permet de naviguer vers la gauche, et la touche qui permet de naviguer vers la droite
      * @param keyCodeToValidate {array} : contient les touches qui permettent de valider le choix
      * @param next {function} : callback qui sera appelé une fois le choix fait
      */
      enable(keyCodeToSelect,keyCodeToValidate,next) {

        // par défaut, on sélectionne le 1er bouton
        this.currentChoice = 1;

        // on stocke le scope "this"
        var _this = this;

        // on prépare la création d'un event local (un event qui ne s'exécute qu'une seule fois)
        var localevent;

        localevent = function(event) {

          // si on arrive ici, c'est que l'utilisateur a appuyé sur une touche

          // on regarde si le joueur a appuyé sur une touche permettant de valider le choix
          for (var i = 0; i < keyCodeToValidate.length; i++) {

            if (keyCodeToValidate[i] == event.keyCode) {

              // si on arrive là, c'est que le joueur a validé son choix

              // on supprime toutes les touches
              for (var j = 0; j < keyCodeToValidate.length; j++) {
                _this.game.keyState[keyCodeToValidate[j]] = false;
                delete _this.game.keyState[keyCodeToValidate[j]];
              }

              // on appelle le callback
              // si ce callback renvoie true, on supprime cette event, sinon on fait rien
              if (next(_this.currentChoice)) {
                document.removeEventListener('keydown', localevent);
              }

              // on arrete la fonction ici
              return;
            }

          }

          // on regarde si l'utilisateur tente de naviguer dans le menu
          for (var i = 0; i < keyCodeToSelect.length; i++) {

            if (keyCodeToSelect[i] == event.keyCode) {

              // si on arrive là, c'est que l'utilisateur appuie sur des bouttons permettant de naviguer dans le menu

              // comme on a que 2 bouttons dans le tableau, on définit arbitrairement que le 1er c'est pour aller à gauche
              // et le 2ème pour aller à droite
              if (i==0) {
                _this.currentChoice--; // on va vers la gauche
              } else {
                _this.currentChoice++; // on va vers la droite
              }

              // si on va trop à gauche, on revient tout à droite, si on va trop à droite, on revient tout à gauche (sinon on fait rien)
              _this.currentChoice = _this.currentChoice < 1 ? _this.countButtons : _this.currentChoice > _this.countButtons ? 1 : _this.currentChoice;

            }

          }
        };

        // on ajoute l'event
        document.addEventListener('keydown', localevent);

      }




    • Une méthode assez soft pour terminer Cool Very Happy

      Code:
      [lang=javascript]/**
      * drawChoiceButton: permet de dessiner les bouttons
      */
      drawChoiceButton() {

        // on récupère le canvas et le contexte
        var canvas = this.canvas;
        var ctx = this.ctx;

        // j'ai littéralement copier/coller ton code ici :D
        var a = '#FF7E24';
        var b = '#FF7E24';
        var c = '#FF7E24';
        var d = '#FF7E24';
        if (this.currentChoice == 1) a = '#FFFF00';
        if (this.currentChoice == 2) b = '#FFFF00';
        if (this.currentChoice == 3) c = '#FFFF00';
        if (this.currentChoice == 4) d = '#FFFF00';

        ctx.lineWidth = 1;
        ctx.strokeStyle = a;
        ctx.strokeRect(40, 400, 100, 40);
        ctx.strokeStyle = b;
        ctx.strokeRect(180, 400, 100, 40);
        ctx.strokeStyle = c;
        ctx.strokeRect(320, 400, 100, 40);
        ctx.strokeStyle = d;
        ctx.strokeRect(460, 400, 100, 40);

        ctx.font = '23px Verdana';
        ctx.textAlign = 'right';
        ctx.fillStyle = a;
        ctx.fillText('FIGHT', 136, 429);
        ctx.fillStyle = b;
        ctx.fillText('ACT', 262, 429);
        ctx.fillStyle = c;
        ctx.fillText('ITEM', 410, 429);
        ctx.font = '22px Verdana';
        ctx.fillStyle = d;
        ctx.fillText('MERCY', 557, 429);

        // là, on utilise les méthodes des assets (je te laisse faire des aller-retour pour comprendre ce qu'il se passe)
        // ne tkt pas, on fait la classe Player juste après :)
        if (this.currentChoice == 1) { this.game.player.drawHeart(52, 420); } else { this.spriteSheet.drawSprite(ctx, 'fight', 45, 405, 15, 28); }
        if (this.currentChoice == 2) { this.game.player.drawHeart(199, 420); } else { this.spriteSheet.drawSprite(ctx, 'act', 192, 412, 15, 28); }
        if (this.currentChoice == 3) { this.game.player.drawHeart(337, 420); } else { this.spriteSheet.drawSprite(ctx, 'item', 330, 408, 15, 28); }
        if (this.currentChoice == 4) { this.game.player.drawHeart(471, 420); } else { this.spriteSheet.drawSprite(ctx, 'mercy', 463, 410, 15, 28); }

      }




    Et voici le CODE COMPLET de cette classe : https://pastebin.com/wuvgeNHv







On va juste créer une classe Player histoire de gérer plus facilement tout ce qui concerne le joueur (ça vie, le contrôle du coeur, ..etc..)

Voici ça structure :
Code:
[lang=javascript]class Player {

  constructor(game,name) {}

  reset() {}

  addAttack(name) {}

  drawAttack(name,mode,next,...args) {}

  getRandomAttack() {}

  drawState() {}

  init() {}

  isAlive() {}

  takeDamage(damage) {}

  drawHeart(x,y,w,h) {}

  isInArea(x,y,w,h) {}

  move(point) {}

  isVulnerable() {}

  checkHitArea(attack) {}

  update() {}

}




    • Voici le constructeur de la classe

      Code:
      [lang=javascript]/**
      * constructor
      * @param game {Game} : un instancier de la classe Game
      * @param name {string} : le nom du joueur
      * @return {Player}
      */
      constructor(game,name) {

        this.game = game;

        this.assetManager = this.game.assetManager;

        this.canvas = this.game.canvas;
        this.ctx = this.game.ctx;

        // on stocke ici l'asset du coeur
        this.heartAsset = null;

        // on stocke les différents assets d'attaques (si jamais on veut une animation différente d'attaque)
        this.attackAssets = {};

        // dégat de l'attaque lorsque le joueur vise à une précision de 100%
        this.attack = 15;

        // servira pour la position du coeur et la taille du coeur
        this.x = 0;
        this.y = 0;
        this.w = 13;
        this.h = 13;

        // la vitesse du coeur
        this.speed = 2;

        // le nom du joueur
        this.name = name || 'Frisk';

        // son niveau
        this.lvl = 1;

        // sa vie
        this.health = 42;

        // sa vie maximale
        this.healthMax = 42;

        // le temps d'invulnerabilité après avoir reçu des dégats
        this.notTakeDamageDuration = 500; // 0.5 secondes
        this.notTakeDamageTime = Date.now();

        // on stocke ici si le joueur est en vie ou pas
        this.isDead = false;

      }




    • Je pense que tu dois avoir l'habitue de voir cette méthode Mr. Green

      Code:
      [lang=javascript]/**
      * reset: permet de reset le joueur
      */
      reset() {

        // on reset sa vie
        this.health = this.healthMax;

        // on le remet à la vie
        this.isDead = false;

        // on pourrait tout aussi bien reset son niveau mais je n'ais pas implémenté cette partie, et je laisserai le faire :)

      }




    • Là, on ajoute simplement un asset pas un instancier de la classe Attack

      Code:
      [lang=javascript]/**
      * addAttack: permet d'ajouter un asset d'attaque avec le nom de l'asset
      * @param name {string} : le nom de l'asset
      */
      addAttack(name) {

        // on récupère simplement l'asset
        this.attackAssets[name] = this.assetManager.getAsset(name);

      }




    • On dessine l'attaque

      Code:
      [lang=javascript]/**
      * drawAttack: permet de dessiner (en animant) une attaque et appelle le callback next une fois l'animation terminée
      * @param name {string} : nom de l'attaque (correspond aussi au nom de l'asset)
      * @param mode {SPRITE_DRAW} : mode de dessin
      * @param next {function} : callback qui sera appelé après que l'animation soit terminée
      */
      drawAttack(name,mode,next,...args) {

        // on anime automatiquement l'asset attaque
        this.attackAssets[name].draw(mode,this.ctx,...args);

        // on regarde si elle est finie
        if (this.attackAssets[name].isAnimationEnded()) {

          // si oui, on appelle le callback
          next();

        }

      }




    • Petite méthode pour sélectionner un asset attaque aléatoirement

      Code:
      [lang=javascript]/**
      * getRandomAttack: renvoie le nom d'une attaque de manière aléatoire
      * @return {string} : on retourne le nom de l'attaque
      */
      getRandomAttack() {
        var index = Utils.getRandomIntInclusive(0,Object.keys(this.attackAssets).length-1);
        return Object.keys(this.attackAssets)[index]; // on récupère le nom des clés (ça renvoie un tableau des noms), et on a plus qu'à récuper le nom avec l'indice choisi au hasard
      }




    • On dessine la vide du joueur, son nom son niveau, ..etc..

      Code:
      [lang=javascript]/**
      * drawState: permet de dessiner l'état du joueur
      * on dessine sa barre de vie, son nom, ..etc..
      */
      drawState() {
        var ctx = this.ctx; // on récupère le contexte 2D

        // comme tu peux le voir j'ai simplement copier/coller ton code

        ctx.font = '12px KulminoituvaRegular';
        ctx.fillStyle = 'rgb(255,255,255)';
        ctx.textAlign = 'left';
        ctx.fillText(this.name, 40, 388);
        ctx.fillText('LV ' + this.lvl, 150, 388);
        ctx.fillText(this.health + ' / ' + this.healthMax, 280 + this.healthMax, 388);

        ctx.font = '10px KulminoituvaRegular';
        ctx.fillText('HP', 238, 387);
        ctx.fillStyle = 'rgb(255,0,0)';
        ctx.fillRect(258,376,this.healthMax,13);
        ctx.fillStyle = 'rgb(255,255,0)';
        ctx.fillRect(258,376,this.health,13);

      }




    • On initialise l'objet

      Code:
      [lang=javascript]/**
      * init: permet d'initialiser l'objet (elle est appelée une seule fois lorsque le jeu s'initialise)
      */
      init() {
        // on récupère simplement l'asset du coeur
        this.heartAsset = this.assetManager.getAsset('heart');
      }




    • Code:
      [lang=javascript]/**
      * isAlive: permet de savoir si le joueur est en vie
      * @return {boolean} : renvoie true si joueur est en vie sinon false
      */
      isAlive() {
        return !this.isDead;
      }




    • Code:
      [lang=javascript]/**
      * takeDamage: permet de faire prendre des dégats au joueur
      * @param damage {int} : les dégats
      */
      takeDamage(damage) {

        // si le joueur est invulnerable, on fait rien
        if (Date.now() - this.notTakeDamageTime <= this.notTakeDamageDuration) return;

        // sinon on soustrait les dégats à sa vie
        this.health -= damage;

        // si sa vie est égale ou plus petit que zéro, on considère le joueur comme mort (logique mdr)
        if (this.health <= 0) {
          this.health = 0; // on remet sa vie bien à 0
          this.isDead = true; // on notifie que le joueur est bien mort
          this.game.changeState(GAME_STATE.GAME_OVER); // on change l'état du jeu et on le met à l'état "GAME_OVER" pour afficher le screen game over
        }

      }




    • Code:
      [lang=javascript]/**
      * drawHeart: permet de dessiner le coeur du joueur
      * @param x {int} : position x du coeur
      * @param y {int} : position y du coeur
      * @param w {int} : largeur du coeur
      * @param h {int} : hauteur du coeur
      * @return {object} : on retourne l'objet {x:x,y:y,w:w,h:h}
      */
      drawHeart(x,y,w,h) {

        var ctx = this.ctx; // on récupère le Contexte 2D

        // si les paramètres x, y, w, ou h sont indéfinis, on les remplace par des valeurs "par défaut"
        // par conséquent, on peut très bien appeler cette méthode sans aucun arguments comme ceci : player.drawHeart()
        x = typeof x !== 'undefined' ? x : this.x;
        y = typeof y !== 'undefined' ? y : this.y;
        w = typeof w !== 'undefined' ? w : this.w;
        h = typeof h !== 'undefined' ? h : this.h;

        // comme le coeur est simplement une image, on a pas besoin de l'animer
        ctx.drawImage(this.heartAsset.image, x-w/2, y-h/2, w, h);

        // on retourne les valeurs utiliser pour dessiner le coeur
        return {x:x,y:y,w:w,h:h};

      }




    • Code:
      [lang=javascript]/**
      * isInArea: permet de tester si un rectangle est bien dans strictement dans la zone
      * @param x {int} : position x du rectangle
      * @param y {int} : position y du rectangle
      * @param w {int} : largeur du rectangle
      * @param h {int} : hauteur du rectangle
      * @return {boolean} : on retourne true si le rectangle est dans la zone, sinon on retourne fasle
      */
      isInArea(x,y,w,h) {
        w = typeof w !== 'undefined' ? w : this.w;
        h = typeof h !== 'undefined' ? h : this.h;
        var area = this.game.area;
        return x-w/2 > 0 && x+w/2 < area.w && y-h/2 > 0 && y+h/2 < area.h;
      }




    • Code:
      [lang=javascript]/**
      * move: permet de déplacer le coeur du joueur de point.x en x et de point.y en y
      * @param point {object} : point qui est sous la fome {x:...,y:...}
      */
      move(point) {
        // si le coeur reste dans la zone, on déplace le coeur sinon on fait rien
        if (this.isInArea(this.x+point.x,this.y+point.y)) {
          this.x += point.x;
          this.y += point.y;
        }
      }




    • Code:
      [lang=javascript]/**
      * isVulnerable: permet de savoir si le joueur est vulnerable
      * @return {boolean} : on renvoie true si le joueur est vulnerable sinon on renvoie false
      */
      isVulnerable() {
        return !this.invulnerable;
      }

      (Avoue, c'est quand même satisfaisant de créer ce genre de méthode Very Happy )



    • J'avoue, le nom de cette méthode n'est pas très significative (si tu veux changer son nom par un meilleure nom Razz Okay )

      Code:
      [lang=javascript]/**
      * checkHitArea: permet de savoir si le coeur est rentré en collision avec l'attaque de l'ennemi
      * @param attack {Attack} : un instancier de la classe Attack
      */
      checkHitArea(attack) {

        // si le joueur est invulnerable, on fait rien
        if (Date.now() - this.notTakeDamageTime <= this.notTakeDamageDuration) return;

        // si on arrive ici, alors le joueur n'est plus invulnerable
        this.invulnerable = false;

        // on récupère ce qui nous intéresse
        var ctx = this.game.ctx;
        var canvas = this.game.canvas;
        var area = this.game.area;

        // on récupère la zone du coeur (il faut faire attention à inverser le scale et le translate)
        var pixel = ctx.getImageData(this.game.pointTranslate.x+(this.x+area.x+1-(this.w-2)/2)*this.game.pointScale.x, this.game.pointTranslate.y+(this.y+area.y+1-(this.h-2)/2)*this.game.pointScale.y, (this.w-2)*this.game.retrieveData('e'), (this.h-2)*this.game.retrieveData('e'));

        // on teste s'il y a un pixel blanc dans cette zone
        for (var i = 0; i < pixel.data.length; i+=4) {
          if (pixel.data[i] > 200 && pixel.data[i+1] > 200 && pixel.data[i+2] > 200) {

            // si oui, on applique les dégats au joueur
            this.takeDamage(attack.damage);

            // on "active" l'invulnerabilité
            this.notTakeDamageTime = Date.now();

            // on notifie le fait que le joueur soit invulnerable
            this.invulnerable = true;

          }
        }
      }




    • Code:
      [lang=javascript]/**
      * update: permet d'update le joueur
      * en particulier, on déplace le coeur à l'appui des touches
      */
      update() {

        // LEFT
        if (this.game.isKeyDown(37) || this.game.isKeyDown(81)) {
          this.move({x:-this.speed,y:0});
        }

        // RIGHT
        if (this.game.isKeyDown(39) || this.game.isKeyDown(68)) {
          this.move({x:this.speed,y:0});
        }

        // UP
        if (this.game.isKeyDown(38) || this.game.isKeyDown(90)) {
          this.move({x:0,y:-this.speed});
        }

        // DOWN
        if (this.game.isKeyDown(40) || this.game.isKeyDown(83)) {
          this.move({x:0,y:this.speed});
        }
      }




    Voici le CODE COMPLET de la classe : https://pastebin.com/h6XrHbFS






On s'attaque enfin à la classe la plus intéressente Mr. Green
Voici sa structure :

Code:
[lang=javascript]class Entity {

  constructor(data) {}

  reset() {}

  resetDamageVariable() {}

  setName(name) {}

  getName() {}

  init(game) {}

  isAlive() {}

  addAttack(name) {}

  removeAttack(name) {}

  selectAttack(...args) {}

  takeDamage(damage,next) {}

  waitKeyDown(keyCodeSkip) {}

  draw() {}

  update() {}

  destroyDialog() { }

  displayDialog(textObject,next) {}

}






    • Code:
      [lang=javascript]/**
      * constructor : permet de construire un ennemi
      * @param data {object}
      * @return {Entity}
      */
      constructor(data) {

        // si aucun argument n'est passé, data sera indéfiné, donc si on fait data['quelque_chose'] le script renverra une erreur,
        // pour empêcher cette erreur, on remplace data par {} donc le cas où data n'est pas défini
        data = data || {};

        // la vie de l'entité
        this.health = data.health || 0;

        // la vie maximale que l'entité peut avoir
        this.healthMax = data.healthMax || this.health;

        // les différents sprites de l'entité (un sprite où il est en vie, un sprite où il est en colère, un sprite où il est mort ... etc ...)
        this.spriteSheetsData = data.spriteSheets || [];

        // on stocke ça dans un objet pour faciliter l'accès
        this.spriteSheets = {};
        for (var i = 0; i < this.spriteSheetsData.length; i++) {
          this.spriteSheets[this.spriteSheetsData[i]] = null;
        }

        // le nom de l'entité (ne tkt pas, il y a une méthode setName pour définir le nom)
        this.name = '';



        // quelques events

        // quand l'entité est créé (lors du reset du jeu et au début du jeu)
        this.oncreate = typeof data.oncreate === 'function' ? data.oncreate : function(){};

        // lorsque l'entité choisi une attaque, cette méthode est appelé, on a juste à retourner une attaque
        // par défaut, on renvoie une attaque de manière aléatoire
        this.onselectattack = typeof data.onselectattack === 'function' ? data.onselectattack : function(){
          var index = Utils.getRandomIntInclusive(0,Object.keys(this.attacks).length-1);
          return this.attacks[Object.keys(this.attacks)[index]];
        };

        // lorsque l'entité est dessinée, cette méthode est appelé
        this.ondraw = typeof data.ondraw === 'function' ? data.ondraw : function(){};

        // lorsque l'entité est mis à jour cette méthode est appelée
        this.onupdate = typeof data.onupdate === 'function' ? data.onupdate : function(){};

        // lorsque l'état global du jeu change, cette méthode est appelée
        this.onchangestate = typeof data.onchangestate === 'function' ? data.onchangestate : function(){};

        // on stocke toutes les autres données ici (ça évite de réécrire une variable déjà existente)
        this.data = {};

        // on stocke toutes les attaques ici (ce sont des instancier de la classe Attack)
        this.attacks = {};

        // on reset les variables lié à l'animation de l'entité lorsqu'elle se prend des dégats du joueur
        this.resetDamageVariable();

        // l'état se sa vie
        this.lifeState = LIFE_STATE.ALIVE;

        // son opacité
        this.opacity = 1;

        // on stocke ici l'objet dialogue (un instancier de la classe Dialog)
        this.dialog = null;

        // on créé l'entité
        this.oncreate();

      }




    • Code:
      [lang=javascript]/**
      * reset: on reste l'entité
      */
      reset() {

        // on reset sa vie
        this.health = this.healthMax;

        // on le remet en vie
        this.lifeState = LIFE_STATE.ALIVE;

        // on "détruit" l'objet dialogue s'il y en a un
        this.dialog = null;

        // on propage l'event reset aux attaques pour reset toutes les attaques
        for (var i in this.attacks) {
          if (this.attacks.hasOwnProperty(i)) {
            this.attacks[i].reset();
          }
        }

        // on reset les variables lié à l'animation de l'entité lorsqu'elle se prend des dégats du joueur
        this.resetDamageVariable();

        // on recréé l'entité
        this.oncreate();
      }




    • Code:
      [lang=javascript]/**
      * resetDamageVariable: permet de reset les variables lié à l'animation de l'entité lorsqu'elle se prend des dégats du joueur
      */
      resetDamageVariable() {
        this.damageTime = Date.now();
        this.damageShake = 0; // si cette variable vaut 1, alors l'entité se déplace à gauche, si cette variable vaut 0, alors l'entité se déplace à droite
        this.damageTransition = 0;
        this.damageLock = false;
        this.damageX = 0; // on fait "trembler" l'entité, on aura besoin d'une variable temporaire pour le déplacement en x
        this.opacity = 1;
      }


      J'avoue les noms des variables sont assez ambigu, pour mieux comprendre à quoi elles servent, tu peux aller regarder la méthode takeDamage



    • Code:
      [lang=javascript]/**
      * setName: permet de définir le nom de l'entité
      * @param name {string} : le nom de l'entité
      */
      setName(name) {
        this.name = name;
      }




    • Code:
      [lang=javascript]/**
      * getName: permet de récupérer le nom de l'entité
      * @return {string} : retourne le nom de l'entité
      */
      getName() {
        return this.name;
      }




    • Code:
      [lang=javascript]/**
      * init: permet d'initialiser l'entité (exécuter une seule fois lorsque le jeu s'initialise)
      * @param game {Game} : un instancier de la classe Game
      */
      init(game) {

        // on stocke uniquement ce qui nous intéresse
        this.game = game;
        this.assetManager = this.game.assetManager;
        this.canvas = this.game.canvas;
        this.ctx = this.game.ctx;

        // on récupère tous les assets
        for (var i = 0; i < this.spriteSheetsData.length; i++) {
          this.spriteSheets[this.spriteSheetsData[i]] = this.assetManager.getAsset(this.spriteSheetsData[i]);
        }

        // on récupère toutes les attaques (instancier de la classe Attack)
        // et on propage l'initialisation
        for (var name in this.attacks) {
          if (this.attacks.hasOwnProperty(name)) {
            this.attacks[name] = this.game.getAttack(name);
            this.attacks[name].init(this.game);
          }
        }

      }




    • Code:
      [lang=javascript]/**
      * isAlive: permet de savoir si l'entité est en vie
      */
      isAlive() {
        return this.lifeState == LIFE_STATE.ALIVE;
      }




    • Code:
      [lang=javascript]/**
      * addAttack: permet d'ajouter une attaque à l'entité
      * j'ai choisi arbitrairement le fait qu'il faut ajouter les attaques avant d'initialiser l'entité
      */
      addAttack(name) {
        this.attacks[name] = null;
      }




    • Code:
      [lang=javascript]/**
      * removeAttack: permet d'enlver une attaque
      */
      removeAttack(name) {

        // on supprime simplement l'objet
        this.attacks[name] = null;
        delete this.attacks[name];

      }




    • Code:
      [lang=javascript]/**
      * selectAttack: permet de sélectionner une attaque
      * @params ...args : correspond à tous les arguments
      */
      selectAttack(...args) {

        // on propage l'event et on propage aussi les arguments
        var attack = this.onselectattack(...args);

        // si l'objet retourné n'est pas un instancier de la class Attack, on fait planter le script avec une erreur pour éviter des bugs
        if (!(attack instanceof Attack)) throw 'Thre returned value of "onselectattack" must be a "new Attack()" Object';

        // si on arrive ici, c'est que l'objet est bien un instancier de la classe Attack, dans ce cas on retourne l'attaque sélectionée
        return attack;
      }




    • Code:
      [lang=javascript]/**
      * takeDamage: permet d'appliquer les dégâts reçu sur la vie de l'entité
      * @param damage {int} : des dégâts
      * @param next {function} : ce callback sera appelé une fois que l'animation de se prendre les dégâts sera finie
      */
      takeDamage(damage,next) {

        // on stocke le scope actuel
        var _this = this;

        var ctx = this.game.ctx;

        // on change l'opacité de l'entité
        this.opacity = 0.8;

        // on fait "trembler" l'entité
        if (Date.now() - this.damageTime > 50) {
          this.damageTime = Date.now();
          if (this.damageShake == 0) {
            this.damageShake = 1;
            this.damageX -= 10;
          } else {
            this.damageShake = 0;
            this.damageX += 10;
          }
        }

        // j'ai simplement repris ton code ici

        ctx.font = '20px KulminoituvaRegular';
        ctx.fillStyle = 'rgb(255,0,0)';
        ctx.textAlign = 'center';
        ctx.fillText(damage, 380 + this.healthMax/2, 99);

        ctx.fillStyle = 'rgb(255,0,0)';
        ctx.fillRect(380,60,this.healthMax,10);
        ctx.fillStyle = 'rgb(0,255,0)';
        ctx.fillRect(380,60,this.health-this.damageTransition,10);


        // si this.damageTransition n'est pas égale à damage, alors l'entité n'a pas fini de se prendre les dégâts, on continue
        if (this.damageTransition < damage) {

          this.damageTransition++;

        } else if (!this.damageLock) {

          // comme cette méthode est exécuté une centaine de fois par seconde, si on bloque pas la fonction, il va se prendre 1 million de dégâts mdr

          this.damageLock = true; // donc on bloque

          // on attend un tout petit pour laisser le temps au joueur de bien voir la barre de vie de l'entité
          setTimeout(function () {

            // on enlève les dégâts à la vie
            _this.health -= damage;


            if (_this.health <= 0) {
              _this.health = 0;

              // pour faire en sorte que l'entité dise ces derniers mots, on met l'entité dans un état "entrain de mourrir"
              _this.lifeState = LIFE_STATE.DYING;
            }

            // on reset toutes les variables liés à l'animation des dégâts
            _this.resetDamageVariable();

            // on appelle le callback pour dire que l'animation où l'entité prend des dégâts est finie
            next();

          }, 600);

        }

      }




    • Code:
      [lang=javascript]/**
      * waitKeyDown: permet d'attendre l'appui d'une touche (attention, ceci ne doit pas être exécuté à 60FPS)
      * @param keyCodeSkip {array} : tableau des touches
      */
      waitKeyDown(keyCodeSkip) {

        // on stocke le scope this
        var _this = this;

        _this.waitKeyDownEventFn = function(event) {

          // si on arrive ici, c'est que l'utilisateur a appuyé sur une touche

          // on vérifie s'il a appuyé sur la touche attendu
          for (var i = 0; i < keyCodeSkip.length; i++) {

            if (keyCodeSkip[i] == event.keyCode) {

              // si oui, on supprime cet event
              document.removeEventListener('keydown', _this.waitKeyDownEventFn);

              // on "réinitialise" les touches
              for (var j = 0; j < keyCodeSkip.length; j++) {
                _this.game.keyState[keyCodeSkip[j]] = false;
                delete _this.game.keyState[keyCodeSkip[j]];
              }

              // on appelle la fonction qui se trouve dans le .then
              _this.resolvetmp();

              break;
            }
          }

        };

        // on pourra faire un .then
        return new Promise(function(resolve, reject) {
          _this.resolvetmp = resolve; // on stocke la fonction du .then ici

          // on démarre l'event
          document.addEventListener('keydown', _this.waitKeyDownEventFn);

        });

      }




    • Code:
      [lang=javascript]/**
      * draw: permet de dessiner l'entité
      */
      draw() {

        // dans un 1er temps, on propage l'event
        if (typeof this.ondraw === 'function') this.ondraw();

        // et on dessine le dialogue s'il y en a un
        if (this.dialog != null) {
          this.dialog.update(this.game);
          this.dialog.draw(this.game.ctx);
        }

      }




    • Code:
      [lang=javascript]/**
      * update: permet d'update l'entité
      */
      update() {

        // on propage simplement l'event
        if (typeof this.onupdate === 'function') this.onupdate();

      }




    • Code:
      [lang=javascript]/**
      * destroyDialog: permet de détruire le dialogue
      */
      destroyDialog() {
        this.dialog = null;
      }




    • Code:
      [lang=javascript]/**
      * displayDialog: permet d'afficher un dialogue
      * @param textObject {Text} : instancier de la classe Text
      * @param next {function} : callback qui sera exécuté une fois que le dialogue sera fini
      */
      displayDialog(textObject,next) {

        // si le dialogue existe déjà, on fait rien
        if (this.dialog != null) {
          return;
        }

        // on créé le dialogue
        this.dialog = new Dialog(textObject,next);

      }




    Voici le CODE COMPLET de la classe : https://pastebin.com/C4Hi1ysU







Bon, j'ai mis un ordre mais en vrai toutes les classes se font coder en même temps
C'est pas : je fais toute la classe là, ensuite toute la classe là (à moins que t'aies fait un cahier des charges hyper précis du système : ce qui est encore mieux pour l'organisation)

Enfin, c'est un peu un mélange des deux, tu prévois certaines méthodes et t'en rajoute après pour simplifier encore plus et rendre la classe plus pratique




    Si tu as bien lu tous les précédents commentaires, tu devrais comprendre comment cette classe fonctionne sans aucun problème
    J'ai juste commentée quelques méthodes

    Code:
    [lang=javascript][scroll]class Game {

      constructor() {

        // on créé le canvas
        this.canvas = document.createElement('canvas');

        // on met le background en noir
        this.canvas.style.background = '#000000';

        // on récupère le contexte 2D
        this.ctx = this.canvas.getContext('2d');

        // contiendra toutes sortes de données
        this.data = {};

        // contiendra l'état des touches
        this.keyState = {};

        // contiendra tous les ennemies
        this.ennemies = {};

        // contiendra toutes les attaques
        this.attacks = {};

        // variable temporaire pour mieux gérer les transitons (j'expliquerai ça un peu plus tard)
        this.transitions = {};
        this.transitionstext = {};

        // on stock simplement le translate et le scale
        this.pointTranslate = {
          x: 0,
          y: 0
        };
        this.pointScale = {
          x: 1,
          y: 1
        };

        // on stocke ici l'ennemy courant
        this.currentEnnemy = null;

        // l'état global du jeu
        this.state = null;

        // l'asset manager
        this.assetManager = new AssetManager(this);

        // la zone où seront dessinés les attaques et le coeur du joueur
        this.area = new Area(this);

        // le joueur
        this.player = new Player(this);

        // l'UI qui gérera les boutons fight, act, item et mercy
        this.UI = new UI(this);

        // on ajoute enfin le canvas dans le DOM
        document.body.appendChild(this.canvas);

      }

      reset() {
        this.transitions = {};
        this.transitionstext = {};
        this.keyState = {};
        for (var i in this.ennemies) {
          if (this.ennemies.hasOwnProperty(i)) {
            this.ennemies[i].reset();
          }
        }
        this.player.reset();
        this.area.reset();
        this.UI.reset();
      }

      isKeyDown(_code) {
        if (typeof _code === 'object') {
          for (var i in _code) {
            if (_code.hasOwnProperty(i)) {
              if (this.keyState[_code[i]]) return true;
            }
          }
          return false;
        }
        return this.keyState[_code];
      }

      startEvents() {
        var _this = this;
        document.addEventListener('keydown',function(event){
          _this.keyState[event.keyCode || event.which] = true;
        });
        document.addEventListener('keyup',function(event){
          _this.keyState[event.keyCode || event.which] = false;
        });
      }

      addAttack(name,attack) {
        this.attacks[name] = attack;
      }

      removeAttack(name) {
        this.attacks[name] = null;
        delete this.attacks[name];
      }

      getAttack(name) {
        return this.attacks[name];
      }

      addEnnemy(name,entity) {
        this.ennemies[name] = entity;
        this.ennemies[name].setName(name);
      }

      removeEnnemy(name) {
        this.ennemies[name] = null;
        delete this.ennemies[name];
      }

      getEnnemy(name) {
        return this.ennemies[name];
      }

      getCurrentEnnemy() {
        return this.ennemies[this.currentEnnemy];
      }

      setCurrentEnnemy(name) {
        this.currentEnnemy = name;
      }

      /**
      * transition: permet d'effectuer une transition (doit être exécutée à 60FPS pour fonctionner)
      * @param name {string} : le nom de la transition (son ID)
      * @param object {object} : un objet de la forme {from:...,to:...,step:...}
      * @param draw {function} : function qui sera exécutée pour dessiner la transtion (son paramètre est la valeur actuelle de la transtion)
      * la fonction est donc de cette forme:
      *    function (value) {
      *      console.log('valeur actuelle de la transtion:',value)
      *    }
      * @param next {function} : function qui sera exécutée lorsque la transtion est finie (cette fonction est exécutée à 60FPS)
      * (remarque: la fonction draw continuera aussi d'être exécutée)
      */
      transition(name,object,draw,next) {
        if (typeof this.transitions[name] === 'undefined') {
          this.transitions[name] = object.from;
        }
        this.transitions[name] += (object.from < object.to ? Math.abs(object.step) : -Math.abs(object.step)) || 1;
        if (object.from < object.to) {
          if (this.transitions[name] > object.to) {
            this.transitions[name] = object.to;
            if (typeof next === 'function') next();
          }
        } else {
          if (this.transitions[name] < object.to) {
            this.transitions[name] = object.to;
            if (typeof next === 'function') next();
          }
        }
        draw(this.transitions[name]);
      }

      /**
      * drawTextTransition: permet d'effectuer une transition (doit être exécutée à 60FPS pour fonctionner)
      * @param name {string} : le nom de la transition (son ID)
      * @param object {object} : un objet de la forme {text:...,tick:...}
      * @param draw {function} : function qui sera exécutée pour dessiner la transtion (son paramètre est la valeur actuelle de la transtion)
      * la fonction est donc de cette forme:
      *    function (value) {
      *      console.log('valeur actuelle de la transtion:',value)
      *    }
      * @param next {function} : function qui sera exécutée lorsque la transtion est finie (cette fonction est exécutée à 60FPS)
      * (remarque: la fonction draw continuera aussi d'être exécutée)
      */
      drawTextTransition(name,object,draw,next) {
        if (typeof this.transitionstext[name] === 'undefined') {
          this.transitionstext[name] = {
            text: '',
            index: 0,
            time: Date.now()
          };
        }
        if (Date.now() - this.transitionstext[name].time > (object.tick || 100)) {
          this.transitionstext[name].time = Date.now();
          if (this.transitionstext[name].index < (object.text||'undefined').length) {
            this.transitionstext[name].text += (object.text||'undefined')[this.transitionstext[name].index];
            this.transitionstext[name].index++;
          }
        }
        draw(this.transitionstext[name].text);
        if (this.transitionstext[name].index == (object.text||'undefined').length) {
          if (typeof next === 'function') next();
        }
      }

      translate() {
        this.ctx.translate(this.pointTranslate.x,this.pointTranslate.y);
      }

      scale() {
        this.ctx.scale(this.pointScale.x,this.pointScale.y);
      }

      /**
      * changeState: permet de changer l'état du jeu
      * @param state {GAME_STATE} : l'état du jeu
      */
      changeState(state) {

        // on stocke le nouvelle état
        this.state = state;

        // on propage le changement de l'état partout
        for (var name in this.ennemies) {
          if (this.ennemies.hasOwnProperty(name)) {
            if (typeof this.ennemies[name].onchangestate === 'function') this.ennemies[name].onchangestate(state);
          }
        }
        if (typeof this.onchangestate === 'function') this.onchangestate(state);

      }

      /**
      * run: permet de lancer le jeu
      */
      run() {

        // on initialise tout
        for (var name in this.ennemies) {
          if (this.ennemies.hasOwnProperty(name)) {
            this.ennemies[name].init(this);
          }
        }
        this.player.init(this);
        this.UI.init(this);

        // on lance la fonction qui fera tourner notre jeu (cette fonction sera exécuté une centaine de fois par seconde)
        this.update();
      }

      waitKeyDown(keyCodeSkip) {
        var _this = this;
        _this.waitKeyDownEventFn = function(event) {
          if (keyCodeSkip.length == 0) {
            document.removeEventListener('keydown', _this.waitKeyDownEventFn);
            _this.keyState[event.keyCode] = false;
            delete _this.keyState[event.keyCode];
            _this.resolvetmp();
            return;
          }
          for (var i = 0; i < keyCodeSkip.length; i++) {
            if (keyCodeSkip[i] == event.keyCode) {
              document.removeEventListener('keydown', _this.waitKeyDownEventFn);
              for (var j = 0; j < keyCodeSkip.length; j++) {
                _this.keyState[keyCodeSkip[j]] = false;
                delete _this.keyState[keyCodeSkip[j]];
              }
              _this.resolvetmp();
              break;
            }
          }
        };
        return new Promise(function(resolve, reject) {
          _this.resolvetmp = resolve;
          document.addEventListener('keydown', _this.waitKeyDownEventFn);
        });
      }

      registerData(name, value) {
        this.data[name] = value;
      }

      retrieveData(name) {
        return (typeof this.data[name] === 'undefined' ? null : this.data[name]);
      }

      addAsset(...args) {
        this.assetManager.addAsset(...args);
      }

      removeAsset(...args) {
        this.assetManager.removeAsset(...args);
      }

      load(next) {

        this.assetManager.loadAll(next);

      }

      update() {
        var _this = this;
        window.requestAnimationFrame(function(){_this.update();});
        if (typeof this.onUpdate === 'function') this.onUpdate();
      }

    }

    (si tu veux d’entraîner à mettre des commentaires, tu sais quoi faire Very Happy Okay )









En vrai, je sais pas si j'ai bien expliqué, donc s'il y a des "zones un peu obscures" dit-le moi Mr. Green




______________________________________________________
la vie est trop courte pour retirer le périphérique USB en toute sécurité...
Si la statue de la liberté lève le bras depuis 125 ans, c'est parce qu'elle cherche du réseau sur son Blackberry Torches...
Grâce à mon nouveau correcteur automatiste sur mon téléphage, je ne fais plus aucune faute d'orthodontie.
Quelqu'un a t il déjà demandé au drapeau japonais ce qu'il enregistre depuis tout ce temps ?
Visit poster’s website
Reply with quote
Post [Javascript] Jeu avec la mécanique de combat d'Undertale 
Flammrock wrote:

J'ai un peu try hard j'avoue, mais j'ai aussi réussi ce petit défi, et d'ailleurs voici la preuve en vidéo :

(le fait que la vidéo ne soit pas très fluide c'est normale, j'utilise hypercam2)


Pourquoi tu ne filmes pas avec la xbox bar (windows+G) qui est intégré de base dans windows 10 ? A moins que tu sois sur mac ou linux ? (ce qui ferais de toi un traitre sachant qu'on est sur un forum sur le batch Bannir )
Par contre gg, je ne pensais pas que tu réussirais, j'ai eu du mal à faire le perfect pour ma part alors que j'ai imaginé et conçu les attaques (et tester le code des 100aines de fois)

Flammrock wrote:

Tu n'utilise ni le html, ni le css pour faire tourner le jeu et c'est ce qu'il faut faire. La majorité des jeux en javascript utilisent le canvas à 100%.

J'ai créé un meme à ce sujet


Flammrock wrote:

En vrai, je sais pas si j'ai bien expliqué, donc s'il y a des "zones un peu obscures" dit-le moi Mr. Green


Au début, je voulais simplement te poser des questions sur ces "zone obscures". Mais en vrai, il y a tout dans tes commentaires (ou sur MDN si vraiment j'ai du mal). Et vu le travail que tu as fourni pour m'aider, je devrais au moins faire un minimum d'effort également.

Donc j'ai eu l'idée de refaire le snake que je crée depuis quelques jours, avec la façon de programmer et structurer que tu essaie de m'apprendre.

Ducoup voilà le code html : https://pastebin.com/rMEi2fyz
Et le code javascript : https://pastebin.com/8Guq1JR6

Et je vais te copier, je vais détailler chacune des classes que j'ai créées :

Code:

Game {
   constructor()
   update()
   draw()
   clavier()
   add_Snake()
   add_Bonus()
   add_Mur()
   reset()
   ini()
   generate_bonus()

   Random()
}


Code:

class Assets {
   constructor()
   draw()
}


Code:

class Snake extends Assets {
   constructor()
   draw()
   moove()
   test_tp()
   moove_tail()
   add_tail()
   collision()
}


Code:

class Bonus extends Assets {
   constructor()
   collision()
}


Code:

class Murs extends Assets {
   constructor()
   collision()
}


Quelques précisions :

- J'ai voulu faire une énumération avec les couleurs comme tu me la montrer, mais je ne sais pas si c'est exactement ça.

- J'aurai voulu faire une sorte de tableau indiquant que "une direction correspond à une touche" comme tu le fais, mais je savais pas trop comment l'intégré au code ducoup j'ai abandonné l'idée.

- Le javascript serait tellement plus simple si on pouvait accéder à l'ensemble des objets contenus dans une classe fille (genre comme dans un tableau).

- Mes fonctions game.draw et game.update sont pas génials je sais... Et je suis conscient qu'il y a encore beaucoup de choses à améliorer, comme intégrer le canvas dans l'objet game ect.. (en plus le jeu actuel est très simple, il y a même pas à gérer de sprites, j'ai choisi la facilité pour commencer)

Honnêtement, je suis encore loin du code que t'as fait pour Totorotale, mais j'ai l'impression d'avoir beaucoup progresser (si tu me permet de m'auto-jeter des fleurs). La manière dont j'ai programmé ce Snake change radicalement de tout ce que j'ai pu programmer avant, et surtout elle est bien mieux. J'ai fait beaucoup moins d'aller-retour dans le code, et j'ai l'impression que celui-ci est plus clair (et structurer, mais il me semble que c'était le but de tout ça). J'ai vraiment hâte de savoir ce que tu penses de ce code !




______________________________________________________
We're just an accident. We're just bad code. - Root
Display posts from previous:
Reply to topic Page 1 of 1
  



Index | Getting a forum | Free support forum | Free forums directory | Report a violation | Cookies | Charte | Conditions générales d'utilisation
Copyright 2008 - 2016 // Batch