Массив actors
содержит объекты, отслеживающие положения и состояния динамических элементов. У каждого из них должно быть свойство pos
, содержащее позицию (координаты верхнего левого угла), свойство size
с размером, и свойство type
со строчкой, описывающей его тип ("lava"
, "coin"
или "player"
).
После построения решётки мы используем метод filter
, чтобы найти объект игрока, хранящийся в свойстве уровня. Свойство status
отслеживает, выиграл игрок или проиграл. Когда это случается, используется finishDelay
, которое держит уровень активным некоторое время для показа простой анимации. (Просто сразу восстанавливать состояние уровня или начинать следующий – это выглядит некрасиво). Этот метод можно использовать, чтобы узнать, закончен ли уровень:
Level.prototype.isFinished = function() {
return this.status != null && this.finishDelay < 0;
};
Действующие лица (актёры)
Для хранения позиции и размера наших актёров мы вернёмся к нашему верному типу Vector
, который группирует координаты x и y в объект.
function Vector(x, y) {
this.x = x; this.y = y;
}
Vector.prototype.plus = function(other) {
return new Vector(this.x + other.x, this.y + other.y);
};
Vector.prototype.times = function(factor) {
return new Vector(this.x * factor, this.y * factor);
};
Метод times
масштабирует вектор, умножая на заданную величину. Это будет удобно, когда нам надо будет умножать вектор скорости на временной интервал, чтобы узнать пройденный путь за это время.
В предыдущей секции конструктором Level
был использован объект actorChars
, чтобы связать символы с функциями конструктора. Объект выглядит так:
var actorChars = {
"@": Player,
"o": Coin,
"=": Lava, "|": Lava, "v": Lava
};
Три символа ссылаются на Lava
. Конструктор Level
передаёт исходный символ актёра в качестве второго аргумента конструктора, и конструктор Lava
использует его для корректировки своего поведения (прыгать по горизонтали, прыгать по вертикали, капать).
Тип player
построен следующим конструктором. У него есть свойство speed
, хранящее его текущую скорость, что поможет нам симулировать импульс и гравитацию.
function Player(pos) {
this.pos = pos.plus(new Vector(0, -0.5));
this.size = new Vector(0.8, 1.5);
this.speed = new Vector(0, 0);
}
Player.prototype.type = "player";
Поскольку высотой игрок в полтора квадратика, его начальная позиция задаётся на полквадрата выше позиции, где расположен символ “@”
. Таким образом его низ совпадает с низом квадрата, в котором он появляется.
При создании динамического объекта Lava
, нам надо проинициализировать объект в зависимости от символа. Динамическая лава двигается с заданной скоростью, пока не встретит препятствие. Затем, если у неё есть свойство repeatPos
, она отпрыгнет назад на стартовую позицию (капающая). Если нет, она инвертирует скорость и продолжает двигаться в обратном направлении (отскакивает). Конструктор задаёт только необходимые свойства. Позже мы напишем метод, который занимается самим движением.
function Lava(pos, ch) {
this.pos = pos;
this.size = new Vector(1, 1);
if (ch == "=") {
this.speed = new Vector(2, 0);
} else if (ch == "|") {
this.speed = new Vector(0, 2);
} else if (ch == "v") {
this.speed = new Vector(0, 3);
this.repeatPos = pos;
}
}
Lava.prototype.type = "lava";
Монеты просты в реализации. Они просто сидят на месте. Но для оживления игры они будут подрагивать, слегка двигаясь по вертикали туда-сюда. Для отслеживания этого, объект coin
хранит основную позицию вместе со свойством wobble
, которое отслеживает фазу движения. Вместе они определяют положение монеты (хранящееся в свойстве pos
).
function Coin(pos) {
this.basePos = this.pos = pos.plus(new Vector(0.2, 0.1));
this.size = new Vector(0.6, 0.6);
this.wobble = Math.random() * Math.PI * 2;
}
Coin.prototype.type = "coin";
В главе 13 мы видели, что Math.sin
даёт координату y точки на круге. Она движется туда и обратно в виде плавной волны, пока мы движемся по кругу, что делает функцию синуса пригодной для моделирования волнового движения.