О чем этот пример
При работе со сложными сценами в Phaser разработчики часто используют контейнеры для группировки игровых объектов. Однако при добавлении Spine-персонажей в контейнеры может возникнуть неочевидное поведение с управлением видимостью. В этой статье мы разберем конкретный пример и объясним, почему простое свойство `visible` не всегда работает ожидаемо с контейнеризованными объектами. Понимание этой механики поможет избежать багов в отображении анимаций и даст более точный контроль над визуальной частью вашей игры.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super({
pack: {
files: [
{ type: 'scenePlugin', key: 'SpinePlugin', url: 'plugins/3.8.95/SpinePluginDebug.js', sceneKey: 'spine' }
]
}
});
}
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('logo', 'assets/sprites/phaser.png');
this.load.setPath('assets/spine/3.8/demos/');
this.load.spine('set1', 'demos.json', [ 'atlas1.atlas' ], true);
}
create ()
{
let boy1 = this.add.spine(120, 400, 'set1.spineboy', 'idle', true).setScale(0.5);
boy1.drawDebug = true;
let boy2 = this.add.spine(400, 400, 'set1.spineboy', 'idle', true).setScale(0.5);
let image1 = this.add.image(200, 450, 'logo').setOrigin(0);
let container = this.add.container();
container.add([ boy1, boy2, image1 ]);
boy2.visible = false;
}
}
const config = {
type: Phaser.WEBGL,
parent: 'phaser-example',
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
scene: Example
};
const game = new Phaser.Game(config);
Что делает пример
В данном примере создается сцена с двумя Spine-персонажами (Spineboy), одним изображением и контейнером. Все три визуальных объекта добавляются в контейнер, после чего у второго персонажа свойство visible устанавливается в false.
На первый взгляд кажется, что второй Spineboy должен стать невидимым. Однако при запуске примера мы видим, что невидимым становится только первый персонаж, хотя свойство visible менялось у второго. Это демонстрирует важную особенность работы контейнеров и Spine-объектов.
boy2.visible = false;
Анализ кода инициализации
Пример начинается с настройки плагина Spine и загрузки ресурсов. Обратите внимание на параметры метода load.spine.
this.load.spine('set1', 'demos.json', [ 'atlas1.atlas' ], true);
Здесь:
- 'set1' — ключ для доступа к данным Spine.
- 'demos.json' — файл с данными скелетной анимации.
- [ 'atlas1.atlas' ] — атлас текстур.
- true — указывает, что загрузка должна быть выполнена с использованием XHR, что необходимо для кросс-доменной загрузки в данном примере.
Создание объектов происходит в методе create. Spine-объекты создаются через фабрику this.add.spine.
let boy1 = this.add.spine(120, 400, 'set1.spineboy', 'idle', true).setScale(0.5);
let boy2 = this.add.spine(400, 400, 'set1.spineboy', 'idle', true).setScale(0.5);
Параметры:
1. X-координата.
2. Y-координата.
3. Ключ ресурса с префиксом ('set1.spineboy').
4. Название анимации по умолчанию ('idle').
5. Флаг loop — true для зацикливания анимации.
У первого объекта включена отладка: boy1.drawDebug = true, что визуализирует его кости и полигоны.
Почему видимость работает не так, как ожидается
Ключевой момент — добавление объектов в контейнер.
let container = this.add.container();
container.add([ boy1, boy2, image1 ]);
Контейнер в Phaser — это объект, который сам управляет трансформациями (позиция, масштаб, поворот) и видимостью своих дочерних элементов. Когда вы устанавливаете свойство container.visible = false, все его дети также становятся невидимыми.
Однако в этом примере мы меняем свойство у дочернего элемента (boy2). Проблема возникает из-за внутренней реализации отрисовки Spine-объектов, особенно когда включен режим drawDebug. В некоторых версиях плагина или при определенных условиях (как в данном примере с багом) изменение свойства visible у отдельного Spine-ребенка внутри контейнера может некорректно взаимодействовать с системой рендеринга контейнера.
Фактически, контейнер может «перехватывать» или сбрасывать состояние видимости своих детей на каждом кадре, основываясь на своем внутреннем состоянии, что приводит к неожиданному результату.
Практическое решение и обходные пути
Как же правильно управлять видимостью объектов внутри контейнера?
1. **Управление через контейнер (не подходит для выборочного скрытия):** Сделать невидимым весь контейнер.
container.visible = false;
2. **Управление через свойство alpha:** Часто более надежный способ для плавного или выборочного скрытия.
boy2.alpha = 0;
// Или для полного управления:
boy2.setVisible(false); // Метод может быть надежнее прямого присваивания
Однако в контексте данного конкретного бага с контейнером, alpha также может вести себя неочевидно.
3. **Извлечение объекта из контейнера:** Если объект должен управляться независимо, его не следует добавлять в контейнер.
// Не добавляем boy2 в контейнер
container.add([ boy1, image1 ]);
// Теперь boy2 существует отдельно, и его видимостью можно управлять напрямую
boy2.visible = false;
4. **Использование групп (Phaser.GameObjects.Group) для логической группировки** без влияния на свойства видимости.
let myGroup = this.add.group();
myGroup.addMultiple([boy1, boy2, image1]);
// Видимостью каждого объекта в группе можно управлять независимо
boy2.visible = false;
Что попробовать дальше
Данный пример наглядно демонстрирует, что взаимодействие контейнеров Phaser со сложными объектами, такими как Spine-анимации, может иметь тонкости. Прямое присваивание свойства visible дочернему объекту внутри контейнера не всегда гарантирует ожидаемый результат из-за внутреннего механизма обновления контейнера.
**Идеи для экспериментов:**
1. Проверьте, как ведет себя свойство visible у других типов объектов (спрайты, текст) внутри этого же контейнера.
2. Попробуйте отключить режим drawDebug у первого персонажа и посмотрите, изменится ли поведение.
3. Создайте иерархию из нескольких вложенных контейнеров и попробуйте управлять видимостью на разных уровнях.
4. Изучите, влияет ли на поведение порядок добавления объектов в массив container.add().
