Некоторые части системы хорошо поддаются разделению на кусочки со строго прописанными интерфейсами, а другие – нет. В попытках инкапсулировать нечто, не имеющее чётких границ, вы гарантированно потратите много сил. Совершив такую ошибку, вы увидите, что интерфейсы становятся чересчур большими и детальными, и что их надо часто менять в процессе эволюции программы.
Одну вещь мы всё-таки инкапсулируем – подсистему рисования. Это сделано специально для того, чтобы в следующей главе мы могли выводить на экран ту же игру другим способом. Спрятав рисование за интерфейс, мы можем просто загрузить ту же программу и подключить к ней новый модуль вывода на экран.
Инкапсулировать код для рисования мы будем, введя объект display
, который выводит уровень на экран. Тип экрана, который мы определяем, зовётся DOMDisplay
, потому что он использует элементы DOM для показа уровня.
Мы используем таблицу стилей для задания цветов и других фиксированных свойств элементов, составляющих игру. Было бы возможно непосредственно назначать стиль элементу через свойство style
при его создании, но программа в этом случае стала бы излишне многословной.
Следующая вспомогательная функция даёт простой способ создания элемента с назначением класса.
function elt(name, className) {
var elt = document.createElement(name);
if (className) elt.className = className;
return elt;
}
Экран создаём, передавая ему родительский элемент, к которому необходимо подсоединиться, и объект уровня.
function DOMDisplay(parent, level) {
this.wrap = parent.appendChild(elt("div", "game"));
this.level = level;
this.wrap.appendChild(this.drawBackground());
this.actorLayer = null;
this.drawFrame();
}
Используя тот факт, что appendChild
возвращает добавленный элемент, мы создаём окружающий элемент wrapper
и сохраняем его в свойстве wrap
.
Неизменный фон уровня рисуется единожды. Актёры перерисовываются каждый раз при обновлении экрана. Свойство actorLayer
используется в drawFrame
для отслеживания элемента, содержащего актёра – чтобы их было легко удалять и заменять.
Координаты и размеры измеряются в единицах, относительных к размеру решётки так, что дистанция в единицу означает один элемент решётки. Когда мы задаём размеры в пикселях, нам нужно будет масштабировать координаты – игра была бы очень мелкой, если б один квадратик задавался одним пикселем. Переменная scale
даёт количество пикселей, которое занимает один элемент решётки.
var scale = 20;
DOMDisplay.prototype.drawBackground = function() {
var table = elt("table", "background");
table.style.width = this.level.width * scale + "px";
this.level.grid.forEach(function(row) {
var rowElt = table.appendChild(elt("tr"));
rowElt.style.height = scale + "px";
row.forEach(function(type) {
rowElt.appendChild(elt("td", type));
});
});
return table;
};
Как мы уже упоминали, фон рисуется через элемент
. Это удобно соответствует тому факту, что уровень задан в виде решётки – каждый ряд решётки превращается в ряд таблицы (элемент ). Строки решётки используются как имена классов ячеек таблицы (
). Следующий CSS приводит фон к необходимому нам внешнему виду:
.background { background: rgb(52, 166, 251);
table-layout: fixed;
border-spacing: 0; }
.background td { padding: 0; }
.lava { background: rgb(255, 100, 100); }
.wall { background: white; }
Некоторые из настроек ( table-layout , border-spacing и padding ) используются для подавления нежелательного поведения по умолчанию. Не нужно, чтобы вид таблицы зависел от содержимого ячеек, и не нужны пробелы между ячейками или отступы внутри них.
Правило background задаёт цвет фона. CSS разрешает задавать цвета словами ( white ) и в формате rgb(R, G, B) , где красная, зелёная и синяя компоненты разделены на три числа от 0 до 255. То есть, в записи rgb(52, 166, 251) красный компонент равен 52, зелёный 166 и синий 251. Поскольку синий компонент самый большой, результирующий цвет будет синеватым. Вы можете видеть, что самый большой компонент в правиле .lava – красный.
Каждый актёр рисуется созданием элемента DOM и заданием позиции и размера, основываясь на свойства актёра. Значения надо умножать на масштаб scale , чтобы переходить от единиц игры к пикселям.
DOMDisplay.prototype.drawActors = function() {
var wrap = elt("div");
this.level.actors.forEach(function(actor) {
var rect = wrap.appendChild(elt("div",
"actor " + actor.type));
rect.style.width = actor.size.x * scale + "px";
rect.style.height = actor.size.y * scale + "px";
|
Читать дальше