Le blog d'Arcade Village

The Island : Vue détaillée des classes de base

Les classes sont en javascript. Je pars du principe que vous avez les connaissances requises pour les comprendre.
Je ne développerai que les fonctions qui sont utiles à la compréhension du mécanisme de la classe.

La classe Bloc



class Bloc
{
    constructor( btype, cfond )
    {
      this.btype = btype;
      this.xc = 0;
      this.yc = 0;
      this.x = 0;
      this.y = 0;
      this.width = WCASE;
      this.height = HCASE;
      this.cfond = cfond;
      this.rf = null;
      this.recx = new Array(4);
      this.recy = new Array(4);
      this.irec = 0;
      this.dcout = 1;  // Coût d'un déplacement sur le bloc
}

    setTabPosition(xc, yc)
    {
        this.xc = xc;
        this.yc = yc;
        this.x = this.xc * this.width;
        this.y = this.yc * this.height;
        this.recy[0] = this.y -2;
        this.recx[0] = this.x  + Math.trunc( Math.random() * this.width );

        this.recy[1] = this.y + this.height;
        this.recx[1] = this.x  + Math.trunc( Math.random() * this.width );

        this.recx[2] = this.x  - 2;
        this.recy[2] = this.y + Math.trunc( Math.random() * this.height );;

        this.recx[3] = this.x + this.width;
        this.recy[3] = this.y + Math.trunc( Math.random() * this.height );;
    }

    getType()
    {
        return this.btype;
    }


    getNutriment()
    {
        return 0;
    }

    getWater()
    {
        return 0;
    }
   
    draw(g, ombre )
    {
     
    }
}

Les blocs sont donc les briques de base du terrain. Chaque bloc est construit avec un type particulier et une couleur de fond.

La fonction setTabPosition permet de placer le bloc sur une position de la grille de jeu.
GetType() rend le type du bloc. Les types que j'ai déjà définis sont :

const BLOC_TYPE_SOIL = 0;
const BLOC_TYPE_SAND = 1;
const BLOC_TYPE_STONE = 2;
const BLOC_TYPE_WATER = 3;

getNutriment() et getWater() permettent à la végétation situé sur le bloc de se nourrir et de se développer.

Draw() est appelé chaque fois que le bloc doit être dessiné. Le paramètre ombre est la transparence. En effet, plus le bloc sera en hauteur, plus il doit être clair pour donner un effet de profondeur. La transparence avec un fond gris permet d'atteindre cet objectif.

Pour chaque élément, nous avons une classe Bloc dérivante. Ainsi, voici une partie de la classe BlocSand qui représente du sable.

class BlocSand extends Bloc
{
    constructor()
    {
        super(BLOC_TYPE_SAND, "#ffffa5");
        this.dcout = 3;  // Coût d'un déplacement sur le bloc
    }

    getNutriment()
    {
        return 3;
    }

    getWater()
    {
        return 1;
    }

}


Et une partie de la classe BlocSoil qui représente de la terre.
class BlocSoil extends Bloc
{
    constructor()
    {
        super(BLOC_TYPE_SOIL, "#856D4D");
    }

    getNutriment()
    {
        return 100;
    }

    getWater()
    {
        return 100;
    }
}

Comme vous pouvez le constater, cette classe fournit plus d'eau et de nutriments que la classe sable et donc, l'herbe s'y développera plus facilement.

La classe Plot (parcelle en Anglais)



class Plot
{
    constructor( maman )
    {
      this.maman = maman;
      this.cx = 0;
      this.cy = 0;
      this.x = 0;
      this.y = 0;
      this.width = WCASE;
      this.height = HCASE;
      this.rf = null;
      this.blocs = Array();
      this.upbloc = -1;
      this.hauteur = 0;
      this.xd = 0;
      this.yb = 0;
      this.tid = 0; // id par rapport au terrain
    }

    addBloc( ibloc )
    {
        this.blocs.push( ibloc);
        this.upbloc = this.blocs.length -1;
        this.hauteur = this.blocs.length;
        this.maman.redrawPlotBack( this );
    }

    eleve()
    {
     this.addBloc(BlocProvider.get(this.getBlocUp().getType()));
     this.blocs[this.upbloc].setTabPosition(this.cx, this.cy);
    }

    eleveTo( hauteur )
    {
        while (hauteur > this.hauteur) this.eleve();
    }

    eleveBloc( ibloc )
    {
     this.addBloc( ibloc );
     this.blocs[this.upbloc].setTabPosition(this.cx, this.cy);
     this.maman.redrawPlotBack( this );
    }

    setBlocUp( ibloc )
    {
      this.blocs[this.upbloc] = ibloc;
      this.blocs[this.upbloc].setTabPosition(this.cx, this.cy);
      this.maman.redrawPlotBack( this );
    }

    getBlocUp()
    {
     return this.blocs[this.upbloc];
    }

    getType()
    {
        return this.blocs[this.upbloc].getType();
    }

    setTabPosition(xc, yc)
    {
        this.cx = xc;
        this.cy = yc;
        this.x = this.cx * this.width;
        this.y = this.cy * this.height;
        this.xd = this.x + this.width - 1;
        this.yb = this.y + this.height - 1;
        this.tid =  this.cx + this.cy * this.maman.ncases_w;

        for (var i = 0 ; i < this.blocs.length ; i++ )
          this.blocs[i].setTabPosition(this.cx, this.cy);
    }

    essaime()
    {
        return this.maman.essaime( this );
    }

    getNutriment()
    {
        return this.blocs[this.upbloc].getNutriment();
    }

    getWater()
    {
        return this.blocs[this.upbloc].getWater();
    }

    attachProducer(fr)
    {
        this.fr = fr;
    }

    detachProducer()
    {
        this.fr = null;
    }

    hasProducer()
    {
        return (this.fr != null );
    }

    getProducer()
    {
        return this.fr;
    }

    getRessource( restype )
    {
      if ( this.hasProducer() )
        return this.fr.getRessource( restype );
      return 0;  
    }

    preleveRessource( restype, amount )
    {
      if ( this.fr.rtype != restype )
        {
         return;
        }
      this.fr.decRessource( amount );      
    }

    getCoutTraverse(creature)
    {
        return this.blocs[this.upbloc].getCoutDeplacement() + ( this.hasProducer() ? this.fr.getCoutDeplacement() : 0 );
    }

Cette classe doit permettre toutes les interactions avec le terrain de jeu. Elle contient dans le tableau blocs tous les blocs empilés, offre des mécanismes d'ajouts de bloc et de modification de bloc. Les créatures et la végétations ne connaîtront qu'elle, elle offre donc la possibilité d'accéder à l'eau et aux nutriments, ainsi qu'aux différentes ressources se trouvant sur la parcelle.

Ressources ? Qu'est à dire ?
Mon but étant de créer un jeu de survie, il faut que le personnage puisse accéder à un certains nombre de ressouces. Pour l'instant, les seuls ressources que j'ai créé sont
RESSOURCE_FOOD, la nourriture
RESSOURCE_WATER, l'eau.

Attention, vous ne devez pas confondre ces ressources avec les fonction getNutriment et getWater du bloc qui sont des ressources inaccessibles pour le joueur ou les animaux, tout comme les sels minéraux contenus dans la terre ne sont pas accessibles aux animaux. Évidemment, à l'image d'autres jeux vidéos, de nouvelles ressources viendront enrichir le jeu (bois, pierre....).
La nouveauté est que le joueur sera en compétition avec la faune pour y accéder.
Dans ces trois fonctions,
detachProducer()
hasProducer()
getProducer(),
Je fais référence à un producer. J'introduirai cette classe dans le prochain chapitre car elle nous permettra de gérer la végétation ou les animaux.

La classe Biome.


La classe Biome est appelé par Gaia pour définir les différentes parties du terrain. C'est une classe très simple.

const BIOME_WIDTH = 10;
const BIOME_HEIGHT = 10;

class Biome
{
    constructor( terrain, cx, cy )
    {
        this.terrain = terrain;
        this.ae = this.terrain.ae; // Pour simplifier les écritures
        this.cx = cx;
        this.cy = cy;
    }

    create()
    {

    }

    addProducers( nproducer, producer_type, bloc_type, max_hauteur)
    {
...
    }


    addNCreatures( creature_type, bloc_type, n)
    {
….
    }
}

J'ai précédemment rappelé la définition du Biome, pour notre jeu, nous pouvons dire qu'un biome contient un nombre de parcelles homogènes offrant la même végétation (des fournisseurs de végétations) et les même espèces animales (creatures).

Tous les biomes dérivent de la classe Biome dont ils implémentent la fonction create().

BiomePasture


Cette classe représente une prairie.

class BiomePasture extends Biome
{
create()
    {
  this.addProducers( BIOME_WIDTH*BIOME_HEIGHT/2, PRODUCER_HERB, BLOC_TYPE_SOIL, 2);   this.addNCreatures( "herbivore1",BLOC_TYPE_SOIL, BIOME_WIDTH*BIOME_HEIGHT/ 20 );
  this.addNCreatures( "predator1", BLOC_TYPE_SOIL, 4 );
    }

}

Comme vous pouvez le pressentir, ce biome ajoute des producteurs d'herbes, des herbivores ("herbivore1") et des prédateur de type "predator1".

BiomeMountain



class BiomeMountain extends Biome
{
  create()
  {
...
  this.addProducers(Math.trunc(BIOME_WIDTH /4), PRODUCER_FRUIT, BLOC_TYPE_SOIL, 5);
  }
}

Ici, on ajoute quelques arbres fruitiers.

La classe Gaia


Cette classe contient donc toutes les parcelles et à partir de biomes, créé le terrain. Je ne vous montre que les fonctions les plus utiles.

class Gaia
{

    constructor( ae )
    {
      this.ae = ae;
      this.ncases_w = 36;
      this.ncases_h = 36;
      //this.ncases_w = 36;
      //this.ncases_h = 36;

      this.ncases = this.ncases_w*this.ncases_h;
      this.cases = new Array(this.ncases);
      this.centrex = this.ncases_w / 2 - 1;
      this.centrey = this.ncases_h / 2 - 1;

      this.biomes = new Array();

...
     }


// Fonction de création du terrain
    genere()
    {
      var p = null;
      var f = null;
      var i = 0;
      var xc = 0;
      var yc = 0;
      var center_x = Math.trunc(this.ncases_w / 2);
      var center_y = Math.trunc(this.ncases_h / 2);
      var d = 0;
      var rayon = Math.max(center_x, center_y);
      var biomes = ["pasture","hill_horizontal","pasture",
                    "hill_vertical", 'lake', 'pasture',
                    "desert", "pasture", "mountain"];

      // Island creation
      for ( yc = 0 ; yc < this.ncases_h ; yc++ )
      {
       for ( xc = 0 ; xc < this.ncases_w ; xc++ )
          {
            p = new Plot(this);
            this.setCase(xc, yc, p);
            p.eleveBloc( BlocProvider.get( BLOC_TYPE_STONE ) );
            if ( yc == 0 || xc == 0 || xc == this.ncases_w -1 || yc == this.ncases_h -1 )
              p.eleveBloc( BlocProvider.get( BLOC_TYPE_WATER ) );
            else
             {
              if ( p.getBlocUp().getType() == BLOC_TYPE_STONE ) // On ajoute de la terre ou de l'eau
                {
                 d = Math.trunc( Math.sqrt( (xc - center_x) * (xc - center_x) + (yc - center_y) * (yc - center_y) ) );
                 if ( (d > rayon ) || ( d == rayon && Math.random() > .25 ) || ( d == rayon-1 && Math.random() > .5 ))
                    p.eleveBloc( BlocProvider.get( BLOC_TYPE_WATER ) ); // C'est de l'eau
                 else  
                    p.eleveBloc( BlocProvider.get( BLOC_TYPE_SOIL ) );
                }              

             }  
          }
      }
      // Biomes
      for ( yc = 3 ; yc < this.ncases_h - 3 ; yc+= BIOME_HEIGHT )
      {
       for ( xc = 3 ; xc < this.ncases_w - 3; xc+= BIOME_WIDTH )
          {
            f = BiomeProvider.get(this, xc, yc, biomes[i++]);
            f.create();
            this.biomes.push(f);
          }
        }  

    }

// Cette fonction est utilisée par un producteur pour essaimer sur les parcelles allentour. C'est pour cela que vous voyez l'herbe se propager
  essaime( p )
  {
    var iadj = Math.trunc(Math.random() * Plot_adjacent_x.length);
    var newp = this.getCase( p.cx + Plot_adjacent_x[iadj], p.cy + Plot_adjacent_y[iadj]);
    if ( newp == null ) return false;
    if ( newp.hasProducer() || newp.getBlocUp().getType() == BLOC_TYPE_WATER ) return false;
    if ( newp.hauteur > p.hauteur && Math.random() > .5 ) return false; // Si en hauteur, moins de chance de monter
    newp.attachProducer( ProducerProvider.get(this, newp, p.getProducer().getType()));
    return true;
  }

 
    getCase(xc, yc)
    {
      if ( xc < 0 || xc >= this.ncases_w || yc < 0 || yc >= this.ncases_h ) return null;
      return this.cases[xc+yc*this.ncases_w];
    }

    setCase(xc, yc, p)
    {
      this.cases[xc+yc*this.ncases_w] = p;
      p.setTabPosition(xc, yc);
   }

   getHauteur( xc, yc)
   {
    if ( xc < 0 || xc >= this.ncases_w || yc < 0 || yc >= this.ncases_h ) return 0;
    return this.getCase(xc, yc).hauteur;
   }

    getCX(px)
    {
     return Math.trunc( px / WCASE - this.xorigine);
    }

    getCY(py)
    {
     return Math.trunc( py / HCASE - this.yorigine);
    }

 
// Dessine les parcelles
 
   drawSimple(g)
   {
     if ( this.bpaint || this.topaint==false) return; // Pour
     this.bpaint = true;
     var i;
     g.setColor("c0c0a0");
     g.fillRect(0, 0, this.ae.width, this.ae.height);
     for (i= 0 ; i < this.ncases ; i++ )
       this.cases[i].draw(g);
     this.bpaint = false;
     this.topaint = false;
  }

  redrawPlotBack( p )
  {
    this.ptoredraw.push(p);
  }

  redrawBack( g )
  {
    while ( this.ptoredraw.length > 0 )
         {
          this.pdraw = this.ptoredraw.pop();
          this.pdraw.draw(g);
         }
  }

  //-----------------------------------------------------------------------------------------------
  //
  // Les ressources
  //
  //----------------------------------------------------------------------------------------------
  drawProducers(g)
  {
    var i;
    for (i= 0 ; i < this.ncases ; i++ )
       {
        if ( this.cases[i].hasProducer() )
           this.cases[i].getProducer().draw(g);

       }
 }

  updateRessources()
  {
    var i;
    for (i= 0 ; i < this.ncases ; i++ )
       {
        if ( this.cases[i].hasProducer() )
        this.cases[i].getProducer().grow();
       }
  }

  getProducer(xc, yc)
  {
    if ( xc < 0 || yc < 0 || xc >= this.ncases_w || yc >= this.ncases_h ) return null;
    return this.cases[xc+yc*this.ncases_w].getProducer();
  }

}



Je vous rappelle que vous pouvez voir le jeu tourner ici

La prochaine fois, nous parlerons des fournisseurs et de l'architecture générale du jeu.