Перейти к основному содержимому

Спрайты тренера со спины и бросок

В начале 2D-сражения персонаж игрока (а в парном сражении — ещё и тренер-союзник) стоит в углу экрана спиной к камере, бросает мяч и уезжает за пределы кадра. Всё это рисует один компонент — BattleTrainerSprite, по одному экземпляру на каждый слот тренера в сцене сражения (AllyTrainerSprites, EnemyTrainerSprites).

Эта страница объясняет, как создаётся такой спрайт и что компонент делает за вас, чтобы бросок выглядел правильно на любом экране.

Что такое спрайт со спины​

Спина персонажа хранится в его ассете CharacterType:

ПолеТипЗначение
BackSpriteMaterialАнимированная спина персонажа: материал, содержащий лист кадров (_SpriteSheet), разложенный по столбцам и строкам (_Flipbook) и отрисовываемый флипбук-шейдером MCE
BackSpriteTimelineFlipbookTimelineЯчейки броска и длительность каждой. Пусто для собственных спрайтов движка

Тип персонажа без материала спины (например, игрок импортированной игры) просто не рисуется: вместо квадрата-заглушки отключается рендерер.

Флипбук и его временная шкала​

По умолчанию бросок проходит ячейки листа в равномерном темпе. Компонент делит AnimationDuration (1 секунда при скорости 1) поровну между ячейками, которые материал объявляет в _Flipbook (если объявлена одна ячейка или ни одной, используется сериализованное значение FlipbookSteps, по умолчанию 5), останавливается на последнем кадре, а не зацикливается, и отпускает мяч на 0,4 секунды.

Для спрайта, который вы рисуете сами, этого достаточно. Но в картридже одни ячейки держатся пару тиков, а одна — пятьдесят. FlipbookTimeline записывает именно это:

ПолеЗначение
cellsЯчейка, показываемая в каждой записи
secondsДлительность каждой записи при скорости 1
releaseEntryЗапись, в которой мяч покидает руку (отрицательное значение — нет такой)

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

С пригодной таблицей:

  • каждая ячейка показывается своё время (ячейка, показываемая на секунде t, — это CellAt(t));
  • мяч отпускается в момент начала записи отпускания (ReleaseSeconds: сумма предыдущих записей);
  • весь бросок длится TotalSeconds, а после него удерживается последняя ячейка.

Таблицу можно построить из чисел картриджа с помощью FlipbookTimeline.FromTicks(cells, ticks, ticksPerSecond, releaseEntry). Например, бросок в HeartGold состоит из 8, 16, 6, 2, 4, 2, 2, 52 и 2 тиков по 1/60 с, и мяч покидает руку в начале четвёртой записи — спустя 8 + 16 + 6 = 30 тиков, то есть через полсекунды.

Бросок идёт по своим часам​

Бросок не привязан к скольжению. ThrowAndSlide(speed, callback) запускает оба, но бросок продвигается в собственном Update компонента (TickThrow): он показывает ячейку таблицы для прошедшего времени, вызывает ваш callback один раз, когда мяч уходит, и заканчивается, когда таблица исчерпана. Скорость сражения масштабирует оба.

Это разделение важно. При скорости скольжения оригинала скольжение (0,45 с) короче броска (мяч уходит только на 0,5 с), поэтому бросок, управляемый завершением скольжения, отпускал мяч слишком рано и обрывал анимацию. Другие гарантии:

  • бросок, начатый во время другого броска, заменяет его, и замещённый никогда не отпускает свой мяч;
  • если объект тренера неактивен и не может обновляться, окончание скольжения отпускает мяч в качестве крайней меры, чтобы сражение никогда не ждало бесконечно.

Скольжение всегда заканчивается чуть за экраном​

После броска тренер уезжает за пределы кадра. Два момента вычисляются за вас:

Скорость. Скольжение линейное, со скоростью SlideUnitsPerSecond мировых единиц в секунду (по умолчанию 3,125: 5 пикселей DS на кадр, как в оригинале). Установите 0, чтобы вместо этого использовать фиксированную SlideDuration (2 секунды при скорости 1).

Конечная точка. В сцене сражения есть маркер (SlideTarget) с одной фиксированной координатой x. Фиксированный x не может подходить для каждого экрана: в кадре 16:9 край гораздо ближе, чем в 21:9 или 32:9, где тренер, скользящий к фиксированному маркеру, остановился бы внутри кадра. Поэтому конечная точка вычисляется для камеры, которая рисует тренера: чуть за краем экрана со стороны маркера, на собственный вылет фигуры (насколько её рисунок выступает за точку опоры в сторону этого края) плюс небольшой запас (SlideExitMargin, 0,1 единицы). Высота и глубина — от маркера. Если камера не ортографическая или используется 3D-сцена, решает маркер сцены.

Попиксельно точная отрисовка​

Лист спины, созданный в пиксель-арте, с точечной фильтрацией и отрисованный в том размере, в котором он был создан, показывает пиксели двух разных ширин (одни занимают на один экранный пиксель больше других), и фигура выглядит неровно. Для таких листов BattleTrainerSprite задаёт размер так, чтобы каждый пиксель листа покрывал целое число экранных пикселей (никогда меньше одного), и ставит ступни фигуры туда, где они были в оригинале:

  • размер — ближайший к исходному масштаб с целым числом пикселей (PixelPerfectHeight);
  • высота выбирается так, чтобы последняя нарисованная строка стоящей фигуры оказалась на нижнем краю экрана (пустые строки под первым кадром считаются по текстуре и уходят ниже края — как и тело в оригинале выходит за нижний край экрана). Лист, текстуру которого нельзя прочитать, сохраняет высоту, заданную сценой;
  • горизонтальная позиция не затрагивается — ею управляет скольжение.

Правило применяется только там, где оно имеет смысл: лист с точечной фильтрацией, ортографическая камера и 2D-сражение. Нарисованная графика с билинейной фильтрацией (крупная иллюстрированная спина) не имеет пикселей, которые нужно сохранять целыми, и остаётся ровно такой, как была создана; то же относится к 3D-сцене. Если лист пропадает, возвращается исходный размер.

Расчёт выполняется непосредственно перед отрисовкой каждой камерой и подключён к собственному событию конвейера рендеринга (beginCameraRendering в URP), поэтому использует итоговый зум камеры, а не значение с предыдущего кадра.

На 3D-сцене​

Когда в сражении используется 3D-сцена, тренер стоит там, где его размещает оригинал, на протяжении всего сражения: его скольжения оставляют его на месте, кадры ожидания зацикливаются по часам игры, а бросок он проигрывает, когда выпускает монстра (ThrowOnStage). Рисунок масштабируется так, чтобы один его пиксель был равен размеру пикселя сцены.

Тень​

Тренер получает тень, как и монстры: дочерний объект рисунка с именем Shadow, окрашенный в серый цвет тени шейдера, лежащий на земле с центром у ступней фигуры, с тем же изображением и отражением, что и рисунок.

Настройка​

  1. Создайте Material спины с флипбук-шейдером, назначьте лист в _SpriteSheet и задайте в _Flipbook число столбцов и строк листа. Листы пиксель-арта должны использовать фильтрацию Point.
  2. Назначьте его в поле Back Sprite ассета CharacterType.
  3. Необязательно, для броска со своим таймингом: заполните Back Sprite Timeline — список ячеек, секунды каждой и запись отпускания. Оставьте пустым для равномерного броска.
  4. В сражении BattleManagerCharactersModule передаёт материал и временную шкалу в BattleTrainerSprite слота (SetMaterial(material, timeline)).

Играм, импортированным из картриджа, ничего из этого не требуется: импортёры задают материал спины и таблицу таймингов каждого персонажа-игрока по данным картриджа (см. Импортированные игры и профили игр).

Справочник​

ТипПространство имёнРоль
BattleTrainerSpriteOpenMon.MCE.BattleРисует и анимирует тренера в сражении: бросок, скольжение, попиксельно точная отрисовка, тень
FlipbookTimelineOpenMon.MCE.BattleЯчейки, времена и запись отпускания броска
CharacterTypeOpenMon.MCE.CharactersХранит BackSprite и BackSpriteTimeline