- Фантастика и фэнтези
- Ироническое фэнтези
- Стимпанк
- Детективная фантастика
- Киберпанк
- Попаданцы
- LitRPG
- Космоопера
- Технофэнтези
- Городская фантастика
- Русское фэнтези
- Юмористическая фантастика
- Космическая фантастика
- Боевое фэнтези
- Эпическая фантастика
- Зарубежная фантастика
- Книги магов
- Мистика
- Разная фантастика
- Разное фэнтези
- Любовное фэнтези
- Романтическая фантастика
- Городское фентези
- Историческая фантастика
- Историческое фэнтези
- Социально-философская фантастика
- Сказочная фантастика
- Иностранное фэнтези
- Романтическое фэнтези
- Ироническая фантастика
- Ужасы и Мистика
- Постапокалипсис
- Героическая фантастика
- Фэнтези
- Научная Фантастика
- Социально-психологическая
- Альтернативная история
- Боевая фантастика
- Разная литература
- Пословицы, поговорки
- Шахматы
- Визуальные искусства
- Фанфик
- Авто и ПДД
- Военное
- Отраслевые издания
- Гиды, путеводители
- Зарубежная образовательная литература
- Военная история
- Цитаты из афоризмов
- Современная литература
- Великолепные истории
- Начинающие авторы
- Современная зарубежная литература
- Боевые искусства
- Изобразительное искусство, фотография
- Кино
- Литература 19 века
- Недвижимость
- Музыка, музыканты
- Готические новеллы
- Истории из жизни
- Культура и искусство
- Спецслужбы
- Зарубежная прикладная литература
- Музыка, танцы
- Подростковая литература
- Прочее
- Военная техника, оружие
- Газеты и журналы
- Периодические издания
- Домоводство, Дом и семья
- Любовные романы
- Экономическая литература
- Научные и научно-популярные книги
- О животных
- Биохимия
- Культурология
- Юриспруденция
- Политика
- Психотерапия
- Языкознание
- Научпоп
- Медицина
- Психология, личное
- Радиотехника
- Филология
- Педагогика
- Образовательная литература
- Деловая литература
- Физика
- Науки: разное
- Химия
- Воспитание детей, педагогика
- Детская психология
- Зарубежная публицистика
- Биология
- Архитектура
- Зарубежная психология
- Науки о космосе
- Социология
- Математика
- География
- Беременность, ожидание детей
- Литературоведение
- Транспорт, военная техника
- Обществознание
- Зоология
- География
- Альтернативная медицина
- Иностранные языки
- Ветеринария
- Рефераты
- Астрология
- Биофизика
- Экология
- История Европы
- Учебники
- Шпаргалки
- Государство и право
- Ботаника
- Религиоведение
- История
- Техническая литература
- Прочая научная литература
- Психология
- Детективы и Триллеры
- Справочная литература
- Проза
- Новелла
- Юмор
- Проза
- Историческая проза
- Русская современная проза
- Советская классическая проза
- Повести
- Очерки
- Эссе
- Рассказы
- Зарубежная современная проза
- Сентиментальная проза
- Зарубежная классика
- Афоризмы
- Эпистолярная проза
- Феерия
- Семейный роман/Семейная сага
- Разное
- Антисоветская литература
- Магический реализм
- Русская классическая проза
- Современная проза
- О войне
- Контркультура
- Классическая проза
- Поэзия, Драматургия
- Детская литература
- Детская фантастика
- Детские остросюжетные
- Детские стихи
- Детские приключения
- Внеклассное чтение
- Учебная литература
- Зарубежные детские книги
- Бизнес
- Загадки
- Книги для подростков
- Школьные учебники
- Детский фольклор
- Книги для дошкольников
- Детские детективы
- Детская познавательная и развивающая литература
- Буквари
- Прочая детская литература
- Сказка
- Детская проза
- Детская образовательная литература
- Юмор
- Документальные книги
- Бизнес
- Внешнеэкономическая деятельность
- Тайм-менеджмент
- Кадровый менеджмент
- О бизнесе популярно
- Управление, подбор персонала
- Бизнес
- Менеджмент и кадры
- Продажи
- Экономика
- Ценные бумаги и инвестиции
- Малый бизнес
- Делопроизводство, офис
- Корпоративная культура, бизнес
- Банковское дело
- Финансы
- Зарубежная деловая литература
- Ораторское искусство / риторика
- Личные финансы
- Интернет-бизнес
- Поиск работы
- Государственное и муниципальное управление
- Бухучет и аудит
- Работа с клиентами
- Менеджмент
- Краткое содержание
- Личная эффективность
- Переговоры
- Приключения
- Старинная литература
- Религия и духовность
- Компьютеры и Интернет
- Блог
Читаем без скачивания Выразительный JavaScript - Марейн Хавербеке
Шрифт:
Интервал:
Закладка:
var img = document.createElement("img");
img.src = "img/player.png";
var spriteW = 24, spriteH = 30;
img.addEventListener("load", function() {
flipHorizontally(cx, 100 + spriteW / 2);
cx.drawImage(img, 0, 0, spriteW, spriteH,
100, 0, spriteW, spriteH);
});
</script>
Хранение и очистка преобразований
Преобразования накапливаются. Всё, что мы рисуем после рисования отзеркаленного персонажа, также будет зеркальным. Это может стать проблемой.
Возможно сохранить текущее преобразование, порисовать что-то, а затем вернуть старое состояние. Так должна поступать функция, делающая временное преобразование системы координат. Сначала мы сохраняем то преобразование, которое использовал код, вызвавший эту функцию. Затем функция отрабатывает на основе преобразований, проведённых на этот момент, и, возможно, добавляет новые. И в конце мы возвращаем преобразования к началу.
Этим занимаются методы save и restore двумерного холста. По сути, они хранят стек состояний преобразований. При вызове save в стек добавляется текущее состояние, а при restore берётся состояние сверху стека и применяется в качестве текущего контекста всех преобразований.
Функция branch в примере показывает, что можно сделать с функцией, которая выполняет преобразования и вызывает другую функцию (в данном случае, саму себя), которая продолжает рисовать с заданными преобразованиями.
Функция рисует древовидную структуру, рисуя линию, потом передвигая центр координат на конец линии, и вызывая себя затем дважды – сначала, повернув влево, а затем вправо. Каждый вызов уменьшает длину ветви, и рекурсия останавливается, когда длина падает меньше 8.
<canvas width="600" height="300"></canvas>
<script>
var cx = document.querySelector("canvas").getContext("2d");
function branch(length, angle, scale) {
cx.fillRect(0, 0, 1, length);
if (length < 8) return;
cx.save();
cx.translate(0, length);
cx.rotate(-angle);
branch(length * scale, angle, scale);
cx.rotate(2 * angle);
branch(length * scale, angle, scale);
cx.restore();
}
cx.translate(300, 0);
branch(60, 0.5, 0.8);
</script>
Если бы не было вызовов save и restore, второй рекурсивный вызов branch начинал бы с позиции и поворота, созданных первым. Он был бы соединён не с текущей веткой, а внутренней правой веткой, нарисованной первым вызовом. В результате получается тоже интересная форма, но уже не древовидная.
Назад к игре
Теперь мы знаем о холсте достаточно, чтобы начать разработку графической системы для игры из предыдущей главы. Новая система не будет показывать только цветные квадратики. Мы будем использовать 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 содержит картинки для элементов, не относящихся к игроку. Слева направо — это стена, лава и монетка.
Спрайты для нашей игры
Клетки фона 20×20 пикселей, так как мы используем ту же шкалу, что была в DOMDisplay. Значит, сдвиг клеток лавы 20 (значение переменной scale), а сдвиг стен 0.
Мы не ждём загрузки спрайта. Вызов drawImage с незагруженной пока картинкой ничего не сделает. Поэтому, на нескольких первых кадрах игра может быть отрисована неверно, но это не так уж критично. Так как мы обновляем экран, правильная сцена появится сразу после окончания загрузки.
Наш персонаж будет использован в качестве игрока. Код его отрисовки должен выбирать правильный спрайт и направление, зависящее от текущего движения игрока. Первые восемь спрайтов содержат анимацию ходьбы. Когда игрок передвигается по полу, мы перебираем их в зависимости от свойства animationTime объекта display. Оно измеряется в секундах, а нам надо менять кадры 12 раз в секунду, поэтому мы умножаем время на 12. Когда игрок стоит, мы рисуем девятый спрайт. В прыжках, которые мы распознаём по тому, что вертикальная скорость отлична от нуля, мы рисуем десятый, самый правый спрайт.