Svelte 3 wurde vor etwas mehr als einem Monat veröffentlicht. Ein guter Moment, um Sie kennenzulernen, dachte ich und durchlief ein ausgezeichnetes Tutorial , das auch ins Russische übersetzt wurde.
Um die Vergangenheit zu festigen, habe ich ein kleines Projekt erstellt und die Ergebnisse mit Ihnen geteilt. Dies ist keine To-More-To-Do-Liste, sondern ein Spiel, in dem Sie von schwarzen Quadraten aus schießen müssen.

0. Für ungeduldig
Tutorial-Repository
Repository mit Extras
Demo
1. Vorbereitung
Wir klonen eine Vorlage für die Entwicklung
git clone https://github.com/sveltejs/template.git
Installieren Sie die Abhängigkeiten.
cd template/ npm i
Wir starten den Dev Server.
npm run dev
Unsere Vorlage finden Sie unter
http: // localhost: 5000 . Der Server unterstützt Hot Reload, sodass unsere Änderungen im Browser angezeigt werden, wenn die Änderungen gespeichert werden.
Wenn Sie die Umgebung nicht lokal bereitstellen möchten, können Sie die Codesandbox- und Stackblitz-Sandboxen online verwenden, die Svelte unterstützen.
2. Der Rahmen des Spiels
Der Ordner src besteht aus zwei Dateien main.js und App.svelte .
main.js ist der Einstiegspunkt in unsere Anwendung. Während der Entwicklung werden wir es nicht berühren. Hier wird die App.svelte-Komponente im Hauptteil des Dokuments bereitgestellt.
App.svelte ist eine Komponente von svelte. Die Komponentenvorlage besteht aus drei Teilen:
<script> </script> <style> h1 { color: purple; } </style> <h1>Hello {name}!</h1>
Die Komponentenstile sind isoliert, es ist jedoch möglich, globale Stile mit der Direktive global () zuzuweisen. Mehr zu Stilen .
Fügen Sie allgemeine Stile für unsere Komponente hinzu.
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>
Erstellen wir den Ordner src / components, in dem unsere Komponenten gespeichert werden
Erstellen Sie in diesem Ordner zwei Dateien, die das Spielfeld und die Steuerelemente enthalten.
src / components / GameField.svelte src / components / Controls.svelte Die Komponente wird per Direktive importiert
import Controls from "./components/Controls.svelte";
Um eine Komponente anzuzeigen, fügen Sie einfach das Komponenten-Tag in das Markup ein. Erfahren Sie mehr über Tags .
<Controls />
Jetzt importieren und zeigen wir unsere Komponenten in 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. Kontrollen
Die Controls.svelte-Komponente besteht aus drei Schaltflächen: nach links bewegen, nach rechts bewegen, feuern. Schaltflächensymbole werden vom svg-Element angezeigt.
Erstellen Sie den Ordner src / asssets , zu dem wir unsere SVG-Symbole hinzufügen.
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>
Fügen Sie die Schaltflächenkomponente src / components / IconButton.svelte hinzu .
Wir akzeptieren Event-Handler von der übergeordneten Komponente. Um den Knopf gedrückt halten zu können, benötigen wir zwei Handler: den Beginn des Drückens und das Ende des Drückens. Wir deklarieren die Variablen start und release , wobei wir Event-Handler für den Beginn und das Ende des Klicks akzeptieren. Wir brauchen auch die aktive Variable, die anzeigt, ob die Taste gedrückt wird oder nicht.
<script> export let start; export let release; export let active; </script>
Wir stilisieren unsere Komponente
<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>
Eine Schaltfläche ist ein Schaltflächenelement, in dem von einer übergeordneten Komponente übertragener Inhalt angezeigt wird. Der Ort, an dem der übertragene Inhalt bereitgestellt wird, wird durch das <slot /> -Tag angezeigt. Erfahren Sie mehr über das Element <slot /> .
<button> <slot /> </button>
Ereignishandler werden mit der Anweisung on: angezeigt, z. B. on: click .
Wir werden Mausereignisse und Berührungsklicks behandeln. Erfahren Sie mehr über die Ereignisbindung .
Die aktive Klasse wird der Basisklasse der Komponente hinzugefügt, wenn die Taste gedrückt wird. Sie können eine Klasse über die class -Eigenschaft zuweisen. Mehr über Klassen
<button on:mousedown={start} on:touchstart={start} on:mouseup={release} on:touchend={release} class={`iconButton ${active ? 'active' : ''}`}> <slot /> </button>
Infolgedessen sieht unsere Komponente folgendermaßen aus:
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>
Jetzt importieren wir unsere Symbole und das Schaltflächenelement in src / components / Controls.svelte und erstellen das Layout.
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>
Unsere Anwendung sollte folgendermaßen aussehen:

4. Das Spielfeld
Das Spielfeld ist eine SVG-Komponente, in der wir unsere Spielelemente (Waffe, Granaten, Gegner) hinzufügen.
Aktualisieren Sie den 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>
Erstellen Sie die Waffe src / components / Cannon.svelte . Laut für ein Rechteck, aber trotzdem.
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>
Jetzt importieren wir unsere Waffe auf dem Spielfeld.
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. Spielzyklus
Wir haben das Grundgerüst des Spiels. Der nächste Schritt besteht darin, eine Spielschleife zu erstellen, die unsere Logik verarbeitet.
Erstellen wir Speicher, in denen Variablen für unsere Logik enthalten sind. Wir benötigen die beschreibbare Komponente aus dem Svelte / Store-Modul. Erfahren Sie mehr über store .
Das Erstellen eines einfachen Repositorys sieht folgendermaßen aus:
Lassen Sie uns den Ordner src / store / erstellen, alle veränderlichen Werte unseres Spiels werden hier gespeichert.
Erstellen Sie die Datei src / store / game.js, in der die Variablen gespeichert werden, die für den allgemeinen Status des Spiels verantwortlich sind.
Erstellen Sie die Datei src / store / cannon.js , in der die für den Zustand der Waffe verantwortlichen Variablen gespeichert werden
Mit Svelte können Sie benutzerdefinierte Repositorys erstellen, die die Arbeitslogik enthalten. Mehr dazu lesen Sie im Lehrbuch . Ich konnte dies nicht wunderbar in das Konzept des Spielzyklus einfügen, daher deklarieren wir nur Variablen im Repository. Wir werden alle Manipulationen mit ihnen im Abschnitt src / gameLoop durchführen .
Die Spielschleife wird mit der Funktion requestAnimationFrame geplant. Eine Reihe von Funktionen, die die Logik des Spiels beschreiben, werden dem Eingang zugeführt. Wenn das Spiel am Ende des Spielzyklus nicht beendet ist, ist die nächste Iteration geplant. In der Spielschleife greifen wir auf den Wert der Variablen isPlaying zu, um zu überprüfen, ob das Spiel beendet wurde.
Mithilfe des Speichers können Sie ein Abonnement für einen Wert erstellen. Wir werden diese Funktionalität in Komponenten verwenden. Im Moment werden wir die Funktion get verwenden, um den Wert der Variablen zu lesen. Um den Wert festzulegen , verwenden wir die .set () -Methode der Variablen.
Sie können den Wert aktualisieren, indem Sie die Methode .update () aufrufen, die eine Funktion als Eingabe verwendet, deren erster Wert dem aktuellen Wert übergeben wird. Weitere Details in der Dokumentation . Alles andere ist reine JS.
src / gameLoop / gameLoop.js Nun beschreiben wir die Logik des Verhaltens unserer Waffe.
src / gameLoop / cannon.js Fügen Sie nun unseren Waffenrotations-Handler zur Spielschleife hinzu.
import { rotateCannon } from "./cannon"; export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon]); };
Aktueller Spielzykluscode:
src / gameLoop / gameLoop.js import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon } from './cannon';
Wir haben eine Logik, die die Waffe drehen kann. Aber wir haben es noch nicht mit dem Drücken von Knöpfen in Verbindung gebracht. Es ist Zeit, es zu tun. Click-Ereignishandler werden zu src / components / Controls.svelte hinzugefügt.
import { direction } from "../stores/cannon.js";
Fügen Sie unsere Handler und den aktuellen Status des Klickens zu den IconButton- Elementen hinzu. Übergeben Sie dazu einfach die Werte an die zuvor erstellten Attribute start , release und active , wie in der Dokumentation beschrieben .
<IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton>
Wir haben den Ausdruck $ für die Richtungsvariable $ verwendet . Diese Syntax macht den Wert reaktiv und abonniert die Änderungen automatisch. Weitere Details in der Dokumentation .
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>
Im Moment, wenn Sie unseren Knopf drücken, erfolgt eine Auswahl, aber die Waffe dreht sich immer noch nicht. Wir müssen den Winkelwert in die Cannon.svelte- Komponente importieren und die Transformationsumwandlungsregeln aktualisieren
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>
Es bleibt, unsere Spieleschleife in der App.svelte- Komponente auszuführen .
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 />
Hurra! Unsere Waffe begann sich zu bewegen.

6. Schüsse
Bringen Sie jetzt unserer Waffe das Schießen bei. Wir müssen die Werte speichern:
- Schießt die Waffe jetzt (der Feuerknopf wird gedrückt);
- Der Zeitstempel des letzten Schusses wird benötigt, um die Feuerrate zu berechnen.
- Anordnung von Muscheln.
Fügen Sie diese Variablen zu unserem Repository src / store / cannon.js hinzu .
src / store / cannon.js import { writable } from 'svelte/store'; export const direction = writable(null); export const angle = writable(0);
Aktualisieren Sie Importe und Spielelogik in src / gameLoop / cannon.js .
src / gameLoop / cannon.js import { get } from 'svelte/store';
Jetzt importieren wir unsere Handler in gameLoop.js und fügen sie der Spielschleife hinzu.
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';
Jetzt müssen wir nur noch die Verarbeitung des Drückens des Feuerknopfs erstellen und die Anzeige der Muscheln auf dem Spielfeld hinzufügen.
Bearbeiten Sie src / components / Controls.svelte .
Fügen Sie nun unsere Handler zum Feuerkontrollknopf hinzu, wie wir es mit den Rotationsknöpfen getan haben
<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>
Es bleibt, um die Muscheln auf dem Spielfeld anzuzeigen. Erstellen Sie zunächst die Projektilkomponente
src / components / Bullet.svelte <script> </script> <g transform={`translate(${bullet.x}, ${bullet.y}) rotate(${bullet.angle})`}> <rect width="3" height="5" fill="#212121" /> </g>
Da die vorhandenen Shells in einem Array gespeichert sind, benötigen wir einen Iterator, um sie anzuzeigen. In svelte gibt es für solche Fälle jeweils eine Richtlinie. Weitere Details in der Dokumentation .
// 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.
Großartig. . 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 .
Fazit
, , React. 60 FPS, Svelte .
Svelte , .