diff --git a/content/posts/pixijs/assets-and-parallax.md b/content/posts/pixijs/assets-and-parallax.md new file mode 100644 index 0000000..e08354f --- /dev/null +++ b/content/posts/pixijs/assets-and-parallax.md @@ -0,0 +1,286 @@ +--- +title: "Pixi.js: Пример эффекта параллакс и использование ассетов" +date: 2022-10-29T00:24:59+03:00 +draft: false +tags: [pixijs, gamedev, tutorial, javascript] +--- + +## Pixi.js 7.0.0 + +4 часа назад вышла [7–я версия](https://github.com/pixijs/pixijs/releases/tag/v7.0.0) +библиотеки [Pixi.js](https://pixijs.com). + +Для тех, кто уже использует Pixi.js подготовлен +[документ](https://github.com/pixijs/pixijs/wiki/v7-Migration-Guide) +по миграции на новую версию. + +В данном примере будем использовать загрузчик ассетов [PIXI.Assets](https://pixijs.download/release/docs/PIXI.Assets.html), +который потеснил [PIXI.Loader](https://pixijs.download/release/docs/PIXI.Loader.html). +Подробнее написано здесь: [v7-Migration-Guide#-replaces-loader-with-assets](https://github.com/pixijs/pixijs/wiki/v7-Migration-Guide#-replaces-loader-with-assets). + +## PIXI.Assets + +> PIXI.Assets — это универсальный инструмент для управления ресурсами в Pixi! +> Суперсовременный и простой в использовании, с достаточной гибкостью, +> чтобы настраивать и делать то, что вам нужно! + +### Загрузка ассетов +Не бойся загружать данные несколько раз — +Pixi.Assets **НИКОГДА** ничего не загрузит больше одного раза. + +Например: + +```javascript +promise1 = PIXI.Assets.load('bunny.png') +promise2 = PIXI.Assets.load('bunny.png') + +// promise1 === promise2 +``` + +Вот несколько примеров загрузки ассетов: + +```javascript +// простой пример +PIXI.Assets.add('bunnyBooBoo', 'bunny.png'); +const bunny = await PIXI.Assets.load('bunnyBooBoo'); + +// несколько значений +PIXI.Assets.add(['burger', 'chicken'], 'bunny.png'); + +const bunny = await PIXI.Assets.load('burger'); +const bunny2 = await PIXI.Assets.load('chicken'); + +// передача параметров объекту +PIXI.Assets.add( + 'bunnyBooBooSmooth', + 'bunny{png,webp}', + {scaleMode:SCALE_MODES.NEAREST}, +); +``` + +## Каркас приложения + +HTML документ `index.html`: + +```html + + + + + + pixi.js - assets & parallax + + + + + + +``` + +Код приложения `app.js` + +```javascript +let app; +let backgroundTextures; +let background = { + sky: null, + back: null, + middle: null, + front: null, + x: 0, + speed: 1, +}; + +/** Инициализация PIXI и настройка PIXI.Assets */ +window.onload = function() { + app = new PIXI.Application({ + width: 640, + height: 360, + backgroundColor: 0x2a2a3a, + }); + document.body.appendChild(app.view); +} + +/** Функция инициализации уровня */ +function initLevel() { + app.ticker.add(gameLoop); +} + +/** Игровой цикл */ +function gameLoop(delta) { + // +} +``` + +В переменной `app` я буду хранить объект `PIXI.Application`, +чтобы можно было получать доступ к нему из других функций. + +В переменную `backgroundTextures` будет загружать текстуры. + +Переменная `background` отвечает за необходимые параметры, +такие как скорость эффекта параллакса и положение тайлов изображений +**sky**, **back**, **middle**, **front**. + +В этом примере я буду использовать ассет +[Desert Parallax Background](https://styloo.itch.io/desert-parallax-background) +за авторством **styloo**. + +В этом ассете есть фон, и три изображения с частями пустыни. + +## Добавление ассетов + +Я распаковал ассет **Desert Parallax Background** в директорию `assets`, +а также переименовал изображения. + +Теперь в функцию `window.onload = function() {}` указываем ресурсы, +которые я буду загружать и вызываем их загрузку. + +```javascript +PIXI.Assets.add('bg_back', '/assets/3.png'); +PIXI.Assets.add('desert_2', '/assets/2.png'); +PIXI.Assets.add('desert_1', '/assets/1.png'); +PIXI.Assets.add('desert_0', '/assets/0.png'); + +let promise = PIXI.Assets.load( + ['bg_back', 'desert_2', 'desert_1', 'desert_0'], + (progress) => { + console.log(`Assets loading progress: ${progress * 100}%`); +}); +``` + +Функция `PIXI.Assets.load` принимает в качестве аргумента имя ассета, который +необходимо загрузить, либо список ассетов. + +Второй необязательный аргумент, это callback функция, +которая вызывается во время загрузки Ассетов. +Функции передается параметр `progress`, +который представляет процент (0.0 - 1.0) загруженных ассетов. + +Функция возвращает **Promise** ассетов, которые были загружены. + +По окончании загрузки ассетов, я вызываю функцию `initLevel()`, +а также передаю в переменную `backgroundTextures` список загруженых ассетов, +в данном случае это текстуры. + +```javascript +promise.then((value) => { + backgroundTextures = value; + initLevel(); +}); +``` + +## Инициализация тайлов + +Для создания тайлов, я написал функцию `createBg()`, которая создаёт объект +[PIXI.TilingSprite](https://pixijs.download/release/docs/PIXI.TilingSprite.html) +и устанавливает позицию на экране. + +```javascript +function createBg(texture) { + let tiling = new PIXI.TilingSprite(texture, 640, 360); + tiling.position.set(0, 0); + app.stage.addChild(tiling); + + return tiling; +} +``` + +Создаёт тайлы: + +```javascript +function initLevel() { + background.sky = createBg(backgroundTextures.bg_back); + background.back = createBg(backgroundTextures.desert_2); + background.middle = createBg(backgroundTextures.desert_1); + background.front = createBg(backgroundTextures.desert_0); + + app.ticker.add(gameLoop); +} +``` + +## Создаём эффект параллакса + +Этот этап самый простой. + +В игровом цикле я изменяю значение переменной `background.x` и +смещаю позицию тайлов. + +```javascript +function gameLoop(delta) { + background.x = (background.x - background.speed); + background.front.tilePosition.x = background.x; + background.middle.tilePosition.x = background.x / 2; + background.back.tilePosition.x = background.x / 4; +} +``` + +## Исходный код + +```javascript +const DEBUG = true; + +let app; +let backgroundTextures; +let background = { + sky: null, + back: null, + middle: null, + front: null, + x: 0, + speed: 1, +}; + +/** PIXI and PIXI.Assets init */ +window.onload = function() { + app = new PIXI.Application({ + width: 640, + height: 360, + backgroundColor: 0x2a2a3a, + }); + document.body.appendChild(app.view); + + PIXI.Assets.add('bg_back', '/assets/3.png'); + PIXI.Assets.add('desert_2', '/assets/2.png'); + PIXI.Assets.add('desert_1', '/assets/1.png'); + PIXI.Assets.add('desert_0', '/assets/0.png'); + +let promise = PIXI.Assets.load( + ['bg_back', 'desert_2', 'desert_1', 'desert_0'], + (progress) => { + if (DEBUG) + console.log(`Assets loading progress: ${progress * 100}%`); + }); + + promise.then((value) => { + backgroundTextures = value; + initLevel(); + }); +} + +/** Level init */ +function initLevel() { + background.sky = createBg(backgroundTextures.bg_back); + background.back = createBg(backgroundTextures.desert_2); + background.middle = createBg(backgroundTextures.desert_1); + background.front = createBg(backgroundTextures.desert_0); + + app.ticker.add(gameLoop); +} + +/** Game loop */ +function gameLoop(delta) { + background.x = (background.x - background.speed); + background.front.tilePosition.x = background.x; + background.middle.tilePosition.x = background.x / 2; + background.back.tilePosition.x = background.x / 4; +} + +/** Create background sprite */ +function createBg(texture) { + let tiling = new PIXI.TilingSprite(texture, 640, 360); + tiling.position.set(0, 0); + app.stage.addChild(tiling); + + return tiling; +} +```