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

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

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

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

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

Всем привет!

Вот и пришло время написать последнюю статью из серии "Игра с Дино", сегодня мы с вами немного докрутим код и добавим немного визуальных и звуковых эффектов, чтобы игру можно было в полной мере назвать копией хромовского Дино!

Обработаем краевые условия

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

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

  createControll() {
    this.restart.on('pointerdown', () => {
      this.dino.setVelocityY(0);
      this.dino.body.height = 92;
      this.dino.body.offset.y = 0;
      this.physics.resume();
      this.obsticles.clear(true, true);
      this.isGameRunning = true;
      this.gameOverScreen.setAlpha(0);
      this.anims.resumeAll();
    });

    this.input.keyboard.on('keydown-SPACE', () => {
      // Добавим проверку, с которой Дино не сможет прыгать,
      // пока игра полностью не запустится (не отрисуется заемля до конца)
      if (!this.dino.body.onFloor() || this.dino.body.velocity.x > 0) { return; }
      
      this.dino.body.height = 92;
      this.dino.body.offset.y = 0;

      this.dino.setTexture('dino', 0);
      this.dino.setVelocityY(-1600);
    });

    this.input.keyboard.on('keydown-DOWN', () => {
      // Добавим проверку, с которой Дино не сможет прыгать,
      // пока игра не идет
      if (!this.dino.body.onFloor() || !this.isGameRunning) { 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;
    });
  }

Добавим облаков

В оригинале игры на заднем фоне летают облака, как вы могли заметить, в файле со статикой есть спрайты облаков, так что давайте добавим их к нам игру.

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

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

    this.environment = this.add.group(); // 1
    this.environment.addMultiple([ // 2
      this.add.image(width / 2, 170, 'cloud'),
      this.add.image(width - 80, 80, 'cloud'),
        this.add.image((width / 1.3), 100, 'cloud')
    ]);
    this.environment.setAlpha(0); // 3
   
    // ...Остальной код функции
  }

  1. Создаем новую группу
  2. Добавляем в нее нужные спрайты облаков
  3. На момент начала игры не отображаем их

Так как мы прячем нашу окружающую среду через setAlpha(0) до начала игры, то в initStartTrigger() нам нужно не забыть их показать:

  initStartTrigger() {
    const { width, height } = this.game.config;
    
    // Обработчик
    this.physics.add.overlap(this.startTrigger, this.dino, () => {
      // Остальной код обработчика...
      
      const startEvent =  this.time.addEvent({
        delay: 1000/60, // Небольшая задержка
        loop: true, // Зациклинность
        callbackScope: this, // Область работы - наш класс
        callback: () => { // Обработчик срабатывания
          // Остальной код обработчика...
  
          // Как только достигним нужной ширины - запустим игру
          if (this.ground.width >= 1000) {
            this.ground.width = width;
            this.isGameRunning = true;
            this.dino.setVelocityX(0);
            this.scoreText.setAlpha(1);
            
            // Добавим окружающей среде появление
            this.environment.setAlpha(1);
            startEvent.remove();
          }
        }
      });

    }, null, this);
  }

Если сейчас запустить игру, то вы увидите, что сейчас облака статичны:

Давайте начнем их двигать вместе с нашей игрой, для этого нужно внести изменения в функцию update():

  update(time, delta) {
    // Остальной код функции...
    
    Phaser.Actions.IncX(this.environment.getChildren(), -3);
    
    // ...Остальной код функции
  }

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

  update(time, delta) {
    // Остальной код функции...

    this.environment.getChildren().forEach(env => {
      if (env.getBounds().right < 0) {
        env.x = this.game.config.width + 30;
      }
    });

    // ...Остальной код функции
  }

Добавляем звуковое сопровождение

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

  • Звук прыжка
  • Звук коллизии с препятствием
  • Звук достижения очередных 100 очков

Давайте создадим звуковые объекты в методе create():

create() {
    // Остальной код функции...
    
    this.jumpSound = this.sound.add('jump', {volume: 0.2});
    this.hitSound = this.sound.add('hit', {volume: 0.2});
    this.reachSound = this.sound.add('reach', {volume: 0.2});
    
    // ...Остальной код функции
  }

Теперь остается только вызвать проигрыш этих звуков в нужный момент, для начала добавим this.hitSound.play(), когда Дино с чем-то сталкивается:

  initColliders() {
    this.physics.add.collider(this.dino, this.obsticles, () => {
      this.highScoreText.x = this.scoreText.x - this.scoreText.width - 20;

      const highScore = this.highScoreText.text.substr(this.highScoreText.text.length - 5);
      const newScore = Number(this.scoreText.text) > Number(highScore) ? this.scoreText.text : highScore;

      this.highScoreText.setText('Top: ' + newScore);
      this.highScoreText.setAlpha(1);

      this.physics.pause();
      this.isGameRunning = false;
      this.anims.pauseAll();
      this.dino.setTexture('dino-hurt');
      this.respawnTime = 0;
      this.gameSpeed = 20;
      this.gameOverScreen.setAlpha(1);
      this.score = 0;
      
      // Добавим звуки
      this.hitSound.play();
    }, null, this);
  }

В методе handleScore() добавим еще одно условие, которое каждые 100 очков будет отыгрывать звук:

  handleScore() {
    this.time.addEvent({
      delay: 1000/10,
      loop: true,
      callbackScope: this,
      callback: () => {
        if (!this.isGameRunning) { return; }

        this.score++;
        this.gameSpeed += 0.01
        
        // Добавим проверку
        if (this.score % 100 === 0) {
          this.reachSound.play();
        }
        
        const score = Array.from(String(this.score), Number);
        for (let i = 0; i < 5 - String(this.score).length; i++) {
          score.unshift(0);
        }

        this.scoreText.setText(score.join(''));
      }
    })
  }

И последнее - озвучка прыжка:

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

    this.input.keyboard.on('keydown-SPACE', () => {
      if (!this.dino.body.onFloor() || this.dino.body.velocity.x > 0) { return; }
      
      // Воспроизведем звук
      this.jumpSound.play();
      
      this.dino.body.height = 92;
      this.dino.body.offset.y = 0;

      this.dino.setTexture('dino', 0);
      this.dino.setVelocityY(-1600);
    });

    // ...Остальной код функции
  }

Последний штрих

В оригинальной игре, когда игрок достигает очередные 100 очков текст немного мерцает, так что давайте тоже добавим это в нашу игру:

  handleScore() {
    this.time.addEvent({
      delay: 1000/10,
      loop: true,
      callbackScope: this,
      callback: () => {
        if (!this.isGameRunning) { return; }

        this.score++;
        this.gameSpeed += 0.01
        
        if (this.score % 100 === 0) {
          this.reachSound.play();
          // Добавляем анимацию
          this.tweens.add({
            targets: this.scoreText, 
            duration: 100,
            repeat: 3,
            alpha: 0,
            yoyo: true
          });
        }
        
        const score = Array.from(String(this.score), Number);
        for (let i = 0; i < 5 - String(this.score).length; i++) {
          score.unshift(0);
        }

        this.scoreText.setText(score.join(''));
      }
    })
  }

А еще, так как нам уже не нужен debug режим, давайте его отключим в файле index.js:

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: false
    }
  },
  scene: [PreloadScene, PlayScene]
};

new Phaser.Game(config);

Итоги

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

Итоговая игра выглядит примерно так (только со звуками):

Полный код игры вы можете найти тут