Создание игр: Jetty Cat
Создание игр: Jetty Cat
Автоматически переведённая страница
К сожалению, на полный ручной перевод у нас не хватает ресурсов.
Если вы увидели ошибку — отправьте пул-риквест с исправлениями (ссылка для редактирования в конце страницы).
Как и в Flappy Bird, в Jetty Cat игрок будет управлять котом, нажимая или нажимая на него, чтобы избежать бесконечных препятствий с помощью реактивного ранца. Сначала мы реализуем основную игровую логику, а потом — интерфейс пользователя. После этого мы отполируем игру, добавив красивые переходы, системы частиц и легкие эффекты.
Вот, что нам нужно сделать:
Примечание
Как вы можете видеть, это не пример "Здравствуй, мир!", а скорее руководство по созданию полной игры с нуля. Уделите себе достаточно времени, чтобы закончить его!
Создание проекта и импорт ресурсов
Откройте файл ct.js и создайте новый проект, введя название и нажав кнопку "Создать". Укажите, где вы хотите сохранить проект. Хорошим выбором будет папка "Документы".
Щелкните вкладку "Ресурсы" в верхней части окна ct.js. Затем откройте свой файл-менеджер и перейдите в папку examples/JettyCat_assets
внутри папки ct.js. Если вы использовали приложение itch.io для установки ct.js, вы можете щелкнуть правой кнопкой мыши на иконке установленной программы в вашей библиотеке и открыть файл-менеджер, чтобы перейти к месту хранения программы. Внутри есть ресурсы, которые мы будем использовать. Перетащите ресурсы из файлового просмотра в ct.js, и он быстро импортирует их в проект.
Нам нужно подготовить эти текстуры: правильно пометьте фоновые изображения как такие и установите формы коллизии, чтобы копии внутри игры точно взаимодействовали друг с другом. Сначала откройте фон для нашего проекта. Нажмите на карту BG_Ground
:
Здесь нам нужно нажать на флажок "Это тайл-фоновое изображение". Это говорит ct.js упаковывать эту текстуру по-другому и позволяет ей повторяться в наших уровнях.
Нажмите "Сохранить" в нижнем левом углу. Теперь сделайте то же самое с текстурой BG_Sky
.
Фон готов! Теперь настройте формы коллизии для наших спрайтов. Нам не нужно настраивать их повсюду, но нам нужно установить их для объектов, которые сталкиваются друг с другом, и для тех, на которые мы нажимаем при игре. Заголовки, такие как Jetty_Cat
, OhNo
и Pause
, не будут интерактивными и будут служить только декорациями; PressHint
будет иметь информативную роль и также не будет напрямую получать нажатия. Но кот и трубы будут сталкиваться друг с другом, а звезды должны знать, когда кот пересекает их.
Откройте ресурс PotatoCat
! Первым делом нам нужно переместить ось текстуры. По умолчанию она показывает квадратную ось, расположенную в левом верхнем углу. Ось - это точка, вокруг которой копия масштабируется и вращается. Поместите ось в центр тела кота. Затем мы определим его форму коллизии. Кот не похож на круг или прямоугольник, поэтому установите форму коллизии в виде многоугольника в левой колонке. Появится пятигранник: вы можете перетаскивать его углы и добавлять новые точки, щелкая по желтым линиям, чтобы лучше обвести силуэт кота. 15 точек достаточно, чтобы нарисовать его.
Совет
Хорошей идеей будет не обрамлять хвост и уши кота. Когда хвост сталкивается с трубой и игрок теряет жизнь, это может показаться несправедливым. В любом случае, хвост слишком гибкий, чтобы вызывать летальные столкновения 😺
После определения формы мы захотим создать одинаковую маску коллизии для текстуры PotatoCat_Stunned
. Но вместо того, чтобы создавать маску точки за точку, давайте просто скопируем эту маску! Нажмите кнопку "Копировать маску коллизии" и затем нажмите кнопку "Вставить маску коллизии" в текстуре PotatoCat_Stunned
. Не забудьте настроить точку оси!
После определения формы нажмите кнопку "Сохранить", чтобы вернуться к списку ресурсов. Нам также нужно настроить текстуру Star
.
Для труб мы будем использовать что-то немного другое. Откройте первое изображение, Tube_01
, и поместите его ось почти внизу спрайта. Помните, что ось влияет не только на вращение, но и на масштабирование? Мы будем повторно использовать одну и ту же текстуру для труб, которые висят сверху экрана и растут снизу. Чтобы сделать это, мы будем отрицательно масштабировать их вокруг нижней оси, чтобы перевернуть конец вниз. Мы даже можем повернуть их позже, и они будут волаться вместе с их основой, оставшейся на месте.
Нам нужно сделать это для всех четырех текстур труб. Затем мы можем начать создавать уровень и кодировать движение!
Создание нашей главной комнаты и перемещение кота
Давайте создадим комнату, где будет происходить вся веселая халатность! Комнаты часто называют уровнями. Вот место, где мы объединяем все наши ресурсы и позволяем им взаимодействовать друг с другом. Откройте вкладку "Ассеты" в верхней части окна ct.js и нажмите кнопку "Новый ассет", а затем выберите "Комната". Назовите комнату "InGame" — мы будем использовать это конкретное имя позже в коде.
Откроется редактор комнаты для именно этой комнаты. Однако вы можете назвать их как угодно; нам просто нужно найти такое название, которое мы сможем запомнить при написании кода меню 😃
Затем, в панели свойств с шестеренкой, нам нужно установить размер нашей комнаты. Устанавливайте его на 1080x1920 пикселей.
Теперь давайте добавим наши фоны. Нажмите кнопку "Фоны" слева, а затем добавьте два фона: один для неба и один для земли. Небо выглядит хорошо как есть, но земля нуждается в доработке. Нажмите шестеренку рядом с текстурой фона в левой колонке и найдите выпадающее меню "Повторять". Установите его на "повторить-x": это сделает фон горизонтально только, так как ось X — горизонтальная (а Y — вертикальная). Затем нам нужно сдвинуть землю вправо вниз по рамке комнаты, изменив поле "Сдвиг по Y".
Подсказка:
Вы можете перемещать комнату, нажимая колесиком мыши и увеличивая масштаб с помощью колесика мыши.
Мы также установим глубину для обоих фонов, чтобы они правильно выровнялись. Глубина — это 3e измерение, которое говорит ct.js, как упорядочивать наши объекты, чтобы небо не совпадалось со всем остальным. Положительные значения приводят объекты ближе к камере, а поэтому объекты с положительной глубиной будут перекрывать те, у которых отрицательная глубина.
Установите значение глубины неба на -20, а земли — на -10. Вот как ct.js будет понимать эти настройки:
Шаблон кошки
Текстуры очень важны для большинства игр, но они ничего не делают сами по себе. Мы уже использовали фоновые текстуры, которые служат для декоративных целей. Шаблоны могут включать игровую логику и используются для создания копий. Копии — это то, что мы добавляем в наши комнаты, а эти копии взаимодействуют друг с другом на экране.
Давайте создадим шаблон для нашей кошки! Откройте вкладку "Ассеты" в верхней части окна ct.js и нажмите кнопку "Новыйассет". Назовите его PotatoCat
и установите текстуру, нажав на "Выбрать квадрат" и выбрав текстуру кошки.
Теперь мы можем добавить кошку в нашу комнату! Перейдите к ней, переключившись обратно на вкладку "Ассеты" и открыв нашу единственную комнату. Когда вы нажмете на "Добавить копии", наша кошка появится в новом окне. Нажмите на кошку, а затем еще раз щелкните по месту, где вы хотите, чтобы появилась копия. Пока нам нужна только одна кошка.
Если вы нажмете на "Пуск", будет запущен отладчик, и мы увидим статичный экран с нашими фоновыми текстурами и кошкой. Кошка не двигается пока, и это то, что мы хотим изменить!
Откройте вкладку "Ассеты" снова, и откройте шаблон кошки. Вы должны увидеть событие "Frame start" с кодом справа. Ct.js выполняет блоки кода в зависимости от события, которое происходит. Нажмите кнопку "Добавить событие", чтобы увидеть некоторые варианты. Вот некоторые важные события:
- "Создание" для кода, который выполняется один раз при создании копии;
- "Frame start", который выполняется на каждом кадре;
- "Frame end" выполняется в конце каждого кадра после других вычислений и обновлений движения;
- "Уничтожение" выполняется один раз при удалении копии.
Здесь мы сделаем следующее:
- Зададим скорость и направление движения кошки в событии "Создание";
- Добавим событие "Действие", которое будет ускорять кошку, чтобы она могла летать вверх.
Нажмите кнопку "Добавить событие", затем найдите событие "Создание" и выберите его. Затем нажмите на блок кода слева, чтобы отобразить его справа, и добавьте следующий код:
this.speed = 500;
this.direction = 0;
this.gravityAcceleration = 7000;
@speed = 500
@direction = 0
@gravityAcceleration = 7000;
this.speed = 500;
означает, что мы будем перемещать кошку на 500 пикселей каждую секунду — примерно половину нашей комнаты.
this.direction = 0;
означает, что мы перемещаем кошку в заданном направлении при 0 градусах. 0 градусов означает, что она будет двигаться вправо, 270 — вверх, 180 — налево, а 90 — вниз.
this.gravityAcceleration = 7000;
будет использовано позже. Это сохранит, как быстро будет ускоряться наша кошка вниз (это не так быстро, как вы думаете).
Теперь давайте переместим нашу кошку каждый раз, когда игрок нажимает на экран. Нам нужно будет поддерживать как события мыши, так и касания мобильного устройства, поэтому мы будем использовать модуль Pointer. Он уже должен быть включен, но если нет, откройте вкладку "Проект" в верхней части окна ct.js, а затем "Catmods" слева. Найдите модуль Pointer
в разделе доступных модулей. Нажмите на него, чтобы включить его — у него будет зеленый флажок с маленькой сплошной стрелкой:
Теперь, в ct.js, методы ввода группируются в Действия. В этом проекте мы будем использовать только один метод ввода — нажатие на экран. На вкладке "Проект" в верхней части экрана нажмите вкладку "Действия и методы ввода" слева.
Есть готовые параметры, которые настраивают действия для нас, но пока давайте создадим свои, нажав кнопку "Создать от нуля". Добавьте наше первое действие, назовите его Poof
. Да. Затем, в правом верхнем углу, нажмите "Добавить метод ввода", и найдите метод "Любое нажатие" под заголовком Pointer. Вы можете использовать поиск, чтобы быстро отфильтровать результаты.
Действие готово, мы можем сохранить его и вернуться к нашей кошке.
Действия? Зачем?
Для опытных разработчиков действия могут показаться лишним шагом здесь, но они проявляют себя, когда нужно поддерживать несколько разных методов ввода. Например, предположим, вы создаете игру, которая поддерживает как клавиатуру, так и геймпад, и клавиатура поддерживает движение по клавишам WASD и стрелкам. Одно действие будет поддерживать все три метода, и ваш код останется тонким, даже если вы добавляете новые методы ввода позже. Кроме того, они могут использоваться со одинаковым кодом!
Вы можете чтать больше о действиях здесь.
Создайте новое действие для события нажатия вниз для кошки. Это параметризованное событие, поэтому вы можете указать, какое действие вы хотите! Выберите действие Poof из списка, а затем добавьте следующее к событию:
this.gravity = this.gravityAcceleration;
this.addSpeed(u.time * 2 * this.gravityAcceleration, 270);
@gravity = @gravityAcceleration
@addSpeed u.time * 2 * @gravityAcceleration, 270
Этот код будет запускаться только при нажатии игроком на экран. Если он сработает, мы определим силу гравитации, которая будет тянуть кота вниз, и добавим скорость, которая будет тянуть кота вверх. Нам нужно умножить добавленную скорость на u.time
, чтобы обеспечить плавное движение в любой момент. Да, мы используем значение this.gravityAcceleration
как для установки гравитации, так и для добавления скорости. Изменение скорости - это ускорение!
u.time
u.time
будет равен 1/60 в большинстве случаев, но этот множитель не следует игнорировать. Если частота кадров игрока падает или игра задерживается по какой-либо причине, u.time
станет более большим значением для компенсации этих падений и задержек кадров. Например, если частота кадров падает с 60 до 30 в секунду, то u.time
временно будет равен 2/60.
Кроме того, u.time
поддерживает растяжение времени игры и позволяет создавать эффекты замедления и паузы в игре. (И мы реализуем эти функции!)
Совет
Также существуют параметризованные события "Action Poof press" и "Action Poof release", которые срабатывают при нажатии и отпускании игроком экрана.
Гравитация, определенная в событии "On Poof down", кажется странной, верно? Это действительно константа, которая лучше подходит для события "Creation", чтобы она устанавливалась один раз с начала и не менялась. Но размещение ее внутри условия с проверкой входных данных добавляет небольшой трюк: кот начнет падать только после взаимодействия игрока с игрой! Таким образом, они не потеряют сразу же, так как кот быстро упадет на землю в противном случае.
Если мы запустим проект сейчас, мы увидим, что кот движется слева направо, а затем реагирует на щелчки и начинает летать и падать. Он быстро вылетает из области просмотра, правда? Давайте изменим это!
Перемещение камеры
В Ct.js есть сущность камера
, которая отвечает за отображение содержимого на вашем экране. У нее много функций, а одна из них — следование копии.
Откройте «Событие создания» нашей кошки и добавьте следующий код:
camera.follow = this;
camera.followY = false;
camera.shiftX = 250;
camera.follow = this
camera.followY = false
camera.shiftX = 250
camera.follow
указывает на копию, за которой должна следовать камера, и мы указываем ей следовать за кошкой, устанавливая this
. this
ссылается на копию, в которой выполняется код. У событий и this
также есть комнаты.
camera.followY = false;
означает, что мы не хотим перемещать камеру по оси Y (по вертикали). Мы будем просто скользить ее вправо.
camera.shiftX = 250;
указывает, что мы хотим, чтобы камера находилась в 250 пикселях справа от кошки. По умолчанию она фокусируется так, чтобы кошка оставалась в центре просмотра.
Если мы запустим игру сейчас, камера будет идеально следовать за нашей кошкой. Ура!
Написание кода для столкновений
Настало время реализовать реальную игровую механику. Мы добавим шаблон для труб, разместим некоторые из них на уровне, а затем напишем код для столкновений как для труб, так и для земли. После этого мы случайным образом изменим текстуры труб, тем самым меняя их высоту.
Добавление труб
Создайте новый шаблон и назовите его Tube
. Выберите его текстуру как одну из длинных труб в нашей коллекции. Затем установите его группу коллизий в "Obstacle".
Затем откройте нашу комнату и добавьте трубы на пол, чтобы проверить коллизии. Откройте комнату InGame
, выберите инструмент "Add copies", выберите шаблон трубы в панели шаблонов и затем добавьте их, щелкнув в просмотре уровня. Нам не понадобится много для тестирования.
Сначала создайте новый шаблон "PotatoCat_Stunned", используя текстуру "PotatoCat_Stunned". Затем в его событии "Creation" добавьте следующее:
this.gravity = 7000;
// Прыжок вправо
this.speed = 1500;
this.direction = -45;
@gravity = 7000;
# Прыжок вправо
@speed = 1500
@direction = -45
Это шаблон застывшего кота, в который мы перейдем при столкновении с препятствием. После создания он будет отжиматься вправо и выстраиваться за пределы экрана со скоростью и гравитацией.
Теперь откройте шаблон "PotatoCat" и создайте новое событие "Collision with a group" с группой по имени "Obstacle". Этот код будет запущен после того, как кот столкнется с трубой. Далее мы добавим следующий код, чтобы уничтожить копию и запустить событие "Destruction":
this.kill = true;
@kill = true;
Наконец, создайте событие "Destruction" в шаблоне PotatoCat, чтобы создать копию PotatoCat_Stunned перед удалением копии. Добавьте следующее:
// Остановите движение камеры
camera.follow = false;
// Создайте копию для анимации смерти
var dummy = templates.copy('PotatoCat_Stunned', this.x, this.y);
// Копируем размер в новую копию
dummy.scale.x = this.scale.x;
dummy.scale.y = this.scale.y;
# Остановите движение камеры
camera.follow = false
# Создайте копию для анимации смерти
dummy = templates.copy 'PotatoCat_Stunned', this.x, this.y
# Копируем размер в новую копию
dummy.scale.x = @scale.x
dummy.scale.y = @scale.y
dummy.scale.x = this.scale.x;
и dummy.scale.y = this.scale.y;
просто гарантируют, что если мы решим позже изменить размер PotatoCat, то шаблон с потрясенным состоянием будет изменен таким же образом.
Время для некоторых тестов! Если кошка резко дрогнет во время столкновения, проверьте, что ее коллизионная форма и ось установлены таким же образом, как и в начальном текстуре.
Сделать так, чтобы кот упал, если он касается земли или верхнего края экрана
По какой-то причине пол и даже небо так же смертельны, как и трубы в игре Flappy Bird. У нас нет шаблона для земли, и потому place
не сработает, а небо — это вовсе не сущность игры. Но они плоские, горизонтальные, и мы можем расширить наш логику столкновения, добавив правила, которые проверяют положение кота в пространстве.
Если мы теперь откроем комнату и перенесем курсор мыши над уровнем, мы увидим текущие координаты в нижнем левом углу. Верхняя сторона начальной рамки всегда на 0 пикселей по оси Y, а верхний край земли находится где-то на 1750 пикселях. Позиции копий определяются this.x
и this.y
, и мы можем прочитать их и сравнить с некоторыми другими значениями.
Измените логику "Старт фрейм" кота следующим образом, чтобы он был ошеломлен при столкновении с землей и небом:
this.move();
if (this.y > 1750 - 200 || // If the cat is below the ground minus its approximate height, or
this.y < 0) { // the cat flew off the upper boundary,
this.kill = true; // remove the cat.
}
@move
if @y > 1750 - 200 or @y < 0 # If the cat is below the ground minus its approximate height, or the cat flew off the upper boundary,
@kill = true # remove the cat.
Случайная высота трубы за счет изменения ее текстуры
Мы можем изменить текстуру в коде для наших труб, чтобы случайным образом менять их высоту, поскольку у нас есть четыре разных текстуры для них.
В CT.js есть встроенный модуль под названием random
, который помогает генерировать случайные значения. Найдите его в разделе "Catmods" из вкладки "Проект" в верхнем меню и включите его. Затем добавьте событие создания в шаблон трубы, откройте код события создания и добавьте следующий фрагмент кода:
this.tex = random.dice(
'Tube_01',
'Tube_02',
'Tube_03',
'Tube_04'
);
@tex = random.dice 'Tube_01', 'Tube_02', 'Tube_03', 'Tube_04'
Функция random.dice
принимает любое количество аргументов и возвращает один из них случайным образом каждый раз, когда она вызывается.
Порада для тестирования! Если ваши трубы выстраиваются неправильно, проверьте, что у вас установлены формы коллизии для всех четырех текстур и что их оси направлены вниз по трубе.
Here is the translation of the provided markdown document from English to Russian:
Генерация труб со временем
Как шаблоны, так и комнаты могут иметь свою логику; они скрыты под кнопкой "События" в верхней панели редактора комнаты. Существует четыре основных события, а также дополнительные:
- "Старт" комнаты, который выполняется один раз при переключении на эту комнату или запуске игры в этой комнате;
- "Фрейм старт", который выполняется каждый фрейм после любого другого события "Фрейм старта" копий;
- "Конец фрейма", который выполняется в конце каждого фрейма;
- "Конец комнаты", который выполняется при переключении на другую комнату или удалении встроенной комнаты со сцены.
Мы сделаем следующее, чтобы создавать новые трубы с течением времени:
- Мы установим переменную в событии «Начало комнаты», которая будет служить нам таймером — она будет считать оставшиеся секунды до создания новых труб;
- Мы создадим событие таймера, которое будет ждать, пока переменная таймера не достигнет нуля.
- Когда событие таймера срабатывает, мы перезагрузим его и создадим новые трубы относительно положения камеры.
- Мы также создадим трубы в верхней части видового поля и используем масштабирование, чтобы перевернуть эти трубы вниз, так что они будут указывать вниз.
Откройте нашу единственную комнату InGame
. Удалите существующие трубы, удерживая клавишу "Ctrl" и перетаскивая мышь, пока активна копирующая инструмент, или выбрав их с помощью инструмента выбора и нажав клавишу "Delete" на клавиатуре. Затем нажмите кнопку "События" в верхней панели.
Добавьте следующую строку в код события «Старт»:
this.timer1 = 5;
@timer1 = 5
Здесь «timer1» — это специальное имя переменной, которое будет автоматически уменьшаться до нуля автоматически без дополнительной программирования. Это соответствует событию "Timer 1" события.
Добавьте событие "TubeSpawn" с именем "Timer 1" и оставьте поле "Описание события" пустым:
// Повторно установите таймер снова
this.timer1 = 2;
// Создайте два труба
// В нижней части камеры справа +250
var tube1 = templates.copy('Tube', camera.right + 250, camera.bottom - 130); // Внизу камеры
var tube2 = templates.copy('Tube', camera.right + 250, camera.top - 70); // Вверху экрана
// Измените текстуру второго труба в зависимости от текстуры первого труба
if (tube1.tex === 'Tube_01') { // Короткий труб будет результатом длинного труб
tube2.tex = 'Tube_04';
} else if (tube1.tex === 'Tube_02') {
tube2.tex = 'Tube_03';
} else if (tube1.tex === 'Tube_03') {
tube2.tex = 'Tube_02';
} else if (tube1.tex === 'Tube_04') { // Длинный труб будет результатом короткого труб
tube2.tex = 'Tube_01';
}
// Теперь, переверните верхний (второй) труб
tube2.scale.y = -1;
# Повторно установите таймер снова
@timer1 = 2;
# Создайте два труба
# В нижней части камеры справа +250
# Tube_01
tube1 = templates.copy 'Tube', camera.right + 250, camera.bottom - 130
# Вверху экрана справа +250
tube2 = templates.copy 'Tube', camera.right + 250, camera.top - 70
# Измените текстуру второго труба в зависимости от текстуры первого труба
if tube1.tex == 'Tube_01' { # Короткий труб будет результатом длинного труб
tube2.tex = 'Tube_04';
} else if tube1.tex == 'Tube_02' {
tube2.tex = 'Tube_03';
} else if tube1.tex == 'Tube_03' {
tube2.tex = 'Tube_02';
} else if tube1.tex == 'Tube_04' { # Длинный труб будет результатом короткого труб
tube2.tex = 'Tube_01';
}
# Теперь, переверните верхний (второй) труб
tube2.scale.y = -1
Здесь много кода!
timer1
будет равен нулю, когда это событие срабатывает. Когда это произойдет, мы устанавливаем его значение снова в положительное число, чтобы он сработал позже. Здесь мы добавляем 2 секунды. ct.js будет автоматически считать его вниз снова, потому что это специальная переменная.
Мы создаем две копии с templates.copy(templateName, xPosition, yPosition)
и храним ссылки на них в переменных tube1
и tube2
. В начале их высота будет полностью нормальной, потому что их код создания с random.dice
будет запущен мгновенно после создания. Это приведет к блокировке пути в хорошей части случаев, когда обе трубы оказались длинными. Чтобы исправить это, мы читаем имя текстуры первой трубы tube1.tex
и устанавливаем текстуру второй трубы tube2
в зависимости от извлеченного значения.
camera.right
, camera.left
, camera.top
, camera.bottom
представляют координаты границ просмотра в игровых координатах. Здесь мы используем их, чтобы создать трубы за пределами экрана, немного вправо, где заканчивается viewport, и выше нижнего и верхнего края viewport.
Наконец, мы переворачиваем вторую трубу, выполнив tube2.scale.y = -1
. Это точно та же операция, которую мы бы сделали при горизонтальном переворачивании изображения в редакторе графики. Для справки, существует также tube2.scale.x
, который устанавливает его горизонтальный масштаб.
Если мы запустим проект сейчас, мы увидим хорошо сгенерированные трубы, которые оставляют небольшой промежуток между ними, чтобы пролезть через него. Но подождите, кошка слишком большая, чтобы пролезть через нее! О нет, может быть, я должен был назвать этот учебник "Жирная кошка"…
Не волнуйтесь, есть решение ✨ Мы будем использовать тот же масштаб, чтобы сделать кошку немного меньше. Значения масштаба могут быть не только 1
и -1
, но и все что между ними, чтобы уменьшить объект или увеличить его больше, чем 1, чтобы увеличить объекты.
Есть два метода масштабирования кошки:
- мы можем добавить строку
this.scale.x = this.scale.y = 0.65;
к событию "Создание" кошки; - или мы можем сделать то же самое, изменив ее в редакторе комнаты с помощью инструмента "Выбор".
Удаление ненужных копий
Когда мы создаем копии через время, их количество будет постоянно увеличиваться. Если мы ничего не сделаем с этим, игра будет медленно съедать так много памяти компьютера, что в конечном итоге она зависнет. Чтобы этого не произошло, мы будем удалять копии, которые оказались слева от камеры.
Добавьте следующий код в событие "start" фрейма трубы:
if (this.x < camera.left - 150) {
this.kill = true;
}
if @x < camera.left - 150
@kill = true
Добавление звёзд
Давайте добавим шаблон для бонусных звёзд, которые будут увеличивать счёт при сборе. Мы сделаем следующее:
- Настройте переменную счёта в коде комнаты "Создание".
- Создайте новый шаблон для бонусных звёзд.
- Добавьте немного логики в событие столкновения звезд с шаблоном, чтобы уничтожать звезду при столкновении с котом.
- Создайте новую комнату и шаблон для нее, чтобы отобразить счёт.
- Поместите эту новую комнату в основную.
Теперь откройте события комнаты InGame
и добавьте строку this.score = 0;
в событие старта комнаты. Это создаст переменную, которую мы сможем редактировать и читать в любом другом экземпляре.
Создайте новый шаблон и назовите его "Star". Задайте текстуру для него.
Создайте событие Столкновения с шаблоном и выберите "PotatoCat" в качестве шаблона. Затем добавьте следующий скрипт:
this.kill = true;
rooms.current.score += 1;
@kill = true
rooms.current.score += 1
Совет
Вместо использования события столкновения можно вызвать place.meet
в условном операторе до выполнения этого кода в событии старта кадра. place.meet
похож на place.occupied
, но он проверяет не против групп коллизий, а против конкретного шаблона.
Это событие проверяет, сталкивается ли звезда с котом. Если да, то this.kill = true
указывает, что звезду нужно удалить. rooms.current.score += 1;
увеличивает нашу переменную балла, которая была создана ранее в коде комнаты "Создание".
Совет
rooms.current
всегда указывает на текущую комнату. Если есть вложенные комнаты, то rooms.current
будет указывать на основную комнату.
Нам также нужно добавить этот код в событие старта кадра, чтобы предотвратить утечку памяти и удалить звезды, которые не были собраны:
if (this.x < camera.left - 150) {
this.kill = true;
}
if @x < camera.left - 150
@kill = true
Создание звёзд
В коде события таймера 1 комнаты добавьте пару строк (отмеченных выделением), которые будут добавлять звезду с 30% шансом где-то между следующими двумя трубами. Это будет использовать методы из модуля random
:
// Wind it again
this.timer1 = 2
// Create two tubes
var tube1 = templates.copy('Tube', camera.right + 250, camera.bottom - 130); // At the bottom of the camera
var tube2 = templates.copy('Tube', camera.right + 250, camera.top - 70); // At the top
// Change second tube's texture depending on which texture is used in the first tube
if (tube1.tex === 'Tube_01') { // Shortest tube will result in the longest tube
tube2.tex = 'Tube_04';
} else if (tube1.tex === 'Tube_02') {
tube2.tex = 'Tube_03';
} else if (tube1.tex === 'Tube_03') {
tube2.tex = 'Tube_02';
} else if (tube1.tex === 'Tube_04') { // Longest will result in the shortest one
tube2.tex = 'Tube_01';
}
// Thus we will always get gaps of the same size, but with random tubes.
// Now, flip the upper (second) tube
tube2.scale.y = -1;
// Create a star bonus with 30% chance somewhere in between top and bottom edge, with 300px padding.
if (random.chance(30)) {
templates.copy('Star', camera.right + 250 + 500, random.range(camera.top + 300, camera.bottom - 300));
}
# Wind it again
@timer1 = 2
# Create two tubes
# At the bottom of the camera
tube1 = templates.copy 'Tube', camera.right + 250, camera.bottom - 130
# At the top
tube2 = templates.copy 'Tube', camera.right + 250, camera.top - 70
# Change second tube's texture depending on which texture is used in the first tube
if tube1.tex == 'Tube_01'
# Shortest tube will result in the longest tube
tube2.tex = 'Tube_04'
else if tube1.tex == 'Tube_02'
tube2.tex = 'Tube_03'
else if tube1.tex == 'Tube_03'
tube2.tex = 'Tube_02'
else if tube1.tex == 'Tube_04'
# Longest will result in the shortest one
tube2.tex = 'Tube_01'
# Thus we will always get gaps of the same size, but with random tubes.
# Now, flip the upper (second) tube
tube2.scale.y = -1
# Create a star bonus with 30% chance somewhere in between top and bottom edge, with 300px padding.
if random.chance(30)
templates.copy 'Star', camera.right + 250 + 500, random.range(camera.top + 300, camera.bottom - 300)
Функция random.chance(30)
возвращает true
30 раз из 100 и false
в остальных случаях. Вы можете отрегулировать значение, чтобы звезды появлялись чаще или реже.
Функция random.range(a, b)
выбирает случайное значение в заданном диапазоне. В нашем случае мы вычисляем две координаты относительно камеры, так что звезды не появляются вблизи земли или верхней кромки экрана.
Создание элемента интерфейса с счетчиком
В ct.js, начиная с версии 1.3, элементы интерфейса обычно создаются в отдельной комнате, которая затем внедряется в другие комнаты. Эти вложенные комнаты также часто называют "слойми".
Перейдите на вкладку "Ассеты" в верхней части окна ct.js и создайте новый стиль под "Новый ассет". Назовите его "Оранжевый". Здесь мы создадим текстовый стиль, который будем использовать для отображения счета, а также других текстовых строк.
На первой вкладке "Шрифт" установите размер шрифта 80 и вес 900. Установите межстрочный интервал 0, а затем выровняйте его по центру. Это сделает текст более толстым и большим.
Перейдите на вкладку "Заполнение" и активируйте его. Давайте создадим вертикальную градиентную заливку. Мы будем использовать бледно-желтый и оранжевый цвета.
Затем перейдите на вкладку "Линия", чтобы активировать ее. Установите цвет линии темно-коричневый, а также вес 10.
Теперь мы можем сохранить стиль. После этого нам понадобятся два новых шаблона, которые будут отображать иконку звезды и счетчик.
Создайте новый шаблон и назовите его StarCounter
. Используем в качестве текстуры нашу Star
текстуру.
Теперь создайте еще один шаблон под названием StarCounterLabel
. Он будет отображать значение счета рядом с копией StarCounter
. Теперь перейдите от анимации к тексту.
После перехода в текстовую копию вы можете нажать на призрачного котика и выбрать нужный стиль. Выберите наш "Оранжевый" стиль.
Нам нужно обновлять текстовую метку на каждом кадре. В событии "Конец кадра" введите строку this.text = rooms.current.score;
. Свойство this.text
позволяет легко редактировать метку текста копии, теперь, когда мы переключили ее на Текст. Вы также можете установить стандартный текст для этого шаблона в правой панели, чтобы он не отображал <Empty>
, когда вы помещаете его в комнату.
this.text = rooms.current.score;
@text = rooms.current.score
Наконец, давайте создадим комнату для этого счетчика и метки и поместим эту комнату внутри основной комнаты. Создайте новую комнату и назовите ее UI_InGame
. Затем установите ее размер просмотра 1080x1920, чтобы он соответствовал размеру просмотра основной комнаты, пометьте ее как слой интерфейса и поместите копию счетчика и метку в верхнем левом углу:
Затем откройте комнату InGame
и добавьте следующий код в конец ее кода комнаты:
this.mainUi = rooms.append("UI_InGame");
@mainUi = rooms.append 'UI_InGame'
Мы будем ссылаться на эту комнату для дальнейшего использования. После этого в уровне должны начать появляться звезды, а счетчик очков должен отображаться в левом верхнем углу видового окна.
Создание меню
Теперь мы добавим несколько комнат с типичными меню, чтобы наша игра выглядела завершенной:
- главное меню;
- экран паузы;
- и экран очков, который будет отображаться при неудаче.
Основной меню
Откройте текстуру Jetty_Cat
и убедитесь, что ее ось находится в центре. Затем создайте новый шаблон с ней. Он будет чисто декоративным, поэтому мы не будем писать здесь код.
Затем откройте текстуру "Button_Play" и убедитесь, что ее ось находится в центре, и ее форма столкновения является круглой.
Далее создайте новый шаблон с этой текстурой. Создайте событие нажатия указателя и добавьте следующее:
rooms.switch('InGame');
rooms.switch 'InGame';
Это проверяет, нажимал ли игрок кнопку, и если да, переключает на наш основной зал.
Совет
Если вы хотите использовать указатель вместо проверки кликов, поскольку кнопка "Play" находится на слое интерфейса, вам необходимо использовать pointer.collidesUi(this)
вместо pointer.collides(copy)
.
Создайте новый зал и назовите его MainMenu
. Добавьте фона в него и расположите недавно созданные копии так, чтобы это выглядело следующим образом:
При удержании клавиши Alt на клавиатуре поместите копии точно так, как вам нужно.
Если ваши копии исчезают или не размещаются должным образом, проверьте, что вы установили глубину ваших фонов на -20 и -10. Они могут перекрывать ваши элементы!
Если мы сейчас запустим игру, она все еще будет начинаться в основном зале. Чтобы изменить это, щелкните правой кнопкой мыши по залу MainMenu
. В контекстном меню выберите "Установить в качестве стартовой комнаты".
Меню паузы
Для меню паузы нам понадобятся несколько новых кнопок и новая комната, которая будет накладываться на нашу основную комнату и интерфейс.
Создайте шаблон для текстуры "Button_Pause". Убедитесь, что текстура "Button_Pause" имеет центр axis и имеет правильную прямоугольную форму, которая полностью охватывает текстуру.
Шаблон "Button_Pause" будет иметь следующий код в событии нажатия указателя:
// Проверяем, нет ли у нас комнат с названием 'UI_Paused'
if (rooms.list['UI_Paused'].length === 0) {
// Создаем комнату UI_Paused, помещаем ее поверх текущей (добавляем ее),
// и указываем, что это слой интерфейса (isUi: true)
rooms.append('UI_Paused', {
isUi: true
});
// Устанавливаем u.delta в 0, эффективно останавливая игру
pixiApp.ticker.speed = 0;
}
# Проверяем, нет ли у нас комнат с названием 'UI_Paused'
if rooms.list['UI_Paused'].length == 0 {
# Создаем комнату UI_Paused, помещаем ее поверх текущей (добавляем ее),
# и указываем, что это слой интерфейса (isUi: true)
settings =
isUi: true
rooms.append 'UI_Paused', settings
# Устанавливаем u.delta в 0, эффективно останавливая игру
pixiApp.ticker.speed = 0
}
- Is this room a UI layer?
true
Запомните имя "UI_Paused". Нам понадобится создать комнату с этим именем немного позже.
pixiApp.ticker.speed
— это умножитель, который влияет на расчет delta
. Когда он установлен в 0, игра фактически останавливается, так как delta
для всех становится равным нулю. Наш кот и таймеры зависят от delta
.
Откройте комнату UI_InGame
и поместите созданный шаблон в верхний правый угол.
После этого создайте два новых шаблона, похожих на те, которые были созданы для MainMenu
, используя текстуры Button_Play
и Pause
. Кнопка должна называться "Button_Continue", хотя.
Кнопка будет иметь следующий код в своем событии нажатия указателя:
rooms.remove(this.getRoom());
pixiApp.ticker.speed = 1;
room = @getRoom()
rooms.remove room
pixiApp.ticker.speed = 1
rooms.remove(room);
удаляет ранее добавленную комнату. Он не может удалить основную комнату, но он создан для удаления вложенных. this.getRoom()
ищет комнату, которая владеет текущей копией. pixiApp.ticker.speed = 1;
возвращает delta
к нормальному поведению, останавливая игру.
Последний шаг — создать эту вложенную комнату, которая будет иметь кнопку «Продолжить» и декоративный заголовок. Создайте комнату под названием UI_Paused
и поместите в нее копию Button_Continue
и заголовок «Пауза». Убедитесь, что вы установили размер просмотра для нее равным 1080x1920!
Экран счета
Последний шаг — создание экрана счета, который будет отображаться после того, как игрок проиграет. Нам понадобится один дополнительный заголовок и шаблон, который будет отображать финальный счет. Для кнопки повторить игру мы повторно используем шаблон Button_Play
.
Создайте шаблон с текстурой OhNo
. Он не должен иметь логики.
Другой, EndGame_ScoreCounter
, будет текстом вместо спрайта, как и наш другой счет. Измените его на Текст и установите стиль «Оранжевый». Он также будет запоминать и отображать высокий счет игрока. Вставьте этот код в событие Создание:
if (!('JettyCat_HighScore' in localStorage)) {
localStorage['JettyCat_HighScore'] = rooms.current.score;
} else if (localStorage['JettyCat_HighScore'] < rooms.current.score) {
localStorage['JettyCat_HighScore'] = rooms.current.score;
}
var scoreText = 'Your score: ' + rooms.current.score + '\nHighscore: ' + localStorage['JettyCat_HighScore'];
this.text = scoreText;
if !('JettyCat_HighScore' of localStorage)
localStorage['JettyCat_HighScore'] = rooms.current.score
else if localStorage['JettyCat_HighScore'] < rooms.current.score
localStorage['JettyCat_HighScore'] = rooms.current.score
scoreText = 'Your score: ' + rooms.current.score + '\nHighscore: ' + localStorage['JettyCat_HighScore']
style = styles.get 'Orange'
@text = scoreText
localStorage
— это встроенный объект, который позволяет хранить текстовые данные в браузере. Более подробно о нем можно прочитать здесь: здесь.
if (!('JettyCat_HighScore' in localStorage))
проверяет, существует ли свойство JettyCat_HighScore
в объекте localStorage
. Это хороший способ проверить, есть ли какие-либо сохраненные данные. Кстати, это работает с копиями, комнатами и другими объектами.
Если там нет записи по данному ключу, мы устанавливаем значение rooms.current.score
в localStorage['JettyCat_HighScore']
. Если сохраненное значение меньше текущего, игрок побил свой счёт! Мы обновляем лучший результат.
Этот код:
var scoreText = 'Ваш счет: ' + rooms.current.score + '\nЛучший счет: ' + localStorage['JettyCat_HighScore'];
scoreText = 'Ваш счет: ' + rooms.current.score + '\nЛучший счет: ' + localStorage['JettyCat_HighScore']
сохраняет строку в временную переменную. Всё, определённое с помощью var
существует только один кадр и в одном событии. Хотя это не служит какой-либо цели, это позволяет писать более чистый код и повторно использовать временные переменные. Комбинация \n
говорит, что там будет пробел. С помощью оператора +
мы объединяем наши строки с текущим и сохраненным значением. Наконец, мы устанавливаем текст дисплея к созданному переменной значению.
Создайте комнату UI_OhNo
с созданным шаблоном.
Поместите копию EndGame_ScoreCounter
туда, где находится x. Теперь мы можем выровнять текст по центру с помощью «Выравнивать по центру» в инструментах UI. Здесь мы можем выбрать «Выравнивать по центру» в разделе «Выравнивание по»:
Последнее, что нам нужно сделать — создать эту комнату при поражении и удалить слой с кнопкой паузы так, чтобы пауза не была возможна во время игры над экраном поражения. Откройте шаблон PotatoCat
и перейдите к событию Уничтожения. Добавьте этот код сразу после строки camera.follow = false;
:
// Удалить слой с кнопкой паузы
rooms.remove(rooms.current.mainUi);
// Подождать 1000 миллисекунд (за один секунду)
u.wait(1000)
.then(() => {
// Создать новую комнату
this.createRoom('UI_OhNo');
})
# Удалить слой с кнопкой паузы
rooms.remove(rooms.current.mainUi);
# Подождать 1000 миллисекунд (за один секунду)
u.wait(1000)
.then(() => {
# Создать новую комнату
this.createRoom 'UI_OhNo';
})
- Is this room a UI layer?
true
И… Вы сделали это! Игра полностью готова к игре!
Совет
u.wait(1000)
— это асинхронный метод, который ждёт одну секунду, а затем выполняет заданный код в части .then(() => {…})
. "Асинхронный" означает, что код выполняется вне события старта Frame и происходит позже в игре.
Вы всегда найдёте структуру method().then(() => {…})
, работая с асинхронными действиями. В мире JavaScript такие действия также называются "Promise". Однако, если вам не нужно их использовать, вы можете обойтись без части .then(() => {…})
.
Совет
u.wait
— это просто другой способ подождать секунду перед выполнением кода. Вы также можете использовать событие Timer 1 и this.timer1
, чтобы сделать то же самое!
Вот и всё!
Для переходов, эффектов частиц и другой изысканной атрибутики посетите вторая часть этого руководства, где мы отполируем игру.
Попробуйте изменить следующее, чтобы потренироваться в программировании:
- Измените движение кошки, чтобы оно было более похожим на Flappy Bird: сделайте так, чтобы кошка резво поднималась вверх при нажатии на экран игроком, но ничего не делала, если игрок продолжает нажимать.
- Добавьте вращающиеся трубы, чтобы сделать игру сложнее.
- Добавьте счетчик жизней и разрешите игроку получить 3 удара, прежде чем он потеряет все очки.
- Добавьте звуки! Посетите документацию по звукам, чтобы узнать, как воспроизводить их в своей игре.