PixiJS 7.4.3 с TypeScript: разработка 2D-приложений

Курс знакомит с созданием 2D-приложений и игр на PixiJS 7.4.3 с использованием TypeScript. Разберём настройку проекта, работу с рендерингом, сценой, ресурсами, интерактивностью, анимациями и базовую оптимизацию.

1. Установка, сборка и типизация проекта на PixiJS 7.4.3

Установка, сборка и типизация проекта на PixiJS 7.4.3

В этой статье мы настроим современный проект на PixiJS 7.4.3 с TypeScript: установим зависимости, запустим dev-сборку, соберём production-бандл и разберёмся, откуда берутся типы и как сделать типизацию устойчивой.

Что мы в итоге получим

  • Проект с быстрым dev-сервером и HMR (горячей заменой модулей)
  • Сборку в production в папку dist
  • TypeScript-конфигурацию, подходящую для PixiJS
  • Стартовый код с PIXI.Application и корректными типами
  • Требования к окружению

  • Установленный Node.js (рекомендуется актуальный LTS)
  • Пакетный менеджер npm (идёт вместе с Node.js)
  • Любой редактор с поддержкой TypeScript (например, VS Code)
  • > В курсе мы будем собирать проект на Vite: он простой, быстрый и отлично дружит с TypeScript.

    Ссылки на официальные источники:

  • PixiJS (репозиторий)
  • Vite (документация)
  • TypeScript (документация)
  • Создание проекта на Vite + TypeScript

    Создадим проект с шаблоном vanilla TypeScript.

    Запустим dev-сервер:

    После запуска Vite покажет локальный адрес (обычно http://localhost:5173).

    Установка PixiJS 7.4.3

    Установим PixiJS, закрепив версию 7.4.3, чтобы поведение и API совпадали с курсом.

    Проверить версию можно так:

    Минимальный стартовый код PixiJS с типами

    Откройте файл src/main.ts и замените содержимое на минимальный пример:

    Что здесь важно:

  • PIXI.Application создаёт приложение и канвас.
  • app.view в PixiJS 7 возвращает элемент канваса; в зависимости от окружения и настроек типы могут быть шире, поэтому иногда удобно явно привести к HTMLCanvasElement.
  • Graphics рисует примитивы (это удобно для первых проверок, не нужны ассеты).
  • Как устроена типизация PixiJS

    PixiJS поставляется уже с TypeScript-типами. Это означает:

  • Отдельный пакет @types/pixi.js обычно не нужен.
  • Автодополнение и проверка типов будут работать сразу после установки pixi.js.
  • Проверить это просто: наведите курсор на PIXI.Application в редакторе и убедитесь, что показываются типы.

    Если у вас есть старые привычки из мира DefinitelyTyped, запомните правило:

  • Если библиотека включает типы внутри себя, устанавливать @types/... не нужно и иногда даже вредно (может подтянуться несовместимая версия типов).
  • Структура проекта и что где лежит

    Типичная структура после создания Vite-проекта:

    | Путь | Назначение | | --- | --- | | index.html | Точка входа HTML, Vite подключает отсюда src/main.ts | | src/main.ts | Главный файл приложения | | tsconfig.json | Настройки TypeScript | | vite.config.ts | Конфигурация сборщика (может отсутствовать по умолчанию) | | dist/ | Результат production-сборки (появится после npm run build) |

    Сборка и запуск в production-режиме

    Основные команды Vite:

  • npm run dev запускает dev-сервер.
  • npm run build собирает production-бандл.
  • npm run preview локально запускает уже собранный dist.
  • Соберём проект:

    Проверим сборку:

    !Диаграмма показывает, как исходники проходят через Vite в режиме разработки и сборки

    Настройка TypeScript для устойчивой разработки

    Откройте tsconfig.json. В Vite-шаблоне обычно уже включены хорошие настройки. Ниже приведён ориентир, который подходит для PixiJS-проектов.

    Пример (не обязательно копировать дословно, важно понимать смысл ключей):

    Ключевые параметры:

  • lib: ["DOM", ...] нужен, чтобы TypeScript знал про браузерные API (window, document, HTMLCanvasElement).
  • strict: true включает строгую проверку типов: поначалу сложнее, но ошибок в графических приложениях становится меньше.
  • noEmit: true говорит TypeScript не генерировать JS-файлы, потому что этим занимается Vite.
  • types: ["vite/client"] добавляет типы Vite, например для импорта ассетов.
  • skipLibCheck: true ускоряет сборку, отключая проверку типов внутри node_modules.
  • Импорт ассетов и типы (кратко)

    В Vite можно импортировать файлы как URL-строки, а TypeScript будет понимать такие импорты благодаря vite/client.

    Пример (пока без загрузки в PixiJS, просто демонстрация типизации импорта):

    Что важно:

  • Переменная bunnyUrl будет строкой с адресом до файла в dev-режиме и в dist.
  • В следующих статьях мы подключим загрузку текстур и научимся правильно организовывать ассеты.
  • Частые проблемы и решения

  • Ошибка, что модуль pixi.js не найден
  • - Убедитесь, что вы выполнили npm install и что в package.json есть pixi.js.
  • Типы не работают и всё становится any
  • - Проверьте, что файл имеет расширение .ts. - Проверьте, что редактор использует локальный TypeScript проекта.
  • Канвас не появляется
  • - Проверьте, что вы действительно сделали document.body.appendChild(app.view ...). - Откройте консоль браузера и посмотрите, нет ли ошибок.

    Что дальше по курсу

    Дальше мы начнём строить правильную структуру приложения на PixiJS: сцена, контейнеры, базовый игровой цикл, работа с Ticker, а затем перейдём к спрайтам, загрузке ресурсов и интерактивности. Текущий результат важен тем, что у нас уже есть повторяемая сборка и строгая типизация, на которую можно опираться во всех последующих темах.

    2. Сцена и рендеринг: Application, Stage, DisplayObject, Container

    Сцена и рендеринг: Application, Stage, DisplayObject, Container

    В прошлой статье мы настроили проект на PixiJS 7.4.3 и TypeScript, запустили dev-сборку и вывели первый примитив на экран. Теперь разберёмся, почему этот код работает: как устроена сцена, что именно рендерится, и как PixiJS организует объекты на экране.

    Эта тема фундаментальна: почти всё в PixiJS строится вокруг дерева объектов сцены и того, как рендерер обходит его каждый кадр.

    Ментальная модель PixiJS

    PixiJS можно понимать как связку из трёх частей:

  • Application: удобная “обвязка” вокруг рендера, тикера и канваса.
  • Renderer: компонент, который рисует текущую сцену в canvas (обычно через WebGL).
  • Scene graph (дерево сцены): иерархия объектов, которую нужно отрисовать.
  • !Общая схема: Application управляет рендерером и обновлением, а рендерер рисует дерево сцены в canvas

    Официальный API-справочник PixiJS (удобно держать под рукой):

  • PixiJS API (release docs)
  • Application: точка входа в приложение

    Обычно PixiJS-приложение начинается с new PIXI.Application(...).

    Типичный код (напоминание, близко к тому, что уже было в проекте):

    Что даёт Application:

  • Создаёт app.view (canvas), куда будет рисоваться.
  • Создаёт app.renderer (рендерер).
  • Создаёт корневой контейнер app.stage.
  • Обычно запускает автоматический цикл рендеринга через тикер.
  • Полезно знать: Application не “магия”, а удобный фасад. В больших проектах иногда осознанно управляют рендером вручную, но начинать проще с Application.

    Stage: корень дерева сцены

    app.stage в PixiJS — это Container, который является корнем дерева отображаемых объектов.

  • Всё, что вы хотите увидеть на экране, должно быть добавлено в app.stage напрямую или косвенно.
  • Если объект не находится в дереве, рендерер его не нарисует.
  • Пример:

    DisplayObject: что такое “объект на сцене”

    Исторически базовым типом в PixiJS является DisplayObject: объект, который может находиться в дереве сцены и иметь трансформации.

    На практике вы почти всегда работаете с наследниками:

  • Container (может содержать детей)
  • Sprite (картинка)
  • Graphics (примитивы)
  • Text / BitmapText (текст)
  • Общие свойства, которые важно понимать с самого начала:

  • position (x, y) — положение в системе координат родителя.
  • scale — масштаб относительно родителя.
  • rotation — поворот (в радианах).
  • alpha — прозрачность.
  • visible — видимость (если false, объект не рисуется).
  • renderable — участвует ли объект в рендере (тонкая настройка, обычно true).
  • Пример: один и тот же объект можно “спрятать” по-разному.

    Container: группировка и иерархия

    Container — это DisplayObject, который может содержать другие DisplayObject.

    Главный смысл контейнеров: вы можете применять трансформации ко всей группе сразу.

    Пример: контейнер-сцена, внутри которого две фигуры. Если сдвинуть контейнер, сдвинутся обе фигуры.

    Здесь важно:

  • Координаты a и b заданы внутри контейнера group.
  • Позиция group задаёт смещение всей группы относительно stage.
  • Как рендерер обходит сцену

    В каждом кадре (когда включён автоматический рендеринг) PixiJS делает примерно следующее:

  • Начинает с stage.
  • Обходит детей контейнеров.
  • Для каждого объекта вычисляет итоговую трансформацию (позиция, масштаб, поворот), учитывая родителей.
  • Рисует только то, что видимо и может быть отрисовано.
  • Практический вывод:

  • Если объект не добавлен через addChild, его не будет на экране.
  • Если вы добавили объект, но он “не там” — почти всегда проблема в координатах, масштабе или порядке слоёв.
  • Порядок отрисовки: кто сверху

    По умолчанию PixiJS рисует детей контейнера в том порядке, в котором они находятся в массиве children.

  • Последний добавленный обычно оказывается “выше” (рисуется позже).
  • Полезные методы:

  • addChild(child) — добавить в конец.
  • addChildAt(child, index) — добавить в конкретную позицию.
  • setChildIndex(child, index) — поменять порядок.
  • removeChild(child) — убрать из контейнера.
  • Пример, где порядок важен:

    zIndex и sortableChildren

    Когда объектов много, удобно сортировать по “слоям” числом zIndex. Для этого нужно включить сортировку у контейнера:

    Что важно:

  • Без sortableChildren = true zIndex сам по себе ничего не гарантирует.
  • Сортировка может иметь стоимость, поэтому включайте её там, где реально нужно.
  • Локальные и глобальные координаты

    Координаты x и y всегда локальные — относительно родителя.

    Иногда нужно перевести точку:

  • из локальной системы объекта в глобальную (координаты stage)
  • или наоборот
  • Для этого есть методы:

  • toGlobal(point)
  • toLocal(point, from)
  • Пример: получить глобальную позицию объекта.

    Здесь new PIXI.Point(0, 0) — это “начало координат” контейнера group (его локальный ноль). Метод toGlobal переводит эту точку в глобальные координаты сцены.

    Практический мини-шаблон: слои сцены

    Полезная привычка: заводить контейнеры под слои, даже в небольших проектах. Например: фон, игровой мир, интерфейс.

    Это упрощает:

  • управление порядком отрисовки
  • временное скрытие целого слоя (layers.ui.visible = false)
  • масштабирование “мира” отдельно от интерфейса
  • Частые ошибки при работе со сценой

  • Объект создан, но не виден
  • - Проверьте, что он добавлен в stage (или в контейнер, который добавлен в stage). - Проверьте visible, alpha, scale. - Проверьте координаты: возможно, объект находится за пределами экрана.
  • Объект “пропал” после removeChild
  • - Это ожидаемо: объект больше не в дереве и не рисуется.
  • Объекты рисуются “в неправильном порядке”
  • - Используйте addChildAt/setChildIndex, либо включите sortableChildren и настройте zIndex.

    Что дальше

    Теперь у нас есть правильная терминология и понимание, как устроено дерево сцены: stage как корень, Container как иерархия, DisplayObject как базовая сущность для трансформаций и рендера.

    В следующих шагах курса на эту модель будет опираться всё остальное: игровой цикл и Ticker, спрайты и текстуры, загрузка ресурсов, интерактивность и обработка событий.

    3. Графика и ресурсы: Sprite, Texture, Assets, шрифты и атласы

    Графика и ресурсы: Sprite, Texture, Assets, шрифты и атласы

    В прошлой статье мы разобрали дерево сцены: Application, stage, Container и то, как PixiJS обходит объекты при рендеринге. Теперь добавим самое важное для реальных приложений: работу с изображениями и другими ресурсами.

    В PixiJS графика чаще всего появляется на экране через связку:

  • Texture: “данные” изображения (что рисовать)
  • Sprite: объект на сцене (где и как рисовать)
  • Assets: загрузка, кэширование и выгрузка ресурсов
  • А для проектов с большим количеством картинок почти всегда нужны:

  • атласы (spritesheets): много спрайтов в одном изображении
  • шрифты: обычный Text и более производительный BitmapText
  • Sprite и Texture: что это и как связаны

    Texture

    Texture — это объект, который представляет загруженное изображение (или часть изображения) и может быть использован для рисования.

  • Текстура не является объектом сцены
  • Её нельзя “добавить” в stage
  • Её можно использовать, например, в Sprite
  • Документация:

  • PIXI.Texture
  • Sprite

    Sprite — это DisplayObject, который рисует прямоугольник с текстурой. Спрайт можно позиционировать, масштабировать, вращать и добавлять в контейнеры.

    Документация:

  • PIXI.Sprite
  • Короткая формула мышления:

  • Texture отвечает на вопрос “что рисуем?”
  • Sprite отвечает на вопрос “где и как рисуем?”
  • !Связь Texture и Sprite и их место в дереве сцены

    Первая загрузка изображения через Assets и создание Sprite

    В PixiJS 7 основной рекомендуемый механизм загрузки — Assets.

    Документация:

  • PIXI.Assets
  • Подготовка ассета в Vite

    Если вы используете Vite (как в первой статье курса), удобно импортировать файл как URL:

    bunnyUrl — это строка-адрес, который работает и в dev, и после npm run build.

    Код: загрузили Texture, создали Sprite, добавили в stage

    Что здесь важно:

  • Assets.load(...) возвращает загруженный ресурс (для картинки — Texture).
  • await нужен, потому что загрузка асинхронная.
  • anchor.set(0.5) ставит “точку привязки” спрайта в центр, чтобы позиционирование было удобнее.
  • Кэш ресурсов: почему повторная загрузка обычно не скачивает файл снова

    Assets внутри ведёт кэш. Это означает:

  • если вы уже загрузили ресурс по ключу/URL и снова вызвали Assets.load с тем же ключом, PixiJS обычно вернёт объект из кэша
  • это ускоряет работу и уменьшает сетевые запросы
  • Практический вывод:

  • Вы можете безопасно вызывать Assets.load из разных мест кода, если договорились об одинаковых ключах.
  • Ключи ресурсов: URL как ключ и собственные ключи

    Самый простой вариант — использовать URL (например, bunnyUrl) как ключ. Но в большом проекте удобнее иметь читаемые ключи, независимые от структуры файлов.

    Для этого есть механизм манифеста и бандлов.

    Manifest и Bundles: организуем загрузку пачками

    Когда ресурсов много, обычно нужно загружать их группами:

  • минимум для стартового экрана
  • всё для уровня
  • всё для конкретной сцены
  • Идея бандла

    Бандл — это именованная группа ресурсов. Вы один раз описываете, что туда входит, а затем загружаете одной командой.

    Пример: инициализация манифеста и загрузка бандла.

    Что запомнить:

  • Вы описываете ресурсы один раз в Assets.init.
  • Затем загружаете пачкой через Assets.loadBundle.
  • В реальном проекте это помогает держать загрузки под контролем и не разбрасывать URL по коду.
  • Атласы (Spritesheet): много спрайтов в одном файле

    Что такое атлас

    Атлас обычно состоит из:

  • одного изображения (например, ui-atlas.png)
  • одного JSON-файла с описанием прямоугольников (например, ui-atlas.json)
  • Каждый прямоугольник описывает, где именно в большой картинке лежит маленькая картинка.

    Зачем это нужно:

  • меньше файлов — меньше запросов
  • удобнее упаковывать интерфейс/анимации
  • лучше контроль над памятью и загрузкой
  • Документация:

  • PIXI.Spritesheet
  • !Пояснение, как JSON атласа связывает имена кадров с областями на общей текстуре

    Загрузка атласа через Assets

    Если вы загружаете JSON атласа через Assets, PixiJS создаёт Spritesheet, а внутри будут доступны текстуры кадров.

    Практические замечания:

  • Имена вроде 'star.png' берутся из JSON атласа.
  • Частая ошибка: опечатка в имени кадра. Если sheet.textures[...] вернул undefined, проверьте ключи.
  • Текстуры кадров “разделяют” одну общую картинку, то есть память расходуется эффективнее, чем если бы вы загрузили десятки отдельных PNG.
  • Шрифты: Text и BitmapText

    В PixiJS есть два популярных подхода к тексту.

    Text: гибко, но может быть тяжело

    Text рисует текст с использованием обычных шрифтов (браузерных или подключённых через CSS). Он удобен для динамических надписей, но частое изменение текста может быть дорогим.

    Документация:

  • PIXI.Text
  • Пример:

    Практический совет:

  • Для UI, где текст меняется редко, Text обычно достаточно.
  • BitmapText: быстро для игр и частых обновлений

    BitmapText использует битмап-шрифт — заранее подготовленный набор символов в виде текстуры (часто тоже через атлас). Это особенно полезно, когда текст часто меняется (очки, таймер, урон).

    Документация:

  • PIXI.BitmapText
  • PIXI.BitmapFont
  • #### Быстрый старт через BitmapFont.from Это способ “сгенерировать” битмап-шрифт на основе системного шрифта (удобно для прототипов).

    Что важно:

  • BitmapFont.from регистрирует шрифт под именем 'uiFont'.
  • BitmapText затем использует это имя.
  • #### Загрузка готового битмап-шрифта как ресурса В production-проектах часто используют заранее подготовленные bitmap-шрифты (BMFont и аналоги), которые поставляются как файл описания и текстура.

    Логика такая:

  • Загружаем описание шрифта через Assets.
  • Создаём BitmapText, указывая имя шрифта, которое задано в файле.
  • Точный формат и пайплайн генерации зависят от инструмента, который вы используете для упаковки шрифта, но принцип в PixiJS один: сначала загрузка ресурсов, потом создание объектов сцены.

    Управление памятью: выгрузка ресурсов

    В приложениях и играх ресурсы должны жить столько, сколько нужно сцене, и не дольше.

    Типичная стратегия:

  • при входе на сцену загружаем её бандл
  • при выходе со сцены удаляем объекты из stage
  • затем выгружаем ресурсы, если они больше не нужны
  • В Assets для этого есть unload и unloadBundle.

    Практический совет:

  • Не выгружайте общий UI и шрифты, если они используются на многих экранах.
  • Выгружайте тяжёлые атласы уровней, если вы точно не вернётесь к ним скоро.
  • Типичные ошибки и как их быстро диагностировать

  • Спрайт не появился
  • 1. Проверьте, что sprite добавлен в stage (или в контейнер, который добавлен в stage). 2. Проверьте координаты и anchor. 3. Убедитесь, что текстура реально загружена: поставьте console.log(texture).

  • sheet.textures['name'] возвращает undefined
  • 1. Проверьте точное имя кадра в JSON атласа. 2. Убедитесь, что загружается именно тот JSON, который вы ожидаете.

  • Текст выглядит размыто
  • 1. Это может быть связано с масштабом и настройками рендера. 2. Для bitmap-шрифтов важно, чтобы размер шрифта и масштаб соответствовали задумке.

    Что дальше

    Теперь у вас есть базовый набор инструментов для реальной графики:

  • вы понимаете разницу между Texture и Sprite
  • умеете загружать ресурсы через Assets и организовывать загрузку бандлами
  • знаете, как использовать атласы и зачем они нужны
  • ориентируетесь в Text и BitmapText
  • Следующий логичный шаг после ресурсов — оживить сцену: игровой цикл, Ticker, анимации и обновление объектов кадр за кадром.

    4. Интерактивность и события: Pointer, hitArea, UI-элементы

    Интерактивность и события: Pointer, hitArea, UI-элементы

    В предыдущих статьях мы построили основу PixiJS-приложения: настроили проект, разобрали дерево сцены (stage, Container) и научились загружать ресурсы (Assets, Texture, Sprite). Следующий шаг к реальным приложениям и играм — интерактивность: реакции на клики, касания, наведение, перетаскивание, создание UI-кнопок и панелей.

    В PixiJS 7.4.3 интерактивность построена вокруг событий указателя (pointer events): единый набор событий для мыши, тача и стилуса.

    Полезные страницы документации PixiJS:

  • EventSystem
  • FederatedPointerEvent
  • Container
  • Sprite
  • Как PixiJS понимает, куда вы кликнули

    Когда вы нажимаете на canvas, браузер генерирует события (мыши или тача). PixiJS получает их через свой EventSystem и делает две ключевые вещи:

  • определяет, какой DisplayObject находится под указателем (hit testing)
  • генерирует Pixi-события и доставляет их объектам сцены
  • !Диаграмма показывает путь события от canvas до объекта на сцене и всплытие по контейнерам

    Практический вывод:

  • объект должен быть в дереве сцены (добавлен в stage или в контейнер внутри него)
  • объект должен быть настроен на участие в событиях
  • у объекта должна быть область, по которой PixiJS сможет понять попадание (геометрия или hitArea)
  • Включаем интерактивность: eventMode

    В PixiJS 7 для управления интерактивностью используется свойство eventMode. Оно задаётся на любом Container/Sprite/Graphics и определяет, будет ли объект участвовать в обработке событий.

    Основные режимы:

  • none — объект не участвует в событиях
  • passive — объект не является целью, но его дети могут получать события
  • static — объект получает события, рассчитан на “UI-элементы” (кнопки, панели)
  • dynamic — объект получает события и рассчитан на объекты, которые активно движутся/меняются (например, для drag-and-drop), чтобы система корректно переоценивала попадания
  • На практике для большинства UI достаточно static, а для перетаскиваемых объектов часто используют dynamic.

    Минимальный пример: сделаем спрайт кликабельным.

    Что важно:

  • eventMode = 'static' включает участие объекта в событиях
  • cursor = 'pointer' меняет курсор, когда указатель над объектом
  • обработчик вешаем через on('pointerdown', ...)
  • Какие pointer-события используются чаще всего

    PixiJS опирается на модель pointer events. На практике вы чаще всего будете использовать:

  • pointerdown — нажали (мышь/палец/стилус)
  • pointerup — отпустили
  • pointerupoutside — отпустили вне объекта (полезно для drag)
  • pointermove — движение указателя
  • pointerover — указатель вошёл в объект
  • pointerout — указатель вышел из объекта
  • pointertap — “тап” (нажал и отпустил без существенного смещения)
  • Пример с наведением:

    Типы событий в TypeScript: FederatedPointerEvent

    В PixiJS 7 вы обычно работаете с PIXI.FederatedPointerEvent. Это объект, в котором есть и координаты, и ссылки на цель события.

    Пример типизированного обработчика:

    Полезные поля и идеи:

  • e.global — позиция указателя в глобальных координатах Pixi-сцены
  • e.target — объект, по которому реально было попадание
  • e.currentTarget — объект, чей обработчик сейчас выполняется (важно при всплытии)
  • Всплытие событий и остановка распространения

    События в PixiJS распространяются похоже на DOM: от целевого объекта вверх по родителям.

    Это удобно для UI:

  • кликнули по кнопке внутри панели
  • панель (родитель) тоже может узнать о событии
  • Иногда всплытие нужно остановить, чтобы родитель не реагировал:

    Практический совет:

  • если у вас есть “фон”, закрывающий попап по клику, используйте всплытие осознанно
  • если клики “пробивают” UI и попадают в мир игры, чаще всего нужно stopPropagation()
  • hit testing и hitArea: делаем область клика предсказуемой

    PixiJS определяет попадание в объект через его геометрию.

  • у Sprite по умолчанию это прямоугольник текстуры
  • у Graphics — нарисованные фигуры
  • у Container по умолчанию нет собственной формы, поэтому контейнер обычно не “кликается”, если вы явно не задали область
  • Зачем нужен hitArea

    hitArea позволяет задать область, по которой объект будет считаться “попавшим”, независимо от реальной картинки.

    Типичные случаи:

  • маленькая иконка, по которой неудобно попадать пальцем
  • кнопка сложной формы, где проще сделать прямоугольный hitbox
  • контейнер-панель, который должен ловить клики как единое целое
  • Пример: расширим область клика у спрайта.

    Обратите внимание на координаты: hitArea задаётся в локальной системе координат объекта. Если у спрайта anchor = 0.5, то его локальный центр — это (0, 0), поэтому прямоугольник удобно задавать симметрично.

    Другие формы, которые часто применяются:

  • PIXI.Circle
  • PIXI.Polygon
  • Пример круглой зоны клика:

    Координаты: global и перевод в локальные

    Для drag-and-drop и UI часто нужно понимать позицию указателя в координатах конкретного контейнера.

    У события есть e.global, а перевод можно сделать через toLocal.

    Пример: получить точку указателя в координатах родителя спрайта.

    Практический смысл:

  • если “мир” игры масштабируется и двигается контейнером, toLocal помогает корректно ставить объект под указатель
  • Пример: перетаскивание (drag-and-drop) спрайта

    Сделаем спрайт, который можно перетаскивать. Важно:

  • на pointerdown запоминаем, что начали перетаскивание
  • на pointermove двигаем объект
  • на pointerup и pointerupoutside завершаем
  • Почему мы считаем dragOffset:

  • чтобы объект не “прыгал” центром под указатель
  • чтобы он сохранял относительное положение точки захвата
  • UI-элементы: строим кнопку как переиспользуемый компонент

    PixiJS не навязывает UI-библиотеку: UI обычно собирают из Container, Graphics, Sprite и Text.

    Ниже — практичный шаблон кнопки:

  • визуальная часть: прямоугольник (Graphics) + текст (Text)
  • интерактивность: eventMode, cursor, hover/press состояния
  • внешний API: onClick колбэк
  • Использование кнопки в приложении:

    Ключевые идеи UI-компонента:

  • UI почти всегда удобнее делать контейнером, чтобы внутри держать фон, текст, иконки
  • hitArea фиксирует кликабельность и защищает от сюрпризов, если визуал меняется
  • состояния hover/press реализуются обычной перерисовкой Graphics или сменой текстуры
  • Память и “утечки”: как правильно снимать обработчики

    Если вы создаёте и уничтожаете сцены (экраны, уровни), важно не оставлять лишние ссылки через подписки.

    Основные правила:

  • если объект больше не нужен, удаляйте его из контейнера: parent.removeChild(obj)
  • если вы вручную подписывались на события, при уничтожении UI/сцены снимайте обработчики: off(...)
  • при полном уничтожении объекта используйте destroy() (осторожно с текстурами и общими ресурсами)
  • Пример аккуратного снятия обработчика:

    Типичные проблемы и быстрая диагностика

  • События не приходят
  • 1. Проверьте, что объект добавлен в stage. 2. Проверьте, что стоит eventMode = 'static' или eventMode = 'dynamic'. 3. Если это Container, проверьте, что задан hitArea (иначе контейнер может не быть целью).

  • Клик срабатывает “не там”
  • 1. Проверьте anchor и локальные координаты. 2. Если используете hitArea, убедитесь, что она задана в локальных координатах объекта.

  • Перетаскивание “отваливается”, если отпустить вне объекта
  • 1. Используйте pointerupoutside вместе с pointerup.

    Что дальше

    Теперь у вас есть практический инструментарий для интерактивности:

  • вы понимаете, как PixiJS находит объект под указателем
  • умеете включать интерактивность через eventMode
  • знаете, зачем нужен hitArea и как он связан с локальными координатами
  • умеете делать drag-and-drop и собирать простые UI-компоненты
  • Следующий логичный шаг после событий — оживить сцену системно: игровой цикл, Ticker, анимации и обновление объектов кадр за кадром, чтобы интерактивные элементы реагировали не только на клики, но и на время, физику и состояние приложения.

    5. Анимация и производительность: Ticker, тайминг, оптимизация

    Анимация и производительность: Ticker, тайминг, оптимизация

    В прошлых статьях мы построили основу PixiJS-приложения: дерево сцены, загрузку ресурсов и интерактивность через pointer-события. Теперь добавим движение во времени и разберёмся, как делать анимации предсказуемыми и как не потерять FPS, когда объектов становится много.

    Ключевая идея: PixiJS рендерит кадры, а вы в каждом кадре обновляете состояние объектов. За “каждый кадр” обычно отвечает Ticker.

    Полезные ссылки на документацию:

  • PIXI.Ticker
  • PIXI.Application
  • requestAnimationFrame
  • !Блок-схема показывает, где в кадре происходит логика и где рендер

    Что такое анимация в PixiJS

    В PixiJS анимация чаще всего означает, что вы меняете свойства объектов сцены со временем:

  • position для перемещения
  • rotation для вращения
  • scale для пульсации
  • alpha для плавных появлений
  • текст, кадры атласа, видимость слоёв
  • Важно разделять два слоя:

  • модель (ваше состояние: скорость, очки, флаги, текущая сцена)
  • представление (спрайты и контейнеры на сцене)
  • Ticker удобен тем, что даёт вам регулярный “тактовый сигнал”, чтобы синхронно обновлять и модель, и представление.

    Ticker: источник времени в PixiJS

    В PixiJS вы встретите Ticker в двух основных формах:

  • app.ticker внутри PIXI.Application
  • PIXI.Ticker.shared как общий тикер (полезен, когда вы не хотите передавать app во все модули)
  • Самый частый сценарий: app.ticker.add

    Application обычно уже настроен так, что:

  • тикер обновляется с частотой кадров
  • рендер происходит автоматически
  • Добавим простую анимацию вращения:

    Этот код работает, но у него есть проблема: скорость вращения зависит от FPS. На слабых устройствах вращение будет медленнее.

    delta, deltaMS и “нормальная” скорость анимации

    Что такое delta

    Обработчик ticker.add может принимать параметр delta.

  • delta — это “шаг кадра” относительно идеальных 60 FPS.
  • если приложение работает ровно в 60 FPS, delta будет примерно равен 1
  • если FPS ниже, delta будет больше (например, около 2 на 30 FPS)
  • Пример:

    Так уже лучше: падение FPS меньше влияет на скорость.

    Почему часто удобнее использовать миллисекунды

    Для движения в пикселях в секунду удобнее опираться на реальное время кадра в миллисекундах.

    У тикера есть значения времени, с которыми проще мыслить:

  • app.ticker.deltaMS — сколько миллисекунд занял шаг тикера
  • app.ticker.elapsedMS — близкая по смыслу величина, часто используется как “прошло времени за кадр”
  • Сделаем движение со скоростью 120 пикселей в секунду:

    Почему делим на 1000:

  • deltaMS измеряется в миллисекундах
  • в “пикселях в секунду” нужна доля секунды, прошедшая за кадр
  • Практическое правило:

  • для простых эффектов допустимо использовать delta
  • для физики, скоростей, таймеров, плавности на разных FPS чаще берут deltaMS
  • Управление тикером: пауза, старт, снятие обработчиков

    Снятие обработчика

    Если вы создаёте экраны/сцены (например, меню и игра), важно не оставлять “висящие” обновления.

    add возвращает сам тикер, а снимать обработчик нужно через remove:

    Пауза приложения

    Останавливать обновление можно так:

    Часто это полезно, когда:

  • открыто модальное окно
  • пользователь свернул вкладку
  • вы показываете экран загрузки
  • В браузере requestAnimationFrame автоматически замедляется во вкладке “в фоне”, но явная пауза всё равно удобна, чтобы не выполнять лишнюю логику.

    Подходы к таймингу: от простого к устойчивому

    Подход “как получится”

    Минус: скорость зависит от FPS.

    Подход “зависит от dt”

    Плюс: примерно одинаково на разных устройствах.

    Фиксированный шаг для логики

    Иногда вам нужно, чтобы логика обновлялась фиксированными порциями времени (например, простая физика, столкновения, воспроизводимость). Тогда делают “аккумулятор” времени:

  • накапливаем прошедшее время
  • пока накопилось больше фиксированного шага, выполняем update(step)
  • ограничиваем число шагов за кадр, чтобы не попасть в “спираль смерти” при сильных лагах
  • Этот подход сложнее, но он делает симуляцию устойчивее.

    !Иллюстрация объясняет разницу между переменным dt и фиксированным шагом

    Что чаще всего “убивает” производительность

    PixiJS быстрый, но его легко заставить тормозить неправильными привычками. Ниже самые типовые причины.

    Оптимизация без боли: практические правила

    Не создавайте объекты каждый кадр

    Плохо:

    Лучше:

    Смысл: меньше мусора для сборщика памяти.

    Используйте атласы, чтобы уменьшить переключения текстур

    Из прошлой статьи про ресурсы: атласы помогают держать множество UI-иконок в одной большой текстуре. Это обычно снижает накладные расходы на переключения.

  • если UI состоит из десятков маленьких PNG по отдельности, вы чаще упираетесь в рендер-нагрузку
  • если всё в одном атласе, рендереру проще группировать отрисовку
  • Осторожно с Graphics

    PIXI.Graphics удобен, но если вы:

  • часто меняете геометрию
  • рисуете много сложных фигур
  • то нагрузка растёт.

    Если фигура статична (например, рамка панели), можно рассмотреть cacheAsBitmap:

    Важно:

  • включайте cacheAsBitmap только для статичных объектов
  • если вы начнёте менять содержимое, PixiJS будет пересоздавать кэш, и может стать хуже
  • Текст: Text против BitmapText

    Из статьи про шрифты:

  • Text удобен, но частое обновление текста может быть дорогим
  • BitmapText обычно лучше для счётчиков, таймеров, часто меняющихся значений
  • Если вы обновляете строку каждый кадр (например, “FPS: ...”), почти всегда выгоднее использовать BitmapText.

    sortableChildren и zIndex включайте осознанно

    Если вы включили container.sortableChildren = true на контейнере с тысячами объектов и при этом часто меняете zIndex, сортировка может стать заметной частью нагрузки.

    Практика:

  • включайте сортировку только там, где она нужна
  • не меняйте zIndex массово каждый кадр без необходимости
  • Интерактивность тоже стоит денег

    Из статьи про события:

  • eventMode = 'dynamic' стоит использовать для реально “движущихся” интерактивных объектов
  • для обычного UI чаще подходит eventMode = 'static'
  • задавайте hitArea, если геометрия сложная или объект мелкий
  • Это делает hit-testing предсказуемее и часто дешевле.

    Снижайте разрешение, если нужно

    На устройствах с высоким devicePixelRatio рендер может стать тяжелее просто из-за количества пикселей.

    Типовая настройка при создании приложения (подбирается под ваш проект):

    Идея:

  • ограничить максимальную “плотность” рендера
  • сохранить приемлемое качество, но не упираться в GPU на слабых устройствах
  • Мини-пример: анимация + интерактивность без зависимости от FPS

    Ниже пример, который связывает прошлую тему (pointer events) и текущую (тайминг): кликом переключаем скорость вращения.

    Что здесь важно:

  • скорость задана в “радианах в секунду”, а не “прибавь чуть-чуть каждый кадр”
  • pointertap срабатывает одинаково для мыши и тача
  • Как проверять производительность на практике

  • Откройте DevTools браузера и измерьте FPS/время кадра в Performance.
  • Смотрите, что доминирует:
  • много времени на CPU в вашем ticker-коде
  • или просадка на рендере (GPU), когда объектов очень много
  • Упрощайте сцену и проверяйте, что именно дало эффект:
  • отключили тени/сложный Graphics
  • собрали картинки в атлас
  • перестали пересоздавать объекты каждый кадр
  • Главный принцип оптимизации: сначала измеряем, потом меняем.

    Что дальше

    Теперь у вас есть “двигатель времени” для PixiJS-приложения:

  • вы умеете анимировать через Ticker
  • понимаете разницу между обновлением “на кадр” и обновлением “по времени”
  • знаете базовые, самые практичные приёмы оптимизации
  • Следующий шаг в развитии проекта обычно связан со структурой: как организовать сцены/экраны, жизненный цикл (enter/exit), загрузку бандлов под конкретную сцену и аккуратную очистку ресурсов и подписок.