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

Назад к игре

Теперь мы знаем о холсте достаточно, чтобы начать разработку графической системы для игры из предыдущей главы. Новая система не будет показывать только цветные квадратики. Мы будем использовать drawImage для рисования картинок, представляющих элементы игры.

Мы определим тип объекта CanvasDisplay, который будет поддерживать тот же интерфейс, что и DOMDisplay из главы 15, а именно, методы drawFrame и clear.

Объект хранит больше информации, чем DOMDisplay. Вместо использования позиции прокрутки элемента DOM, он отслеживает окно просмотра, которое сообщает, какую часть уровня мы сейчас видим. Также он отслеживает время и использует это, чтобы решить, какой кадр анимации показывать. И ещё он хранит свойство flipPlayer, чтобы даже когда игрок стоял на месте, он был повёрнут в ту сторону, в которую шёл в последний раз.

function CanvasDisplay(parent, level) {
  this.canvas = document.createElement("canvas");
  this.canvas.width = Math.min(600, level.width * scale);
  this.canvas.height = Math.min(450, level.height * scale);
  parent.appendChild(this.canvas);
  this.cx = this.canvas.getContext("2d");
  this.level = level;
  this.animationTime = 0;
  this.flipPlayer = false;
  this.viewport = {
    left: 0,
    top: 0,
    width: this.canvas.width / scale,
    height: this.canvas.height / scale
  };
  this.drawFrame(0);
}
CanvasDisplay.prototype.clear = function() {
  this.canvas.parentNode.removeChild(this.canvas);
};

В 15 главе мы передавали размер шага в drawFrame из-за счётчика animationTime, несмотря на то, что DOMDisplay его не использовал. Наша новая функция drawFrame использует его для отсчёта времени, чтобы переключаться между кадрами анимации в зависимости от текущего времени.

CanvasDisplay.prototype.drawFrame = function(step) {
  this.animationTime += step;
  this.updateViewport();
  this.clearDisplay();
  this.drawBackground();
  this.drawActors();
};

Кроме отслеживания времени, метод обновляет окно просмотра текущей позиции игрока, заполняет холст цветом фона, и рисует фон и актёров. Заметьте, что всё происходит не так, как в главе 15, где мы рисовали фон один раз, а затем прокручивали оборачивающий элемент DOM для перемещения по нему.

Так как формы на холсте – всего лишь пиксели, после их отрисовки их нельзя сдвинуть (или убрать). Единственным способом обновить холст будет очистить его и перерисовать сцену.

Метод updateViewport похож на метод scrollPlayerIntoView из DOMDisplay. Он проверяет, не находится ли игрок слишком близко к краю экрана и двигает окно просмотра, если это случается.

CanvasDisplay.prototype.updateViewport = function() {
  var view = this.viewport, margin = view.width / 3;
  var player = this.level.player;
  var center = player.pos.plus(player.size.times(0.5));
  if (center.x < view.left + margin)
    view.left = Math.max(center.x - margin, 0);
  else if (center.x > view.left + view.width - margin)
    view.left = Math.min(center.x + margin - view.width,
                         this.level.width - view.width);
  if (center.y < view.top + margin)
    view.top = Math.max(center.y - margin, 0);
  else if (center.y > view.top + view.height - margin)
    view.top = Math.min(center.y + margin - view.height,
                        this.level.height - view.height);
};

Вызовы Math.max и Math.min гарантируют, что окно просмотра не будет показывать пространство за пределами уровня. Math.max(x, 0) гарантирует, что итоговое число не меньше нуля. Сходным образом Math.min гарантирует, что значение не превысит заданную границу.

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

CanvasDisplay.prototype.clearDisplay = function() {
  if (this.level.status == "won")
    this.cx.fillStyle = "rgb(68, 191, 255)";
  else if (this.level.status == "lost")
    this.cx.fillStyle = "rgb(44, 136, 214)";
  else
    this.cx.fillStyle = "rgb(52, 166, 251)";
  this.cx.fillRect(0, 0,
                   this.canvas.width, this.canvas.height);
};

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

var otherSprites = document.createElement("img");
otherSprites.src = "img/sprites.png";
CanvasDisplay.prototype.drawBackground = function() {
  var view = this.viewport;
  var xStart = Math.floor(view.left);
  var xEnd = Math.ceil(view.left + view.width);
  var yStart = Math.floor(view.top);
  var yEnd = Math.ceil(view.top + view.height);
  for (var y = yStart; y < yEnd; y++) {
    for (var x = xStart; x < xEnd; x++) {
      var tile = this.level.grid[y][x];
      if (tile == null) continue;
      var screenX = (x - view.left) * scale;
      var screenY = (y - view.top) * scale;
      var tileX = tile == "lava" ? scale : 0;
      this.cx.drawImage(otherSprites,
                        tileX,         0, scale, scale,
                        screenX, screenY, scale, scale);
    }
  }
};

Непустые клетки (null) рисуются через drawImage. Изображение otherSprites содержит картинки для элементов, не относящихся к игроку. Слева направо — это стена, лава и монетка.


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


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