HTML5 Phaser ECMAScript 6

Часть 19: Анимация видимого элемента и польза ООП

Как вы уже заметили, всю логику игры я разбиваю на элементы интерфейса. У меня нет необходимости держать весь код программы в памяти, достаточно решать мелкие задачи, а после собирать все в одну большую рабочую машину. Но ООП имеет еще и другое преимущество.

Как вы помните, метод createFromObjects() только подготавливал объекты, а реальное создание происходило в методе init(). А необходимость вызывать init() метод после создания объекта делало код мягко говоря не правильным. Но как оказалось в JavaScript 5/6 есть возможность установить getter и setter методы для объекта. Это такие функции которые отвечают за установку или считывание свойств объекта.

За всю мою практику, я еще не встречал лучшего практического примера, демонстрирующего необходимость их использовать. После создания объекта, Tilemap присваивает каждому индивидуальные свойства, заданные заранее в программе Tiled. Вот как раз этот факт и натолкнул меня на идею, перехватить момент установки такого свойства и произвести нужные манипуляции:

set icon_key(value) {
  this.icon_sprite = new Phaser.Sprite(this.game, 70, 65, value, 1);
  this.icon_sprite.anchor.setTo(0.5);
  this.addChild(this.icon_sprite);
}

Как только Tilemap будет задавать свойство icon_key новому объекту, мы перехватим это событие, создадим нужный спраит и добавим его на панель. Обратите внимание, я заведомо не стал писать код, который обновлял бы спраит, если он уже добавлен на панель, так как в моей программе иконка панели никогда не меняется. По той же схеме, я решил задачу и с тремя звездами, которые добавляются при установке значения star_key.

свойство объекта заданное в Tiled редакторе для которого мы написали setter метод

А вот с анимацией мне пришлось немного поэкспериментировать. У меня было всего два варианта: одновременная и последовательная. Анимировать все звезды одновременно — смотреться слишком просто. В тоже время, последовательная анимация занимала много времени (новая начинала анимацию, когда предыдущая завершала ее). По этому я решил немного ускорить последовательную и запускать анимацию следующей звезды с задержкой в 200 миллисекунд:

for (var i=1; i<=this.grade; i++)
  if (this.star_list[i]) {
    this.star_list[i].scale.setTo(0);
    this.tween.push(this.game.add.tween(this.star_list[i].scale).to({x: 1, y: 1}, 1000, Phaser.Easing.Bounce.Out, true, (i-1)*200));
  }

Получилось очень красиво… но для большего эффекта, анимацию звезд надо было включать при появлении кнопки на экране. Лучшим кандидатом на это, было свойство onShow(). Как оказалось у объектов в Phaser такого нет! По этому к решению задачи я подошел с другой стороны.

В Phaser есть возможность включить контроль выхода и входа элемента на сцену. Так как данная процедура требует процессорных ресурсов, по умолчанию она отключена для всех объектов. Ее можно включить свойством checkWorldBounds. Дальше все просто, добавляем listener функцию, которая будет выполняться когда объект будет входить на сцену: this.events.onEnterBounds.add().

объекты которые уже считаются видимыми для Phaser, хотя пользователь их еще не видит

Недостаток такого подходя в том, что входом считается даже частичное появление элемента на сцене. А так как у нас большая полоска сверху и снизу экрана накрывает кнопки, анимация кнопок начинается задолго до того, как они реально видны пользователю. Костылем стало: запускать анимацию с задержкой в 300 миллисекунд.

Ради эксперимента пробовал поменять bounds для world, но это автоматом меняло размер экрана игры (что вполне логично). Конечно можно создать невидимый прямоугольник, сделать его проходимым для других объектов и установить функцию на момент столкновения с кнопками… но сейчас я предпочитаю не тратить больше времени на сцену меню. Настало время переходить к новой, ИГРОВОЙ сцене! Ниже привожу весь код класса:

export default class LevelButton extends Phaser.Sprite {
  constructor(game, x, y, key, frame) {
    super(game, x, y, key, frame);

    this.grade = -1;
    this.tween = [];

    this.new_frame = 0;
    this.grade_frame = 1;

    this.star_list = {};

    this.checkWorldBounds = true;
    this.events.onEnterBounds.add(function() {
      this._animate_stars(300);
    }, this);
  }

  set icon_key(value) {
    this.icon_sprite = new Phaser.Sprite(this.game, 70, 65, value, 1);
    this.icon_sprite.anchor.setTo(0.5);
    this.addChild(this.icon_sprite);

    this.set_lesson_skin();
  }

  set star_key(value) {
    this.star_list[1] = new Phaser.Sprite(this.game, 30, 125, value, 0);
    this.star_list[1].anchor.setTo(0.5);
    this.star_list[1].scale.setTo(0);
    this.addChild(this.star_list[1]);

    this.star_list[2] = new Phaser.Sprite(this.game, 71, 138, value, 0);
    this.star_list[2].anchor.setTo(0.5);
    this.star_list[2].scale.setTo(0);
    this.addChild(this.star_list[2]);

    this.star_list[3] = new Phaser.Sprite(this.game, 110, 125, value, 0);
    this.star_list[3].anchor.setTo(0.5);
    this.star_list[3].scale.setTo(0);
    this.addChild(this.star_list[3]);

    this.set_lesson_skin();
  }

  update() {
  }

  set_exam_skin() {
    this.new_frame = 2;
    this.grade_frame = 3;
    for (var i in this.star_list)
      this.star_list[i].frame = 1;
    this._update_skin();
  }

  set_lesson_skin() {
    this.new_frame = 0;
    this.grade_frame = 1;
    for (var i in this.star_list)
      this.star_list[i].frame = 0;
    this._update_skin();
  }

  set_grade(grade) {
    this.grade = grade;
    this._update_skin();
  }

  _update_skin() {
    if (this.grade >= 0 && this.grade <= 3) {
      this.frame = this.grade_frame;
      if (this.icon_sprite)
        this.icon_sprite.frame = 0;
    } else {
      this.frame = this.new_frame;
      if (this.icon_sprite)
        this.icon_sprite.frame = 1;
    }

    for (var i in this.star_list)
      this.star_list[i].scale.setTo(0);
    this._animate_stars();
  }

  _animate_stars(delay) {
    if (delay === undefined) delay = 0;

    for (var i in this.tween)
      this.tween[i].stop();

    for (var i=1; i<=this.grade; i++)
      if (this.star_list[i]) {
        this.star_list[i].scale.setTo(0);
        this.tween.push(this.game.add.tween(this.star_list[i].scale).to({x: 1, y: 1}, 1000, Phaser.Easing.Bounce.Out, true, delay+(i-1)*200));
      }
  }
}

 

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *