---
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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>pixi.js - assets &amp; parallax</title>
    <script type="text/javascript" src="pixi.js"></script>
</head>
<body>
    <script type="text/javascript" src="app.js"></script>
</body>
</html>
```

Код приложения `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;
}
```
