Connectez votre rameur d’appartement avec Chrome - Partie 1

WiiFit, AppleHealt, Google Fit, tout ça c’est du passé ! Place à SkiffSimulator !

rameur connecté

Nous allons voir à travers cet article comment réaliser un rameur connecté et ainsi vous permettre de vous amuser en faisant du sport !

La version présentée dans cet article est une V1 dépendant d’un ordinateur. Il pourrait être très facile de faire évoluer l’application pour qu’elle soit autonome sur un équipement de type Raspberry par la suite.

Principe

Grâce à l’API serial de Google Chrome, nous allons relier directement notre rameur à notre navigateur pour créer un jeu qui nous permettra de jouer à un jeu 8bit. Voici globalement un schéma symbolisant le montage à effectuer :

montage rameur

Nous allons faire communiquer 2 programmes entre eux :

  • un sketch Arduino qui va mesurer la distance du joueur sur le rameur
  • une Application Chrome avec d’un côté :
    1. la partie Chrome App qui va lire le port série
    2. la partie Jeux qui va lire les informations provenant de la Chrome App

Comme tout ceci n’est pas bien compliqué, j’ai décidé de tout coder from scratch afin de me faire la main sur les possibilités offertes par cet écosystème. Côté application web, nous avons un simple canvas afin de tirer parti de l’accélération matérielle. Côté matériel, j’ai opté un simple arduino avec un capteur ultrason.

Shopping List

Voici les pré requis en terme d’achat pour réaliser cette démonstration :

  1. un rameur (~20€ sur le bon coin)
  2. un Arduinio nano (~trouvé à 6€ sur tinyDeal)
  3. une breadboard (~2€ sur tinyDeal)
  4. un capteur ultrason HC-SR04(~1,5€ sur tinyDeal)
  5. un fil MiniUSB -> USB (fourni avec l’arduino)
  6. des fils pour notre montage
  7. un ordinateur avec Chrome

Un jeu en HTML ?

Avant de commencer, il m’a fallu me renseigner sur le fonctionnement d’un jeu et voir comment j’allais procéder pour respecter au mieux les bonnes pratiques en vigueur.

Globalement, un jeu possède plusieurs briques qui fonctionnent en parallèle afin de minimiser le blocage de l’UI. Pour rappel, un jeu est considéré comme fluide s’il est à 60fps ce qui veut dire que chaque affichage ne doit pas dépasser les 13ms. Afin de respecter au mieux cette contrainte, j’ai découpé mon programme :

  • la brique qui s’occupe de l’affichage va lire dans un modèle partagé
  • la brique qui s’occupe de lire les données de l’arduino va alimenter ce modèle partagé et faire les calculs nécessaires

De cette façon, j’ai une séparation propre de mes interactions et des actions provenant de l’extérieur pouvant parfois bloquer mon interface. Il est à noter qu’avec ce fonctionnement, je tolère une désyncrhonisation entre l’état de mon modèle et mon affichage. Je pars du principe que celle-ci sera de maximum 13ms, ce qui est acceptable.

Sketch Arduino

sketch arduino

//Pour le cpateur à ultrasons

int TriggerPin = 8;

//Trig pin

int EchoPin = 5;

//Echo pin

long distance;

void setup() {

Serial.begin(9600);

//Mise en entrées de Pins

//On initialise le capteur à ultrasons

pinMode(TriggerPin, OUTPUT);

digitalWrite(TriggerPin, LOW);

pinMode(EchoPin, INPUT);

delay(100);

Serial.println(« Fin SETUP capteurs »);

}

void loop() {

distance = lire_distance();

Serial.print(« D »);

Serial.println(distance);

//Envoi des données en BT :

delay(50);

}

long lire_distance() {

long lecture_echo;

digitalWrite(TriggerPin, HIGH);

delayMicroseconds(10);

digitalWrite(TriggerPin, LOW);

lecture_echo = pulseIn(EchoPin, HIGH);

long cm = lecture_echo / 58;

return(cm);

}

Le fonctionnement est très simple : il suffit de lire la mesure de distance dès que l’on en obtient une, puis on la retranscrit directement sur le port série.

ChromeApp ?

Comme il s’agit d’une application chrome, nous devons créer un fichier manifest.jsonManifest de SkiffSumulator qui correspond au fichier de configuration de l’application chrome.

Structure de l’application

L’application possède donc plusieurs scripts qui vont tourner en parallèle afin de faire fonctionner le jeu. Voici la structure de mon projet côté application web :

  • assets : répertoire possèdant tous les fichiers de ressources du jeuj (Fonts, images, sons)
  • javascript : ensemble des srcipts javascript constituant l’application
  • scss : fichier sass qui vont servir à générer le css

Nous allons nous attarder uniquement sur les scripts car c’est dans cette partie que se situe toute l’intelligence du jeu. En effet, le fichier html est très sommaire car il ne contient qu’un canvas :

<!DOCTYPE html>
<html lang= »en »>
<head>
<meta charset= »UTF-8″>
<title>Skiff Almost Simulator</title>
<link rel= »icon » type= »image/png » href= »./assets/images/icon48.png » />
<link rel= »stylesheet »href= »./css/app.css »/>
</head>
<body>
<canvas id= »skiff »></canvas>
<input id= »user » type= »text » placeholder=’Enter your name’/>
<script type= »text/javascript » src= »./javascript/resources.js »></script>
<script type= »text/javascript » src= »./javascript/audio.js »></script>
<script type= »text/javascript » src= »./javascript/const.js »></script>
<script type= »text/javascript » src= »./javascript/chrome_storage.js »></script>
<script type= »text/javascript » src= »./javascript/chrome_serial.js »></script>
<script type= »text/javascript » src= »./javascript/screen_accueil.js »></script>
<script type= »text/javascript » src= »./javascript/screen_action.js »></script>
<script type= »text/javascript » src= »./javascript/screen_end.js »></script>
<script type= »text/javascript » src= »./javascript/app.js »></script>
</body>
</html>

Scripts et rôles

Voici les différents fichiers et leurs rôles :

  • js : coeur de l’application, il s’agit du point d’entrée de l’application et il agit comme un chef d’orchestre. C’est dans un sens le contrôleur de notre application
  • js : fichier servant à gérer la lecture des fichiers audio
  • js : fichier contenant le code spécifique à Chrome qui va nous permettre de lire directement depuis le port série de l’ordinateur
  • js : fichier utilitaire qui expose de façon uniforme une API de localstorage au cas où l’application devrait tourner en dehors de Chrome (plus de détails plus loin dans l’article)
  • js : fichier regroupant toutes les constantes du jeu. Il peut s’agir de simples constantes ou de variables d’ajustement servant lors de la calibration du jeu
  • js : fichier permettant d’exposer un mécanisme de chargement de ressources graphiques en vue de les exploiter par la suite dans le programme
  • js : fichier contenant tout le code spécifique à l’affichage de l’écran d’accueil
  • js : fichier contenant tout le code spécifique à l’affichage pendant le jeu
  • js : fichier contenant tout le code spécifique à l’affiche de l’écran de fin

Le Reveal Module Pattern a été choisi comme pattern car il permet de fonctionner en module javascript et d’offrir un découpage propre du code tout en maîtrisant les méthodes exposées.

Déroulement du programme :

Prenons les différents points méritant de l’attention :

Démarage (App.js)

//API

function init() {

window.addEventListener(‘load’,

pageLoad);

}

return {

init : init, … }

}

();

AppSAS.init();

On démarre l’application dès que la page est prête.

‘use strict’;

var AppSAS = AppSAS || function() {

… function pageLoad() {

// On se connecte à l’arduino

try {

skiffSimulatorChrome.initArduino();

}

catch(err) {

console.error(« Error : %s \n %s »,

err.message, err.stack);

}

On doit faire appel au module qui va lire les données de l’arduino.

// On initialise le canvas

ui.input = document.getElementById(‘user’);

ui.canvas = document.getElementById(‘skiff’);

ui.canvas.width = window.innerWidth;

ui.canvas.height = window.innerHeight;

ui.context = ui.canvas.getContext(‘2d’);

ui.canvas.addEventListener(‘click’, checkClick, false);

// On précharge toutes les ressources nécessaires

ui.resources.loadSprites([ {

title : ‘logo’, url : ‘assets/images/logo.png’}

, {

title : ‘game_over’, url : ‘assets/images/gameover.png’}

, {

title : ‘rive_gauche_portrait’, url : ‘assets/images/riviere_gauche_portrait.png’}

, … ]) .then(function(value) {

paintSkiff();

}

).catch(function(err) {

console.error(« Error : %s \n %s », err.message, err.stack); }

);

}

On initialise notre canvas ainsi que les ressources graphiques du projet. On n’affiche le jeu qu’une fois ces dernières chargées.

En fonctionnement (app.js)

Une fois l’application réellement démarrée avec la méthode paintSkiff. Nous allons simplement déléguer l’affichage aux méthodes appropriées :

// Gère l’affichage de l’écran

function paintSkiff() {

try {

… // Affichage des décors

paintBackground();

if (gameModel.stateGame === constState.STATE_ACCUEIL) {

ScreenSasAccueil.paintSkiffAccueil();

StorageSAS.manageGhost();

}

else if (gameModel.stateGame === constState.STATE_RUNNING) {

// On doit peindre le fantome du jeu en deuxième car son alpha nous indique où il est

ScreenSasAction.paintSkiffAction();

ScreenSasAction.paintSkiffGhost();

// On ajoute l’état à l’historique

gameModel.currentHistory.push( {

direction : gameModel.direction,

distanceSkiff : + gameModel.distanceSkiff,

distanceArduino : + gameModel.distanceArduino }

);

gameModel.step++;

}

if (gameModel.stateGame === constState.STATE_END) {

ScreenSasEnd.paintSkiffEnd();

}

window.requestAnimationFrame(paintSkiff);

}

catch (err) {

console.error(« Error : %s \n %s »,

err.message, err.stack);

}

}

Il est à noter que l’on utilise la méthode window.requestAnimtionFrame. Cette dernière est très importante car elle permet d’optimiser l’affichage de nos écrans en fonction de la puissance de la machine. En effet, la méthode de callback ne sera appelée une fois le navigateur prêt à effectuer une nouvelle mise à jour graphique. Il faut donc utiliser cette méthode à la place d’un setInterval

Affichage des écrans

L’affichage des écrans se fait toujours de la même façon :

  1. on nettoie le canvas
  2. on dessine une ou des images sur le canvas

L’affichage d’un écran se fait toujours de la façon suivante :

 

ui.context.drawImage(imgSource //L’image source

, sx //sx clipping de l’image originale

, sy //sy clipping de l’image originale

, sw // swidth clipping de l’image originale

, sh // sheight clipping de l’image originale

, dx // x Coordonnées dans le dessin du canvas

, dy // y Coordonnées dans le dessing du canvas

, dw // width taille du dessin

, dh // height taille du dessin

);

canvas

Les animations / constructions des écrans ne sont en fait qu’une succession de drawImage ou fillText.

 

La suite dans un 2e article….

 

 

Jean François Garreau

Consultant Expert Innovation SQLI Nantes

0 commentaires

votre commentaire

Se joindre à la discussion ?
Vous êtes libre de contribuer !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Inscription newsletter

Ne manquez plus nos derniers articles !