Une histoire de pizzas : coup de pinceau
(lecture : 19 minutes) â sĂ©rie : Ergogames, une histoire de pizzas
Vous aimez les pizzas ? Vous aimez les ratons laveurs ? Alors voici dĂ©jĂ le cinquiĂšme Ă©pisode de ma sĂ©rie dâarticles retraçant la conception dâun mini-jeu vidĂ©o !
Dans le deuxiĂšme article, on ne sâĂ©tait pas trop embĂȘtĂ© pour les graphismes : on avait fait de gros blocs de couleurs moches. Il est toutefois temps de corriger ça en passant un coup de peinture sur le jeu. Notre raton va bientĂŽt ressembler Ă un raton !
Le premier ingrĂ©dient quâil nous faut, câest un graphiste ; et surprise, je ne le suis pas. Mes anciennes collĂšgues de uxShadow ont fait appel Ă Goulven Barron pour rĂ©aliser les graphismes du jeu (merci Ă lui !), je nâavais ainsi plus quâĂ les intĂ©grer.
Pour cet article, je me suis donc contenté de récupérer des images
du jeu dâorigine et de les dĂ©poser dans un rĂ©pertoire assets (quâon peut
traduire par « ressources » en bon françois).
Ensuite, il ne nous reste plus quâĂ attacher chaque entitĂ© Ă une image et Ă
lâafficher. Pour cela, on va tout simplement crĂ©er un nouveau composant au sein
des entitĂ©s (asset) qui contiendra lâURL vers lâimage et
ajouter cette derniĂšre au HTML Ă lâaide dâune balise <img />.
<div id="app">
<div class="board">
<!-- ... -->
<div
v-for="entity in store"
:key="entity.id"
:class="['board-cell-entity', entity.id]"
:style="entityPosition(entity)"
>
<!-- On affiche une image si l'entité possÚde un composant "asset" -->
<img
v-if="entity.asset"
:src="entity.asset"
alt=""
class="entity-asset"
/>
<!-- Sinon, on continue d'afficher le label -->
<div v-else-if="entity.label" class="entity-label">
{{ entity.label }}
</div>
</div>
<!-- ... -->
</div>
</div>
<script>
// ...
let gameStore = [
// On se contente d'ajouter un composant "asset" Ă chacune de nos
// entitĂ©s. Il sâagit juste d'une URL vers une image.
{
id: 'meiko',
asset: 'assets/meiko-face.svg',
// ...
},
{
id: 'mozza',
asset: 'assets/pot-mozzarella.svg',
// ...
},
{
id: 'tomato',
asset: 'assets/sauce-tomate.svg',
// ...
},
{
id: 'dough',
asset: 'assets/pate-pizza-boule.png',
// ...
},
{
id: 'oven',
asset: 'assets/four.svg',
// ...
},
{
id: 'hatch',
asset: 'assets/passeplat.svg',
// ...
},
{
id: 'fridge',
asset: 'assets/refrigirateur.svg',
// ...
},
{
id: 'workplan',
asset: 'assets/plan-travail.svg',
// ...
},
{
id: 'shelf',
asset: 'assets/etagere-horizontale.svg',
// ...
},
];
// ...
</script>
<style type="text/css">
/* ... */
/*
On vire quasiment tout le CSS concernant les entités (sauf les z-index)
et on le remplace par... seulement ce max-width pour éviter que les
images débordent des cases. Vous vous attendiez à plus de CSS ? :)
*/
.board-cell-entity img {
max-width: 100%;
}
/* Bon OK, on en ajoute un pour que la mozza ne déborde pas trop du frigo */
.board-cell-entity.mozza img {
max-width: 85%;
}
/* ... */
</style>
Et voilĂ , en seulement quelques lignes, on a largement transformĂ© notre jeu. On est dĂ©sormais visuellement trĂšs proches du jeu dâorigine. Un petit dĂ©tail toutefois reste Ă rĂ©gler : on aimerait bien que Meiko se tourne dans la direction vers laquelle on vient de cliquer. Pour cela on va avoir besoin de 4 images reprĂ©sentant Meiko : une pour chaque direction. La question qui se pose est : « comment changer dâimage ? »
Je vais aborder ici deux solutions possibles pour faire cela. Le choix que jâai fait nâest pas forcĂ©ment le meilleur, mais il sera justifiĂ©. Libre Ă vous de penser que câest vraiment nâimporte quoi et de prĂ©fĂ©rer lâautre solution.
La premiĂšre façon de faire est de regrouper toutes les images au sein dâun seul fichier : un « sprite » (ou lutin, câest WikipĂ©dia qui le dit, jâai toujours entendu parler que de sprite). Ensuite, il faut afficher seulement une partie de lâimage, en fonction dâune classe CSS par exemple. Je ne rentre pas dans le dĂ©tail de lâimplĂ©mentation, les mĂ©thodes sont de plus sensiblement diffĂ©rentes si vous avez un PNG ou un SVG. Cette technique me posait plusieurs problĂšmes :
- gĂ©nĂ©rer un sprite est un poil plus pĂ©nible (soit on complique la tĂąche de la personne qui gĂ©nĂšre lâimage, soit on complique la chaĂźne de build du projet)
- je ne savais pas faire avec des SVG et nâavais pas envie de passer trop de temps lĂ -dessus, ayant des choses plus intĂ©ressantes Ă dĂ©velopper par ailleurs (câest important dâutiliser son temps Ă bon escient đ)
- je pensais que ça me poserait des problÚmes de responsive design (problÚmes grandement exagérés par le fait que je ne savais pas faire, tout est lié !)
Bref, je suis parti sur une seconde solution plus naĂŻve, moins performante et moins Ă©lĂ©gante, mais qui me permettait au moins dâavancer sans me poser trop de questions.
La technique est simple. Chaque image se trouve dans un fichier Ă part, et on
les rattache via un composant assets (notez le « s » final, câest un autre
composant !) qui va regrouper ces images, indexées par des directions. Lors de
lâaffichage, on va calculer dynamiquement lâimage Ă afficher en fonction de la
direction vers laquelle Meiko est dirigĂ©. Ăa ressemble à ça :
<div id="app">
<div class="board">
<!-- ... -->
<div
v-for="entity in store"
:key="entity.id"
:class="['board-cell-entity', entity.id]"
:style="entityPosition(entity)"
>
<!-- On affiche une image si l'entité possÚde un composant "asset" -->
<img
v-if="entity.asset"
:src="entity.asset"
alt=""
class="entity-asset"
/>
<!--
Ou bien un composant "assets", il faut alors calculer
dynamiquement l'image Ă afficher.
-->
<img
v-else-if="entity.assets"
:src="entityAssetImage(entity)"
alt=""
class="entity-asset"
/>
<!-- Sinon, on continue d'afficher le label -->
<div v-else-if="entity.label" class="entity-label">
{{ entity.label }}
</div>
</div>
<!-- ... -->
</div>
</div>
<script>
// ...
let gameStore = [
{
id: 'meiko',
// Pour Meiko, on remplace le composant "asset" par "assets". Cela
// nous permettra d'associer plusieurs images, la bonne sera
// choisie dynamiquement lors de l'affichage.
assets: {
bottom: 'assets/meiko-face.svg',
right: 'assets/meiko-droite.svg',
left: 'assets/meiko-gauche.svg',
top: 'assets/meiko-dos.svg',
},
// On ajoute également un composant "direction" à Meiko
direction: 'bottom',
// ...
},
// ...
];
const app = new Vue({
// ...
methods: {
// ...
// Cette méthode JS calcule dynamiquement l'image à afficher pour
// les entités possédant un composant "assets"
entityAssetImage(entity) {
if (entity.assets == null || entity.direction == null) {
return '';
}
// Comme le composant "assets" est indexé par les directions,
// récupérer la bonne image se fait trÚs facilement. Ce code
// nâest pas trĂšs robuste mais comme le jeu est trĂšs simple
// avec peu de contributeurices, on peut sâen contenter pour
// l'instant.
return entity.assets[entity.direction];
},
},
});
// ...
</script>
La derniÚre étape consiste à changer effectivement la direction de Meiko en
fonction dâoĂč le clic a Ă©tĂ© effectuĂ©. Vous lâaurez compris : on va modifier
lâĂ©tat du store, cela se passe donc au sein de notre systĂšme onClickSystem.
function onClickSystem(e, store) {
// ...
// Notre code doit s'exécuter avoir vérifié que Meiko peut accéder à la
// case ciblée, sinon il ne doit pas bouger du tout.
const meiko = getEntity(store, 'meiko');
if (!positionIsAccessibleFor(meiko, position)) {
return store;
}
// On veut désormais changer la direction de Meiko : il suffit de
// comparer sa position à la position de la case ciblée pour savoir
// vers oĂč Meiko doit se tourner.
let direction = meiko.direction;
if (position.x > meiko.position.x) { direction = 'right'; }
else if (position.x < meiko.position.x) { direction = 'left'; }
else if (position.y < meiko.position.y) { direction = 'top'; }
else if (position.y > meiko.position.y) { direction = 'bottom'; }
let updatedStore = setEntityComponents(store, meiko.id, { direction });
// On fait bien attention Ă utiliser le updatedStore Ă partir dâici au
// lieu du store sinon Meiko ne se tournera pas.
const entitiesAtPosition = searchEntitiesAt(updatedStore, position);
if (entitiesAtPosition.some((entity) => entity.obstruct)) {
return updatedStore;
}
updatedStore = setEntityComponents(updatedStore, meiko.id, { position });
return updatedStore;
}
Avec ça on a fini notre coup de peinture sur le jeu. Dâailleurs, on a mĂȘme terminĂ© de toucher au HTML concernant la zone de jeu elle-mĂȘme : mĂȘme pas 50 lignes de code. Vous remarquerez sans doute en testant le jeu (le rĂ©sultat est ici) que Meiko ne change pas immĂ©diatement de direction : il sâagit du temps de charger lâimage la premiĂšre fois. On corrigera ça plus tard dans un article bonus. Pour lâinstant, on a quelque chose de bien plus important Ă faire : ajouter les Ă©tapes ainsi que les actions pour avancer dans le jeu. Ce sera lâobjet des deux prochains articles (au moins !)