Чуть больше месяца назад вышел релиз Svelte 3. Хороший момент для знакомства, — подумал я и пробежался по отличному туториалу, который еще и переведен на русский. Для закрепления пройденного я сделал небольшой проект и делюсь результатами с вами. Это не one-more-todo-list, а игра, в которой нужно отстреливаться от черных квадратов.   Репозиторий туториала
Репозиторий с дополнениями
Демо Клонируем шаблон для разработки  Устанавливаем зависимости. Запускаем dev сервер. Наш шаблон доступен по адресу
http://localhost:5000. Сервер поддерживает hot reload, поэтому наши изменения будут видны в браузере по мере сохранения изменений. Если вы не хотите разворачивать среду локально, то можете использовать онлайн песочницы codesandbox и stackblitz, которые поддерживают Svelte. Папка src состоит из двух файлов main.js и App.svelte.
main.js — это точка входа в наше приложение. Во время разработки мы ее трогать не будем. Здесь компонент App.svelte монтируется в body документа.
App.svelte — это компонент svelte. Шаблон компонента состоит из трех частей: Стили компонента изолированы, но есть возможность назначить глобальные стили директивой :global(). Подробнее о стилях.
Добавим общие стили для нашего компонента Давайте создадим папку src/components, в которой будут храниться наши компоненты
В этой папке создадим два файла, которые будут содержать игровое поле и элементы управления. Импорт компонента осуществляется директивой Для отображения компонента достаточно вставить тег компонента в разметку. Подробнее о тегах. Теперь импортируем и отобразим наши компоненты в App.svelte. Компонент Controls.svelte будет состоять из трех кнопок: движение влево, движение вправо, огонь. Иконки кнопок будут отображаться svg элементом.
Создадим папку src/asssets, в которую добавим наши svg иконки. Добавим компонент кнопки src/components/IconButton.svelte.
Мы будем принимать обработчики событий из родительского компонента. Для того, чтобы можно было зажать кнопку, нам понадобятся два обработчика: начало нажатия и конец нажатия. Объявим переменные start и release, куда будем принимать обработчики событий начала и окончания нажатия. Еще нам понадобится переменная active, которая будет отображать, нажата кнопка или нет. Стилизуем наш компонент Кнопка представляет собой button элемент, внутри которого отображается контент, переданный из родительского компонента. Место, где будут монтироваться переданный контент обозначается тегом <slot/>. Подробнее об элементе <slot/>. Обработчики событий обозначаются через директиву on:, например, on:click.
Мы будем обрабатывать события мыши и тач нажатия. Подробнее о привязке событий.
К базовому классу компонента будет добавляться класс active, если кнопка нажата. Назначить класс можно свойством class. Подробнее о классах В итоге наш компонент будет выглядеть следующим образом: Теперь импортируем наши иконки и элемент кнопки в src/components/Controls.svelte и сверстаем расположение. Наше приложение должно выглядеть так:
 Игровое поле представляет собой svg компонент, куда мы будем добавлять наши элементы игры (пушку, снаряды, противников).
Обновим код src/components/GameField.svelte Создадим пушку src/components/Cannon.svelte. Громко сказано для прямоугольника, но тем не менее. Теперь импортируем нашу пушку на игровое поле. У нас есть базовый каркас игры. Следующий шаг — создать игровой цикл, который будет обрабатывать нашу логику.
Создадим хранилища, где будут содержаться переменные для нашей логики. Нам понадобится компонент writable из модуля svelte/store. Подробнее о store.
Создание простого хранилища выглядит так: Создадим папку src/stores/, здесь будут храниться все изменяемые значения нашей игры.
Создадим файл src/stores/game.js, в котором будут храниться переменные, отвечающие за общее состояние игры. Создадим файл src/stores/cannon.js, в котором будут храниться переменные, отвечающие за состояние пушки Svelte позволяет создавать пользовательские хранилища, включающие логику работы. Подробнее об этом можно почитать в учебнике. У меня не получилось красиво вписать это в концепцию игрового цикла, поэтому в хранилище мы только объявляем переменные. Все манипуляции с ними мы будем производить в разделе src/gameLoop. Игровой цикл будет планироваться с помощью функции requestAnimationFrame. На вход будет подаваться массив из функций, описывающий логику игры. По завершении игрового цикла, если игра еще не закончена, планируется следующая итерация. В игровом цикле мы будет обращаться к значению переменной isPlaying, чтобы проверить, не закончилась ли игра.  Используя хранилище можно создавать подписку на значение. Этот функционал мы будем использовать в компонентах. Пока для чтения значения переменной будем использовать функцию get. Для установки значения будем использовать метод .set() переменной.
Обновить значение можно вызвав метод .update(), который на вход принимает функцию, в первый аргумент которого передается текущее значение. Подробнее в документации. Все остальное — чистый JS. Теперь опишем логику поведения нашей пушки.  Теперь добавим наш обработчик поворота пушки в игровой цикл.  Текущий код игрового цикла: У нас есть логика, которая умеет поворачивать пушку. Но мы еще не связали ее с нажатием кнопок. Самое время сделать это. Обработчики событий нажатий будем добавлять в src/components/Controls.svelte. Добавим наши обработчики и текущее состояние нажатия в элементы IconButton. Для этого просто передадим значения в ранее созданные атрибуты start, release и active, как описано в документации.  Мы использовали выражение $ для переменной $direction. Этот синтаксис делает значение реактивным, автоматически создавая подписку на изменения. Подробнее в документации. На данный момент при нажатии у нашей кнопки происходит выделение, но пушка еще не поворачивается. Нам необходимо импортировать значение angle в компонент Cannon.svelte и обновить правила трансформации transform Осталось запустить наш игровой цикл в компоненте App.svelte. Ура! Наша пушка начала двигаться.
 Теперь научим нашу пушку стрелять. Нам нужно хранить значения: Добавим эти переменные в наше хранилище src/stores/cannon.js. Обновим импорты и игровую логику в src/gameLoop/cannon.js.  Теперь импортируем наши обработчики в gameLoop.js и добавим их в игровой цикл. Теперь нам осталось создать обработку нажатия кнопки огонь и добавить отображение снарядов на игровом поле.
Отредактируем src/components/Controls.svelte. Теперь добавим наши обработчики к кнопке, управляющей огнем, как мы делали это с кнопками поворота Осталось отобразить снаряды на игровом поле. Сначала создадим компонент снаряда Поскольку снаряды у нас хранятся в массиве, нам понадобится итератор для их отображения. В svelte для таких случаев есть директива Each. Подробнее в документации.  Теперь наша пушка умеет стрелять.
 Отлично. Для минимального геймплея нам осталось добавить врагов. Давайте создадим хранилище src/stores/enemy.js. Создадим обработчики игрового цикла для врагов в src/gameLoop/enemy.js Добавим обработчики врагов в наш игровой цикл. Создадим компонент src/components/Enemy.js по аналогии со снарядом. Осталось импортировать компонент врага, массив с объектами врагов в наше игровое поле и отобразить их в цикле Each Враг наступает!
 Пока наши снаряды пролетают мимо, не причинив никакого вреда врагам.
Самое время добавить обработку столкновений. Общая игровая логика будет жить в файле src/gameLoop/game.js. Описание методики расчета столкновений можно прочитать на MDN Осталось добавить обработчик столкновений в игровой цикл. Отлично, наши снаряды научились поражать цель.
 Если вы дожили до этого момента и не потеряли интерес к нашим квадратным войнам, то у меня есть список ToDo на самостоятельное изучение: Мою реализацию этого списка вы можете посмотреть на github и в демо. Эту игру, в качестве обучающего примера, я пытался сделать на React. Из коробки мне не удалось завести игру в 60 FPS, а вот со Svelte получилось с первой попытки.
Попробуйте Svelte прямо сейчас, вам понравится. CTO