Position, Velocity, Acceleration

Voilà ainsi introduit le sujet de notre nouvel article tournant autour du jeu vidéo. Nous allons faire un peu de math simplifiée pour animer nos objets de jeu, avec un tout petit peu de physique à l’intérieur du dedans pour rendre cela plus chouettos*.

Donc, partons d’un projet tout simple qui permet d’afficher des choses à l’écran avec un minimum d’effort.

Partons d’un micro mini framework tout simple que voici: ecsfmk, pour Entity Component System Framework. OUaaahhhh encore plein de termes tous pourris !

Non non, que nenni, il s’agit bien de choses signifiantes 🙂 Le principe de ce pattern architectural ECS consiste en un découpage intelligent des objets à manipuler.

Un super article (mais en anglais) présente une implémentation type de ce pattern.

On en extrait les données et les traitements par domaine. Ainsi ou peu séparer les calculs (ce qui va nous intéresser ici) du processus de rendu visuels. On obtient alors des composants spécialisés ainsi que les traitements s’y rattachant (appelés ici System). Ces composants sont assemblés au sein d’entités, comme un personnage, une voiture, ou que sais-je encore.

On est au croisement du bon design objet, de la réutilisation, et de la spécialisation.

Ok, maintenant, que nous avons notre framework de base, let us dive into ze code.

POO

Partons d’un objet simple, un rectangle que nous appellerons « Car » (comme voiture pour les non anglophones):

fichier: Car.java

public class Car {
  Vector2D position;
  Rectangle size;
}

Nous sommes en présence d’un simple pojo. si nous voulons en faire quelques chose, il faut le tracer à l’écran, non ?

Ajoutons la méthode ad-hoc dans la classe Car:

public void render(Graphics2D g){
  g.setColor(Color.GRAY);
  g.fillRect(
    (int)position.x, (int)position.y,
    (int)size.x, (int)size.y);
}

Voila bien une belle classe.

Il nous faudrait pouvoir changer la position de cette voiture en fonction de l’action de quelques touches du clavier, non ? Qu’à cela ne tienne!

public void update(float dt){
  position.x = position.x+velocity.x*dt;
  position.y = position.y+velocity.y*dt;
}

Bon ok, la velocity on voit ce que cela peut-être, c’est la vitesse de l’objet. Nous pourrions faire varier celle-ci en fonction des touches pressées LEFT, RIGHT, UP et DOWN.

Mais si nous voulons respecter un peu plus la physique des choses, je vous propose d’ajouter quelques composantes sympathiques comme l’accélération, la masse, la résistance ainsi que l’élasticité 🙂

Physique et mécanique

car-class-diagram
The new Car class with real physic inside

Mais définissons un peu tous ces nouveaux paramètres:

  • accélération: c’est la force en 2 dimension que subit l’objet,
  • masse: le poids de l’objet , mais sans l’effet de la gravité,
  • résistance: la résistance, c’est un facteur qui correspond à une force de frottement s’appliquant à notre objet. Ce facteur est rattaché à la nature du matériaux de l’objet. L’acier opposera moins de frottement qu’un bout de caoutchouc.
  • élasticité: c’est un facteur traduisant la faculté d’un objet (et donc de son matériaux) à rebondir 🙂

Notre objet car devient un peu plus complexe :

Ok, maintenant que nous avons ces paramètres, penchons nous sur notre formule de calcul de la position de notre objet.

position = position 
           + vélocité x t 
           + 1/2 (accélération x résistance x 1/mass) x t²

Cette magnifique formule va se traduire dans notre code (avec l’aide d’une super classe Vector2D) par:

public void update(float dt){
  ...
  // compute acceleration
  acceleration = acceleration.multiply(resistance)
      .multiply(1.0f / mass)
      .multiply(50.0f * dt); 
  
  // compute velocity
  velocity.x += (acceleration.x * t * t); 
  velocity.y += (acceleration.y * t * t); 
  
  // -- update Position (System) 
  position.x += 0.5f * (velocity.x * t); 
  position.y += 0.5f * (velocity.y * t);
  ...
}

il y a quelques facteurs fixe ajouté ici, ils permettent de fluidifier et de changer l’échelle lors de l’affichage.

Donc, nous avons maintenant un joli programme qui permet de faire fonctionner un objet sensible à la mécanique.

screenshot_20180211-113933
Un joli parallélépipède rectangle soumis à la force (mécanique) !

Comme vous pouvez le constater sur la capture ci-dessus, j’ai également ajouté quelques informations graphique de débug:

  • ligne verte: vecteur accélération,
  • ligne rouge: vecteur vitesse,
  • lignes en pointillés: les différents vecteurs force appliqués à notre objet (dont la gravité terrestre ! ).

Tout cela est activable/désactivable avec un simple appui sur la touche [D].

La petite démo proposée par le projet githubesque permet de jouer un peu avec ces concepts. (avec en prime quelques premiers pas sur le pattern Entity-Component-System.

ECS

ECS est donc l’abréviation du nom du pattern d’architecture Entity-Component-System. Ce pattern permet un découplage fort et une organisation particulière du code, en suivant de très près les concepts énoncés par la Programmation Orientée Objet.

Je ne ferai pas un cour ici sur la POO (ou OOP in english) mais sachez que l’organisation du code et le découpage propre des fonctionnalités est le but de ECS.

En plus, ECS permet même de la réutilisation de composants (si si ;).

Dans notre exemple, le code de l’objet Car est splitté en plusieurs composants, ayant chacun une responsabilité précise:

  • l’un s’occupe de la position PositionComponent,
  • l’autre des déplacement PhysicComponent,
  • et un dernier du rendu RenderComponent.

Ainsi, chaque composant est dédié à une tâche précise.

Ensuite, l’entité qui nous intéresse, ne fait que rassembler ces composants:

Fichier ECS de Car.java

class Car {
  PositionComponent pos;
  PhysicComponent physic;
  RenderComponent render;
}

En exemple, les composants PositionComponent et PhysicComponent sont définis comme ci-dessous:

Fichier ECS de PositionComponent.java

class PositionComponent implements Component {
  Vector2D position;
  Rectangle size;
}

Fichier ECS de MoveComponent.java

class PhysicComponent implements Component {
  Vector2D acceleration;
  Vector2D velocity;
  float mass;
  float elasticity;
  float resistence;
}

Ces deux fichiers sont des vues simplifiées bien sûr, histoire que l’article reste lisible. Pour le code complet, je vous invite à regarder de plus près sur le repo github.

Ensuite les méthodes de Car.java update(float dt) et render(Graphics2D g) sont déléguées à des systèmes spécialisés.

On crée alors les classes InputSystem, MoveSystem et RenderSystem sont les implémentation des mécaniques de base issues de l’objet initial Car.

Ainsi la classe MoveSystem reprend dans les grandes lignes le corps de la méthode update(float dt) de Car.

class MoveSystem implements System {
  public void update(float dt){
    ...
    // compute acceleration
    acceleration = acceleration.multiply(resistance)
       .multiply(1.0f / mass)
       .multiply(50.0f * dt); 
 
    // compute velocity
    velocity.x += (acceleration.x * t * t); 
    velocity.y += (acceleration.y * t * t); 
 
    // -- update Position (System) 
    position.x += 0.5f * (velocity.x * t); 
    position.y += 0.5f * (velocity.y * t);
    ...
  }
}

Je ne m’étendrai pas sur les 2 autres systèmes, InputSystem et RenderSystem, car ceux-ci reprennent les méthodes input(InputHandler ih) et render(Graphics2D g) des objets Application et Car.

La classe principale de notre application de démonstration et de ce fait réduite en taille, et la lisibilité de chacunes de ses tâches clairement identifiées.

Quand on dit qu’un code bien structuré est important 😉

Et vive le Java

McG.

*Chouettos, beau, tricky, hype, fun. mot désuet depuis 1992.

Publicités

Les commentaires sont fermés.

Propulsé par WordPress.com.

Retour en haut ↑

%d blogueurs aiment cette page :