Mengembangkan game di Svelte 3

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.


gambar


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> // JS   export let name; </script> <style> /* CSS   */ 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%; /*     100% */ } :global(body) { height: 100%; /*     100% */ overscroll-behavior: none; /*  pull to refresh*/ 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
 <div>GameField</div> 

src / components / Controls.svelte
 <div>Controls</div> 

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> //   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> <!--  . ,     , , ,  react --> <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 { /*   flex     */ display: flex; align-items: center; justify-content: center; /*    60px */ 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> //      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> <LeftArrow /> </IconButton> <IconButton> <RightArrow /> </IconButton> </div> <IconButton> <Bullet /> </IconButton> </div> </div> 

Aplikasi kita akan terlihat seperti ini:
gambar


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"> <!--    viewBox          --> <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> <!--      svg .   <g>     --> <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> //    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"> <!--    --> <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:


 //     import { writable } from "svelte/store"; //      null export const isPlaying = writable(null); 

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.


src / stores / game.js
 //     import { writable } from "svelte/store"; //        ,    true/false export const isPlaying = writable(false); 

Buat file src / store / cannon.js , di mana variabel-variabel yang bertanggung jawab atas keadaan pistol akan disimpan


src / store / cannon.js
 //     import { writable } from "svelte/store"; //    ,     . //    'left', 'right', null,    export const direction = writable(null); //     export const angle = writable(0); 

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
 //     import { isPlaying } from '../stores/game'; //    get     ,  . import { get } from 'svelte/store'; //      function startLoop(steps) { window.requestAnimationFrame(() => { //      steps.forEach(step => { //    -  if (typeof step === 'function') step(); }); //    ,    if (get(isPlaying)) startLoop(steps); }); } //       export const startGame = () => { //  ,      true isPlaying.set(true); //   .     startLoop([]); }; //       export function stopGame() { //  ,      false isPlaying.set(false); } 

Sekarang kami menggambarkan logika perilaku senjata kami.


src / gameLoop / cannon.js
 //    get     ,  . import { get } from 'svelte/store'; //      cannon import { angle, direction } from '../stores/cannon.js'; //      export function rotateCannon() { //     const currentAngle = get(angle); //    ,   ,    switch (get(direction)) { //    ""     -45°, //      0.4 case 'left': if (currentAngle > -45) angle.update(a => a - 0.4); break; //    ""     45°, //      0.4 case 'right': if (currentAngle < 45) angle.update(a => a + 0.4); break; default: break; } } 

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'; //     function startLoop(steps) { window.requestAnimationFrame(() => { steps.forEach(step => { if (typeof step === 'function') step(); }); if (get(isPlaying)) startLoop(steps); }); } export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon]); //      }; export function stopGame() { isPlaying.set(false); } 

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"; //       //    const resetDirection = () => direction.set(null); const setDirectionLeft = () => direction.set("left"); const setDirectionRight = () => direction.set("right"); 

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"; //     import { direction } from "../stores/cannon.js"; //    const resetDirection = () => direction.set(null); const setDirectionLeft = () => direction.set("left"); const setDirectionRight = () => direction.set("right"); </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> //      import { angle } from "../stores/cannon.js"; </script> <style> .cannon { transform-origin: 4px 55px; } </style> <!--    rotate(${$angle})--> <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"; //     import { startGame } from "./gameLoop/gameLoop"; //  startGame(); </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.
gambar


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); //   export const isFiring = writable(false); export const lastFireAt = writable(0); export const bulletList = writable([]); 

Perbarui impor dan logika game di src / gameLoop / cannon.js .


src / gameLoop / cannon.js
 import { get } from 'svelte/store'; //   import { angle, direction, isFiring, lastFireAt, bulletList } from '../stores/cannon.js'; export function rotateCannon() { const currentAngle = get(angle); switch (get(direction)) { case 'left': if (currentAngle > -45) angle.update(a => a - 0.4); break; case 'right': if (currentAngle < 45) angle.update(a => a + 0.4); break; default: break; } } //   export function shoot() { //           800 , //          if (get(isFiring) && Date.now() - get(lastFireAt) > 800) { lastFireAt.set(Date.now()); //         . //  id   Math.random    bulletList.update(bullets => [...bullets, { x: 238, y: 760, angle: get(angle), id: () => Math.random() + Date.now() }]); } } //    export function moveBullet() { //    ,      y  -20, //          . //    , ,     . //      , ? bulletList.update(bullets => bullets.map(bullet => ({ ...bullet, y: bullet.y - 20, x: (780 - bullet.y) * Math.tan((bullet.angle * Math.PI) / 180) + 238, })), ); } //    ,     . export function clearBullets() { bulletList.update(bullets => bullets.filter(bullet => bullet.y > 0)); } //     Id. ,        export function removeBullet(id) { bulletList.update(bullets => bullets.filter(bullet => bullet.id !== id)); } 

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'; //        import { rotateCannon, shoot, moveBullet, clearBullets } from "./cannon"; function startLoop(steps) { window.requestAnimationFrame(() => { steps.forEach(step => { if (typeof step === 'function') step(); }); if (get(isPlaying)) startLoop(steps); }); } export const startGame = () => { isPlaying.set(true); //      startLoop([rotateCannon, shoot, moveBullet, clearBullets ]); }; export function stopGame() { isPlaying.set(false); } 

Sekarang kita hanya perlu membuat pemrosesan menekan tombol api dan menambahkan tampilan kerang di lapangan bermain.
Edit src / components / Controls.svelte .


 //  ,       import { direction, isFiring } from "../stores/cannon.js"; //      const startFire = () => isFiring.set(true); const stopFire = () => isFiring.set(false); 

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"; //  ,       import { direction, isFiring } from "../stores/cannon.js"; const resetDirection = () => direction.set(null); const setDirectionLeft = () => direction.set("left"); const setDirectionRight = () => direction.set("right"); //      const startFire = () => isFiring.set(true); const stopFire = () => isFiring.set(false); </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> //   bullet  ,    export let bullet; </script> <!--  -  svg  --> <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"; //    import Bullet from "./Bullet.svelte"; //      import { bulletList } from "../stores/cannon"; </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> 

.
gambar


7.


Bagus . src/stores/enemy.js .


src/stores/enemy.js
 import { writable } from "svelte/store"; //   export const enemyList = writable([]); //      export const lastEnemyAddedAt = writable(0); 

src/gameLoop/enemy.js


src/gameLoop/enemy.js
 import { get } from 'svelte/store'; //      import { enemyList, lastEnemyAddedAt } from '../stores/enemy.js'; //    export function addEnemy() { //         2500 , //     if (Date.now() - get(lastEnemyAddedAt) > 2500) { //      lastEnemyAddedAt.set(Date.now()); //        1  499 // (   ) enemyList.update(enemies => [ ...enemies, { x: Math.floor(Math.random() * 449) + 1, y: 0, id: () => Math.random() + Date.now(), }, ]); } } //   .       0.5 export function moveEnemy() { enemyList.update(enemyList => enemyList.map(enemy => ({ ...enemy, y: enemy.y + 0.5, })), ); } //      id,     export function removeEnemy(id) { enemyList.update(enemies => enemies.filter(enemy => enemy.id !== id)); } 

.


src/gameLoop/gameLoop.js
 import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon, shoot, moveBullet, clearBullets } from './cannon'; //      import { addEnemy, moveEnemy } from './enemy'; function startLoop(steps) { window.requestAnimationFrame(() => { steps.forEach(step => { if (typeof step === 'function') step(); }); if (get(isPlaying)) startLoop(steps); }); } export const startGame = () => { isPlaying.set(true); //      startLoop([rotateCannon, shoot, moveBullet, clearBullets, addEnemy, moveEnemy]); }; export function stopGame() { isPlaying.set(false); } 

src/components/Enemy.js .


src/components/Enemy.js
 <script> //   enemy   ,   export let enemy; </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"; //    import Enemy from "./Enemy.svelte"; import { bulletList } from "../stores/cannon"; //      import { enemyList } from "../stores/enemy"; </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> 

!
gambar


8.


, .
. src/gameLoop/game.js . MDN


src/gameLoop/game.js
 import { get } from 'svelte/store'; //    import { bulletList } from '../stores/cannon'; //    import { enemyList } from '../stores/enemy'; //     import { removeBullet } from './cannon'; //     import { removeEnemy } from './enemy'; //       . //     ,   svg,   , //        . const enemyWidth = 30; const bulletWidth = 5; const enemyHeight = 30; const bulletHeight = 8; //    export function checkCollision() { get(bulletList).forEach(bullet => { get(enemyList).forEach(enemy => { if ( bullet.x < enemy.x + enemyWidth && bullet.x + bulletWidth > enemy.x && bullet.y < enemy.y + enemyHeight && bullet.y + bulletHeight > enemy.y ) { //   ,         removeBullet(bullet.id); removeEnemy(enemy.id); } }); }); } 

.


src/gameLoop/gameLoop.js
 import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon, shoot, moveBullet, clearBullets } from './cannon'; //    import { checkCollision } from './game'; import { addEnemy, moveEnemy } from './enemy'; function startLoop(steps) { window.requestAnimationFrame(() => { steps.forEach(step => { if (typeof step === 'function') step(); }); if (get(isPlaying)) startLoop(steps); }); } export const startGame = () => { isPlaying.set(true); //      startLoop([rotateCannon, shoot, moveBullet, clearBullets, addEnemy, moveEnemy, checkCollision]); }; export function stopGame() { isPlaying.set(false); } 

, .
gambar


9.


, ToDo :


  • , ;
  • ;
  • ;
  • . svelte ;
  • ;
  • . .

github .


Kesimpulan


, , React. 60 FPS, Svelte .
Svelte , .

Source: https://habr.com/ru/post/id452684/


All Articles