Книга: Выразительный 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
содержит картинки для элементов, не относящихся к игроку. Слева направо — это стена, лава и монетка.
- 4.2.6. Способы задавания ролей и ситуации в ролевой игре
- 14. Машинные забавы, или Стратегия компьютера при игре в калах
- Как в игре Cranium остался пластилин
- Метод четырех дверей в обучении: предоставьте в игре выбор
- Правило № 3. Относитесь к торгу, как к игре
- 16. Рисование на холсте
- Текст
- Решайте задачи в определенной очередности; не бойтесь при необходимости возвращаться назад
- 2. МГУ. Много лет назад
- Глава 7 Назад в будущее: дальновидные вопросы
- Назад в будущее