Svelte 3 fue lanzado hace poco m谩s de un mes. Un buen momento para conocerte, pens茅, y le铆 un excelente tutorial , que tambi茅n fue traducido al ruso.
Para consolidar el pasado, hice un peque帽o proyecto y comparto los resultados contigo. Esta no es una lista m谩s de tareas pendientes, sino un juego en el que debes disparar desde casillas negras.

0. Para impacientes
Repositorio tutorial
Repositorio con Extras
Demo
1. Preparaci贸n
Clonamos una plantilla para el desarrollo
git clone https://github.com/sveltejs/template.git
Instalar las dependencias.
cd template/ npm i
Iniciamos el servidor de desarrollo.
npm run dev
Nuestra plantilla est谩 disponible en
http: // localhost: 5000 . El servidor admite la recarga en caliente, por lo que nuestros cambios ser谩n visibles en el navegador a medida que se guarden los cambios.
Si no desea implementar el entorno localmente, puede usar los sandboxes codesandbox y stackblitz en l铆nea que admiten Svelte.
2. El marco del juego.
La carpeta src consta de dos archivos main.js y App.svelte .
main.js es el punto de entrada a nuestra aplicaci贸n. Durante el desarrollo, no lo tocaremos. Aqu铆 el componente App.svelte est谩 montado en el cuerpo del documento.
App.svelte es un componente de svelte. La plantilla del componente consta de tres partes:
<script> </script> <style> h1 { color: purple; } </style> <h1>Hello {name}!</h1>
Los estilos de componentes est谩n aislados, pero es posible asignar estilos globales con la directiva: global (). M谩s sobre estilos .
Agregue estilos comunes para nuestro componente.
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>
Creemos la carpeta src / components en la que se almacenar谩n nuestros componentes.
En esta carpeta, cree dos archivos que contendr谩n el campo de juego y los controles.
src / components / GameField.svelte src / components / Controls.svelte El componente se importa por directiva
import Controls from "./components/Controls.svelte";
Para mostrar un componente, simplemente inserte la etiqueta del componente en el marcado. Obtenga m谩s informaci贸n sobre las etiquetas .
<Controls />
Ahora importamos y mostramos nuestros componentes en 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. Controles
El componente Controls.svelte constar谩 de tres botones: mover hacia la izquierda, mover hacia la derecha, disparar. El elemento svg mostrar谩 los iconos de los botones.
Cree la carpeta src / asssets , a la que agregamos nuestros iconos 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>
Agregue el componente de bot贸n src / components / IconButton.svelte .
Aceptaremos controladores de eventos del componente principal. Para poder mantener presionado el bot贸n, necesitamos dos controladores: el inicio del clic y el final del clic. Declaramos las variables inicio y lanzamiento , donde aceptaremos controladores de eventos para el inicio y el final del clic. Tambi茅n necesitamos la variable activa , que se mostrar谩 si se presiona el bot贸n o no.
<script> export let start; export let release; export let active; </script>
Estilizamos nuestro componente
<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 bot贸n es un elemento de bot贸n dentro del cual se muestra el contenido transferido desde un componente primario. El lugar donde se montar谩 el contenido transferido se indica mediante la etiqueta <slot />. Obtenga m谩s informaci贸n sobre el elemento <slot /> .
<button> <slot /> </button>
Los controladores de eventos se indican mediante la directiva on: por ejemplo, on: click .
Manejaremos los eventos del mouse y los clics t谩ctiles. Obtenga m谩s informaci贸n sobre el enlace de eventos .
La clase activa se agregar谩 a la clase base del componente si se presiona el bot贸n. Puede asignar una clase por la propiedad de clase. M谩s sobre clases
<button on:mousedown={start} on:touchstart={start} on:mouseup={release} on:touchend={release} class={`iconButton ${active ? 'active' : ''}`}> <slot /> </button>
Como resultado, nuestro componente se ver谩 as铆:
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>
Ahora importamos nuestros 铆conos y el elemento del bot贸n en src / components / Controls.svelte y conformamos el dise帽o.
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>
Nuestra aplicaci贸n deber铆a verse as铆:

4. El campo de juego
El campo de juego es un componente de svg, donde agregaremos nuestros elementos de juego (arma, proyectiles, oponentes).
Actualice el c贸digo 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>
Cree la pistola src / components / Cannon.svelte . Fuerte para un rect谩ngulo, pero no obstante.
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>
Ahora importamos nuestra arma en el campo de juego.
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. Ciclo de juego
Tenemos el marco b谩sico del juego. El siguiente paso es crear un ciclo de juego que procese nuestra l贸gica.
Creemos almacenes donde se contendr谩n variables para nuestra l贸gica. Necesitaremos el componente de escritura del m贸dulo svelte / store. Obtenga m谩s informaci贸n sobre la tienda .
Crear un repositorio simple se ve as铆:
Creemos la carpeta src / stores / , todos los valores mutables de nuestro juego se almacenar谩n aqu铆.
Cree el archivo src / stores / game.js en el que se almacenar谩n las variables responsables del estado general del juego.
Cree el archivo src / stores / cannon.js , en el que se almacenar谩n las variables responsables del estado de la pistola.
Svelte le permite crear repositorios personalizados que incluyen la l贸gica del trabajo. Puedes leer m谩s sobre esto en el libro de texto . No podr铆a encajar perfectamente en el concepto del ciclo del juego, por lo que solo declaramos variables en el repositorio. Realizaremos todas las manipulaciones con ellos en la secci贸n src / gameLoop .
El ciclo del juego se planificar谩 utilizando la funci贸n requestAnimationFrame . Una serie de funciones que describen la l贸gica del juego se alimentar谩n a la entrada. Al final del ciclo del juego, si el juego no est谩 terminado, se planifica la siguiente iteraci贸n. En el ciclo del juego, accederemos al valor de la variable isPlaying para verificar si el juego ha finalizado.
Con el almacenamiento, puede crear una suscripci贸n a un valor. Utilizaremos esta funcionalidad en componentes. Por ahora, usaremos la funci贸n get para leer el valor de la variable. Usaremos el m茅todo .set () de la variable para establecer el valor.
Puede actualizar el valor llamando al m茅todo .update () , que toma una funci贸n como entrada, cuyo primer valor pasa el valor actual. M谩s detalles en la documentaci贸n . Todo lo dem谩s es puro JS.
src / gameLoop / gameLoop.js Ahora describimos la l贸gica del comportamiento de nuestra arma.
src / gameLoop / cannon.js Ahora agregue nuestro controlador de rotaci贸n de armas al bucle del juego.
import { rotateCannon } from "./cannon"; export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon]); };
C贸digo actual del ciclo del juego:
src / gameLoop / gameLoop.js import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon } from './cannon';
Tenemos l贸gica que puede rotar el arma. Pero a煤n no lo hemos asociado con presionar botones. Es hora de hacerlo. Los controladores de eventos Click se agregar谩n a src / components / Controls.svelte .
import { direction } from "../stores/cannon.js";
Agregue nuestros controladores y el estado actual de hacer clic en los elementos IconButton . Para hacer esto, simplemente pase los valores a los atributos creados previamente, start , release y active , como se describe en la documentaci贸n .
<IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton>
Utilizamos la expresi贸n $ para la variable $ direction . Esta sintaxis hace que el valor sea reactivo, suscribi茅ndose autom谩ticamente a los cambios. M谩s detalles en la documentaci贸n .
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>
Por el momento, al presionar nuestro bot贸n, se produce una selecci贸n, pero la pistola a煤n no gira. Necesitamos importar el valor del 谩ngulo en el componente Cannon.svelte y actualizar las reglas de transformaci贸n de transformaci贸n.
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>
Queda por ejecutar nuestro bucle de juego en el componente 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 />
隆Hurra! Nuestra pistola comenz贸 a moverse.

6. Disparos
Ahora ense帽a nuestra arma para disparar. Necesitamos almacenar los valores:
- 驴El arma dispara ahora (se presiona el bot贸n de disparo);
- La marca de tiempo del 煤ltimo disparo, debe calcular la velocidad de disparo;
- Matriz de conchas.
Agregue estas variables a nuestro repositorio src / stores / cannon.js .
src / stores / cannon.js import { writable } from 'svelte/store'; export const direction = writable(null); export const angle = writable(0);
Actualice las importaciones y la l贸gica del juego en src / gameLoop / cannon.js .
src / gameLoop / cannon.js import { get } from 'svelte/store';
Ahora importamos nuestros controladores en gameLoop.js y los agregamos al bucle del juego.
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';
Ahora solo tenemos que crear el proceso de presionar el bot贸n de disparo y agregar la visualizaci贸n de proyectiles en el campo de juego.
Edite src / components / Controls.svelte .
Ahora agregue nuestros controladores al bot贸n de control de fuego, como lo hicimos con los botones de rotaci贸n
<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>
Queda por mostrar los proyectiles en el campo de juego. Primero, crea el componente de proyectil
src / components / Bullet.svelte <script> </script> <g transform={`translate(${bullet.x}, ${bullet.y}) rotate(${bullet.angle})`}> <rect width="3" height="5" fill="#212121" /> </g>
Dado que los shells que tenemos est谩n almacenados en una matriz, necesitamos un iterador para mostrarlos. En esbelto hay una directiva cada para tales casos. M谩s detalles en la documentaci贸n .
// 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.
Genial . 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 .
Conclusi贸n
, , React. 60 FPS, Svelte .
Svelte , .