Blog tech

Découverte du langage Haxe

Rédigé par Arnaud Morisset | 7 novembre 2016

Découverte du langage Haxe

Le développement d’applications multiplateformes a toujours été un calvaire pour les développeurs, notamment dans le domaine du jeu vidéo où l’on doit s’adapter aux spécificités du matériel et jongler entre les différentes API graphiques. La technologie Haxe tente de résoudre ce problème en vous permettant de compiler votre application pour de nombreuses plateformes tout en gardant la même base de code.

Présentation de Haxe

Haxe est un ensemble d’outils crée par Nicolas Canasse. Cet ancien Flasheur a conçu ces outils pour développer des jeux multiplateformes tout en conservant la simplicité et l’efficacité de l’API graphique de Flash. Depuis plusieurs années, cet ensemble d’outils est devenu Open Source et une fondation a même été créée pour en assurer le développement.

On distingue trois outils :

  • Le langage Haxe et sa bibliothèque standard.
  • NekoVM, une machine virtuelle pour exécuter directement le langage.
  • Le compilateur qui permet d’exporter vers d’autres plateformes.

Installation de l’environnement

Avant toute installation, sachez que vous pouvez tester le langage avec l’éditeur en ligne de la fondation Haxe.

Vous retrouverez les instructions d’installation spécifiques à votre système d’exploitation sur le site officiel. Vous pourrez également l’installer via votre gestionnaire de paquets habituel si vous êtes sous GNU/Linux ou MacOS (via brew). Sous Windows, je vous recommande l’installation de l’IDE HaxeDevelop qui contient un logiciel de gestion de dépendances, cela vous permettra d’installer Haxe ainsi que plusieurs bibliothèques et frameworks via une interface graphique.

Pour le choix de l’éditeur, vous pourrez sûrement conserver votre chouchou vu le nombre de plugins disponible. Que vous soyez sous Emacs, VIM ou Sublime Text, vous trouverez votre bonheur sur la page dédiée.

Premier programme et première compilation

Une fois Haxe installé et votre éditeur favori configuré en conséquence, je vous invite à créer un fichier Main.hx et à y copier le code suivant :

class Main {
    static function main() {
        trace("Hello, World!");
    }
}

Le compilateur est accessible via votre terminal, placez-vous au niveau du fichier et lancez la compilation via :

haxe -main Main -js Main.js

Ce qui vous donnera un fichier Main.js, ce dernier n’est pas fait pour être lu par un humain, mais par curiosité nous pouvons y jeter un oeil.

// Generated by Haxe 3.3.0
(function () { "use strict";
    var Main = function() {};
    Main.main = function() {
	    console.log("Hello, World!");
    };

    Main.main();
})();

Vous constaterez qu’il s’agit d’un code javascript “pure”, indépendant de tout code Haxe. Il est donc exécutable seul (avec NodeJS par exemple).

node Main.js
> Hello, World!

Et si on essayait avec Python pour voir ?

main -main Main.hx -python Main.py

python Main.py
> Hello, World!

Vous pouvez vous amuser à tester les différents formats présents dans la documentation du compilateur.

La bibliothèque standard

Tout bon langage vient avec un ensemble de fonctions indispensables à la création de programme, Haxe n’échappe pas à la règle et nous propose une bibliothèque standard bien fournie.

Ces ensembles de fonctions sont regroupés dans des classes directement utilisables depuis votre programme. Voici un exemple pour effectuer quelques opérations mathématiques :

class Main {
  static function main() {
    var x = 4;
    var y = 19.5;

    trace(Math.sqrt(x)); // 2.0
    trace(Math.abs(x));  // 4.0
    trace(Math.ceil(y)); // 20
  }
}

De la même manière, la librairie standard vous donne accès à un système de tests unitaires via le package haxe.unit.

// Création d'une classe de test
class UselessTest extends haxe.unit.TestCase  {
  public function testAdd() {
    assertEquals(2, 1+1);
  }
}

// Création d'un lanceur de test
class Main {
  static function main() {
    var r = new haxe.unit.TestRunner();
    r.add(new UselessTest());
    r.run();
  }
}
haxe -main Main.hx -js Main.js

node Main.js
> Class: UselessTest .

OK 1 tests, 0 failed, 1 success

Le nombre de fonctionnalités ne s’arrête pas là : Parsing JSON/XML, fonctions Lambda, macros pour la compilation, etc. … Tout ce qui peut vous interesser est décrit dans la documentation de la librairie standard.

Une animation multiplateforme

Afin de terminer en beauté, je vous propose de réaliser une petite animation 2D que nous testerons sur Desktop, web et mobile. Pour faciliter le développement de cette application, nous allons nous servir de OpenFL.

Il s’agit d’une API graphique inspirée de Flash dédiée au développement de jeux multiplateformes. Son utilisation est similaire à l’API graphique native de Flash, l’intérêt réside dans une bibliothèque fournie avec : Lime. Cette dernière propose un support unifié pour HTML5, Android, macOS, Linux…

Pour l’installer, rien de plus simple. Haxe possède un gestionnaire de dépendances nommé HaxeLib, la commande suivante vous permet d’installer OpenFL :

# Installation et activation de OpenFL
haxelib install openfl
haxelib run openfl setup

# On vérifie que OpenFL est bien reconnu
openfl

Utilisateurs de macOS, vous devrez surement spécifier le chemin d’installation à Haxelib. Cela se fait avec les commandes suivantes :

sudo haxelib setup /usr/local/lib/haxe/lib
sudo chmod 775 /usr/local/lib/haxe/lib

Maintenant que OpenFL est présent, nous pouvons créer un projet de la manière suivante :

openfl create project BouncingBall

La commande nous a créé la structure du projet :

.
+- BouncingBall
|  +- Source
|     +- Main.hx
|  +- Assets
|  +- BouncingBall.hxproj
|  +- project.xml

L’arborescence reste assez claire, votre code ira dans le répertoire Source et vos Assets (réalisation graphique et sonore) dans le répertoire éponyme. Les fichiers project.xml et Bouncingball.hxproj sont eux des fichiers de configuration, il vous faudra les éditer si vous souhaitez ajouter des dépendances ou changer la configuration de vos fenêtres (taille, FPS…).

Nous allons commencer par éditer le fichier project.xml. Vous pouvez mettre à jour le nom du paquet, son numéro de version, le nom de votre société, etc. …

<!-- project.xml -->
<?xml version="1.0" encoding="utf-8"?>
<project>

    <meta title="BouncingBall" package="com.synbioz.bouncingball" version="1.0.0" company="Synbioz" />
    <app main="Main" path="Export" file="BouncingBall" />

    <source path="Source" />
    <haxelib name="openfl" />

    <assets path="Assets" rename="assets" />

    <!-- Parametres de la fenetre -->
    <window width="800" height="800" fps="60" background="#ffffff" vsync="true" />

</project>

Pour lancer l’application, il vous suffit de passer par l’outil CLI de OpenFL :

# En version web HTML5 (disponible sur localhost:3000)
openfl test html5

# En version Desktop (avec la machine virtuelle Neko)
openfl test neko

L’export HTML5 vous donnera uniquement une page blanche pour l’instant, mais l’export Desktop devrait vous afficher une jolie fenêtre de 800x800px.

La page est un peu déprimante pour l’instant, nous allons essayer de dessiner une balle et la faire rebondir sur les bords de l’écran. Pour faire ça proprement, commencez par ajouter une classe Ball dans un fichier Ball.hx à côté de votre Main.hx.

// Ball.hx

package;

import openfl.display.Sprite;

// Notre objet Ball est un Sprite, un objet graphique animé
class Ball extends Sprite {

  public function new() {
    super();

    // On prépare le tracé avec la couleur en paramètre
    this.graphics.beginFill(0x000000);

    // On dessine un cercle
    this.graphics.drawCircle(0, 0, 50);

    // Fin du tracé
    this.graphics.endFill();
  }

}

Nous avons maintenant une classe dont le rôle est de tracer un cercle lors de son instanciation. Il va nous falloir ajouter un objet Ball dans notre scène. Direction le fichier Main.hx pour la suite des événements.

// Main.hx

package;

import openfl.display.Sprite;

class Main extends Sprite {

  private var ball : Ball;

  public function new () {
    super();

    /* On instancie un objet Ball
       avant de l'ajouter à la scène courante */
    ball = new Ball();
    ball.x = 350;
    ball.y = 350;
    this.addChild(ball);
  }

}

Avec ces modifications, vous devriez désormais avoir un cercle noir au milieu de l’écran. Étant donné que nous souhaitons le voir se déplacer, nous allons ajouter une fonction chargée de calculer la direction de la balle.

Avant de l’implémenter, créez deux attributs dans la classe Ball, un pour la vitesse et l’autre pour le mouvement (qui représente une direction).

// Ball.hx

import openfl.geom.Point;



public var speed : Int;
public var movement : Point;



public function new() {
  super();

  speed = 7;
  movement = new Point(0, 0);

  
}

De retour dans Main.hx, nous allons ajouter la fonction pour calculer la direction de la balle.

// Main.hx

import openfl.geom.Point;



/* Calcule une nouvelle direction pour la balle  */
private function bounceBall() : Void {
  var randomAngle : Float = (Math.random() * Math.PI / 2) - 45;

  ball.movement.x = Math.cos(randomAngle) * ball.speed;
  ball.movement.y = Math.sin(randomAngle) * ball.speed;
}

Cette fonction va calculer un nouvel angle et une nouvelle direction pour la balle afin de simuler un rebond. Pour pouvoir animer la balle, il va nous falloir une fonction appelée automatiquement à chaque Frame (soit 60 fois par seconde vue notre configuration).

Pour cela, on va se servir des événements :

// Main.hx

// Ajouter l'import avant la déclaration de la classe
import openfl.events.Event;

Ajoutez le code suivant dans le constructeur de Main.hx :

// On appel BounceBall pour avoir une direction initiale
this.bounceBall();

// On définit une fonction de callback appellée à chaque Frame
this.addEventListener(Event.ENTER_FRAME, everyFrame);

Nous avons ajouté un écouteur sur l’événement ENTER_FRAME, autrement dit une fonction nommée everyFrame sera appelée à chaque fois qu’une Frame commencera.

Voici la fonction everyFrame :

// Main.hx

private function everyFrame(event : Event) : Void {

  // À chaque Frame, on met à jour les coordonnées de la balle
  ball.x += ball.movement.x;
  ball.y += ball.movement.y;

  var limit : Int = (this.ball.width / 2);

  // Si on touche un bord, on part dans l'autre sens
  if (ball.x < limit || ball.x > stage.stageWidth - limit)  ball.movement.x *= -1;
  if (ball.y < limit || ball.y > stage.stageHeight - limit) ball.movement.y *= -1;

  // Selon le bord atteint, on repositionne la balle
  // et l'on calcule un nouvel angle
  if (ball.movement.x < 0  && ball.x < limit) {
    this.bounceBall();
    ball.x = limit;
  }

  if (ball.movement.x > 0  && ball.x > stage.stageWidth - limit) {
    this.bounceBall();
    ball.x = stage.stageWidth - limit;
  }

  if (ball.movement.y < 0  && ball.y < limit) {
    this.bounceBall();
    ball.y = limit;
  }

  if (ball.movement.y > 0  && ball.y > stage.stageHeight - limit) {
    this.bounceBall();
    ball.y = stage.stageHeight - limit;
  }
}

La variable limit correspond à la moitié de la longueur du rayon de la balle. On l’utilise dans notre gestion des collisions car les calculs se font selon le point d’origine de l’objet, soit le centre de la balle dans notre cas.

Si vous tentez de lancer l’application avec Neko ou HTML5, vous devriez avoir une jolie balle qui rebondit sur les bords de l’écran.

Essayons de finaliser le projet en préparant, une version release :

Pour desktop (exemple avec neko) :

openfl build neko

Sous mac, vous pouvez vous rendre dans le dossier Export/mac64/neko/bin/, vous y retrouverer un fichier .app parfaitement autonome que vous pourrez redistribuer. Le principe sera le même pour la majorité des systèmes d’exploitation. Sous GNU/Linux ou Windows, le dossier Export contiendra un fichier exécutable correspondant à votre système.

Pour le web (exemple avec HTML5) :

openfl build html5

Un chemin similaire vous donnera accès aux fichiers HTML et javascript.

Pour mobile (exemple avec Android) :

# Si vous n'avez pas déjà les outils de développement Android.
openfl setup android

# Souvenez-vous que pour une véritable release, il vous faudra une clé PlayStore.
openfl build android

Vous avez compris la logique : vous retrouverez un .apk dans votre dossier Export.

Conclusion

J’espère que cette petite escapade dans le monde de Haxe vous aura plu. À l’heure où les projets de Serious Games et autres Advergames se multiplient, Haxe s’avère être un allié de poids dans la réalisation de jeux multiplateformes.

L’équipe Synbioz.

Libres d’être ensemble.