}
};
Метод вычисляет занимаемые телом ячейки решётки, применяя Math.floor
и Math.ceil
на координатах тела. Помните, что размеры ячеек – 1×1 единиц. Округляя границы тела вверх и вниз, мы получаем промежуток из ячеек фона, которых касается тело.
Поиск столкновений на решётке
Если тело высовывается из уровня, мы всегда возвращаем “wall”
для двух сторон и верха и “lava”
для низа. Это обеспечит гибель игрока при выходе за пределы уровня. Когда тело внутри решётки, мы в цикле проходим блок квадратов решётки, найденный округлением координат, и возвращаем содержимое первого непустого квадратика.
Столкновения игрока с другими актёрами (монеты, движущаяся лава) обрабатываются после сдвига игрока. Когда движение приводит его к другому актёру, срабатывает соответствующий эффект (сбор монет или гибель).
Этот метод сканирует массив актёров, в поисках того, который накладывается на заданный аргумент:
Level.prototype.actorAt = function(actor) {
for (var i = 0; i < this.actors.length; i++) {
var other = this.actors[i];
if (other != actor &&
actor.pos.x + actor.size.x > other.pos.x &&
actor.pos.x < other.pos.x + other.size.x &&
actor.pos.y + actor.size.y > other.pos.y &&
actor.pos.y < other.pos.y + other.size.y)
return other;
}
};
Актёры и действия
Метод animate
типа Level
даёт возможность всем актёрам уровня сдвинуться. Аргумент step
задаёт временной промежуток. Объект keys
содержит информацию про стрелки клавиатуры, нажатые игроком.
var maxStep = 0.05;
Level.prototype.animate = function(step, keys) {
if (this.status != null)
this.finishDelay -= step;
while (step > 0) {
var thisStep = Math.min(step, maxStep);
this.actors.forEach(function(actor) {
actor.act(thisStep, this, keys);
}, this);
step -= thisStep;
}
};
Когда у свойства уровня status
есть значение, отличное от null
(а это бывает, когда игрок выиграл или проиграл), мы уменьшить до нуля счётчик finishDelay
, считающий время между моментом, когда произошёл выигрыш или проигрыш и моментом, когда надо заканчивать показ уровня.
Цикл while
делит временной интервал на удобные мелкие куски. Он следит, чтобы промежутки были не больше maxStep
. К примеру, шаг в 0,12 секунды будет нарезан на два шага по 0,05 и остаток в 0,02
У объектов актёров есть метод act
, который принимает временной шаг, объект level
и объект keys
. Вот он для типа Lava
, который игнорирует объект key:
Lava.prototype.act = function(step, level) {
var newPos = this.pos.plus(this.speed.times(step));
if (!level.obstacleAt(newPos, this.size))
this.pos = newPos;
else if (this.repeatPos)
this.pos = this.repeatPos;
else
this.speed = this.speed.times(-1);
};
Он считает новую позицию, добавляя результат умножения временного промежутка и текущей скорости к старой позиции. Если новую позицию не занимает препятствие, происходит перемещение. Если препятствие существует, поведение зависит от типа блока лавы. У капающей лавы есть свойство repeatPos
, и она при встрече с препятствием отражается в обратную сторону. Прыгающая лава просто инвертирует скорость (умножает на -1), чтобы продолжить движение в обратном направлении.
Монеты используют метод act
, чтобы дрожать. Столкновения они игнорируют, поскольку они просто подрагивают внутри своего квадрата, а столкновения с игроком будут обрабатываться методом act
игрока.
var wobbleSpeed = 8, wobbleDist = 0.07;
Coin.prototype.act = function(step) {
this.wobble += step * wobbleSpeed;
var wobblePos = Math.sin(this.wobble) * wobbleDist;
this.pos = this.basePos.plus(new Vector(0, wobblePos));
};
Свойство wobble
обновляется, чтобы следить за временем, и потом используется как аргумент Math.sin
для создания волны, которая используется для подсчёта новой позиции.
Остаётся игрок. Движение игрока обрабатывается по разным осям отдельно, потому что встреча с полом не должна мешать горизонтальному перемещению, а встреча со стеной – падению или прыжку. Этот метод работает с горизонтальным перемещением.
var playerXSpeed = 7;
Player.prototype.moveX = function(step, level, keys) {
this.speed.x = 0;
if (keys.left) this.speed.x -= playerXSpeed;
if (keys.right) this.speed.x += playerXSpeed;