Svelte 3 est sorti il ​​y a un peu plus d'un mois. Un bon moment pour vous rencontrer, pensai-je, et parcouru un excellent tutoriel , qui a également été traduit en russe.
Pour consolider le passé, j'ai réalisé un petit projet et partagé les résultats avec vous. Ce n'est pas une liste de tâches de plus, mais un jeu dans lequel vous devez tirer à partir de carrés noirs.

0. Pour impatient
Dépôt de didacticiels
Référentiel avec extras
Démo
1. Préparation
Nous clonons un modèle de développement
git clone https://github.com/sveltejs/template.git
Installez les dépendances.
cd template/ npm i
Nous démarrons le serveur de développement.
npm run dev
Notre modèle est disponible sur
http: // localhost: 5000 . Le serveur prend en charge le rechargement Ă chaud, nos modifications seront donc visibles dans le navigateur lors de leur enregistrement.
Si vous ne souhaitez pas déployer l'environnement localement, vous pouvez utiliser les sandbox codesandbox et stackblitz en ligne qui prennent en charge Svelte.
2. Le cadre du jeu
Le dossier src se compose de deux fichiers main.js et App.svelte .
main.js est le point d'entrée de notre application. Pendant le développement, nous ne le toucherons pas. Ici, le composant App.svelte est monté dans le corps du document.
App.svelte est un composant de svelte. Le modèle de composant se compose de trois parties:
<script> </script> <style> h1 { color: purple; } </style> <h1>Hello {name}!</h1>
Les styles de composants sont isolés, mais il est possible d'assigner des styles globaux avec la directive: global (). Plus sur les styles .
Ajoutez des styles communs pour notre composant.
src / App.svelte <script> export let name; </script> <style> :global(html) { height: 100%; } :global(body) { height: 100%; overscroll-behavior: none; user-select: none; margin: 0; background-color: #efefef; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } </style> <h1>Hello {name}!</h1>
Créons le dossier src / components dans lequel nos composants seront stockés
Dans ce dossier, créez deux fichiers qui contiendront le terrain de jeu et les contrôles.
src / components / GameField.svelte src / components / Controls.svelte Le composant est importé par directive
import Controls from "./components/Controls.svelte";
Pour afficher un composant, insérez simplement la balise du composant dans le balisage. En savoir plus sur les tags .
<Controls />
Maintenant, nous importons et affichons nos composants dans App.svelte.
src / App.svelte <script> </script> <style> :global(html) { height: 100%; } :global(body) { height: 100%; overscroll-behavior: none; user-select: none; margin: 0; background-color: #efefef; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } </style> <Controls /> <GameField />
3. ContrĂ´les
Le composant Controls.svelte sera composé de trois boutons: déplacer à gauche, déplacer à droite, tirer. Les icônes des boutons seront affichées par l'élément svg.
Créez le dossier src / asssets , auquel nous ajoutons nos icônes svg.
src / assets / Bullet.svelte <svg height="40px" viewBox="0 0 427 427.08344" width="40px"> <path d="m341.652344 38.511719-37.839844 37.839843 46.960938 46.960938 37.839843-37.839844c8.503907-8.527344 15-18.839844 19.019531-30.191406l19.492188-55.28125-55.28125 19.492188c-11.351562 4.019531-21.664062 10.515624-30.191406 19.019531zm0 0" /> <path d="m258.65625 99.078125 69.390625 69.390625 14.425781-33.65625-50.160156-50.160156zm0 0" /> <path d="m.0429688 352.972656 28.2812502-28.285156 74.113281 74.113281-28.28125 28.28125zm0 0" /> <path d="m38.226562 314.789062 208.167969-208.171874 74.113281 74.113281-208.171874 208.171875zm0 0" /> </svg>
src / assets / LeftArrow.svelte <svg width="40px" height="40px" viewBox="0 0 292.359 292.359" transform="translate(-5 0)"> <path d="M222.979,5.424C219.364,1.807,215.08,0,210.132,0c-4.949,0-9.233,1.807-12.848,5.424L69.378,133.331 c-3.615,3.617-5.424,7.898-5.424,12.847c0,4.949,1.809,9.233,5.424,12.847l127.906,127.907c3.614,3.617,7.898,5.428,12.848,5.428 c4.948,0,9.232-1.811,12.847-5.428c3.617-3.614,5.427-7.898,5.427-12.847V18.271C228.405,13.322,226.596,9.042,222.979,5.424z" /> </svg>
src / assets / RightArrow.svelte <svg width="40px" height="40px" viewBox="0 0 292.359 292.359" transform="translate(5 0) rotate(180)"> <path d="M222.979,5.424C219.364,1.807,215.08,0,210.132,0c-4.949,0-9.233,1.807-12.848,5.424L69.378,133.331 c-3.615,3.617-5.424,7.898-5.424,12.847c0,4.949,1.809,9.233,5.424,12.847l127.906,127.907c3.614,3.617,7.898,5.428,12.848,5.428 c4.948,0,9.232-1.811,12.847-5.428c3.617-3.614,5.427-7.898,5.427-12.847V18.271C228.405,13.322,226.596,9.042,222.979,5.424z" /> </svg>
Ajoutez le composant de bouton src / components / IconButton.svelte .
Nous accepterons les gestionnaires d'événements du composant parent. Pour pouvoir maintenir le bouton enfoncé, nous avons besoin de deux gestionnaires: le début du clic et la fin du clic. Nous déclarons les variables start et release , où nous accepterons les gestionnaires d'événements pour le début et la fin du clic. Nous avons également besoin de la variable active , qui s'affichera si le bouton est enfoncé ou non.
<script> export let start; export let release; export let active; </script>
Nous stylisons notre composant
<style> .iconButton { display: flex; align-items: center; justify-content: center; width: 60px; height: 60px; border: 1px solid black; border-radius: 50px; outline: none; background: transparent; } .active { background-color: #bdbdbd; } </style>
Un bouton est un élément de bouton dans lequel le contenu transféré d'un composant parent est affiché. L'endroit où le contenu transféré sera monté est indiqué par la balise <slot />. En savoir plus sur l'élément <slot /> .
<button> <slot /> </button>
Les gestionnaires d'événements sont indiqués à l'aide de la directive on: par exemple, on: click .
Nous gérerons les événements de la souris et les clics tactiles. En savoir plus sur la liaison d'événements .
La classe active sera ajoutée à la classe de base du composant si le bouton est enfoncé. Vous pouvez affecter une classe par la propriété class. En savoir plus sur les cours
<button on:mousedown={start} on:touchstart={start} on:mouseup={release} on:touchend={release} class={`iconButton ${active ? 'active' : ''}`}> <slot /> </button>
En conséquence, notre composant ressemblera à ceci:
src / components / IconButton.svelte <script> export let start; export let release; export let active; </script> <style> .iconButton { display: flex; align-items: center; justify-content: center; width: 60px; height: 60px; border: 1px solid black; border-radius: 50px; outline: none; background: transparent; } .active { background-color: #bdbdbd; } </style> <button on:mousedown={start} on:touchstart={start} on:mouseup={release} on:touchend={release} class={`iconButton ${active ? 'active' : ''}`}> <slot /> </button>
Maintenant, nous importons nos icônes et l'élément bouton dans src / components / Controls.svelte et créons la disposition.
src / components / Controls.svelte <script> </script> <style> .controls { position: fixed; bottom: 0; left: 0; width: 100%; } .container { display: flex; justify-content: space-between; margin: 1rem; } .arrowGroup { display: flex; justify-content: space-between; width: 150px; } </style> <div class="controls"> <div class="container"> <div class="arrowGroup"> <IconButton> <LeftArrow /> </IconButton> <IconButton> <RightArrow /> </IconButton> </div> <IconButton> <Bullet /> </IconButton> </div> </div>
Notre application devrait ressembler Ă ceci:

4. Le terrain de jeu
Le terrain de jeu est un composant svg, où nous ajouterons nos éléments de jeu (pistolet, obus, adversaires).
Mettre Ă jour le code src / components / GameField.svelte
src / components / GameField.svelte <style> .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: flex-start; max-height: 100%; } </style> <div class="container"> <svg viewBox="0 0 480 800"> </svg> </div>
Créez l' arme src / components / Cannon.svelte . Fort pour un rectangle, mais néanmoins.
src / components / Cannon.svelte <style> .cannon { transform-origin: 4px 55px; } </style> <g class="cannon" transform={`translate(236, 700)`}> <rect width="8" height="60" fill="#212121" /> </g>
Maintenant, nous importons notre arme sur le terrain de jeu.
src / GameField.svelte <script> </script> <style> .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: flex-start; max-height: 100%; } </style> <div class="container"> <svg viewBox="0 0 480 800"> <Cannon /> </svg> </div>
5. Cycle de jeu
Nous avons le cadre de base du jeu. La prochaine étape consiste à créer une boucle de jeu qui traitera notre logique.
Créons des stockages où les variables de notre logique seront contenues. Nous aurons besoin du composant inscriptible du module svelte / store. En savoir plus sur le magasin .
La création d'un référentiel simple ressemble à ceci:
Créons le dossier src / stores / , toutes les valeurs modifiables de notre jeu seront stockées ici.
Créez le fichier src / stores / game.js dans lequel les variables responsables de l'état général du jeu seront stockées.
Créez le fichier src / stores / cannon.js , dans lequel seront stockées les variables responsables de l'état de l'arme
Svelte vous permet de créer des référentiels personnalisés qui incluent la logique de travail. Vous pouvez en savoir plus à ce sujet dans le manuel . Je ne pouvais pas intégrer cela magnifiquement dans le concept du cycle de jeu, donc nous ne déclarons que des variables dans le référentiel. Nous effectuerons toutes les manipulations avec eux dans la section src / gameLoop .
La boucle de jeu sera planifiée à l'aide de la fonction requestAnimationFrame . Un tableau de fonctions décrivant la logique du jeu sera fourni à l'entrée. À la fin du cycle de jeu, si le jeu n'est pas terminé, la prochaine itération est planifiée. Dans la boucle du jeu, nous accèderons à la valeur de la variable isPlaying pour vérifier si le jeu est terminé.
En utilisant le stockage, vous pouvez vous abonner à une valeur. Nous utiliserons cette fonctionnalité dans les composants. Pour l'instant, nous allons utiliser la fonction get pour lire la valeur de la variable. Pour définir la valeur, nous utiliserons la méthode .set () de la variable.
Vous pouvez mettre à jour la valeur en appelant la méthode .update () , qui prend une fonction en entrée, dont la première valeur est passée la valeur actuelle. Plus de détails dans la documentation . Tout le reste est pur JS.
src / gameLoop / gameLoop.js Nous décrivons maintenant la logique du comportement de notre arme.
src / gameLoop / cannon.js Ajoutez maintenant notre gestionnaire de rotation des armes Ă feu Ă la boucle de jeu.
import { rotateCannon } from "./cannon"; export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon]); };
Code du cycle de jeu actuel:
src / gameLoop / gameLoop.js import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon } from './cannon';
Nous avons une logique qui peut faire tourner le pistolet. Mais nous ne l'avons pas encore associé à la poussée des boutons. Il est temps de le faire. Les gestionnaires d'événements de clic seront ajoutés à src / components / Controls.svelte .
import { direction } from "../stores/cannon.js";
Ajoutez nos gestionnaires et l'état actuel des clics aux éléments IconButton . Pour ce faire, passez simplement les valeurs aux attributs précédemment créés start , release et active , comme décrit dans la documentation .
<IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton>
Nous avons utilisé l'expression $ pour la variable $ direction . Cette syntaxe rend la valeur réactive, souscrivant automatiquement aux modifications. Plus de détails dans la documentation .
src / components / Controls.svelte <script> import IconButton from "./IconButton.svelte"; import LeftArrow from "../assets/LeftArrow.svelte"; import RightArrow from "../assets/RightArrow.svelte"; import Bullet from "../assets/Bullet.svelte"; </script> <style> .controls { position: fixed; bottom: 0; left: 0; width: 100%; } .container { display: flex; justify-content: space-between; margin: 1rem; } .arrowGroup { display: flex; justify-content: space-between; width: 150px; } </style> <div class="controls"> <div class="container"> <div class="arrowGroup"> <IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton> </div> <IconButton> <Bullet /> </IconButton> </div> </div>
Pour le moment, en appuyant sur notre bouton, une sélection se produit, mais le pistolet ne tourne toujours pas. Nous devons importer la valeur d' angle dans le composant Cannon.svelte et mettre à jour les règles de transformation de transformation
src / components / Cannon.svelte <script> </script> <style> .cannon { transform-origin: 4px 55px; } </style> <g class="cannon" transform={`translate(236, 700) rotate(${$angle})`}> <rect width="8" height="60" fill="#212121" /> </g>
Il reste à exécuter notre boucle de jeu dans le composant App.svelte .
import { startGame } from "./gameLoop/gameLoop"; startGame();
App.svelte <script> import Controls from "./components/Controls.svelte"; import GameField from "./components/GameField.svelte"; </script> <style> :global(html) { height: 100%; } :global(body) { height: 100%; overscroll-behavior: none; user-select: none; margin: 0; background-color: #efefef; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; } </style> <Controls /> <GameField />
Hourra! Notre arme a commencé à bouger.

6. Coups de feu
Maintenant, apprenez Ă notre fusil Ă tirer. Nous devons stocker les valeurs:
- Le pistolet tire-t-il maintenant (le bouton de tir est enfoncé);
- L'horodatage du dernier coup, vous devez calculer la cadence de tir;
- Tableau de coquillages.
Ajoutez ces variables à notre référentiel src / stores / cannon.js .
src / stores / cannon.js import { writable } from 'svelte/store'; export const direction = writable(null); export const angle = writable(0);
Mettez Ă jour les importations et la logique du jeu dans src / gameLoop / cannon.js .
src / gameLoop / cannon.js import { get } from 'svelte/store';
Maintenant, nous importons nos gestionnaires dans gameLoop.js et les ajoutons Ă la boucle de jeu.
import { rotateCannon, shoot, moveBullet, clearBullets } from "./cannon"; export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon, shoot, moveBullet, clearBullets ]); };
src / gameLoop / gameLoop.js import { isPlaying } from '../stores/game'; import { get } from 'svelte/store';
Il ne nous reste plus qu'à créer le traitement en appuyant sur le bouton de tir et à ajouter l'affichage des obus sur le terrain de jeu.
Modifiez src / components / Controls.svelte .
Ajoutez maintenant nos gestionnaires au bouton de contrĂ´le de tir, comme nous l'avons fait avec les boutons de rotation
<IconButton start={startFire} release={stopFire} active={$isFiring}> <Bullet /> </IconButton>
src / components / Controls.svelte <script> import IconButton from "./IconButton.svelte"; import LeftArrow from "../assets/LeftArrow.svelte"; import RightArrow from "../assets/RightArrow.svelte"; import Bullet from "../assets/Bullet.svelte"; </script> <style> .controls { position: fixed; bottom: 0; left: 0; width: 100%; } .container { display: flex; justify-content: space-between; margin: 1rem; } .arrowGroup { display: flex; justify-content: space-between; width: 150px; } </style> <div class="controls"> <div class="container"> <div class="arrowGroup"> <IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton> </div> <IconButton start={startFire} release={stopFire} active={$isFiring}> <Bullet /> </IconButton> </div> </div>
Reste à afficher les obus sur le terrain de jeu. Tout d'abord, créez le composant projectile
src / components / Bullet.svelte <script> </script> <g transform={`translate(${bullet.x}, ${bullet.y}) rotate(${bullet.angle})`}> <rect width="3" height="5" fill="#212121" /> </g>
Puisque les shells que nous avons sont stockés dans un tableau, nous avons besoin d'un itérateur pour les afficher. Dans svelte, il existe une directive Each pour de tels cas. Plus de détails dans la documentation .
// bulletList, bullet. // id , svelte , . // key React {#each $bulletList as bullet (bullet.id)} <Bullet {bullet}/> {/each}
src/components/GameField.svelte <script> import Cannon from "./Cannon.svelte"; </script> <style> .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: flex-start; max-height: 100%; } </style> <div class="container"> <svg viewBox="0 0 480 800"> {#each $bulletList as bullet (bullet.id)} <Bullet {bullet} /> {/each} <Cannon /> </svg> </div>
.

7.
Super. . src/stores/enemy.js .
src/stores/enemy.js import { writable } from "svelte/store";
src/gameLoop/enemy.js
src/gameLoop/enemy.js import { get } from 'svelte/store';
.
src/gameLoop/gameLoop.js import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon, shoot, moveBullet, clearBullets } from './cannon';
src/components/Enemy.js .
src/components/Enemy.js <script> </script> // , . <g transform={`translate(${enemy.x}, ${enemy.y})`} > <rect width="30" height="30" fill="#212121" /> </g>
, Each
src/components/GameField.svelte <script> import Cannon from "./Cannon.svelte"; import Bullet from "./Bullet.svelte"; </script> <style> .container { flex-grow: 1; display: flex; flex-direction: column; justify-content: flex-start; max-height: 100%; } </style> <div class="container"> <svg viewBox="0 0 480 800"> {#each $enemyList as enemy (enemy.id)} <Enemy {enemy} /> {/each} {#each $bulletList as bullet (bullet.id)} <Bullet {bullet} /> {/each} <Cannon /> </svg> </div>
!

8.
, .
. src/gameLoop/game.js . MDN
src/gameLoop/game.js import { get } from 'svelte/store';
.
src/gameLoop/gameLoop.js import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon, shoot, moveBullet, clearBullets } from './cannon';
, .

9.
, ToDo :
github .
Conclusion
, , React. 60 FPS, Svelte .
Svelte , .