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

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 содержит картинки для элементов, не относящихся к игроку. Слева направо — это стена, лава и монетка.