Tirer un faisceau laser

pour Andy (Rosny-sous-bois)

Les capacités spéciales permettent de donner au joueur un sentiment de puissance et peuvent lui permettre de surmonter les obstacles du jeu de façon créative. Si elles sont trop puissantes, les capacités spéciales peuvent nuire à l'expérience de jeu en réduisant sa part de challenge, tandis que lorsqu'elles sont rares, elles peuvent être une surprise mémorable pour le joueur. Pour mesurer l'usage d'une capacité, il est possible de l'associer à un objet à trouver dans le jeu, ou encore ralentir le temps de rechargement du pouvoir pour obliger le joueur à en mesurer l'usage.

Le rayon laser peut être utilisé comme une capacité offensive du joueur pour faire disparaître les ennemis, mais aussi comme un moyen d'éliminer certains blocs et ainsi débloquer certains passages.

Importer des effets sonores

Tu peux réaliser tes effets sonores toi-même, où utiliser ceux-ci dessous (click droit -> enregistrer-sous).

Charger le son dans le programme

Importe ensuite ton son dans ton projet en la plaçant dans le bon dossier : assets/sounds/. Puis, ajoute cette ligne au début de ton script game.js.

// chargement des sons du jeu
loadSound( "hit"    , "assets/sounds/hit.mp3"    ) ;
loadSound( "portal" , "assets/sounds/portal.mp3" ) ;

loadSound( "mystic" , "assets/sounds/mystic.mp3" ) ; loadSound( "dune" , "assets/sounds/dune.mp3" ) ;

Créer un nouveau composant

Pour créer cette capacité, nous allons devoir créer un composant. Celui-ci comprend beaucoup de paramètres, tu peux les modifier si tu le souhaites, mais attention au résultat ! Rends-toi dans le script component.js et ajoutes-y cette fonction.

function laserBeam( laserBeamKey = "space" ){ const bh = 40 , bw = 360 ; // taille du faiseau const lt = 0.8 ; // durée de l'animation de lancement const sul = 0.8 ; // vitesse à laquelle l'énergie se vide const srl = 2 ; // vitesse à laquelle l'énergie se remplie //rotation du personnage const rla = 10 , rls = 30 ; // pendant le lancement : amplitude, vitesse const rfa = 6 , rfs = 20 ; // pendant le faisceau : amplitude, vitesse // animation du faisceau const fsw = 1600 ; // vitesse de déploiement horizontal du faisceau const fsrw = 800 , fsrh = 1000 ; // vitesse de retrécissement horizontal,vertical const hv = 10 , sv = 20 ; // variation de hauteur : amplitude, vitesse // opacité du faisceau const opa = 0.1 , ops = 30 ; // variation de hauteur : amplitude, vitesse; // taille du cercle de chargement const cr = 60 , cm = 30 ; // taille maximale, taille minimale ; // animation caméra const vibrations = 4 ; // effets sonores const fx = play("dune", {volume: 0.2,}) ; const fx1 = play("mystic", {volume: 0.2,}) ; // faisceau const beam = add([ rect(0,0), color(155,255,255), area(), pos(0, 0), origin("left"), opacity(), ]); // lancement const loadingBeam = add([ circle(1), color(155,255,255), pos(0, 0), origin("bot"), opacity(), ]); let loading = 0 , energy = 1 ; let isOn = false , isReady = true ; let lastPX = 0 ; return { add(){ beam.onCollide("destructible" , (d) => { destroy(d); play("hit"); }); }, update(){ if ( isKeyDown(laserBeamKey) && isReady ){ if (isOn) this.shoutBeam(); else this.loadBeam() } else this.resetBeam(); loadingBeam.radius = (loading/lt * (cr-cm) ) + cm ; loadingBeam.opacity = loading/lt; loadingBeam.pos = this.pos; beam.pos = this.pos; camScale(1+loading*0.25); if(lastPX>this.pos.x) beam.origin = 'right' ; if(lastPX<this.pos.x) beam.origin = 'left' ; if(!isKeyDown(laserBeamKey)) isReady = true ; lastPX = this.pos.x ; }, shoutBeam(){ beam.height = Math.sin(time()*sv) * hv + bh; beam.width = Math.min(bw, beam.width + dt() * fsw); beam.opacity = Math.sin(time()*ops)*opa+(1-opa); energy = Math.max( 0 , energy-dt() * sul ) ; this.angle = Math.sin(time()*rfs)*rfa; shake(vibrations); if (energy==0) isReady=false; }, loadBeam(){ fx1.play(); loading += dt(); this.angle = Math.sin(loading/lt*rls)*rla*loading/lt; if (loading>lt) { isOn = true ; loading = 0 ; loadingBeam.radius = 1 ; fx.play(); } }, resetBeam(){ if (isOn) shake(0); fx.stop(); fx1.stop(); loading = Math.max(0, loading - dt() * 10); beam.height = Math.max(0, beam.height - dt() * fsrh); beam.width = Math.max(0, beam.width - dt() * fsrw); energy = Math.min(1, energy + dt() * srl ) ; this.angle = 0 ; loadingBeam.radius = 1 ; isOn = false ; }, } }

Ajouter le nouveau composant

Nous devons maintenant ajouter ce nouveau composant au joueur. Dans le script game.js, à l'intérieur de la scène game, trouve la variable player et ajoutes-y cette ligne.

// définition du joueur
const player = add([

  pos(0,0) ,
  area() ,
  body() ,
  controller() ,
  sprite( "bean" ) ,
  origin( "center" ) ,

  laserBeam() ,

  "player" ,

]);

Enfin, il ne te reste plus qu'à ajouter le label destructible à tous les objets qui peuvent être détruits par cette capacité.

facultatif : Afficher une barre d'énergie

Tu peux choisir d'afficher une barre pour que ton joueur puisse voir l'état de sa jauge d'énergie. Ce peut-être un moyen de lui signifier l'existence de cette capacité, ou de lui montrer l'état de sa jauge si l'utilisation de la capacité est limitée.

Créer un nouveau composant

Ce composant va nous permettre d'afficher la jauge. Tu vas pouvoir l'utiliser pour cette capacité mais aussi pour d'autres si tu le souhaites. Pour cela, ajoute cette fonction dans le script component.js.

function UImeter( x = 14 , y = 14 , w = 160 , h = 30 , op = 4){ let value = 0 ; const bgRect = add([ rect(w,h,{radius:6}), color(255,255,255), opacity(0.2), outline(op), fixed(), pos(x, y), ]); const valueRect = add([ rect(w-op,h-op,{radius:4}), color(155,255,255), fixed(), pos(x+2, y+2), ]); return { update(){ valueRect.width = value * (w-op) ; }, setValue(v){ value = v ; } } }

Ajouter le nouveau composant

Nous allons maintenant ajouter ce nouveau composant au joueur. Retourne dans le script game.js, à l'intérieur de la scène game, puis ajoute cette ligne dans la variable player.

// définition du joueur
const player = add([

  pos(0,0) ,
  area() ,
  body() ,
  controller() ,
  sprite( "bean" ) ,
  origin( "center" ) ,
  laserBeam() ,

  UImeter() ,

  "player" ,
]);

La barre est maintenant visible mais ne représente pas encore l'état de l'effet.

Mettre à jour la valeur

Pour que cette jauge affiche la bonne valeur, rends-toi à nouveau dans le script component.js et ajoute cette simple ligne dans le composant laserBeam.

update(){
        
  if ( isKeyDown("space") && isReady ){
          
    if (isOn) this.shoutBeam();
    else      this.loadBeam()
          
  }
  else this.resetBeam();
        
  loadingBeam.radius  = (loading/lt * (cr-cm) ) + cm ;
  loadingBeam.opacity = loading/lt;
  loadingBeam.pos     = this.pos;
  beam.pos            = this.pos;
        
  camScale(1+loading*0.25);
        
  if(isKeyPressed('left'))  beam.origin='right';
  if(isKeyPressed('right')) beam.origin='left';
  if(!isKeyDown("space"))   isReady=true;
        
  this.setValue(energy) ;
        
},

BONUS : Créer des blocs de sable

Il peut être intéressant que cette nouvelle aptitude du joueur ait un impact sur l'environnement du jeu. Le hack ci-dessous te propose de créer un bloc destructible avec un composant qui le rend sensible à la gravité, comme dans l'exemple en haut de la page.

Importer une image

Pour commencer, il va te falloir une image. Réalises-en une si tu ne l'as pas déjà fait, ou utilise celle ci-dessous (click droit -> enregistrer-sous).

Charger l'image dans le programme

Importe ensuite ton image dans ton projet en la plaçant dans le bon dossier : assets/sprites/. Puis, ajoute cette ligne au début de ton script game.js, comme suit.

// chargement des images du jeu
loadSprite( "bean"   , "assets/sprites/bean.png"   ) ;
loadSprite( "grass"  , "assets/sprites/grass.png"  ) ;
loadSprite( "portal" , "assets/sprites/portal.png" ) ;

loadSprite( "sand"   , "assets/sprites/sand.png"   ) ;

Déclarer un symbole

Nous allons maintenant déclarer un nouveau type d'objet que tu vas pouvoir placer dans ton jeu. Dans cet exemple nous avons choisi le caractère S. Dans le script level.js, trouve la variable LEVEL_CONFIG et ajoutes-y le code ci-dessous, comme suit.

// défini ce à quoi correspond chaque symbole dans le niveau
const LEVEL_CONFIG = {
  
  // taille en pixel de chaque case
  width: 64,
  height: 64,

  // sol
  "=": () => [
    sprite("grass"), 
    area(), 
    solid(), 
    origin("bot")
  ],
  
// sable "S": () => [ sprite("sand"), area(), solid(), body(), origin("bot"), "destructible", ],
};

Placer les blocs

Tu vas maintenant pouvoir choisir l'emplacement de tes blocs destructibles dans ton niveau. Toujours dans le script level.js, trouve le tableau LEVELS et places des caractères S là où tu le souhaites.

// plan du niveau
const LEVELS = [
  
  [
    "        S            " ,
    "    S   S       S    " ,
    "    S   S   S   S    " ,
    "    S   S   S   S    " ,
    "    S   S   S   S    " ,
    " #  S   S   S   S  @ " ,
    "=====================" ,
  ],
  
];

Tout à déjà l'air de fonctionner, à un détail près. Les blocs peuvent tomber sur la tête de personnage et y rester, l'empêchant ainsi de sauter. Cela peut être amusant, mais peut aussi être gênant pour la suite de ton jeu. Cette dernière étape va permettre aux blocs qui tombent sur ton joueur de se détruire automatiquement.

Créer un nouveau composant

Pour cela, une fois de plus, il va nous falloir un nouveau composant. Rends-toi une dernière fois dans le script component.js et ajoutes-y cette fonction.

// composant permettant au sable de disparaître s'il tombe sur le joueur function sand() { return { require: [ "pos" , "area", "body" , "solid" ], add() { this.on("collide", (obj, col) => { if (obj.is("player") && col.isBottom()) { destroy(this); play("hit"); } }) }, } }

Ajouter le nouveau composant

Il ne nous reste plus qu'à ajouter ce nouveau composant à notre nouvel objet. Retourne dans le script level.js, et retrouve dans la variable LEVEL_CONFIG le passage où sont déclarés nos blocs, puis ajoutes-y ces lignes, comme suit.

 // sable
"S": () => [
  sprite("sand"), 
  area(), 
  solid(), 
  body(),
  origin("bot"),

  sand(),

  "destructible",
],