Svelte 3 dirilis sedikit lebih dari sebulan yang lalu. Saat yang tepat untuk bertemu dengan Anda, saya pikir, dan menjalankan tutorial yang sangat baik , yang juga diterjemahkan ke dalam bahasa Rusia.
Untuk mengkonsolidasikan masa lalu, saya membuat proyek kecil dan membagikan hasilnya dengan Anda. Ini bukan satu-lagi-daftar-todo, tetapi permainan di mana Anda harus menembak dari kotak hitam.

0. Untuk tidak sabar
Gudang tutorial
Repositori dengan Ekstra
Demo
1. Persiapan
Kami mengkloning templat untuk pengembangan
git clone https://github.com/sveltejs/template.git
Instal dependensi.
cd template/ npm i
Kami memulai server dev.
npm run dev
Template kami tersedia di
http: // localhost: 5000 . Server mendukung pemuatan ulang panas, sehingga perubahan kami akan terlihat di browser saat perubahan disimpan.
Jika Anda tidak ingin menyebarkan lingkungan secara lokal, Anda dapat menggunakan codesandbox dan stackblitz sandboxes online yang mendukung Svelte.
2. Kerangka permainan
Folder src terdiri dari dua file main.js dan App.svelte .
main.js adalah titik masuk ke aplikasi kita. Selama pengembangan, kami tidak akan menyentuhnya. Di sini komponen App.svelte dipasang di badan dokumen.
App.svelte adalah komponen dari langsing. Templat komponen terdiri dari tiga bagian:
<script> </script> <style> h1 { color: purple; } </style> <h1>Hello {name}!</h1>
Gaya komponen terisolasi, tetapi dimungkinkan untuk menetapkan gaya global dengan arahan: global (). Lebih lanjut tentang gaya .
Tambahkan gaya umum untuk komponen kami.
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>
Mari kita membuat folder src / components di mana komponen kita akan disimpan
Di folder ini, buat dua file yang akan berisi bidang bermain dan kontrol.
src / components / GameField.svelte src / components / Controls.svelte Komponen diimpor oleh direktif
import Controls from "./components/Controls.svelte";
Untuk menampilkan komponen, cukup masukkan tag komponen ke markup. Pelajari lebih lanjut tentang tag .
<Controls />
Sekarang kami mengimpor dan menampilkan komponen kami di 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. Kontrol
Komponen Controls.svelte akan terdiri dari tiga tombol: bergerak ke kiri, bergerak ke kanan, api. Ikon tombol akan ditampilkan oleh elemen svg.
Buat folder src / asssets , yang kami tambahkan ikon svg kami.
src / aset / 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 / aset / 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 / aset / 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>
Tambahkan komponen tombol src / components / IconButton.svelte .
Kami akan menerima event handler dari komponen induk. Untuk dapat menahan tombol, kita perlu dua penangan: awal klik dan akhir klik. Kami mendeklarasikan variabel mulai dan dirilis , di mana kami akan menerima penangan acara untuk awal dan akhir klik. Kami juga membutuhkan variabel aktif , yang akan ditampilkan jika tombol ditekan atau tidak.
<script> export let start; export let release; export let active; </script>
Kami menyesuaikan dgn mode komponen kami
<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>
Tombol adalah elemen tombol tempat konten yang ditransfer dari komponen induk ditampilkan. Tempat di mana konten yang ditransfer akan dipasang ditunjukkan oleh tag <slot />. Pelajari lebih lanjut tentang elemen <slot /> .
<button> <slot /> </button>
Penangan acara ditunjukkan menggunakan arahan on :, misalnya, pada: klik .
Kami akan menangani acara mouse dan menyentuh klik. Pelajari lebih lanjut tentang pengikatan acara .
Kelas aktif akan ditambahkan ke kelas dasar komponen jika tombol ditekan. Anda dapat menetapkan kelas berdasarkan properti kelas. Lebih lanjut tentang kelas
<button on:mousedown={start} on:touchstart={start} on:mouseup={release} on:touchend={release} class={`iconButton ${active ? 'active' : ''}`}> <slot /> </button>
Akibatnya, komponen kami akan terlihat seperti ini:
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>
Sekarang kita mengimpor ikon dan elemen tombol kita ke src / components / Controls.svelte dan membuat tata letak.
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>
Aplikasi kita akan terlihat seperti ini:

4. Lapangan bermain
Lapangan bermain adalah komponen svg, di mana kami akan menambahkan elemen permainan kami (senjata, peluru, lawan).
Perbarui kode 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>
Buat pistol src / components / Cannon.svelte . Keras untuk persegi panjang, tapi tetap saja.
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>
Sekarang kami mengimpor senjata kami di lapangan bermain.
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. Siklus permainan
Kami memiliki kerangka dasar permainan. Langkah selanjutnya adalah membuat loop game yang akan memproses logika kita.
Mari kita buat penyimpanan di mana variabel untuk logika kita akan terkandung. Kita akan membutuhkan komponen yang dapat ditulis dari modul langsing / toko. Pelajari lebih lanjut tentang toko .
Membuat repositori sederhana terlihat seperti ini:
Mari kita buat src / stores / folder, semua nilai yang bisa berubah dari game kita akan disimpan di sini.
Buat file src / stores / game.js di mana variabel yang bertanggung jawab untuk keadaan umum game akan disimpan.
Buat file src / store / cannon.js , di mana variabel-variabel yang bertanggung jawab atas keadaan pistol akan disimpan
Svelte memungkinkan Anda membuat repositori khusus yang menyertakan logika kerja. Anda dapat membaca lebih lanjut tentang ini di buku teks . Saya tidak bisa memasukkan ini dengan indah ke dalam konsep siklus permainan, jadi kami hanya mendeklarasikan variabel dalam repositori. Kami akan melakukan semua manipulasi dengan mereka di bagian src / gameLoop .
Gim game akan direncanakan menggunakan fungsi requestAnimationFrame . Berbagai fungsi yang menggambarkan logika permainan akan dimasukkan ke input. Pada akhir siklus permainan, jika permainan tidak selesai, iterasi berikutnya direncanakan. Dalam loop game, kami akan mengakses nilai variabel isPlaying untuk memeriksa apakah game telah berakhir.
Menggunakan penyimpanan, Anda dapat membuat langganan ke suatu nilai. Kami akan menggunakan fungsi ini dalam komponen. Untuk saat ini, kita akan menggunakan fungsi get untuk membaca nilai variabel. Untuk mengatur nilainya, kita akan menggunakan metode .set () dari variabel.
Anda dapat memperbarui nilai dengan memanggil metode .update () , yang mengambil fungsi sebagai input, nilai pertama yang melewati nilai saat ini. Lebih detail dalam dokumentasi . Segala sesuatu yang lain adalah JS murni.
src / gameLoop / gameLoop.js Sekarang kami menggambarkan logika perilaku senjata kami.
src / gameLoop / cannon.js Sekarang tambahkan handler rotasi pistol kami ke loop game.
import { rotateCannon } from "./cannon"; export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon]); };
Kode siklus permainan saat ini:
src / gameLoop / gameLoop.js import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon } from './cannon';
Kami memiliki logika yang dapat memutar pistol. Namun kami belum mengaitkannya dengan menekan tombol. Saatnya melakukannya. Penangan acara klik akan ditambahkan ke src / components / Controls.svelte .
import { direction } from "../stores/cannon.js";
Tambahkan penangan kami dan status mengklik saat ini ke elemen IconButton . Untuk melakukan ini, cukup berikan nilai ke atribut yang dibuat sebelumnya mulai , lepaskan dan aktif , seperti yang dijelaskan dalam dokumentasi .
<IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton>
Kami menggunakan ekspresi $ untuk variabel $ direction . Sintaks ini membuat nilai reaktif, secara otomatis berlangganan perubahan. Lebih detail dalam dokumentasi .
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>
Saat ini, ketika menekan tombol kami, sebuah pilihan terjadi, tetapi pistol masih tidak berputar. Kita perlu mengimpor nilai sudut ke komponen Cannon.svelte dan memperbarui aturan transformasi transformasi
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>
Tetap menjalankan putaran permainan kami di komponen 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 />
Hore! Pistol kami mulai bergerak.

6. Tembakan
Sekarang ajarkan senjata kami untuk menembak. Kita perlu menyimpan nilai:
- Apakah pistol menembak sekarang (tombol api ditekan);
- Cap waktu tembakan terakhir, Anda perlu menghitung laju api;
- Array kerang.
Tambahkan variabel-variabel ini ke repositori src / store / cannon.js kami .
src / store / cannon.js import { writable } from 'svelte/store'; export const direction = writable(null); export const angle = writable(0);
Perbarui impor dan logika game di src / gameLoop / cannon.js .
src / gameLoop / cannon.js import { get } from 'svelte/store';
Sekarang kita mengimpor handler kita ke gameLoop.js dan menambahkannya ke loop game.
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';
Sekarang kita hanya perlu membuat pemrosesan menekan tombol api dan menambahkan tampilan kerang di lapangan bermain.
Edit src / components / Controls.svelte .
Sekarang tambahkan handler kami ke tombol kontrol api, seperti yang kami lakukan dengan tombol rotasi
<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>
Tetap menampilkan kerang di lapangan bermain. Pertama, buat komponen proyektil
src / components / Bullet.svelte <script> </script> <g transform={`translate(${bullet.x}, ${bullet.y}) rotate(${bullet.angle})`}> <rect width="3" height="5" fill="#212121" /> </g>
Karena shell yang kita miliki disimpan dalam array, kita membutuhkan iterator untuk menampilkannya. Dalam langsing ada arahan Setiap kasus tersebut. Lebih detail dalam dokumentasi .
// 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.
Bagus . 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 .
Kesimpulan
, , React. 60 FPS, Svelte .
Svelte , .