Браузерные игры
April 3, 2022

JS + Phaser пишем игру с Дино (часть 2) 🦖

Перед тем как начать:

В прошлой части мы с вами настроили сборку проекта, загрузили статику и настроили наши первые анимации, если вы это пропустили, то советую вернуться к части 1.

Код, который уже готов живет тут.

В этой части мы с вами добавим для Дино оставшиеся анимации и контролы действия, а так же настроим плавный старт игры!

Включим debug режим

Для начала давайте на время разработки включим debug режим для нашей игры, для этого в index.js в конфиге нужно заменить debug: false на debug: true:

import Phaser from 'phaser';

import PlayScene from './PlayScene';
import PreloadScene from './PreloadScene';

const config = {
  type: Phaser.AUTO,
  width: 1000,
  height: 340,
  pixelArt: true,
  transparent: true,
  physics: {
    default: 'arcade',
    arcade: {
      // Включаем debug
      debug: true
    }
  },
  scene: [PreloadScene, PlayScene]
};

new Phaser.Game(config);

Теперь мы можем с вами видеть границы спрайта Дино:

Учимся пригибаться

Теперь можно добавить для Дино возможность пригибаться, для этого нам нужно обработать клавишу вниз.

Вернемся к нашей функции, которая обрабатывает нажатие клавиш и добавим следующий код:

createControll() {
    // остальной код этой функции
    
    this.input.keyboard.on('keydown-DOWN', () => {
      if (!this.dino.body.onFloor()) { return; }

      this.dino.body.height = 58;
      this.dino.body.offset.y = 34;
    });

    this.input.keyboard.on('keyup-DOWN', () => {
      this.dino.body.height = 92;
      this.dino.body.offset.y = 0;
    });
 }

Как вы можете заметить, мы добавили два обработчика. Первый будет срабатывать когда мы нажимаем кнопку вниз, а второй - когда отпускаем.

В этих обработчиках мы меняем высоту нашего спрайта, зачем спросите вы?

Все просто, давайте посмотрим на спрайты разных состояний Дино:

Думаю, после этой картинки понятно, почему когда мы нажимаем клавишу вниз - нам нужно уменьшить рост Дино, а когда отпускаем - вернуть его в исходное состояние.

Сейчас в браузере при нажатии клавиши вниз мы получим следующее:

Дальше давайте создадим анимацию для того, чтобы потом ее применить к Дино при нажатии клавиши вниз:

initAnims() {
    // Остальной код функции

    this.anims.create({
      key: 'dino-down-anim',
      frames: this.anims.generateFrameNumbers('dino-down', {start: 0, end: 1}),
      frameRate: 10,
      repeat: -1
    });
}

А в функции update() внесем изменения так, чтобы мы начали смотреть на высоту персонажа при выборе того, какую анимацию рисовать:

update() {
    this.ground.tilePositionX += this.gameSpeed;

    if (this.dino.body.deltaAbsY() > 0) {
      this.dino.anims.stop();
      this.dino.setTexture('dino', 0);
    } else {
      // Исходя из высоты выбираем анимацию
      this.dino.body.height <= 58
        ? this.dino.play('dino-down-anim', true)
        : this.dino.play('dino-run', true);
    }
  }

Отслеживаем начало игры

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

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

Таким триггером у нас будет:

create() {
  // Остальной код функции
  this.startTrigger = this.physics.add.sprite(0, 10).setOrigin(0, 1).setImmovable();
  this.initStartTrigger();
}

Можно заметить, что у нас появилась вот такая вот странная штука вверху экрана:

Давайте сделаем первое прикосновение с добавленным нами спрайтом - триггером для начала игры, для этого нужно создать метод initStartTrigger(), который как раз и позволит нам это отслеживать:

  initStartTrigger() {
    const { width, height } = this.game.config;
    
    // Обработчик
    this.physics.add.overlap(this.startTrigger, this.dino, () => {
      // Если параметр y, у нашего страйта триггера === 10,
      // то переместим наш триггер, чтобы больше его не трогать
      if (this.startTrigger.y === 10) {
        this.startTrigger.body.reset(0, height);
        return;
      }

      // Как только мы начнем игру, 
      // то можно перестать слушать данный обработчик
      this.startTrigger.disableBody(true, true);

    }, null, this);
  }

Сейчас у нас получилось следующее:

Дальше нам нужно с вами завести флаг начала игры:

create() {
  // Остальной код функции
  this.isGameRunning = false;
}

А в нашей функции update(), если игра еще не началась - то нужно убрать все обработки:

  update() {
    // Ничего не делаем, если игра не началась
    if (!this.isGameRunning) { return; }
    
    this.ground.tilePositionX += this.gameSpeed;

    if (this.dino.body.deltaAbsY() > 0) {
      this.dino.anims.stop();
      this.dino.setTexture('dino', 0);
    } else {
      this.dino.body.height <= 58
        ? this.dino.play('dino-down-anim', true)
        : this.dino.play('dino-run', true);
    }
  }

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

  create() {
    // Остальной код функции
    
    // Ставим вместо width -> 88px
    this.ground = this.add.tileSprite(0, height, 88, 26, 'ground').setOrigin(0, 1);
  }

После чего добавим обработку начала игры в наш метод initStartTrigger()

Так этот файл будет выглядеть после всех доработок:

  initStartTrigger() {
    const { width, height } = this.game.config;
    
    // Обработчик
    this.physics.add.overlap(this.startTrigger, this.dino, () => {
      // Если параметр y, у нашего страйта триггера === 10,
      // то переместим наш триггер, чтобы больше его не трогать
      if (this.startTrigger.y === 10) {
        this.startTrigger.body.reset(0, height);
        return;
      }

      // Как только мы начнем игру, 
      // то можно перестать слушать данный обработчик
      this.startTrigger.disableBody(true, true);

      const startEvent =  this.time.addEvent({
        delay: 1000/60, // Небольшая задержка
        loop: true, // Зациклинность
        callbackScope: this, // Область работы - наш класс
        callback: () => { // Обработчик срабатывания
          // Немного переместим нашего Дино
          this.dino.setVelocityX(80);
          // И активируем ему анимацию
          this.dino.play('dino-run', 1);
  
          // Плавно будем добавлять ширину земли
          // но только пока она будет меньше ширины canvas
          if (this.ground.width < width) {
            this.ground.width += 17 * 2;
          }
  
          // Как только достигним нужной ширины - запустим игру
          if (this.ground.width >= 1000) {
            this.ground.width = width;
            this.isGameRunning = true;
            this.dino.setVelocityX(0);
            startEvent.remove();
          }
        }
      });

    }, null, this);
  }

И получится у нас вот такое:

На этом я закончу вторую часть из цикла статей по созданию игры с Дино, надеюсь вам было интересно и вы попробовали сами с нуля создать свою игру!

Вы можете найти код для этого урока тут

JS + Phaser пишем игру с Дино (часть 3) 🦖->