Guest User

Untitled

a guest
May 4th, 2020
39
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1.  
  2. // 3 types of Assets
  3. const ASSET_TYPE = {
  4.   IMAGE  : 0x000001,
  5.   SPRITE : 0x000002,
  6.   AUDIO  : 0x000003
  7. };
  8.  
  9. // 7 types of game state
  10. const GAME_STATE = {
  11.   SETUP         : 0x000000,
  12.   WIN           : 0x000001,
  13.   GAME_OVER     : 0x000002,
  14.   CHOICE        : 0x000003,
  15.   DIALOG        : 0x000004,
  16.   ATTACK_PLAYER : 0x000005,
  17.   ATTACK_ENTITY : 0x000006,
  18. };
  19.  
  20. // 2 types of draw mode
  21. const SPRITE_DRAW = {
  22.   AUTO_INDEX : 0x1, // draw sprite automatically with index table (spriteSheet where sprite are all same dimension, need width and height opt)
  23.   AUTO_NAME  : 0x2  // draw sprite automatically with name table (spriteSheet where sprite are NOT all same dimension, need sprites opt)
  24. };
  25.  
  26.  
  27. // 4 types of UI Buttons
  28. const UI_BUTTON = {
  29.   FIGHT  : 0x1,
  30.   ACTION : 0x2,
  31.   ITEM   : 0x3,
  32.   MERCY  : 0x4
  33. };
  34.  
  35.  
  36. // 3 State when Fight
  37. const FIGHT_STATE = {
  38.   FIGHTBAR_MOVE    : 0x1,
  39.   ANIMATION_ATTACK : 0x2,
  40.   SHOW_DAMAGE      : 0x3
  41. };
  42.  
  43. // 3 STATE OF LIFE
  44. const LIFE_STATE = {
  45.   ALIVE : 0x1,
  46.   DYING : 0x2,
  47.   DIE   : 0x3
  48. };
  49.  
  50. // Add ProtoType
  51. CanvasRenderingContext2D.prototype.fillTextM = function(text, x, y) {
  52.   var lineHeight = this.measureText("M").width * 1.2;
  53.   var lines = text.split('\n');
  54.   for (var i = 0; i < lines.length; ++i) {
  55.     this.fillText(lines[i], x, y); y += lineHeight*1.8;
  56.   }
  57. }
  58. CanvasRenderingContext2D.prototype.roundRect = function(x, y, ww, hh, r) {
  59.   if (ww < 2 * r) r = ww / 2;
  60.   if (hh < 2 * r) r = hh / 2;
  61.   this.beginPath();
  62.   this.moveTo(x+r, y);
  63.   this.arcTo(x+ww, y, x+ww, y+hh, r);
  64.   this.arcTo(x+ww, y+hh, x, y+hh, r);
  65.   this.arcTo(x, y+hh, x, y, r);
  66.   this.arcTo(x, y, x+ww, y, r);
  67.   this.closePath();
  68.   this.fill();
  69. }
  70.  
  71.  
  72. var Utils = {
  73.   getRandomIntInclusive: function(min, max) {
  74.     min = Math.ceil(min);
  75.     max = Math.floor(max);
  76.     return Math.floor(Math.random() * (max - min +1)) + min;
  77.   }
  78. };
  79.  
  80.  
  81.  
  82. class Asset {
  83.  
  84.   constructor(type,path) {
  85.  
  86.     // le type de l'asset : image, son, ...etc...
  87.     this.type = type;
  88.  
  89.     // le chemin vers le fichier (exemple: son/exemple.wav ou image/sprite.png ...etc..)
  90.     this.path = path;
  91.  
  92.     // permet de savoir si notre asset est chargé ou pas (de base l'asset n'est pas chargé)
  93.     this.loaded = false;
  94.  
  95.   }
  96.  
  97.  
  98.   /**
  99.   * isLoad: renvoie true si l'asset est chargé, sinon renvoie false
  100.   */
  101.   isLoad() {
  102.     return this.loaded;
  103.   }
  104.  
  105.   /**
  106.   * load: on charge l'asset
  107.   */
  108.   load() {
  109.  
  110.     // Cette classe est une classe de base, on laisse les classes enfants définir cette méthode (elles vont overwrite cette méthode)
  111.     return new Promise(function(resolve, reject) {resolve();});
  112.  
  113.   }
  114.  
  115.   /**
  116.   * destroy: on détruit l'asset (ce qui peut soulager la mémoire)
  117.   */
  118.   destroy() {} // même remarque que pour la méthode load
  119.  
  120. }
  121.  
  122. class AudioAsset extends Asset {
  123.  
  124.   constructor(...args) {
  125.  
  126.     // nouvelle façon (avec le ES6) d'envoyer tout les arguments vers le constructeur de la classe parent (ici la classe Asset)
  127.     // on est obligé d'appeler le constructeur avec super (sinon ça renvoie une erreur)
  128.     super(...args);
  129.  
  130.     // on créé notre élément audio (on ne l'ajoute pas au dom, ce n'est pas nécessaire)
  131.     this.audio = document.createElement('audio');
  132.  
  133.   }
  134.  
  135.  
  136.   /**
  137.   * load: on charge le son
  138.   */
  139.   load() {
  140.  
  141.     // on stocke le scope actuelle (le this correspond à l'objet qui a été créé avec new)
  142.     var _this = this;
  143.  
  144.     // j'expliquerai plus tard comme l'utiliser, mais en gros, ça ce comporte comme un callback
  145.     return new Promise(function(resolve, reject) {
  146.  
  147.       // on load le son
  148.       _this.audio.src = _this.path;
  149.       _this.loaded = true;
  150.       _this.audio.load();
  151.  
  152.       // on appelle le "callback"
  153.       resolve();
  154.  
  155.     });
  156.  
  157.   }
  158.  
  159.   /**
  160.   * destroy: on détruit l'asset (ce qui peut soulager la mémoire)
  161.   */
  162.   destroy() {
  163.  
  164.     // on stop correctement le son
  165.     this.audio.pause();
  166.     this.audio.currentTime = 0;
  167.  
  168.     // on détruit l'objet
  169.     this.audio = null;
  170.  
  171.     // du coup, l'asset n'est plus charger
  172.     this.loaded = false;
  173.  
  174.   }
  175.  
  176. }
  177.  
  178. class ImageAsset extends Asset {
  179.  
  180.   constructor(...args) {
  181.     super(...args);
  182.  
  183.     // on créé simplement un objet Image
  184.     this.image = new Image();
  185.  
  186.   }
  187.  
  188.   /**
  189.   * load: on charge l'image
  190.   */
  191.   load() {
  192.     var _this = this;
  193.     return new Promise(function(resolve, reject) {
  194.  
  195.       // on utilise l'event onload pour savoir quand l'image a fini de se charger
  196.       _this.image.onload = function() {
  197.  
  198.         _this.loaded = true; // l'image a finit de se charger
  199.  
  200.         // on appele le "callback"
  201.         resolve();
  202.  
  203.       };
  204.  
  205.       // s'il y a une erreur lors du chargement (l'image n'existe plus, ...etc...)
  206.       _this.image.onerror = function() {
  207.  
  208.         _this.loaded = false; // on rédfinit loaded à false pour être sûr qu'elle soit bien à false
  209.         // (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)
  210.  
  211.         // on appelle la fonction qui gère les erreurs
  212.         reject();
  213.  
  214.       };
  215.  
  216.       // on commence a charger l'image
  217.       _this.image.src = _this.path;
  218.  
  219.  
  220.     });
  221.   }
  222.  
  223.   /**
  224.   * destroy: on détruit l'asset (ce qui peut soulager la mémoire)
  225.   */
  226.   destroy() {
  227.  
  228.     // on détruit simplement l'image
  229.     this.image = null;
  230.  
  231.     // du coup, l'asset n'est plus charger
  232.     this.loaded = false;
  233.  
  234.   }
  235.  
  236. }
  237.  
  238. class SpriteAsset extends ImageAsset {
  239.  
  240.   constructor(...args) {
  241.     super(...args);
  242.  
  243.     // 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)
  244.     var opt = args.length > 0 ? typeof args[args.length-1] === 'object' ? args[args.length-1] : {} : {};
  245.  
  246.     // ces paramètres sont uniquement pour les spritesheets avec tous les sprites à la même taille
  247.     this.width = typeof opt.width === 'number' ? opt.width : 1; // on définit la largeur d'une "sous-image"
  248.     this.height = typeof opt.height === 'number' ? opt.height : 1; // on définit la hauteur d'une "sous-image"
  249.  
  250.     // on définit le temps minimal avant de passer à la prochaine image dans le cas d'une animation
  251.     this.tick = typeof opt.tick === 'number' ? opt.tick :  1000/30; // par défaut 30FPS
  252.  
  253.     // ces paramètres sont uniquement pour les spritesheets avec des sprites de tailles différentes
  254.     // ils sont stockés dans un tableau comme ceci :
  255.     // [{
  256.     //   name: "sous-image-1"
  257.     //   x: 0, y: 0, w: 50, h: 50
  258.     // },{....
  259.     // x, y, w, h : c'est pour localiser la "sous-image" dans l'image
  260.     this.sprites = typeof opt.sprites !== 'undefined' ? opt.sprites : [];
  261.     this.spritesobj = {};
  262.     for (var i = 0; i < this.sprites.length; i++) {
  263.       this.spritesobj[this.sprites[i].name] = this.sprites[i]; // on stocke ça dans un objet pour faciliter l'accès
  264.     }
  265.  
  266.     // contient des index (dans le cas où les "sous-images" sont de même taille),
  267.     // ou les noms des "sous-images" (dans le cas où les "sous-images" sont de tailles différentes)
  268.     this.animation = typeof opt.animation === 'undefined' ? [] : opt.animation;
  269.  
  270.     this.animationIndex = 0; // index qui nous servira de repère dans le tableau animation
  271.  
  272.     // variable "temporaire" qui rend possible l'animation
  273.     this.time = Date.now();
  274.  
  275.   }
  276.  
  277.   /**
  278.   * getPosition: on récupère les positions x et y de la "sous-image" seulement avec son index
  279.   * (uniquement à utiliser dans le cas où tous les sprites sont de même tailles, ce qui est le cas la pluspart du temps)
  280.   * @param index {int} : l'indice de la "sous-image"
  281.   * @return {object} : on retourne la position en x et y de la "sous-image"
  282.   */
  283.   getPosition(index) {
  284.     return {
  285.       x:(index%(Math.ceil(this.image.width/this.width)))*this.width,
  286.       y:Math.floor(index/(Math.ceil(this.image.width/this.width)))*this.height
  287.     }
  288.   }
  289.  
  290.   /**
  291.   * getMaxIndex: on récupère l'indice maximale à partir de la taille des "sous-images" et de l'image
  292.   * (uniquement à utiliser dans le cas où tous les sprites sont de même tailles, ce qui est le cas la pluspart du temps)
  293.   * @return {int} : on retourne l'indice maximale
  294.   */
  295.   getMaxIndex() {
  296.     return Math.ceil(this.image.width/this.width) * Math.ceil(this.image.height/this.height) - 1;
  297.   }
  298.  
  299.   /**
  300.   * drawSpriteByIndex: on dessine la n-ème "sous-image" seulement avec l'indice
  301.   * (uniquement à utiliser dans le cas où tous les sprites sont de même tailles, ce qui est le cas la pluspart du temps)
  302.   * (ce qui automatise l'opération, on est quitte de chercher et de mettre nous-même les coordonnées)
  303.   * @param ctx {CanvasRenderingContext2D} : le context 2D du canvas
  304.   * @param index {int} : l'indice de la "sous-image"
  305.   * @params ...args {array} : x, y, width, height : endroit où on dessine l'image
  306.   */
  307.   drawSpriteByIndex(ctx,index,...args) {
  308.     var point = this.getPosition(index); // on récupère les coordonées à partir de l'indice
  309.     ctx.drawImage(this.image, point.x, point.y, this.width, this.height, ...args); // on dessine la "sous-image"
  310.   }
  311.  
  312.   /**
  313.   * getSprite: on récupère simplement la "sous-image" à parti de son nom
  314.   * (uniquement à utiliser dans le cas où tous les sprites sont de tailles différentes)
  315.   * @param name {string} : nom de la "sous-image"
  316.   * @return {object} : on retourne l'objet associé
  317.   */
  318.   getSprite(name) {
  319.     return this.spritesobj[name];
  320.   }
  321.  
  322.   /**
  323.   * drawSprite: on dessine la "sous-image" définit par son nom
  324.   * (uniquement à utiliser dans le cas où tous les sprites sont de tailles différentes)
  325.   * (ce qui automatise l'opération, on est quitte de chercher et de mettre nous-même les coordonnées)
  326.   * @param ctx {CanvasRenderingContext2D} : le context 2D du canvas
  327.   * @param name {string} : le nom de la "sous-image"
  328.   * @params ...args {array} : x, y, width, height : endroit où on dessine l'image
  329.   */
  330.   drawSprite(ctx,name,...args) {
  331.     ctx.drawImage(this.image, this.spritesobj[name].x, this.spritesobj[name].y, this.spritesobj[name].w, this.spritesobj[name].h, ...args);
  332.   }
  333.  
  334.   /**
  335.   * draw: on dessine et on anime la "sous-image" en précisant le mode avec les indices ou avec les noms
  336.   * cette fonction permet d'animer automatiquement un spritesheet
  337.   * @param ctx {CanvasRenderingContext2D} : le context 2D du canvas
  338.   * @param mode {SPRITE_DRAW} : le mode utilisé (avec les indices ou avec les noms)
  339.   * @params ...args {array} : x, y, width, height : endroit où on dessine l'image
  340.   */
  341.   draw(mode,ctx,...args) {
  342.  
  343.     // this.animation est censé contenir les indices ou les noms pour pouvoir animer
  344.     // (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)
  345.     if (this.animation.length == 0 && mode == SPRITE_DRAW.AUTO_NAME) return;
  346.  
  347.     // si on est là, c'est que le tableau this.animation contient les noms des "sous-images" à dessiner à la suite
  348.     // sinon elle est vide et on est dans le mode SPRITE_DRAW.AUTO_INDEX
  349.     // donc si le tableau est vide et qu'on est dans le mode SPRITE_DRAW.AUTO_INDEX, je dessine les "sous-images"
  350.     // dans l'ordre croissant 0,1,2,3....
  351.  
  352.  
  353.     // si le tableau est vide, j'utilise les indices dans l'odre croissant
  354.     // sinon je suis les indices (ou les noms) du tableau et this.animationIndex devient un indice pour itérer dans le tableau
  355.     var index = this.animation.length == 0 ? this.animationIndex : this.animation[this.animationIndex];
  356.  
  357.     // suivant le mode, on utilise la fonction adapté
  358.     switch (mode) {
  359.       case SPRITE_DRAW.AUTO_INDEX:
  360.         this.drawSpriteByIndex(ctx,index,...args);
  361.         break;
  362.       case SPRITE_DRAW.AUTO_NAME:
  363.         this.drawSprite(ctx,index,...args);
  364.         break;
  365.       default:
  366.  
  367.     }
  368.  
  369.     // on anime
  370.     if (Date.now() - this.time > this.tick) {
  371.  
  372.       this.time = Date.now(); // tu connais déjà ce principe :)
  373.  
  374.       // on incrémente l'indice pour dessiner la prochaine "sous-image"
  375.       this.animationIndex++;
  376.  
  377.       // 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())
  378.       if (this.animation.length == 0 && (this.getMaxIndex() == this.animationIndex - 1)) {
  379.  
  380.         this.animationIndex = 0; // on recommence l'animation
  381.  
  382.       // on regarde si notre indice correspond à la longueur du tableau, au quel cas, on recommence l'animation
  383.       } else if (this.animationIndex == this.animation.length) {
  384.  
  385.         this.animationIndex = 0; // on recommence l'animation
  386.  
  387.       }
  388.     }
  389.  
  390.   }
  391.  
  392.   /**
  393.   * isAnimationEnded: permet de savoir si l'animation est à la dernière "sous-image"
  394.   * @return {boolean} : on renvoie true si l'animation est terminé, sinon en renvoie false
  395.   */
  396.   isAnimationEnded() {
  397.     return (this.animation.length == 0 && (this.getMaxIndex() - 1 == this.animationIndex)) || (this.animationIndex == this.animation.length - 1);
  398.   }
  399.  
  400. }
  401.  
  402.  
  403. class AssetManager {
  404.  
  405.   constructor(game) {
  406.  
  407.     this.game = game; // l'objet qui contiendra tout
  408.     this.assets = {}; // toutes les assets seront stockés ici
  409.  
  410.     this.countLoadedSuccess = 0; // le nombre d'assets chargés avec succès
  411.     this.countLoadedError = 0; // le nombre d'assets non-chargés
  412.     this.countLoadedTotal = 0; // le nombre total d'assets chargés
  413.     this.totalToLoaded = 0; // le nombre total d'assets à charger
  414.  
  415.   }
  416.  
  417.   /**
  418.   * loadAll: permet de charger toutes les assets
  419.   * @param next {function} : le callback qui sera appelé une fois le chargement de toutes les assets terminés
  420.   */
  421.   loadAll(next) {
  422.     var _this = this;
  423.  
  424.     // on réinitialise les variables
  425.     this.countLoadedSuccess = 0; // le nombre d'assets chargés avec succès
  426.     this.countLoadedError = 0; // le nombre d'assets non-chargés
  427.     this.countLoadedTotal = 0; // le nombre total d'assets chargés
  428.  
  429.     // on itère sur toutes les assets
  430.     for (var name in this.assets) {
  431.       if (this.assets.hasOwnProperty(name)) {
  432.  
  433.         // on charge l'asset
  434.         this.assets[name].load()
  435.  
  436.         // l'interêt de la promise est ici, si le chargement est un succès,
  437.         // alors on appelle la fonction resolve (de la promise) qui nous envoie dans le "then"
  438.         .then(function(){
  439.           // load success
  440.           _this.countLoadedSuccess++;
  441.         })
  442.  
  443.         // sinon, c'est la fonction reject (de la promise) qui est appelé, et on arrive dans le catch
  444.         .catch(function(e){
  445.           // load fail
  446.           _this.countLoadedError++;
  447.           console.warn(e);
  448.         })
  449.  
  450.         // dans tout les cas, que ce soit un succès ou pas, on exécute le finally
  451.         .finally(function(){
  452.           // finally
  453.           _this.countLoadedTotal++;
  454.           if (_this.countLoadedTotal==_this.totalToLoaded) {
  455.             next(); // si tout est chargé on appelle le callback
  456.           }
  457.         });
  458.  
  459.  
  460.       }
  461.  
  462.     }
  463.   }
  464.  
  465.   /**
  466.   * addAsset: permet d'ajouter un asset
  467.   * @param name {string} : le nom de l'asset
  468.   * @param type {ASSET_TYPE} : le type de l'asset
  469.   * @params ...args {array} : le reste des arguments
  470.   */
  471.   addAsset(name,type,...args) {
  472.  
  473.     // on créé automatiquement l'asset adéquat selon le type
  474.     switch (type) {
  475.       case ASSET_TYPE.IMAGE:
  476.           this.assets[name] = new ImageAsset(type,...args);
  477.         break;
  478.       case ASSET_TYPE.AUDIO:
  479.           this.assets[name] = new AudioAsset(type,...args);
  480.         break;
  481.       case ASSET_TYPE.SPRITE:
  482.           this.assets[name] = new SpriteAsset(type,...args);
  483.         break;
  484.       default:
  485.           this.assets[name] = new Asset(type,...args);
  486.     }
  487.  
  488.     // on incrémente le nombre d'assets
  489.     this.totalToLoaded++;
  490.  
  491.   }
  492.  
  493.   /**
  494.   * removeAsset: permet de supprimer un asset
  495.   * @param name {string} : le nom de l'asset
  496.   */
  497.   removeAsset(name) {
  498.  
  499.     // on désincrémente le nombre d'assets
  500.     this.totalToLoaded--;
  501.  
  502.     // si l'asset a bien été chargé, on désincrémente this.countLoadedSuccess sinon this.countLoadedError
  503.     if (this.assets[name].isLoad()) {
  504.       this.countLoadedSuccess--;
  505.     } else {
  506.       this.countLoadedError--;
  507.     }
  508.  
  509.     // on détruit l'asset proprement
  510.     this.assets[name].destroy();
  511.  
  512.     // on l'enlève de l'objet
  513.     this.assets[name] = null;
  514.     delete this.assets[name];
  515.   }
  516.  
  517.   /**
  518.   * getAsset: permet de récupérer un asset
  519.   * @param name {string} : le nom de l'asset
  520.   */
  521.   getAsset(name) {
  522.     return this.assets[name];
  523.   }
  524.  
  525. }
  526.  
  527.  
  528.  
  529.  
  530.  
  531.  
  532.  
  533.  
  534. // Undertale UI Like
  535. class UI {
  536.  
  537.   /**
  538.   * constructor
  539.   * @param game {Game} : un instancier de la classe Game
  540.   * @return {UI}
  541.   */
  542.   constructor(game) {
  543.  
  544.     this.game = game;
  545.  
  546.     this.assetManager = this.game.assetManager;
  547.  
  548.     this.canvas = this.game.canvas;
  549.     this.ctx = this.game.ctx;
  550.  
  551.     // on stockera ici l'asset de l'UI
  552.     this.spriteSheet = null;
  553.  
  554.     // l'indice
  555.     this.currentChoice = 0;
  556.  
  557.     // le nombre de boutons
  558.     this.countButtons = 4;
  559.  
  560.     // le temps que dure le moment où le coeur du joueur se retrouve tout seul sur le fond noir (sans être briser)
  561.     this.gameOverKeepHeartDuration = 1200;
  562.     this.gameOverKeepHeartTime = Date.now();
  563.  
  564.     // le temps que dure le moment où le coeur se brise
  565.     this.gameOverKeepBreakHeartDuration = 500;
  566.     this.gameOverKeepBreakHeartTime = Date.now();
  567.  
  568.     // on stockera ici les différentes étapes de l'affiche du game over
  569.     this.gameOverState = 0x0;
  570.  
  571.  
  572.   }
  573.  
  574.   /**
  575.   * reset: permet de reset l'UI
  576.   */
  577.   reset() {
  578.  
  579.     this.gameOverState = 0x0;
  580.     this.currentChoice = 0;
  581.  
  582.   }
  583.  
  584.   /**
  585.   * gameOverPrepare: permet de préparer le game over
  586.   */
  587.   gameOverPrepare() {
  588.     // on passe à l'état 0x1 (j'aurais pu utiliser une énumération pour ça)
  589.     this.gameOverKeepHeartTime = Date.now();
  590.     this.gameOverKeepBreakHeartTime = Date.now();
  591.     this.gameOverState = 0x1;
  592.   }
  593.  
  594.   /**
  595.   * init: permet d'initialiser l'UI
  596.   */
  597.   init() {
  598.     // on récupère simplement l'asset
  599.     this.spriteSheet = this.assetManager.getAsset('interface.sprite');
  600.   }
  601.  
  602.   /**
  603.   * draw: permet de dessiner l'UI
  604.   */
  605.   draw() {
  606.  
  607.     // pour l'instant, on dessine les boutons
  608.     this.drawChoiceButton();
  609.  
  610.   }
  611.  
  612.   /**
  613.   * disable: permet de désactiver le choix du menu
  614.   */
  615.   disable() {
  616.     this.currentChoice = 0;
  617.   }
  618.  
  619.   /**
  620.   * enable: permet d'activer le choix du menu
  621.   * @param keyCodeToSelect {array} : contient la touche qui permet de naviguer vers la gauche, et la touche qui permet de naviguer vers la droite
  622.   * @param keyCodeToValidate {array} : contient les touches qui permettent de valider le choix
  623.   * @param next {function} : callback qui sera appelé une fois le choix fait
  624.   */
  625.   enable(keyCodeToSelect,keyCodeToValidate,next) {
  626.  
  627.     // par défaut, on sélectionne le 1er bouton
  628.     this.currentChoice = 1;
  629.  
  630.     // on stocke le scope "this"
  631.     var _this = this;
  632.  
  633.     // on prépare la création d'un event local (un event qui ne s'exécute qu'une seule fois)
  634.     var localevent;
  635.  
  636.     localevent = function(event) {
  637.  
  638.       // si on arrive ici, c'est que l'utilisateur a appuyé sur une touche
  639.  
  640.       // on regarde si le joueur a appuyé sur une touche permettant de valider le choix
  641.       for (var i = 0; i < keyCodeToValidate.length; i++) {
  642.  
  643.         if (keyCodeToValidate[i] == event.keyCode) {
  644.  
  645.           // si on arrive là, c'est que le joueur a validé son choix
  646.  
  647.           // on supprime toutes les touches
  648.           for (var j = 0; j < keyCodeToValidate.length; j++) {
  649.             _this.game.keyState[keyCodeToValidate[j]] = false;
  650.             delete _this.game.keyState[keyCodeToValidate[j]];
  651.           }
  652.  
  653.           // on appelle le callback
  654.           // si ce callback renvoie true, on supprime cette event, sinon on fait rien
  655.           if (next(_this.currentChoice)) {
  656.             document.removeEventListener('keydown', localevent);
  657.           }
  658.  
  659.           // on arrete la fonction ici
  660.           return;
  661.         }
  662.  
  663.       }
  664.  
  665.       // on regarde si l'utilisateur tente de naviguer dans le menu
  666.       for (var i = 0; i < keyCodeToSelect.length; i++) {
  667.  
  668.         if (keyCodeToSelect[i] == event.keyCode) {
  669.  
  670.           // si on arrive là, c'est que l'utilisateur appuie sur des bouttons permettant de naviguer dans le menu
  671.  
  672.           // comme on a que 2 bouttons dans le tableau, on définit arbitrairement que le 1er c'est pour aller à gauche
  673.           // et le 2ème pour aller à droite
  674.           if (i==0) {
  675.             _this.currentChoice--; // on va vers la gauche
  676.           } else {
  677.             _this.currentChoice++; // on va vers la droite
  678.           }
  679.  
  680.           // si on va trop à gauche, on revient tout à droite, si on va trop à droite, on revient tout à gauche (sinon on fait rien)
  681.           _this.currentChoice = _this.currentChoice < 1 ? _this.countButtons : _this.currentChoice > _this.countButtons ? 1 : _this.currentChoice;
  682.  
  683.         }
  684.  
  685.       }
  686.     };
  687.  
  688.     // on ajoute l'event
  689.     document.addEventListener('keydown', localevent);
  690.  
  691.   }
  692.  
  693.   /**
  694.   * drawChoiceButton: permet de dessiner les bouttons
  695.   */
  696.   drawChoiceButton() {
  697.  
  698.     // on récupère le canvas et le contexte
  699.     var canvas = this.canvas;
  700.     var ctx = this.ctx;
  701.  
  702.     // j'ai littéralement copier/coller ton code ici :D
  703.     var a = '#FF7E24';
  704.     var b = '#FF7E24';
  705.     var c = '#FF7E24';
  706.     var d = '#FF7E24';
  707.     if (this.currentChoice == 1) a = '#FFFF00';
  708.     if (this.currentChoice == 2) b = '#FFFF00';
  709.     if (this.currentChoice == 3) c = '#FFFF00';
  710.     if (this.currentChoice == 4) d = '#FFFF00';
  711.  
  712.     ctx.lineWidth = 1;
  713.     ctx.strokeStyle = a;
  714.     ctx.strokeRect(40, 400, 100, 40);
  715.     ctx.strokeStyle = b;
  716.     ctx.strokeRect(180, 400, 100, 40);
  717.     ctx.strokeStyle = c;
  718.     ctx.strokeRect(320, 400, 100, 40);
  719.     ctx.strokeStyle = d;
  720.     ctx.strokeRect(460, 400, 100, 40);
  721.  
  722.     ctx.font = '23px Verdana';
  723.     ctx.textAlign = 'right';
  724.     ctx.fillStyle = a;
  725.     ctx.fillText('FIGHT', 136, 429);
  726.     ctx.fillStyle = b;
  727.     ctx.fillText('ACT', 262, 429);
  728.     ctx.fillStyle = c;
  729.     ctx.fillText('ITEM', 410, 429);
  730.     ctx.font = '22px Verdana';
  731.     ctx.fillStyle = d;
  732.     ctx.fillText('MERCY', 557, 429);
  733.  
  734.     // là, on utilise les méthodes des assets (je te laisse faire des aller-retour pour comprendre ce qu'il se passe)
  735.     // ne tkt pas, on fait la classe Player juste après :)
  736.     if (this.currentChoice == 1) { this.game.player.drawHeart(52, 420); } else { this.spriteSheet.drawSprite(ctx, 'fight', 45, 405, 15, 28); }
  737.     if (this.currentChoice == 2) { this.game.player.drawHeart(199, 420); } else { this.spriteSheet.drawSprite(ctx, 'act', 192, 412, 15, 28); }
  738.     if (this.currentChoice == 3) { this.game.player.drawHeart(337, 420); } else { this.spriteSheet.drawSprite(ctx, 'item', 330, 408, 15, 28); }
  739.     if (this.currentChoice == 4) { this.game.player.drawHeart(471, 420); } else { this.spriteSheet.drawSprite(ctx, 'mercy', 463, 410, 15, 28); }
  740.  
  741.   }
  742.  
  743. }
  744.  
  745.  
  746. class Area {
  747.  
  748.   constructor(game) {
  749.  
  750.     this.game = game;
  751.  
  752.     this.canvas = this.game.canvas;
  753.     this.ctx = this.game.ctx;
  754.  
  755.     this.reset();
  756.  
  757.   }
  758.  
  759.   /**
  760.   * reset: on reset la zone
  761.   */
  762.   reset() {
  763.  
  764.     this.x = 0;
  765.     this.y = 0;
  766.     this.w = 520;
  767.     this.h = 130;
  768.  
  769.     this.xm = 0;
  770.     this.ym = 0;
  771.     this.wm = 0;
  772.     this.hm = 0;
  773.  
  774.     this.rw = 0;
  775.     this.rh = 0;
  776.  
  777.     // on simplifie l'utilisation d'affichage du message de status
  778.     this.statusMessageText = '';
  779.     this.statusMessageDisplay = true;
  780.  
  781.     this.playerHeartControl = false; // si c'est true, on dessine le coeur du joueur et on active les controls clavier
  782.  
  783.     this.resizenext = null; // callback appelé une fois que la zone a fini de se redimensionner
  784.  
  785.     this.resetFightBar(); // on reset la "fight bar"
  786.   }
  787.  
  788.   /**
  789.   * resize: permet de redimensionner la zone
  790.   * @param w {int} : la nouvelle longueur
  791.   * @param h {int} : la nouvelle hauteur
  792.   * @param next {function} : cette fonction sera appelée une fois que la zone aura fini de se redimensionner
  793.   */
  794.   resize(w,h,next) {
  795.     this.rw = w;
  796.     this.rh = h;
  797.     this.resizenext = next;
  798.   }
  799.  
  800.   /**
  801.   * changeStatusMessage: permet de changer le message status
  802.   * @param text {string} : le nouveau status
  803.   */
  804.   changeStatusMessage(text) {
  805.     this.statusMessageText = text;
  806.   }
  807.  
  808.   /**
  809.   * showStatusMessage: on affiche le message status
  810.   */
  811.   showStatusMessage() {
  812.     this.statusMessageDisplay = true;
  813.   }
  814.  
  815.   /**
  816.   * hideStatusMessage: on cache le message status
  817.   */
  818.   hideStatusMessage() {
  819.     this.statusMessageDisplay = false;
  820.   }
  821.  
  822.   /**
  823.   * draw: on dessine la zone
  824.   */
  825.   draw() {
  826.  
  827.     // on récupère le contexte 2D du canvas
  828.     var ctx = this.ctx;
  829.  
  830.     // on dessine le rectangle
  831.     ctx.strokeStyle = 'rgb(255,255,255)';
  832.     ctx.lineWidth = 2;
  833.     ctx.strokeRect(this.x, this.y, this.w, this.h);
  834.  
  835.     // si le message status est "activé", on le dessine
  836.     if (this.statusMessageDisplay) {
  837.       ctx.font = '14px KulminoituvaRegular';
  838.       ctx.fillStyle = 'rgb(255,255,255)';
  839.       ctx.textAlign = 'left';
  840.       ctx.fillTextM(this.statusMessageText, 50, 265);
  841.     }
  842.  
  843.     // on propage l'event
  844.     if (typeof this.ondraw === 'function') this.ondraw();
  845.  
  846.     // si on aactivé le control du coeur du joueur
  847.     if (this.playerHeartControl) {
  848.  
  849.       var s = ctx.globalAlpha; // on sauvegarde cette valeur
  850.  
  851.       // si le joueur est invulnerable, c'est qu'il s'est pris des dégats, on le rend transparent temporairement
  852.       if (!this.game.player.isVulnerable()) {
  853.         ctx.globalAlpha = 0.4;
  854.       }
  855.  
  856.       // on dessine le coeur du joueur
  857.       this.game.player.drawHeart(this.game.player.x+this.x,this.game.player.y+this.y);
  858.  
  859.       // on réinitialise le globalAlpha
  860.       if (!this.game.player.isVulnerable()) {
  861.         ctx.globalAlpha = s;
  862.       }
  863.  
  864.       // on fais bouger le coeur du joueur
  865.       this.game.player.update();
  866.  
  867.     }
  868.  
  869.   }
  870.  
  871.   /**
  872.   * resetFightBar: permet de reset la barre de fight
  873.   */
  874.   resetFightBar() {
  875.     this.fightBar = {
  876.       speed: 6,
  877.       x: 0,
  878.       y: 0,
  879.       hit: false,
  880.       miss: false,
  881.       state: FIGHT_STATE.FIGHTBAR_MOVE, // l'état de la barre
  882.       time: Date.now(),
  883.       tick: 125
  884.     };
  885.   }
  886.  
  887.   /**
  888.   * enablePlayer: permet d'activer les controles du coeur du joueur
  889.   */
  890.   enablePlayer() {
  891.     this.playerHeartControl = true;
  892.  
  893.     // on replace le coeur du joueur au centre
  894.     this.game.player.x = this.w/2;
  895.     this.game.player.y = this.h/2;
  896.   }
  897.  
  898.   /**
  899.   * enablePlayer: permet de désactiver les controles du coeur du joueur
  900.   */
  901.   disablePlayer() {
  902.     this.playerHeartControl = false;
  903.   }
  904.  
  905.   /**
  906.   * update: permet de mettre à jour la zone
  907.   */
  908.   update() {
  909.  
  910.     // comme c'est toi qui à fait ce code, je n'ai pas besoin de l'expliquer :)
  911.     // pour éviter de rendre le tout trop compliquer d'un coup, j'ai pas cherché à l'optimiser
  912.     var y = 0;
  913.     this.x = 300-this.w/2;
  914.     this.y = 300-this.h/2 + y;
  915.     this.xm = 300-this.rw/2;
  916.     this.ym = 300-this.rh/2 + y;
  917.     this.wm = this.rw;
  918.     this.hm = this.rh;
  919.     var v = 10;
  920.     for (var i = 0; i < v; i++) {
  921.       if (this.xm < this.x) this.x -= 1;
  922.       if (this.xm > this.x) this.x += 1;
  923.       if (this.ym < this.y) this.y -= 1;
  924.       if (this.ym > this.y) this.y += 1;
  925.       if (this.wm < this.w) this.w -= 1;
  926.       if (this.wm > this.w) this.w += 1;
  927.       if (this.hm < this.h) this.h -= 1;
  928.       if (this.hm > this.h) this.h += 1;
  929.     }
  930.  
  931.     // si la zone est redimensionner correctement, on appelle le callback this.resizenext s'il existe
  932.     if (this.xm == this.x && this.ym == this.y && this.wm == this.w && this.hm == this.h) {
  933.       if (typeof this.resizenext === 'function') {
  934.         this.resizenext();
  935.         this.resizenext = null;
  936.       }
  937.     }
  938.   }
  939.  
  940. }
  941.  
  942.  
  943. class Player {
  944.  
  945.   /**
  946.   * constructor
  947.   * @param game {Game} : un instancier de la classe Game
  948.   * @param name {string} : le nom du joueur
  949.   * @return {Player}
  950.   */
  951.   constructor(game,name) {
  952.  
  953.     this.game = game;
  954.  
  955.     this.assetManager = this.game.assetManager;
  956.  
  957.     this.canvas = this.game.canvas;
  958.     this.ctx = this.game.ctx;
  959.  
  960.     // on stocke ici l'asset du coeur
  961.     this.heartAsset = null;
  962.  
  963.     // on stocke les différents assets d'attaques (si jamais on veut une animation différente d'attaque)
  964.     this.attackAssets = {};
  965.  
  966.     // dégat de l'attaque lorsque le joueur vise à une précision de 100%
  967.     this.attack = 15;
  968.  
  969.     // servira pour la position du coeur et la taille du coeur
  970.     this.x = 0;
  971.     this.y = 0;
  972.     this.w = 13;
  973.     this.h = 13;
  974.  
  975.     // la vitesse du coeur
  976.     this.speed = 2;
  977.  
  978.     // le nom du joueur
  979.     this.name = name || 'Frisk';
  980.  
  981.     // son niveau
  982.     this.lvl = 1;
  983.  
  984.     // sa vie
  985.     this.health = 42;
  986.  
  987.     // sa vie maximale
  988.     this.healthMax = 42;
  989.  
  990.     // le temps d'invulnerabilité après avoir reçu des dégats
  991.     this.notTakeDamageDuration = 500; // 0.5 secondes
  992.     this.notTakeDamageTime = Date.now();
  993.  
  994.     // on stocke ici si le joueur est en vie ou pas
  995.     this.isDead = false;
  996.  
  997.   }
  998.  
  999.   /**
  1000.   * reset: permet de reset le joueur
  1001.   */
  1002.   reset() {
  1003.  
  1004.     // on reset sa vie
  1005.     this.health = this.healthMax;
  1006.  
  1007.     // on le remet à la vie
  1008.     this.isDead = false;
  1009.  
  1010.     // on pourrait tout aussi bien reset son niveau mais je n'ais pas implémenté cette partie, et je laisserai le faire :)
  1011.  
  1012.   }
  1013.  
  1014.   /**
  1015.   * addAttack: permet d'ajouter un asset d'attaque avec le nom de l'asset
  1016.   * @param name {string} : le nom de l'asset
  1017.   */
  1018.   addAttack(name) {
  1019.  
  1020.     // on récupère simplement l'asset
  1021.     this.attackAssets[name] = this.assetManager.getAsset(name);
  1022.  
  1023.   }
  1024.  
  1025.   /**
  1026.   * drawAttack: permet de dessiner (en animant) une attaque et appelle le callback next une fois l'animation terminée
  1027.   * @param name {string} : nom de l'attaque (correspond aussi au nom de l'asset)
  1028.   * @param mode {SPRITE_DRAW} : mode de dessin
  1029.   * @param next {function} : callback qui sera appelé après que l'animation soit terminée
  1030.   */
  1031.   drawAttack(name,mode,next,...args) {
  1032.  
  1033.     // on anime automatiquement l'asset attaque
  1034.     this.attackAssets[name].draw(mode,this.ctx,...args);
  1035.  
  1036.     // on regarde si elle est finie
  1037.     if (this.attackAssets[name].isAnimationEnded()) {
  1038.  
  1039.       // si oui, on appelle le callback
  1040.       next();
  1041.  
  1042.     }
  1043.  
  1044.   }
  1045.  
  1046.   /**
  1047.   * getRandomAttack: renvoie le nom d'une attaque de manière aléatoire
  1048.   * @return {string} : on retourne le nom de l'attaque
  1049.   */
  1050.   getRandomAttack() {
  1051.     var index = Utils.getRandomIntInclusive(0,Object.keys(this.attackAssets).length-1);
  1052.     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
  1053.   }
  1054.  
  1055.   /**
  1056.   * drawState: permet de dessiner l'état du joueur
  1057.   * on dessine sa barre de vie, son nom, ..etc..
  1058.   */
  1059.   drawState() {
  1060.     var ctx = this.ctx; // on récupère le contexte 2D
  1061.  
  1062.     // comme tu peux le voir j'ai simplement copier/coller ton code
  1063.  
  1064.     ctx.font = '12px KulminoituvaRegular';
  1065.     ctx.fillStyle = 'rgb(255,255,255)';
  1066.     ctx.textAlign = 'left';
  1067.     ctx.fillText(this.name, 40, 388);
  1068.     ctx.fillText('LV ' + this.lvl, 150, 388);
  1069.     ctx.fillText(this.health + ' / ' + this.healthMax, 280 + this.healthMax, 388);
  1070.  
  1071.     ctx.font = '10px KulminoituvaRegular';
  1072.     ctx.fillText('HP', 238, 387);
  1073.     ctx.fillStyle = 'rgb(255,0,0)';
  1074.     ctx.fillRect(258,376,this.healthMax,13);
  1075.     ctx.fillStyle = 'rgb(255,255,0)';
  1076.     ctx.fillRect(258,376,this.health,13);
  1077.  
  1078.   }
  1079.  
  1080.   /**
  1081.   * init: permet d'initialiser l'objet (elle est appelée une seule fois lorsque le jeu s'initialise)
  1082.   */
  1083.   init() {
  1084.     // on récupère simplement l'asset du coeur
  1085.     this.heartAsset = this.assetManager.getAsset('heart');
  1086.   }
  1087.  
  1088.   /**
  1089.   * isAlive: permet de savoir si le joueur est en vie
  1090.   * @return {boolean} : renvoie true si joueur est en vie sinon false
  1091.   */
  1092.   isAlive() {
  1093.     return !this.isDead;
  1094.   }
  1095.  
  1096.   /**
  1097.   * takeDamage: permet de faire prendre des dégats au joueur
  1098.   * @param damage {int} : les dégats
  1099.   */
  1100.   takeDamage(damage) {
  1101.  
  1102.     // si le joueur est invulnerable, on fait rien
  1103.     if (Date.now() - this.notTakeDamageTime <= this.notTakeDamageDuration) return;
  1104.  
  1105.     // sinon on soustrait les dégats à sa vie
  1106.     this.health -= damage;
  1107.  
  1108.     // si sa vie est égale ou plus petit que zéro, on considère le joueur comme mort (logique mdr)
  1109.     if (this.health <= 0) {
  1110.       this.health = 0; // on remet sa vie bien à 0
  1111.       this.isDead = true; // on notifie que le joueur est bien mort
  1112.       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
  1113.     }
  1114.  
  1115.   }
  1116.  
  1117.   /**
  1118.   * drawHeart: permet de dessiner le coeur du joueur
  1119.   * @param x {int} : position x du coeur
  1120.   * @param y {int} : position y du coeur
  1121.   * @param w {int} : largeur du coeur
  1122.   * @param h {int} : hauteur du coeur
  1123.   * @return {object} : on retourne l'objet {x:x,y:y,w:w,h:h}
  1124.   */
  1125.   drawHeart(x,y,w,h) {
  1126.  
  1127.     var ctx = this.ctx; // on récupère le Contexte 2D
  1128.  
  1129.     // si les paramètres x, y, w, ou h sont indéfinis, on les remplace par des valeurs "par défaut"
  1130.     // par conséquent, on peut très bien appeler cette méthode sans aucun arguments comme ceci : player.drawHeart()
  1131.     x = typeof x !== 'undefined' ? x : this.x;
  1132.     y = typeof y !== 'undefined' ? y : this.y;
  1133.     w = typeof w !== 'undefined' ? w : this.w;
  1134.     h = typeof h !== 'undefined' ? h : this.h;
  1135.  
  1136.     // comme le coeur est simplement une image, on a pas besoin de l'animer
  1137.     ctx.drawImage(this.heartAsset.image, x-w/2, y-h/2, w, h);
  1138.  
  1139.     // on retourne les valeurs utiliser pour dessiner le coeur
  1140.     return {x:x,y:y,w:w,h:h};
  1141.  
  1142.   }
  1143.  
  1144.   /**
  1145.   * isInArea: permet de tester si un rectangle est bien dans strictement dans la zone
  1146.   * @param x {int} : position x du rectangle
  1147.   * @param y {int} : position y du rectangle
  1148.   * @param w {int} : largeur du rectangle
  1149.   * @param h {int} : hauteur du rectangle
  1150.   * @return {boolean} : on retourne true si le rectangle est dans la zone, sinon on retourne fasle
  1151.   */
  1152.   isInArea(x,y,w,h) {
  1153.     w = typeof w !== 'undefined' ? w : this.w;
  1154.     h = typeof h !== 'undefined' ? h : this.h;
  1155.     var area = this.game.area;
  1156.     return x-w/2 > 0 && x+w/2 < area.w && y-h/2 > 0 && y+h/2 < area.h;
  1157.   }
  1158.  
  1159.   /**
  1160.   * move: permet de déplacer le coeur du joueur de point.x en x et de point.y en y
  1161.   * @param point {object} : point qui est sous la fome {x:...,y:...}
  1162.   */
  1163.   move(point) {
  1164.     // si le coeur reste dans la zone, on déplace le coeur sinon on fait rien
  1165.     if (this.isInArea(this.x+point.x,this.y+point.y)) {
  1166.       this.x += point.x;
  1167.       this.y += point.y;
  1168.     }
  1169.   }
  1170.  
  1171.   /**
  1172.   * isVulnerable: permet de savoir si le joueur est vulnerable
  1173.   * @return {boolean} : on renvoie true si le joueur est vulnerable sinon on renvoie false
  1174.   */
  1175.   isVulnerable() {
  1176.     return !this.invulnerable;
  1177.   }
  1178.  
  1179.   /**
  1180.   * checkHitArea: permet de savoir si le coeur est rentré en collision avec l'attaque de l'ennemi
  1181.   * @param attack {Attack} : un instancier de la classe Attack
  1182.   */
  1183.   checkHitArea(attack) {
  1184.  
  1185.     // si le joueur est invulnerable, on fait rien
  1186.     if (Date.now() - this.notTakeDamageTime <= this.notTakeDamageDuration) return;
  1187.  
  1188.     // si on arrive ici, alors le joueur n'est plus invulnerable
  1189.     this.invulnerable = false;
  1190.  
  1191.     // on récupère ce qui nous intéresse
  1192.     var ctx = this.game.ctx;
  1193.     var canvas = this.game.canvas;
  1194.     var area = this.game.area;
  1195.  
  1196.     // on récupère la zone du coeur (il faut faire attention à inverser le scale et le translate)
  1197.     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'));
  1198.  
  1199.     // on teste s'il y a un pixel blanc dans cette zone
  1200.     for (var i = 0; i < pixel.data.length; i+=4) {
  1201.       if (pixel.data[i] > 200 && pixel.data[i+1] > 200 && pixel.data[i+2] > 200) {
  1202.  
  1203.         // si oui, on applique les dégats au joueur
  1204.         this.takeDamage(attack.damage);
  1205.  
  1206.         // on "active" l'invulnerabilité
  1207.         this.notTakeDamageTime = Date.now();
  1208.  
  1209.         // on notifie le fait que le joueur soit invulnerable
  1210.         this.invulnerable = true;
  1211.  
  1212.       }
  1213.     }
  1214.   }
  1215.  
  1216.   /**
  1217.   * update: permet d'update le joueur
  1218.   * en particulier, on déplace le coeur à l'appui des touches
  1219.   */
  1220.   update() {
  1221.  
  1222.     // LEFT
  1223.     if (this.game.isKeyDown(37) || this.game.isKeyDown(81)) {
  1224.       this.move({x:-this.speed,y:0});
  1225.     }
  1226.  
  1227.     // RIGHT
  1228.     if (this.game.isKeyDown(39) || this.game.isKeyDown(68)) {
  1229.       this.move({x:this.speed,y:0});
  1230.     }
  1231.  
  1232.     // UP
  1233.     if (this.game.isKeyDown(38) || this.game.isKeyDown(90)) {
  1234.       this.move({x:0,y:-this.speed});
  1235.     }
  1236.  
  1237.     // DOWN
  1238.     if (this.game.isKeyDown(40) || this.game.isKeyDown(83)) {
  1239.       this.move({x:0,y:this.speed});
  1240.     }
  1241.   }
  1242.  
  1243. }
  1244.  
  1245.  
  1246. class Text {
  1247.  
  1248.   constructor(text,tick,keyCodeSkip) {
  1249.     this.text = text;
  1250.     this.tick = tick;
  1251.     this.keyCodeSkip = keyCodeSkip || [];
  1252.   }
  1253.  
  1254. }
  1255.  
  1256.  
  1257. // Undertale Dialog Like
  1258. class Dialog {
  1259.  
  1260.   /**
  1261.   * constructor
  1262.   * @param textObject {Text} : l'objet Text créer avec new Text()
  1263.   * @param next {function} : callback qui sera appelé une fois que le dialogue sera fini
  1264.   */
  1265.   constructor(textObject,next) {
  1266.  
  1267.     // notre objet Text
  1268.     this.textObject = textObject;
  1269.  
  1270.     // le texte qui sera affiché
  1271.     this.displayText = '';
  1272.  
  1273.     // on stocke ici l'indice
  1274.     this.index = 0;
  1275.  
  1276.     // on stocke le callback
  1277.     this.next = next || function(){};
  1278.  
  1279.     // tu sais à quoi sert cette variable :)
  1280.     this.time = Date.now();
  1281.  
  1282.     // une variable qui indiquera si le dialogue est fini ou pas
  1283.     this.ended = false;
  1284.  
  1285.   }
  1286.  
  1287.   /**
  1288.   * end: permet de terminer le dialogue (par exemple, à l'appui d'une touche, on appelle cette fonction et ça termine le dialogue)
  1289.   */
  1290.   end() {
  1291.     this.displayText = this.textObject.text; // on affiche le texte finale
  1292.     this.ended = true; // le dialogue est fini
  1293.     if (typeof this.next === 'function') this.next(); // on appelle le callback
  1294.   }
  1295.  
  1296.   /**
  1297.   * update: permet de mettre à jour le dialogue
  1298.   * @param game {Game} : un instancier de la classe Game
  1299.   */
  1300.   update(game) {
  1301.  
  1302.     // si le dialogue est fini, on a plus rien a mettre à jour
  1303.     if (this.ended) return;
  1304.  
  1305.     // à l'appui d'une touche, on termine le dialogue
  1306.     if (this.textObject.keyCodeSkip.length > 0) {
  1307.       if (game.isKeyDown(this.textObject.keyCodeSkip)) {
  1308.         this.end(); // force the dialog to end
  1309.       }
  1310.     }
  1311.  
  1312.     // on "anime" l'écriture du dialogue tous les "this.textObject.tick" millisecondes
  1313.     if (Date.now() - this.time > this.textObject.tick) {
  1314.       this.time = Date.now();
  1315.       this.displayText += this.textObject.text[this.index];
  1316.       this.index++;
  1317.       if (this.index == this.textObject.text.length) {
  1318.         this.ended = true;
  1319.         if (typeof this.next === 'function') this.next();
  1320.       }
  1321.     }
  1322.   }
  1323.  
  1324.   /**
  1325.   * draw: permet de dessiner le dialogue
  1326.   * @param ctx {CanvasRenderingContext2D} : le Contexte 2D du canvas
  1327.   */
  1328.   draw(ctx) {
  1329.     ctx.fillStyle = 'rgb(255,255,255)';
  1330.     ctx.beginPath();
  1331.     ctx.moveTo(416, 85);
  1332.     ctx.lineTo(380, 95);
  1333.     ctx.lineTo(416, 105);
  1334.     ctx.fill();
  1335.     ctx.fillStyle = 'rgb(255,255,255)';
  1336.     ctx.roundRect(415,50,155,80,13);
  1337.     ctx.font = '9px KulminoituvaRegular';
  1338.     ctx.fillStyle = 'rgb(0,0,0)';
  1339.     ctx.textAlign = 'left';
  1340.     ctx.fillTextM(this.displayText, 423, 69);
  1341.   }
  1342.  
  1343. }
  1344.  
  1345.  
  1346.  
  1347. // Undertale Attack Like
  1348. class Attack {
  1349.  
  1350.   constructor(data) {
  1351.  
  1352.     // si aucun argument n'est passé, data sera indéfiné, donc si on fait data['quelque_chose'] le script renverra une erreur,
  1353.     // pour empêcher cette erreur, on remplace data par {} donc le cas où data n'est pas défini
  1354.     data = data || {};
  1355.  
  1356.     // data.spriteSheets est un tableau qui contient le nom des assets
  1357.     this.spriteSheetsData = data.spriteSheets || [];
  1358.  
  1359.     // on organise ça dans un objet pour faciliter l'accès
  1360.     this.spriteSheets = {};
  1361.     for (var i = 0; i < this.spriteSheetsData.length; i++) {
  1362.       this.spriteSheets[this.spriteSheetsData[i]] = null;
  1363.     }
  1364.  
  1365.     // les dégats de l'attaque
  1366.     this.damage = typeof data.damage !== 'undefined' ? data.damage : 4;
  1367.  
  1368.     // tous les "events"
  1369.     this.oncreate = typeof data.oncreate === 'function' ? data.oncreate : function(){};
  1370.     this.onstart = typeof data.onstart === 'function' ? data.onstart.bind({parent:this,caller:'onstart'}) : function(){};
  1371.     this.onend = typeof data.onend === 'function' ? data.onend.bind({parent:this,caller:'onend'}) : function(){};
  1372.     this.ondraw = typeof data.ondraw === 'function' ? data.ondraw.bind({parent:this,caller:'ondraw'}) : function(){};
  1373.     this.onupdate = typeof data.onupdate === 'function' ? data.onupdate : function(){};
  1374.  
  1375.     // l'état local de l'attaque
  1376.     this.state = 'onstart';
  1377.  
  1378.     // on stocke les données temporaires de l'état ici
  1379.     this.stateData = {};
  1380.  
  1381.     // on propose à l'utilisateur de mettre ces variables dedans (ce qui est conseillé pour éviter de réécrire des variables existantes)
  1382.     this.data = {};
  1383.  
  1384.   }
  1385.  
  1386.   /**
  1387.   * reset: permet de reset l'attaque
  1388.   */
  1389.   reset() {
  1390.     this.state = 'onstart'; // on revient à l'état initiale de l'attaque
  1391.     this.stateData = {};
  1392.     this.oncreate();
  1393.   }
  1394.  
  1395.   /**
  1396.   * init: permet d'initialiser l'attaque
  1397.   * @param game {Game} : un instancier de la classe Game
  1398.   */
  1399.   init(game) {
  1400.  
  1401.     // on stocke les variables dont on aura besoin
  1402.     this.game = game;
  1403.     this.assetManager = this.game.assetManager;
  1404.     this.area = this.game.area;
  1405.     this.player = this.game.player;
  1406.  
  1407.     // on "reset" l'attaque
  1408.     this.oncreate();
  1409.  
  1410.   }
  1411.  
  1412.   /**
  1413.   * run: permet de faire "tourner l'attaque", cette fonction est exécuté à 60FPS
  1414.   */
  1415.   run() {
  1416.  
  1417.     // on récupère le scope actuel, le "this" correspond à notre objet attaque (construit avec new Attack())
  1418.     var _this = this;
  1419.  
  1420.     // on ecécute la fonction "onstart", "ondraw", ou "onend" (à 60FPS)
  1421.     this[this.state](function(__this){
  1422.  
  1423.       // ici le "__this", correspond au scope lorsque qu'on ".bind" les 3 fonctions,
  1424.       // donc le "__this" contient ceci : {parent:_this,caller:...} (le "_this" est le même que celui juste au-dessus)
  1425.       // ainsi, comme leur variable "caller" porte leur nom, on est en mesure de savoir quelle fonction est appelée
  1426.  
  1427.       // l'objectif ici, c'est de faire en sorte que cette fonction ne soit appelé seulement une fois
  1428.       // (je rappele qu'on est dans une fonction qui est exécuté une centaine de fois par seconde)
  1429.  
  1430.       // si "_this.stateData[__this.caller]" est défini, c'est que la fonction a déjà été appelé à l'état "__this.caller"
  1431.       if (typeof _this.stateData[__this.caller] !== 'undefined') return;
  1432.  
  1433.       // si on arrive là, c'est la fonction next n'a jamais été appelé à cet état de l'attaque, donc on fais en sorte,
  1434.       // que ce soit le 1er appelle mais aussi surtout le dernier appelle
  1435.       _this.stateData[__this.caller] = true;
  1436.  
  1437.       // on passe à la fonction suivante
  1438.       if (_this.state == 'onstart') {
  1439.         _this.state = 'ondraw';
  1440.       } else if (_this.state == 'ondraw') {
  1441.         _this.state = 'onend';
  1442.       } else if (_this.state == 'onend') {
  1443.         _this.reset(); // reset attack
  1444.       }
  1445.  
  1446.     });
  1447.   }
  1448.  
  1449.   draw() {
  1450.     if (typeof this.ondraw === 'function') this.ondraw();
  1451.   }
  1452.  
  1453.   update() {
  1454.     if (typeof this.onupdate === 'function') this.onupdate();
  1455.   }
  1456.  
  1457. }
  1458.  
  1459.  
  1460. // Undertale Entity Like
  1461. class Entity {
  1462.  
  1463.   /**
  1464.   * constructor : permet de construire un ennemi
  1465.   * @param data {object}
  1466.   * @return {Entity}
  1467.   */
  1468.   constructor(data) {
  1469.  
  1470.     // si aucun argument n'est passé, data sera indéfiné, donc si on fait data['quelque_chose'] le script renverra une erreur,
  1471.     // pour empêcher cette erreur, on remplace data par {} donc le cas où data n'est pas défini
  1472.     data = data || {};
  1473.  
  1474.     // la vie de l'entité
  1475.     this.health = data.health || 0;
  1476.  
  1477.     // la vie maximale que l'entité peut avoir
  1478.     this.healthMax = data.healthMax || this.health;
  1479.  
  1480.     // 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 ...)
  1481.     this.spriteSheetsData = data.spriteSheets || [];
  1482.  
  1483.     // on stocke ça dans un objet pour faciliter l'accès
  1484.     this.spriteSheets = {};
  1485.     for (var i = 0; i < this.spriteSheetsData.length; i++) {
  1486.       this.spriteSheets[this.spriteSheetsData[i]] = null;
  1487.     }
  1488.  
  1489.     // le nom de l'entité (ne tkt pas, il y a une méthode setName pour définir le nom)
  1490.     this.name = '';
  1491.  
  1492.  
  1493.  
  1494.     // quelques events
  1495.  
  1496.     // quand l'entité est créé (lors du reset du jeu et au début du jeu)
  1497.     this.oncreate = typeof data.oncreate === 'function' ? data.oncreate : function(){};
  1498.  
  1499.     // lorsque l'entité choisi une attaque, cette méthode est appelé, on a juste à retourner une attaque
  1500.     // par défaut, on renvoie une attaque de manière aléatoire
  1501.     this.onselectattack = typeof data.onselectattack === 'function' ? data.onselectattack : function(){
  1502.       var index = Utils.getRandomIntInclusive(0,Object.keys(this.attacks).length-1);
  1503.       return this.attacks[Object.keys(this.attacks)[index]];
  1504.     };
  1505.  
  1506.     // lorsque l'entité est dessinée, cette méthode est appelé
  1507.     this.ondraw = typeof data.ondraw === 'function' ? data.ondraw : function(){};
  1508.  
  1509.     // lorsque l'entité est mis à jour cette méthode est appelée
  1510.     this.onupdate = typeof data.onupdate === 'function' ? data.onupdate : function(){};
  1511.  
  1512.     // lorsque l'état global du jeu change, cette méthode est appelée
  1513.     this.onchangestate = typeof data.onchangestate === 'function' ? data.onchangestate : function(){};
  1514.  
  1515.     // on stocke toutes les autres données ici (ça évite de réécrire une variable déjà existente)
  1516.     this.data = {};
  1517.  
  1518.     // on stocke toutes les attaques ici (ce sont des instancier de la classe Attack)
  1519.     this.attacks = {};
  1520.  
  1521.     // on reset les variables lié à l'animation de l'entité lorsqu'elle se prend des dégats du joueur
  1522.     this.resetDamageVariable();
  1523.  
  1524.     // l'état se sa vie
  1525.     this.lifeState = LIFE_STATE.ALIVE;
  1526.  
  1527.     // son opacité
  1528.     this.opacity = 1;
  1529.  
  1530.     // on stocke ici l'objet dialogue (un instancier de la classe Dialog)
  1531.     this.dialog = null;
  1532.  
  1533.     // on créé l'entité
  1534.     this.oncreate();
  1535.  
  1536.   }
  1537.  
  1538.   /**
  1539.   * reset: on reste l'entité
  1540.   */
  1541.   reset() {
  1542.  
  1543.     // on reset sa vie
  1544.     this.health = this.healthMax;
  1545.  
  1546.     // on le remet en vie
  1547.     this.lifeState = LIFE_STATE.ALIVE;
  1548.  
  1549.     // on "détruit" l'objet dialogue s'il y en a un
  1550.     this.dialog = null;
  1551.  
  1552.     // on propage l'event reset aux attaques pour reset toutes les attaques
  1553.     for (var i in this.attacks) {
  1554.       if (this.attacks.hasOwnProperty(i)) {
  1555.         this.attacks[i].reset();
  1556.       }
  1557.     }
  1558.  
  1559.     // on reset les variables lié à l'animation de l'entité lorsqu'elle se prend des dégats du joueur
  1560.     this.resetDamageVariable();
  1561.  
  1562.     // on recréé l'entité
  1563.     this.oncreate();
  1564.   }
  1565.  
  1566.   /**
  1567.   * resetDamageVariable: permet de reset les variables lié à l'animation de l'entité lorsqu'elle se prend des dégats du joueur
  1568.   */
  1569.   resetDamageVariable() {
  1570.     this.damageTime = Date.now();
  1571.     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
  1572.     this.damageTransition = 0;
  1573.     this.damageLock = false;
  1574.     this.damageX = 0; // on fait "trembler" l'entité, on aura besoin d'une variable temporaire pour le déplacement en x
  1575.     this.opacity = 1;
  1576.   }
  1577.  
  1578.   /**
  1579.   * setName: permet de définir le nom de l'entité
  1580.   * @param name {string} : le nom de l'entité
  1581.   */
  1582.   setName(name) {
  1583.     this.name = name;
  1584.   }
  1585.  
  1586.   /**
  1587.   * getName: permet de récupérer le nom de l'entité
  1588.   * @return {string} : retourne le nom de l'entité
  1589.   */
  1590.   getName() {
  1591.     return this.name;
  1592.   }
  1593.  
  1594.   /**
  1595.   * init: permet d'initialiser l'entité (exécuter une seule fois lorsque le jeu s'initialise)
  1596.   * @param game {Game} : un instancier de la classe Game
  1597.   */
  1598.   init(game) {
  1599.  
  1600.     // on stocke uniquement ce qui nous intéresse
  1601.     this.game = game;
  1602.     this.assetManager = this.game.assetManager;
  1603.     this.canvas = this.game.canvas;
  1604.     this.ctx = this.game.ctx;
  1605.  
  1606.     // on récupère tous les assets
  1607.     for (var i = 0; i < this.spriteSheetsData.length; i++) {
  1608.       this.spriteSheets[this.spriteSheetsData[i]] = this.assetManager.getAsset(this.spriteSheetsData[i]);
  1609.     }
  1610.  
  1611.     // on récupère toutes les attaques (instancier de la classe Attack)
  1612.     // et on propage l'initialisation
  1613.     for (var name in this.attacks) {
  1614.       if (this.attacks.hasOwnProperty(name)) {
  1615.         this.attacks[name] = this.game.getAttack(name);
  1616.         this.attacks[name].init(this.game);
  1617.       }
  1618.     }
  1619.  
  1620.   }
  1621.  
  1622.   /**
  1623.   * isAlive: permet de savoir si l'entité est en vie
  1624.   */
  1625.   isAlive() {
  1626.     return this.lifeState == LIFE_STATE.ALIVE;
  1627.   }
  1628.  
  1629.   /**
  1630.   * addAttack: permet d'ajouter une attaque à l'entité
  1631.   * j'ai choisi arbitrairement le fait qu'il faut ajouter les attaques avant d'initialiser l'entité
  1632.   */
  1633.   addAttack(name) {
  1634.     this.attacks[name] = null;
  1635.   }
  1636.  
  1637.   /**
  1638.   * removeAttack: permet d'enlver une attaque
  1639.   */
  1640.   removeAttack(name) {
  1641.  
  1642.     // on supprime simplement l'objet
  1643.     this.attacks[name] = null;
  1644.     delete this.attacks[name];
  1645.  
  1646.   }
  1647.  
  1648.   /**
  1649.   * selectAttack: permet de sélectionner une attaque
  1650.   * @params ...args : correspond à tous les arguments
  1651.   */
  1652.   selectAttack(...args) {
  1653.  
  1654.     // on propage l'event et on propage aussi les arguments
  1655.     var attack = this.onselectattack(...args);
  1656.  
  1657.     // 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
  1658.     if (!(attack instanceof Attack)) throw 'Thre returned value of "onselectattack" must be a "new Attack()" Object';
  1659.  
  1660.     // 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
  1661.     return attack;
  1662.   }
  1663.  
  1664.   /**
  1665.   * takeDamage: permet d'appliquer les dégâts reçu sur la vie de l'entité
  1666.   * @param damage {int} : des dégâts
  1667.   * @param next {function} : ce callback sera appelé une fois que l'animation de se prendre les dégâts sera finie
  1668.   */
  1669.   takeDamage(damage,next) {
  1670.  
  1671.     // on stocke le scope actuel
  1672.     var _this = this;
  1673.  
  1674.     var ctx = this.game.ctx;
  1675.  
  1676.     // on change l'opacité de l'entité
  1677.     this.opacity = 0.8;
  1678.  
  1679.     // on fait "trembler" l'entité
  1680.     if (Date.now() - this.damageTime > 50) {
  1681.       this.damageTime = Date.now();
  1682.       if (this.damageShake == 0) {
  1683.         this.damageShake = 1;
  1684.         this.damageX -= 10;
  1685.       } else {
  1686.         this.damageShake = 0;
  1687.         this.damageX += 10;
  1688.       }
  1689.     }
  1690.  
  1691.     // j'ai simplement repris ton code ici
  1692.  
  1693.     ctx.font = '20px KulminoituvaRegular';
  1694.     ctx.fillStyle = 'rgb(255,0,0)';
  1695.     ctx.textAlign = 'center';
  1696.     ctx.fillText(damage, 380 + this.healthMax/2, 99);
  1697.  
  1698.     ctx.fillStyle = 'rgb(255,0,0)';
  1699.     ctx.fillRect(380,60,this.healthMax,10);
  1700.     ctx.fillStyle = 'rgb(0,255,0)';
  1701.     ctx.fillRect(380,60,this.health-this.damageTransition,10);
  1702.  
  1703.  
  1704.     // si this.damageTransition n'est pas égale à damage, alors l'entité n'a pas fini de se prendre les dégâts, on continue
  1705.     if (this.damageTransition < damage) {
  1706.  
  1707.       this.damageTransition++;
  1708.  
  1709.     } else if (!this.damageLock) {
  1710.  
  1711.       // 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
  1712.  
  1713.       this.damageLock = true; // donc on bloque
  1714.  
  1715.       // on attend un tout petit pour laisser le temps au joueur de bien voir la barre de vie de l'entité
  1716.       setTimeout(function () {
  1717.  
  1718.         // on enlève les dégâts à la vie
  1719.         _this.health -= damage;
  1720.  
  1721.  
  1722.         if (_this.health <= 0) {
  1723.           _this.health = 0;
  1724.  
  1725.           // pour faire en sorte que l'entité dise ces derniers mots, on met l'entité dans un état "entrain de mourrir"
  1726.           _this.lifeState = LIFE_STATE.DYING;
  1727.         }
  1728.  
  1729.         // on reset toutes les variables liés à l'animation des dégâts
  1730.         _this.resetDamageVariable();
  1731.  
  1732.         // on appelle le callback pour dire que l'animation où l'entité prend des dégâts est finie
  1733.         next();
  1734.  
  1735.       }, 600);
  1736.  
  1737.     }
  1738.  
  1739.   }
  1740.  
  1741.   /**
  1742.   * waitKeyDown: permet d'attendre l'appui d'une touche (attention, ceci ne doit pas être exécuté à 60FPS)
  1743.   * @param keyCodeSkip {array} : tableau des touches
  1744.   */
  1745.   waitKeyDown(keyCodeSkip) {
  1746.  
  1747.     // on stocke le scope this
  1748.     var _this = this;
  1749.  
  1750.     _this.waitKeyDownEventFn = function(event) {
  1751.  
  1752.       // si on arrive ici, c'est que l'utilisateur a appuyé sur une touche
  1753.  
  1754.       // on vérifie s'il a appuyé sur la touche attendu
  1755.       for (var i = 0; i < keyCodeSkip.length; i++) {
  1756.  
  1757.         if (keyCodeSkip[i] == event.keyCode) {
  1758.  
  1759.           // si oui, on supprime cet event
  1760.           document.removeEventListener('keydown', _this.waitKeyDownEventFn);
  1761.  
  1762.           // on "réinitialise" les touches
  1763.           for (var j = 0; j < keyCodeSkip.length; j++) {
  1764.             _this.game.keyState[keyCodeSkip[j]] = false;
  1765.             delete _this.game.keyState[keyCodeSkip[j]];
  1766.           }
  1767.  
  1768.           // on appelle la fonction qui se trouve dans le .then
  1769.           _this.resolvetmp();
  1770.  
  1771.           break;
  1772.         }
  1773.       }
  1774.  
  1775.     };
  1776.  
  1777.     // on pourra faire un .then
  1778.     return new Promise(function(resolve, reject) {
  1779.       _this.resolvetmp = resolve; // on stocke la fonction du .then ici
  1780.  
  1781.       // on démarre l'event
  1782.       document.addEventListener('keydown', _this.waitKeyDownEventFn);
  1783.  
  1784.     });
  1785.  
  1786.   }
  1787.  
  1788.   /**
  1789.   * draw: permet de dessiner l'entité
  1790.   */
  1791.   draw() {
  1792.  
  1793.     // dans un 1er temps, on propage l'event
  1794.     if (typeof this.ondraw === 'function') this.ondraw();
  1795.  
  1796.     // et on dessine le dialogue s'il y en a un
  1797.     if (this.dialog != null) {
  1798.       this.dialog.update(this.game);
  1799.       this.dialog.draw(this.game.ctx);
  1800.     }
  1801.  
  1802.   }
  1803.  
  1804.   /**
  1805.   * update: permet d'update l'entité
  1806.   */
  1807.   update() {
  1808.  
  1809.     // on propage simplement l'event
  1810.     if (typeof this.onupdate === 'function') this.onupdate();
  1811.  
  1812.   }
  1813.  
  1814.   /**
  1815.   * destroyDialog: permet de détruire le dialogue
  1816.   */
  1817.   destroyDialog() {
  1818.     this.dialog = null;
  1819.   }
  1820.  
  1821.   /**
  1822.   * displayDialog: permet d'afficher un dialogue
  1823.   * @param textObject {Text} : instancier de la classe Text
  1824.   * @param next {function} : callback qui sera exécuté une fois que le dialogue sera fini
  1825.   */
  1826.   displayDialog(textObject,next) {
  1827.  
  1828.     // si le dialogue existe déjà, on fait rien
  1829.     if (this.dialog != null) {
  1830.       return;
  1831.     }
  1832.  
  1833.     // on créé le dialogue
  1834.     this.dialog = new Dialog(textObject,next);
  1835.  
  1836.   }
  1837.  
  1838. }
  1839.  
  1840.  
  1841.  
  1842.  
  1843.  
  1844.  
  1845.  
  1846.  
  1847.  
  1848.  
  1849.  
  1850.  
  1851.  
  1852.  
  1853.  
  1854.  
  1855.  
  1856.  
  1857. class Game {
  1858.  
  1859.   constructor() {
  1860.  
  1861.     // on créé le canvas
  1862.     this.canvas = document.createElement('canvas');
  1863.  
  1864.     // on met le background en noir
  1865.     this.canvas.style.background = '#000000';
  1866.  
  1867.     // on récupère le contexte 2D
  1868.     this.ctx = this.canvas.getContext('2d');
  1869.  
  1870.     // contiendra toutes sortes de données
  1871.     this.data = {};
  1872.  
  1873.     // contiendra l'état des touches
  1874.     this.keyState = {};
  1875.  
  1876.     // contiendra tous les ennemies
  1877.     this.ennemies = {};
  1878.  
  1879.     // contiendra toutes les attaques
  1880.     this.attacks = {};
  1881.  
  1882.     // variable temporaire pour mieux gérer les transitons (j'expliquerai ça un peu plus tard)
  1883.     this.transitions = {};
  1884.     this.transitionstext = {};
  1885.  
  1886.     // on stock simplement le translate et le scale
  1887.     this.pointTranslate = {
  1888.       x: 0,
  1889.       y: 0
  1890.     };
  1891.     this.pointScale = {
  1892.       x: 1,
  1893.       y: 1
  1894.     };
  1895.  
  1896.     // on stocke ici l'ennemy courant
  1897.     this.currentEnnemy = null;
  1898.  
  1899.     // l'état global du jeu
  1900.     this.state = null;
  1901.  
  1902.     // l'asset manager
  1903.     this.assetManager = new AssetManager(this);
  1904.  
  1905.     // la zone où seront dessinés les attaques et le coeur du joueur
  1906.     this.area = new Area(this);
  1907.  
  1908.     // le joueur
  1909.     this.player = new Player(this);
  1910.  
  1911.     // l'UI qui gérera les boutons fight, act, item et mercy
  1912.     this.UI = new UI(this);
  1913.  
  1914.     // on ajoute enfin le canvas dans le DOM
  1915.     document.body.appendChild(this.canvas);
  1916.  
  1917.   }
  1918.  
  1919.   reset() {
  1920.     this.transitions = {};
  1921.     this.transitionstext = {};
  1922.     this.keyState = {};
  1923.     for (var i in this.ennemies) {
  1924.       if (this.ennemies.hasOwnProperty(i)) {
  1925.         this.ennemies[i].reset();
  1926.       }
  1927.     }
  1928.     this.player.reset();
  1929.     this.area.reset();
  1930.     this.UI.reset();
  1931.   }
  1932.  
  1933.   isKeyDown(code) {
  1934.     if (typeof code === 'object') {
  1935.       for (var i in code) {
  1936.         if (code.hasOwnProperty(i)) {
  1937.           if (this.keyState[code[i]]) return true;
  1938.         }
  1939.       }
  1940.       return false;
  1941.     }
  1942.     return this.keyState[code];
  1943.   }
  1944.  
  1945.   startEvents() {
  1946.     var _this = this;
  1947.     document.addEventListener('keydown',function(event){
  1948.       _this.keyState[event.keyCode || event.which] = true;
  1949.     });
  1950.     document.addEventListener('keyup',function(event){
  1951.       _this.keyState[event.keyCode || event.which] = false;
  1952.     });
  1953.   }
  1954.  
  1955.   addAttack(name,attack) {
  1956.     this.attacks[name] = attack;
  1957.   }
  1958.  
  1959.   removeAttack(name) {
  1960.     this.attacks[name] = null;
  1961.     delete this.attacks[name];
  1962.   }
  1963.  
  1964.   getAttack(name) {
  1965.     return this.attacks[name];
  1966.   }
  1967.  
  1968.   addEnnemy(name,entity) {
  1969.     this.ennemies[name] = entity;
  1970.     this.ennemies[name].setName(name);
  1971.   }
  1972.  
  1973.   removeEnnemy(name) {
  1974.     this.ennemies[name] = null;
  1975.     delete this.ennemies[name];
  1976.   }
  1977.  
  1978.   getEnnemy(name) {
  1979.     return this.ennemies[name];
  1980.   }
  1981.  
  1982.   getCurrentEnnemy() {
  1983.     return this.ennemies[this.currentEnnemy];
  1984.   }
  1985.  
  1986.   setCurrentEnnemy(name) {
  1987.     this.currentEnnemy = name;
  1988.   }
  1989.  
  1990.   /**
  1991.   * transition: permet d'effectuer une transition (doit être exécutée à 60FPS pour fonctionner)
  1992.   * @param name {string} : le nom de la transition (son ID)
  1993.   * @param object {object} : un objet de la forme {from:...,to:...,step:...}
  1994.   * @param draw {function} : function qui sera exécutée pour dessiner la transtion (son paramètre est la valeur actuelle de la transtion)
  1995.   * la fonction est donc de cette forme:
  1996.   *    function (value) {
  1997.   *      console.log('valeur actuelle de la transtion:',value)
  1998.   *    }
  1999.   * @param next {function} : function qui sera exécutée lorsque la transtion est finie (cette fonction est exécutée à 60FPS)
  2000.   * (remarque: la fonction draw continuera aussi d'être exécutée)
  2001.   */
  2002.   transition(name,object,draw,next) {
  2003.     if (typeof this.transitions[name] === 'undefined') {
  2004.       this.transitions[name] = object.from;
  2005.     }
  2006.     this.transitions[name] += (object.from < object.to ? Math.abs(object.step) : -Math.abs(object.step)) || 1;
  2007.     if (object.from < object.to) {
  2008.       if (this.transitions[name] > object.to) {
  2009.         this.transitions[name] = object.to;
  2010.         if (typeof next === 'function') next();
  2011.       }
  2012.     } else {
  2013.       if (this.transitions[name] < object.to) {
  2014.         this.transitions[name] = object.to;
  2015.         if (typeof next === 'function') next();
  2016.       }
  2017.     }
  2018.     draw(this.transitions[name]);
  2019.   }
  2020.  
  2021.   /**
  2022.   * drawTextTransition: permet d'effectuer une transition (doit être exécutée à 60FPS pour fonctionner)
  2023.   * @param name {string} : le nom de la transition (son ID)
  2024.   * @param object {object} : un objet de la forme {text:...,tick:...}
  2025.   * @param draw {function} : function qui sera exécutée pour dessiner la transtion (son paramètre est la valeur actuelle de la transtion)
  2026.   * la fonction est donc de cette forme:
  2027.   *    function (value) {
  2028.   *      console.log('valeur actuelle de la transtion:',value)
  2029.   *    }
  2030.   * @param next {function} : function qui sera exécutée lorsque la transtion est finie (cette fonction est exécutée à 60FPS)
  2031.   * (remarque: la fonction draw continuera aussi d'être exécutée)
  2032.   */
  2033.   drawTextTransition(name,object,draw,next) {
  2034.     if (typeof this.transitionstext[name] === 'undefined') {
  2035.       this.transitionstext[name] = {
  2036.         text: '',
  2037.         index: 0,
  2038.         time: Date.now()
  2039.       };
  2040.     }
  2041.     if (Date.now() - this.transitionstext[name].time > (object.tick || 100)) {
  2042.       this.transitionstext[name].time = Date.now();
  2043.       if (this.transitionstext[name].index < (object.text||'undefined').length) {
  2044.         this.transitionstext[name].text += (object.text||'undefined')[this.transitionstext[name].index];
  2045.         this.transitionstext[name].index++;
  2046.       }
  2047.     }
  2048.     draw(this.transitionstext[name].text);
  2049.     if (this.transitionstext[name].index == (object.text||'undefined').length) {
  2050.       if (typeof next === 'function') next();
  2051.     }
  2052.   }
  2053.  
  2054.   translate() {
  2055.     this.ctx.translate(this.pointTranslate.x,this.pointTranslate.y);
  2056.   }
  2057.  
  2058.   scale() {
  2059.     this.ctx.scale(this.pointScale.x,this.pointScale.y);
  2060.   }
  2061.  
  2062.   /**
  2063.   * changeState: permet de changer l'état du jeu
  2064.   * @param state {GAME_STATE} : l'état du jeu
  2065.   */
  2066.   changeState(state) {
  2067.  
  2068.     // on stocke le nouvelle état
  2069.     this.state = state;
  2070.  
  2071.     // on propage le changement de l'état partout
  2072.     for (var name in this.ennemies) {
  2073.       if (this.ennemies.hasOwnProperty(name)) {
  2074.         if (typeof this.ennemies[name].onchangestate === 'function') this.ennemies[name].onchangestate(state);
  2075.       }
  2076.     }
  2077.     if (typeof this.onchangestate === 'function') this.onchangestate(state);
  2078.  
  2079.   }
  2080.  
  2081.   /**
  2082.   * run: permet de lancer le jeu
  2083.   */
  2084.   run() {
  2085.  
  2086.     // on initialise tout
  2087.     for (var name in this.ennemies) {
  2088.       if (this.ennemies.hasOwnProperty(name)) {
  2089.         this.ennemies[name].init(this);
  2090.       }
  2091.     }
  2092.     this.player.init(this);
  2093.     this.UI.init(this);
  2094.  
  2095.     // on lance la fonction qui fera tourner notre jeu (cette fonction sera exécuté une centaine de fois par seconde)
  2096.     this.update();
  2097.   }
  2098.  
  2099.   waitKeyDown(keyCodeSkip) {
  2100.     var _this = this;
  2101.     _this.waitKeyDownEventFn = function(event) {
  2102.       if (keyCodeSkip.length == 0) {
  2103.         document.removeEventListener('keydown', _this.waitKeyDownEventFn);
  2104.         _this.keyState[event.keyCode] = false;
  2105.         delete _this.keyState[event.keyCode];
  2106.         _this.resolvetmp();
  2107.         return;
  2108.       }
  2109.       for (var i = 0; i < keyCodeSkip.length; i++) {
  2110.         if (keyCodeSkip[i] == event.keyCode) {
  2111.           document.removeEventListener('keydown', _this.waitKeyDownEventFn);
  2112.           for (var j = 0; j < keyCodeSkip.length; j++) {
  2113.             _this.keyState[keyCodeSkip[j]] = false;
  2114.             delete _this.keyState[keyCodeSkip[j]];
  2115.           }
  2116.           _this.resolvetmp();
  2117.           break;
  2118.         }
  2119.       }
  2120.     };
  2121.     return new Promise(function(resolve, reject) {
  2122.       _this.resolvetmp = resolve;
  2123.       document.addEventListener('keydown', _this.waitKeyDownEventFn);
  2124.     });
  2125.   }
  2126.  
  2127.   registerData(name, value) {
  2128.     this.data[name] = value;
  2129.   }
  2130.  
  2131.   retrieveData(name) {
  2132.     return (typeof this.data[name] === 'undefined' ? null : this.data[name]);
  2133.   }
  2134.  
  2135.   addAsset(...args) {
  2136.     this.assetManager.addAsset(...args);
  2137.   }
  2138.  
  2139.   removeAsset(...args) {
  2140.     this.assetManager.removeAsset(...args);
  2141.   }
  2142.  
  2143.   load(next) {
  2144.  
  2145.     this.assetManager.loadAll(next);
  2146.  
  2147.   }
  2148.  
  2149.   update() {
  2150.     var _this = this;
  2151.     window.requestAnimationFrame(function(){_this.update();});
  2152.     if (typeof this.onUpdate === 'function') this.onUpdate();
  2153.   }
  2154.  
  2155. }
Add Comment
Please, Sign In to add comment