Книга: Выразительный JavaScript

Актёры и действия

Актёры и действия

Метод animate типа Level даёт возможность всем актёрам уровня сдвинуться. Аргумент step задаёт временной промежуток. Объект keys содержит информацию про стрелки клавиатуры, нажатые игроком.

var maxStep = 0.05;
Level.prototype.animate = function(step, keys) {
  if (this.status != null)
    this.finishDelay -= step;
  while (step > 0) {
    var thisStep = Math.min(step, maxStep);
    this.actors.forEach(function(actor) {
      actor.act(thisStep, this, keys);
    }, this);
    step -= thisStep;
  }
};

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

Цикл while делит временной интервал на удобные мелкие куски. Он следит, чтобы промежутки были не больше maxStep. К примеру, шаг в 0,12 секунды будет нарезан на два шага по 0,05 и остаток в 0,02

У объектов актёров есть метод act, который принимает временной шаг, объект level и объект keys. Вот он для типа Lava, который игнорирует объект key:

Lava.prototype.act = function(step, level) {
  var newPos = this.pos.plus(this.speed.times(step));
  if (!level.obstacleAt(newPos, this.size))
    this.pos = newPos;
  else if (this.repeatPos)
    this.pos = this.repeatPos;
  else
    this.speed = this.speed.times(-1);
};

Он считает новую позицию, добавляя результат умножения временного промежутка и текущей скорости к старой позиции. Если новую позицию не занимает препятствие, происходит перемещение. Если препятствие существует, поведение зависит от типа блока лавы. У капающей лавы есть свойство repeatPos, и она при встрече с препятствием отражается в обратную сторону. Прыгающая лава просто инвертирует скорость (умножает на -1), чтобы продолжить движение в обратном направлении.

Монеты используют метод act, чтобы дрожать. Столкновения они игнорируют, поскольку они просто подрагивают внутри своего квадрата, а столкновения с игроком будут обрабатываться методом act игрока.

var wobbleSpeed = 8, wobbleDist = 0.07;
Coin.prototype.act = function(step) {
  this.wobble += step * wobbleSpeed;
  var wobblePos = Math.sin(this.wobble) * wobbleDist;
  this.pos = this.basePos.plus(new Vector(0, wobblePos));
};

Свойство wobble обновляется, чтобы следить за временем, и потом используется как аргумент Math.sin для создания волны, которая используется для подсчёта новой позиции.

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

var playerXSpeed = 7;
Player.prototype.moveX = function(step, level, keys) {
  this.speed.x = 0;
  if (keys.left) this.speed.x -= playerXSpeed;
  if (keys.right) this.speed.x += playerXSpeed;
  var motion = new Vector(this.speed.x * step, 0);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle)
    level.playerTouched(obstacle);
  else
    this.pos = newPos;
};

Перемещение подсчитывается на основе состояния клавиш «направо» и «налево». Когда перемещение приводит к встрече с препятствием, вызывается метод уровня playerTouched, который обрабатывает гибель в лаве и сбор монеток. В ином случае объект обновляет свою позицию.

Движение по вертикали работает сходным образом, но симулирует прыжки и гравитацию.

var gravity = 30;
var jumpSpeed = 17;
Player.prototype.moveY = function(step, level, keys) {
  this.speed.y += step * gravity;
  var motion = new Vector(0, this.speed.y * step);
  var newPos = this.pos.plus(motion);
  var obstacle = level.obstacleAt(newPos, this.size);
  if (obstacle) {
    level.playerTouched(obstacle);
    if (keys.up && this.speed.y > 0)
      this.speed.y = -jumpSpeed;
    else
      this.speed.y = 0;
  } else {
    this.pos = newPos;
  }
};

В начале метода игрок ускоряется по вертикали, чтобы обеспечить гравитацию. Гравитация, скорость прыжка и все остальные константы в игре были подобраны методом проб и ошибок. Я проверял разные значения, пока меня не удовлетворил результат.

Затем мы снова проверяем препятствия. Если мы его встретили, возможны два варианта. Когда нажата клавиша «вверх», и мы двигаемся вниз (то есть, мы встретились с чем-то, что находится под нами), скорости присваивается довольно большое отрицательное значение. В результате игрок прыгает. В ином случае, мы просто во что-то врезались и скорость обнуляется.

Сам метод act следующий:

Player.prototype.act = function(step, level, keys) {
  this.moveX(step, level, keys);
  this.moveY(step, level, keys);
  var otherActor = level.actorAt(this);
  if (otherActor)
    level.playerTouched(otherActor.type, otherActor);
  // Losing animation
  if (level.status == "lost") {
    this.pos.y += step;
    this.size.y -= step;
  }
};

После движения метод проверяет других актёров, с которыми игрок сталкивается, и опять вызывает playerTouched, если таковой нашёлся. В этот раз он передаёт вторым аргументом объект actor, так как если другим актёром была монетка, метод playerTouched должен знать, какую именно монетку мы собрали.

В финале, когда игрок погибает (дотронувшись до лавы), мы делаем небольшую анимацию, из-за которой персонаж сжимается (или тонет), уменьшая высоту объекта player.

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

Level.prototype.playerTouched = function(type, actor) {
  if (type == "lava" && this.status == null) {
    this.status = "lost";
    this.finishDelay = 1;
  } else if (type == "coin") {
    this.actors = this.actors.filter(function(other) {
      return other != actor;
    });
    if (!this.actors.some(function(actor) {
      return actor.type == "coin";
    })) {
      this.status = "won";
      this.finishDelay = 1;
    }
  }
};

Когда мы тронули лаву, статус игры устанавливается в “lost”. Когда собрана монетка, она удаляется из массива актёров, а если это была последняя – статус игры меняется на “won”. Всё это даёт нам уровень, пригодный для анимации. Не хватает только кода, её обрабатывающего.

Оглавление книги


Генерация: 1.487. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз