Выбрать главу

  var height = this.wrap.clientHeight;

  var margin = width / 3;

  // The viewport

  var left = this.wrap.scrollLeft, right = left + width;

  var top = this.wrap.scrollTop, bottom = top + height;

  var player = this.level.player;

  var center = player.pos.plus(player.size.times(0.5))

                 .times(scale);

  if (center.x < left + margin)

    this.wrap.scrollLeft = center.x - margin;

  else if (center.x > right - margin)

    this.wrap.scrollLeft = center.x + margin - width;

  if (center.y < top + margin)

    this.wrap.scrollTop = center.y - margin;

  else if (center.y > bottom - margin)

    this.wrap.scrollTop = center.y + margin - height;

};

Метод нахождения центра игрока показывает, как методы наших типов Vector позволяют записывать расчёты, производимые с объектами, наглядно. Чтобы найти центр актёра, мы добавляем его позицию (его левый верхний угол) и половину высоты. Это центр в координатах уровня, но нам он нужен в координатах пикселей, поэтому мы умножаем результирующий вектор на наш масштаб.

Затем серия проверок подтверждает, что игрок не находится вне доступного пространства. Иногда в результате будут заданы неправильные координаты прокрутки, ниже нуля или больше, чем размер прокручиваемого элемента. Но это не страшно – DOM автоматически ограничит их допустимыми значениями. Если назначить scrollLeft значение -10, он будет равен 0.

Было бы немного проще пробовать прокручивать позицию игрока в центр окна просмотра – но это создаёт неприятный дрожащий эффект. Во время прыжков вид будет постоянно двигаться вверх и вниз. Гораздо приятнее иметь «нейтральную» зону в середине экрана, где можно двигаться, не вызывая прокрутки.

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

DOMDisplay.prototype.clear = function() {

  this.wrap.parentNode.removeChild(this.wrap);

};

Теперь мы можем показать наш уровень.

<link rel="stylesheet" href="css/game.css">

<script>

  var simpleLevel = new Level(simpleLevelPlan);

  var display = new DOMDisplay(document.body, simpleLevel);

</script>

Тэг <link> при использовании с rel="stylesheet" позволяет загружать файл с CSS. Файл game.css содержит необходимые для игры стили.

Движение и столкновение

Теперь нам надо добавить обработку движений – самое интересное в игре. Простой подход, который используют большинство игр – разделить время на небольшие отрезки, и на каждом шаге сдвигать актёров на дистанцию, соответствующую их скорости (расстояние в секунду), умноженное на длительность временного отрезка (в секундах).

Это просто. Сложность в том, что надо обрабатывать взаимодействие предметов. Когда игрок касается пола или стены, он не должен проходить насквозь. Игра должна замечать, когда движение одного объекта приводит к столкновению с другим и реагировать соответственно. Стены останавливают движение, монеты собираются, и так далее.

В общем случае эта задача не такая простая. Можно найти библиотеки, обычно называющиеся «физическими движками», симулирующие взаимодействия между физическими объектами в двух или трёх измерениях. В этой главе мы поступим проще, так как нам нужно обрабатывать столкновения только прямоугольных объектов.

Перед тем, как сдвинуть игрока или блок лавы, мы проверяем, не приведёт ли нас движение внутрь непустой части фона. Если да – мы отменяем движение. Реакция на это будет зависеть от типа актёра – игрок останавливается, лава отскакивает.

Подход требует использования небольших отрезков времени, чтобы объекты останавливались до соприкосновения. Если взять слишком большие отрезки, игрок будет зависать над землёй. Можно было бы использовать более сложный вариант – вычислить место непосредственного соприкосновения и подвинуть актёра туда. Мы поступим проще, и скроем его проблемы, выбрав небольшие временные отрезки.

Метод сообщает, не пересекается ли прямоугольник (заданный позицией и размером) с каким-либо непустым пространством фоновой решётки.

Level.prototype.obstacleAt = function(pos, size) {

  var xStart = Math.floor(pos.x);

  var xEnd = Math.ceil(pos.x + size.x);

  var yStart = Math.floor(pos.y);

  var yEnd = Math.ceil(pos.y + size.y);

  if (xStart < 0 || xEnd > this.width || yStart < 0)

    return "wall";

  if (yEnd > this.height)

    return "lava";

  for (var y = yStart; y < yEnd; y++) {

    for (var x = xStart; x < xEnd; x++) {

      var fieldType = this.grid[y][x];

      if (fieldType) return fieldType;

    }