تم إصدار Svelte 3 منذ أكثر من شهر بقليل. اعتقدت ، إنها لحظة جيدة لمقابلتك ، وتمر عبر برنامج تعليمي ممتاز ، والذي تم ترجمته أيضًا إلى الروسية.
لتوحيد الماضي ، قمت بعمل مشروع صغير وتبادل النتائج معك. هذه ليست قائمة واحدة فقط ، لكنها لعبة تحتاج إلى إطلاق النار فيها من المربعات السوداء.

0. لفارغ الصبر
مستودع تعليمي
مستودع مع إضافات
عرض
1. التحضير
نحن استنساخ قالب للتنمية
git clone https://github.com/sveltejs/template.git
تثبيت التبعيات.
cd template/ npm i
نبدأ خادم ديف.
npm run dev
قالبنا متاح في
http: // localhost: 5000 . يدعم الخادم التحديث السريع ، لذلك ستكون تغييراتنا مرئية في المتصفح عند حفظ التغييرات.
إذا كنت لا ترغب في نشر البيئة محليًا ، فيمكنك استخدام أكواد الترميز وصناديق الرمل stackblitz عبر الإنترنت التي تدعم Svelte.
2. إطار اللعبة
يتكون مجلد src من ملفين main.js و App.svelte .
main.js هو نقطة الدخول إلى طلبنا. أثناء التطوير ، لن نلمسها. هنا يتم تثبيت مكون App.svelte في نص المستند.
App.svelte هو مكون من svelte. يتكون قالب المكون من ثلاثة أجزاء:
<script> </script> <style> h1 { color: purple; } </style> <h1>Hello {name}!</h1>
أنماط المكونات معزولة ، لكن من الممكن تعيين أنماط عمومية باستخدام التوجيه: global (). المزيد عن الأنماط .
إضافة أنماط مشتركة لمكوننا.
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>
لنقم بإنشاء مجلد src / components الذي سيتم تخزين مكوناتنا فيه
في هذا المجلد ، قم بإنشاء ملفين يحتويان على حقل التحكم وعناصر التحكم.
src / مكونات / GameField.svelte src / مكونات / Controls.svelte يتم استيراد المكون عن طريق التوجيه
import Controls from "./components/Controls.svelte";
لعرض مكون ، فقط أدخل علامة المكون في الترميز. معرفة المزيد عن العلامات .
<Controls />
الآن نحن استيراد وعرض مكوناتنا في 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. الضوابط
يتكون مكون Controls.svelte من ثلاثة أزرار: حرك اليسار ، تحرك لليمين ، إطلاق نار. سيتم عرض أيقونات الأزرار بواسطة عنصر svg.
قم بإنشاء مجلد src / asssets ، نضيف إليه أيقونات svg الخاصة بنا.
src / الأصول / 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 / الأصول / 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 / الأصول / 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>
إضافة عنصر الزر 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>
الزر هو عنصر زر يتم من خلاله عرض المحتوى المنقول من مكون أصل. يشار إلى المكان الذي سيتم فيه تحميل المحتوى المنقول بالعلامة <slot />. تعرف على المزيد حول العنصر <slot /> .
<button> <slot /> </button>
تتم الإشارة إلى معالجات الأحداث باستخدام on: التوجيه ، على سبيل المثال ، on: click .
سنتعامل مع أحداث الماوس ونقرات اللمس. تعرف على المزيد حول ربط الحدث .
ستتم إضافة الفئة النشطة إلى الفئة الأساسية للمكون في حالة الضغط على الزر. يمكنك تعيين فئة حسب خاصية الفصل. المزيد عن الفصول
<button on:mousedown={start} on:touchstart={start} on:mouseup={release} on:touchend={release} class={`iconButton ${active ? 'active' : ''}`}> <slot /> </button>
نتيجة لذلك ، سيبدو مكوننا كما يلي:
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>
نحن الآن نستورد أيقوناتنا وعنصر الزر في src / components / Controls.svelte ونقوم بتكوين التصميم.
src / مكونات / 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>
يجب أن يبدو طلبنا كما يلي:

4. الملعب
ميدان اللعب هو أحد مكونات svg ، حيث سنضيف عناصر لعبتنا (البندقية ، القذائف ، الخصوم).
تحديث src / components / GameField.svelte code
src / مكونات / 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>
قم بإنشاء src / components / Cannon.svelte gun . بصوت عال للمستطيل ، ولكن مع ذلك.
src / المكونات / Cannon.svelte <style> .cannon { transform-origin: 4px 55px; } </style> <g class="cannon" transform={`translate(236, 700)`}> <rect width="8" height="60" fill="#212121" /> </g>
الآن نحن استيراد بندقية لدينا في الملعب.
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. دورة اللعبة
لدينا الإطار الأساسي للعبة. والخطوة التالية هي إنشاء حلقة لعبة تعالج منطقنا.
لنقم بإنشاء مخازن حيث سيتم احتواء متغيرات منطقنا. سنحتاج إلى مكون قابل للكتابة من وحدة svelte / store. تعلم المزيد عن المتجر .
يبدو إنشاء مستودع بسيط كما يلي:
فلنقم بإنشاء src / stores / folder ، وسيتم تخزين جميع القيم القابلة للتغيير لعبتنا هنا.
قم بإنشاء ملف src / stores / game.js حيث سيتم تخزين المتغيرات المسؤولة عن الحالة العامة للعبة.
قم بإنشاء ملف src / stores / cannon.js ، حيث سيتم تخزين المتغيرات المسؤولة عن حالة السلاح
يسمح لك Svelte بإنشاء مستودعات مخصصة تتضمن منطق العمل. يمكنك قراءة المزيد عن هذا في الكتاب المدرسي . لا يمكنني دمج هذا بشكل جميل في مفهوم دورة اللعبة ، لذلك نحن فقط نعلن عن المتغيرات في المستودع. سنقوم بإجراء جميع عمليات التلاعب معهم في قسم src / gameLoop .
سيتم تخطيط حلقة اللعبة باستخدام الدالة requestAnimationFrame . سيتم تغذية مجموعة من الوظائف التي تصف منطق اللعبة بالإدخال. في نهاية دورة اللعبة ، إذا لم تنته اللعبة ، فسيتم التخطيط للتكرار التالي. في حلقة اللعبة ، سنصل إلى قيمة المتغير isPlaying للتحقق مما إذا كانت اللعبة قد انتهت.
باستخدام التخزين ، يمكنك إنشاء اشتراك في قيمة. سوف نستخدم هذه الوظيفة في المكونات. في الوقت الحالي ، سوف نستخدم دالة get لقراءة قيمة المتغير. سوف نستخدم طريقة .set () للمتغير لضبط القيمة.
يمكنك تحديث القيمة عن طريق استدعاء الأسلوب .update () ، والذي يأخذ دالة كمدخل ، يتم تمرير القيمة الحالية إلى الوسيطة الأولى. مزيد من التفاصيل في الوثائق . كل شيء آخر هو JS النقي.
src / gameLoop / gameLoop.js الآن وصفنا منطق سلوك سلاحنا.
src / gameLoop / cannon.js الآن قم بإضافة معالج تدوير البندقية إلى حلقة اللعبة.
import { rotateCannon } from "./cannon"; export const startGame = () => { isPlaying.set(true); startLoop([rotateCannon]); };
رمز دورة اللعبة الحالية:
src / gameLoop / gameLoop.js import { isPlaying } from '../stores/game'; import { get } from 'svelte/store'; import { rotateCannon } from './cannon';
لدينا منطق يمكن أن يدور البندقية. لكننا لم نربطها بعد بكبسة الأزرار. حان الوقت للقيام بذلك. ستتم إضافة معالجات الأحداث انقر فوق src / components / Controls.svelte .
import { direction } from "../stores/cannon.js";
أضف معالجاتنا والحالة الحالية للنقر على عناصر IconButton . للقيام بذلك ، ما عليك سوى تمرير القيم إلى السمات التي تم إنشاؤها سابقًا والتي تبدأ ، وتحرر وتنشط ، كما هو موضح في الوثائق .
<IconButton start={setDirectionLeft} release={resetDirection} active={$direction === 'left'}> <LeftArrow /> </IconButton> <IconButton start={setDirectionRight} release={resetDirection} active={$direction === 'right'}> <RightArrow /> </IconButton>
استخدمنا التعبير $ لمتغير الاتجاه $ . بناء الجملة هذا يجعل القيمة متفاعلة ، الاشتراك تلقائيًا في التغييرات. مزيد من التفاصيل في الوثائق .
src / مكونات / 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>
في الوقت الحالي ، عند الضغط على زرنا ، يحدث اختيار ، ولكن البندقية لا تزال لا تدور. نحتاج إلى استيراد قيمة الزاوية إلى مكون Cannon.svelte وتحديث قواعد تحويل التحويل
src / المكونات / 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>
يبقى تشغيل حلقة لعبتنا في مكون 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 />
الصيحة! بدأ سلاحنا في التحرك.

6. الطلقات
الآن تعليم سلاحنا لإطلاق النار. نحن بحاجة إلى تخزين القيم:
- هل تطلق النار من البندقية الآن (يتم الضغط على زر النار) ؛
- الطابع الزمني للرصاصة الأخيرة ، تحتاج إلى حساب معدل إطلاق النار.
- مجموعة من القذائف.
أضف هذه المتغيرات إلى مستودع src / stores / cannon.js .
src / stores / cannon.js import { writable } from 'svelte/store'; export const direction = writable(null); export const angle = writable(0);
تحديث الواردات ومنطق اللعبة في src / gameLoop / cannon.js .
src / gameLoop / cannon.js import { get } from 'svelte/store';
الآن نستورد معالجاتنا إلى gameLoop.js ونضيفهم إلى حلقة اللعبة.
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';
الآن علينا فقط إنشاء معالجة للضغط على زر إطلاق النار وإضافة عرض الأصداف في الملعب.
تحرير src / المكونات / Controls.svelte .
أضف الآن معالجاتنا إلى زر التحكم في الحريق ، كما فعلنا مع أزرار الدوران
<IconButton start={startFire} release={stopFire} active={$isFiring}> <Bullet /> </IconButton>
src / مكونات / 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>
يبقى لعرض الأصداف في الملعب. أولا ، إنشاء مكون قذيفة
src / المكونات / Bullet.svelte <script> </script> <g transform={`translate(${bullet.x}, ${bullet.y}) rotate(${bullet.angle})`}> <rect width="3" height="5" fill="#212121" /> </g>
نظرًا لأن الأصداف التي تم تخزينها في صفيف ، فإننا نحتاج إلى مكرر لعرضها. في svelte هناك كل توجيه لمثل هذه الحالات. مزيد من التفاصيل في الوثائق .
// 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.
ممتاز. . 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 .
استنتاج
, , React. 60 FPS, Svelte .
Svelte , .