Перейти к основному содержанию

Перемещение копий в ct.js


Перемещение копий в ct.js

Автоматически переведённая страница

К сожалению, на полный ручной перевод у нас не хватает ресурсов.
Если вы увидели ошибку — отправьте пул-риквест с исправлениями (ссылка для редактирования в конце страницы).

Перемещение является важнейшей частью любой игры, и знать, как оно работает в ct.js, крайне важно. Существуют различные подходы к кодированию перемещения, которые мы обсудим здесь.

Основы движения

Позиция

Позиция каждого объекта определяется параметрами this.x и this.y. Параметр this.x увеличивается, двигаясь с левой стороны к правой, а параметр this.y увеличивается, двигаясь с верхней стороны к нижней. Это точка оси копии — точка, вокруг которой вращается и масштабируется копия.

Скорость

Изменение свойств this.x и this.y определяет скорость объектов. Если у вас this.speed равен 100, это означает, что копия будет перемещаться на 100 пикселей за каждую секунду. Скорость объекта имеет также направление, которое устанавливается свойством this.direction.

Скорость и ее направление объекта могут быть разложены на вертикальные и горизонтальные компоненты. В ct.js они определяются как this.vspeed и this.hspeed. Когда вы меняете this.vspeed или this.hspeed, this.speed и this.direction автоматически обновляются, и наоборот.

Гравитация и ускорение

Копии могут быть ускоряны гравитацией с помощью параметров this.gravity и this.gravityDir. Гравитация будет изменять скорость копии на каждом кадре, увеличивая ее на this.gravity в заданном направлении каждые секунда.


Только this.x и this.y сами по себе влияют на визуальное положение объектов. Чтобы сделать так, чтобы другие свойства работали, в ct.js есть метод this.move(), а модуль place предоставляет методы this.moveBullet(cgroup) и this.moveSmart(cgroup). (Модуль place включен по умолчанию во всех новых проектах.) Вам также может не понадобиться использовать их вообще. Из-за этого существует несколько способов программирования движения копий.

this.move()

Функция this.move() может быть вызвана на шаге кода любой шаблонной карты, чтобы переместить копию в соответствии с параметрами this.speed и this.direction. Поскольку сама функция не проверяет столкновения, вам необходимо будет реализовать свою логику обработки столкновений или использовать другие методы. (См. ниже.) Однако эта функция может быть достаточно для аркадных шутеров от первого лица. Она также используется для перемещения пуль и других объектов, которые уничтожаются при столкновении.

Пример: Установите скорость копии в соответствии с вводом игрока и переместите ее.

Код кадра:

this.hspeed = actions.MoveX.value * 5;
this.vspeed = actions.MoveY.value * 5;
this.move();

Пример: Задайте скорость и направление копии и перемещайте ее.

Код создания:

this.speed = 15;
this.direction = 90;

Код кадра:

this.move();

Пример: Следуйте за копией шаблона "Character"

Код кадра:

var character = templates.list['Character'][0];
// Проверьте, является ли `character` допустимой копией (если он действительно существует в игре).
if (templates.valid(character)) {
    this.speed = 300;
    // Вычислите направление из текущего местоположения до позиции `character`.
    this.direction = u.pointDirection(this.x, this.y, character.x, character.y);
} else {
    // Остановите движение, если `character` не существует.
    this.speed = 0;
}
this.move();

Шаблон "Character" - это пример шаблона копии в игре. Он определяет поведение персонажа и его взаимодействие с игровым миром. В приведенном выше коде мы получаем доступ к первому элементу списка шаблонов templates.list['Character'], который представляет собой копию шаблона "Character". Затем мы проверяем, является ли эта копия допустимой с помощью функции templates.valid(). Если копия допустима, мы устанавливаем скорость движения (this.speed) и вычисляем направление (this.direction) на основе текущего местоположения и позиции character. Если копия недопустима, мы устанавливаем скорость движения равной нулю, останавливая движение. Наконец, вызывается функция this.move(), которая выполняет фактическое движение в соответствии с рассчитанным направлением.

this.moveBullet(cgroup)

this.moveBullet(cgroup) — это метод из ct.place, который проверяет столкновения при движении копий и может быть вызван в коде On Step на каждом кадре для точного движения копий с высокой скоростью.

Если, например, у вас есть небольшие быстро движущиеся проекции, которые пролетают через довольно узкие стены, эти проекции могут пролететь через них. Это происходит потому, что пули прыгают над стенами в одном кадре, не вызывая столкновения.

Чтобы этого избежать, вы можете использовать this.moveBullet(cgroup), чтобы двигать проекции шаг за шагом, выполняя несколько проверок столкновений на каждом кадре.

cgroup — это группа столкновений. Существует также вариант метода this.moveBullet(cgroup, precision), где precision — это длина каждого шага в пикселях. По умолчанию он установлен на 1. Для быстро движущихся проектов, однако, вам часто нужно установить его на значение, которое находится между радиусом и диаметром этого проекта.

Примечание

Обратите внимание, что вы должны использовать this.moveBullet(cgroup) с осторожностью, устанавливая его точность, так как слишком много пуль, использующих этот метод, приведет к тому, что в вашей игре будет слишком много проверок столкновений, что может сповільнить ее работу.

Вызов this.moveBullet(cgroup) не обрезает поверхности — он остановится прямо рядом с препятствием, если только это препятствие не обрезает вашу копию или сама копия не будет преобразована.

Чтобы проверить, столкнулись ли ваша копия и препятствие, вы можете проверить результат this.moveBullet(cgroup). Он будет false, если столкновения не произошло, true, если столкновение произошло с плиткой, и копией, если столкновение произошло.

Пример: Установите скорость и направление копии и постоянно ее перемещайте.

В событии создания:

this.speed = 15;
this.direction = 90;

В событии кадра:

this.moveBullet('Solid');

Пример: Уничтожь копию и ее препятствие при контакте копии с препятствием из группы столкновений "Враг"

В событии создания

this.speed = 15;
this.direction = 90;

В событии кадра:

var obstacle = this.moveBullet('Solid');
// `obstacle` может также вернуть `true`, если был контакт с плитой.
// Хотя, вряд ли у вас будет плита с группой столкновений "Враг",
// но давайте сделаем дополнительную проверку :)
// Убедитесь, что было препятствие и оно было копией.
if (obstacle && templates.isCopy(obstacle)) {
    // Контакта!
    // Препятствие является копией, и мы можем напрямую записать в него.
    obstacle.kill = true;
    this.kill = true;
}

this.moveSmart(cgroup)

this.moveSmart работает в основном так же, как и this.moveBullet, поскольку выполняет множество проверок столкновений во время движения в заданном направлении. Разница в том, что this.moveSmart вычисляет столкновения по осям X и Y отдельно, отсюда и название. Это может показаться незначительным изменением, но в результате мы получаем «скользящее» движение, которое помогает избежать препятствий, которые могут встретиться на пути.

Без использования this.moveSmart(cgroup), копия застрянет у первого встречного препятствия:

С использованием this.moveSmart(cgroup), она скользит мимо препятствия и затем продолжает свое движение в первоначальном направлении, как только препятствий больше нет:

Из-за этого moveSmart часто используется для перемещения персонажей — и даже мобов — в играх. Кроме того, он работает как с платформером, так и с видом сверху! Для платформеров вам нужно только сбросить this.vspeed, если под копией есть препятствие. В противном случае копия будет мгновенно падать на ближайшую платформу из-за накопительной гравитации, как только она скатывается с края. Сброс this.vspeed при наличии препятствия сверху также предотвратит застревание у потолка 😃

Метод this.moveSmart() также возвращает значения о успешных столкновениях. Поскольку копия все еще может перемещаться по одной оси, пока блокируется на другой, метод возвращает одно из следующих значений:

  • false, если не было контактов с препятствиями;
  • объект с свойствами x и y, если был по крайней мере один контакт с каждой стороны. Каждое свойство может иметь значение false (не было столкновения на этой оси), true (сошлось с плиткой) или ссылку на другую копию.

Пример: Движение для игры сверху вниз с столкновением с группой "Solid"

Предположим, у вас есть действия MoveX и MoveY.

На этапе:

this.vspeed = actions.MoveY.value * 10;
this.hspeed = actions.MoveX.value * 10;
this.moveSmart('Solid');

Пример: Движение для платформерной игры

Предположим, у вас есть действия MoveX и Jump.

На создании:

this.gravity = 0,5;
this.gravityDir = 270;

В событии начала кадра:

this.hspeed = actions.MoveX.value * 10;

// Существует ли что-то под персонажем?
if (place.occupied(this, this.x, this.y + 1, 'Solid')) {
    // Проверяем, хочет ли игрок прыгать.
    if (actions.Jump.down) {
        this.vspeed = -15;
    }
}

// Перемещаем копию
const collided = this.moveSmart('Solid');

// Проверяем, было ли столкновение и было ли оно по оси Y
if (collided && collided.y) {
    // Создаем вертикальную скорость равной нулю
    this.vspeed = 0;
}

Перемещение по сетке

Чтобы точно перемещать копии по сетке, обычно требуется использовать методы, отличные от тех, что описаны выше. Эти три метода предназначены для свободного перемещения в реальном времени и не будут идеально прилегать к сетке из-за компенсации задержки и u.time.

В настоящее время существуют два относительно простых способа перемещения копий с использованием сеточного выравнивания: вручную изменяя значения x и y или используя модуль tween.

Пример: переместить копию на 64 пикселя при нажатии клавиши

Предполагается, что у вас есть действия MoveX и MoveY.

В событии начала кадра:

// Функция Math.sign возвращает -1, если значение отрицательное, и 1, если оно положительное.
// actions.ActionName.value возвращает значения от -1 до 1, а также все значения между ними при использовании, например, джойстика. Таким образом, мы получим либо -64, либо 64 в каждом измерении.
// Для правильной настройки действий см. страницу «Действия» на странице советов и трюков.
if (actions.MoveX.pressed) {
    this.x += Math.sign(actions.MoveX.value) * 64;
}
if (actions.MoveY.pressed) {
    this.y += Math.sign(actions.MoveY.value) * 64;
}

Пример: Медленно перемещайте копию, останавливаясь на клетках сетки

Предположим, у вас есть действия MoveX и MoveY.
Убедитесь, что ваша копия прилипает к сетке в начале уровня.

В событии начала кадра:

// % означает "Получить остаток деления". 64x64-я сетка
// разделяет каждую ось на 64 пикселя.

// Если копия на сетке...
if (this.x % 64 === 0 && this.y % 64 === 0) {
    // Остановите движение
    this.speed = 0;

    // Но также, если мы нажимаем клавиши движения, примените скорость.
    // Это произойдет, если мы на сетке,
    // так как весь код находится внутри условия `if`.
    if (actions.MoveX.pressed) {
        this.hspeed = Math.sign(actions.MoveX.value) * 8;
    }
    if (actions.MoveY.pressed) {
        this.vspeed = Math.sign(actions.MoveY.value) * 8;
    }
}

// Примените движение в соответствии с ранее установленными значениями
this.x += this.hspeed;
this.y += this.vspeed;

Пример: переместить копию по сетке с помощью tween

tween создает плавные анимации значений и может быть использован для движения по сетке. Этот подход предполагает, что у вас включен tween в настройках проекта -> Котомоды и что у вас есть действия под названием MoveX и MoveY.

Событие создания:

this.moving = false;

Событие начала кадра:

// Если копия еще не двигается...
if (!this.moving) {
    // Проверяем нажатия клавиш
    if (actions.MoveX.down) {
        // Запускаем движение
        this.moving = true;
        tween.add({
            obj: this,
            fields: {
                x: this.x + Math.sign(actions.MoveX.value) * 64
            },
            duration: 650 // 0,65 с
        })
        .then(() => {
            // Когда анимация закончится, установите this.moving в false, чтобы показать, что копия больше не двигается.
            this.moving = false;
        });
    }
    // То же самое, но для оси Y
    if (actions.MoveY.down) {
        this.moving = true;
        tween.add({
            obj: this,
            fields: {
                y: this.y + Math.sign(actions.MoveY.value) * 64
            },
            duration: 650
        })
        .then(() => {
            this.moving = false;
        });
    }
}

Стратегии, позволяющие избежать соприкосновения с другими объектами

Представьте, что у вас есть игровой персонаж, перемещающийся с видом сверху, и стены, которые не должны позволять персонажу проходить через них. Существуют два способа выполнения проверки столкновений:

  1. Переместите персонажа так, как он есть, а затем переместите его из области возможного столкновения.
  2. Проверьте наличие столкновений сначала, а затем переместите персонажа, если существует свободное пространство.

this.moveBullet и this.moveSmart следуют второму стратегии и часто достаточны для предотвращения соприкосновения. Но если вы не используете эти методы, вам придется придерживаться первого подхода или самостоятельно проверять возможные столкновения перед перемещением. Вот как это сделать.

Избегание столкновений

Самый простой способ избежать столкновений — это прыгать к предыдущим координатам после движения. У каждой копии есть this.xprev и this.yprev.

Пример: Если столкновение с препятствием, вернись к предыдущим координатам, чтобы не перекрывать его

В событии начала кадра:

this.move();
if (place.occupied(this, 'Solid')) {
    this.x = this.xprev;
    this.y = this.yprev;
}

Пример: Отталкивайтесь от коллайдера, если копия столкнулась

Иногда вы неизбежно сталкиваетесь с препятствиями при следовании стратегии «сначала проверяйте столкновения, а затем перемещайтесь». Это может произойти по разным причинам:

  • В копию что-то врезалось.
  • Вашу копию масштабируют или поворачивают, и она оказывается внутри других объектов.
  • Объекты вокруг вас поворачиваются и масштабируются.

Вы можете получить препятствие из place.occupied и переместить свою копию подальше от него.

В событии начала кадра:

const obstacle = place.occupied(this, 'Solid');
// Если там было препятствие, а оно копией...
if (templates.isCopy(obstacle)) {
    // Получите направление от препятствия до копии
    const repelDirection = u.pointDirection(obstacle.x, obstacle.y, this.x, this.y);
    // Эти две строки переместят копию на 3 пикселя в заданном направлении
    this.x += u.ldx(3, repelDirection);
    this.y += u.ldy(3, repelDirection);
} else {
    this.move();
}