Пишем Space Invaders при помощи Corona. Реализация геймплея. Часть 2

Итак, мы продолжаем писать игру Space Invaders на языке программирования Lua. Если вы пропустили предыдущие статьи, то мы настоятельно рекомендуем вам прочитать их. Прилагаем полный список всех уроков в этой серии:

  1. Настройка проекта.
  2. Реализация геймплея. Часть 1.
  3. Реализация геймплея. Часть 2.
  4. Заключение.

Игровой уровень

Итак, в прошлых частей серии мы реализовали меню с движущимися звездами и кнопкой startButton, по нажатию на которую начинается игра. Но вы увидите всего лишь черный экран, поскольку игровой уровень еще не написан нами. Давайте как раз займемся этим.

Шаг 1: Локальные переменные

Вставьте нижеприведенный код в файл gamelevel.lua перед ключевым словом return. Это локальные файлы, которые нам понадобятся для самой игры. Названия большинства переменных говорящие, но для тех, смысл которых понятен не сразу, есть комментарии:

Шаг 2: Добавление звезд

В игровой сцене, как и в главном меню, будут пролетать звезды. Чтобы добавить их, пропишите следующий код в gamelevel.lua:

Как и раньше, чтобы заставить звезды двигаться, мы напишем следующий код в методе scene:show:

Мы удаляем предыдущую сцену и добавляем слушателя события enterFrame. Как уже говорилось в прошлой статье, если был добавлен слушатель, его где-то обязательно нужно удалять. Функция scene:hide отлично подходит для этого:

И не забываем добавить слушателей для create, show и hide методов:

Если теперь запустить игру, то, нажав на кнопку startButton, вы увидите движущиеся звезды.

Шаг 3: Добавление игрока

Давайте теперь создадим героя игры и научим его двигаться. Для движения мы будем использовать акселерометр, но специально для симулятора сделаем альтернативный способ управления — кнопками. Добавьте следующий код в gamelevel.lua:

Герой является объектом класса SpriteObject. По сути — это всего лишь картинка, но мы сможем анимировать ее. Для этого воспользуемся двумя изображениями: в одном двигатель включен, в другом — выключен.

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

Таблица options хранит высоту, ширину и количество кадров в анимации. Переменная playerSheet является объектом класса ImageSheet, который в качестве параметров принимает изображение и таблицу options.

Переменная sequenceData требуется для создания анимации. Она хранит:

  • ключ start, который означает, с какого кадра начнется анимация;
  • ключ count, обозначающий количество кадров;
  • ключ time — время между кадрами;
  • ключ loopCount — количество повторений анимации (0 — бесконечное).

Далее мы задаем спрайту название, координаты, вызываем метод play и вставляем его на сцену.

Для того, чтобы отлавливать коллизии, мы прибегнем к помощи встроенного в Corona физического движка, который использует возможности Box2D. Обработка столкновений по умолчанию использует метод ограничительной рамки. Этот метод хорошо работает только для прямоугольных и круглых объектов, но совсем не приемлем для сложных фигур. Посмотрите на изображение ниже, чтобы лучше понять суть проблемы.

Collision1

Обратите внимание, что выстрел не коснулся корабля, а столкновение произошло, что не есть хорошо.

Чтобы преодолеть это ограничение, можно использовать параметры формы — множество координат вершин фигуры. Эти координаты довольно трудоемко получать вручную, но используя программу PhysicsEditor можно сократить время до 7-10 секунд.

Переменная physicsData по сути и есть файл, экспортируемый из программы PhysicsEditor. Мы вызываем метод физического движка addBody для того, чтобы передать границы физического тела. В результате при обработке столкновения будут учитываться фактические границы космического корабля.

Collision2

И в самом конце инициализируем gravityScale нулем, так как не хотим использовать воздействие гравитации. А теперь вызываем метод setupPlayer в scene:create:

И если вы запустите игру, то увидите вот такую картину:

player_thruster

Шаг 4: Движение игрока

Как мы уже говорили, игрок будет управлять кораблем при помощи акселерометра. Добавьте следующий код в gamelevel.lua:

Метод onAccelerate будет вызываться каждый раз, когда значения акселерометра поменяются. Важно знать, что использование акселерометра сильно расходует заряд батареи, а потому — удаляйте слушатель события акселерометра, если он не используется.

Если вы тестируете игру на телефоне, то при наклоне устройства позиция корабля будет меняться. Но на симуляторе нет возможности протестировать работу акселерометра, поэтому добавим кнопки для управления кораблем

Шаг 5: Отладочные кнопки

Добавьте следующий код в gamelevel.lua, чтобы отрисовать отладочные кнопки управления космическим кораблем:

Этот код использует метод newRext объекта Display для рисования прямоугольников, на которые мы вешаем слушателей. По нажатию на эти «кнопки» будет вызываться метод movePlayer.

Стрельба

Шаг 1: Добавление и удаление выстрелов

Когда игрок нажмет на экран своего смартфона, корабль сделает один выстрел. Мы будем ограничивать частоту выстрелов с помощью простого таймера:

Для начала проверяется, может ли игрок стрелять. Если может, то мы создаем выстрел, задаем ему название (это потребуется позже для идентификации объекта) и крепим к нему динамическое физическое тело. В конце мы задаем линейное ускорение при помощи функции setLinearVelocity. Подробнее о свойствах и методах физического объекта вы можете узнать из официальной документации.

Далее мы загружаем и воспроизводим звук выстрела, присваиваем переменной canFireBullet значение false и запускаем таймер, по истечении которого переменная canFireBullet станет равной true.

Теперь осталось добавить слушателей:

И не забудем удалить их:

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

Шаг 2: Удаление выстрелов

Всякий раз, когда создается пуля, она сохраняется в таблице playerBullets. Таким образом мы можем с легкостью ссылаться на каждую пулю и проверять ее свойства. Пробегаясь по всей таблице и проверяя y-координату пули, можно сделать вывод о том, улетела ли она за пределы экрана или нет. И в соответствии с этим, удалять ее или оставлять.

Важно отметить, что мы пробегаем по таблице в обратном порядке. В противном случае можно получить ошибку отсутствия элемента. Также важно: перед удалением объекта требуется присвоить ему значение nil.

Теперь нам нужно сообразить, где лучше всего использовать эту функцию. Проверять пули на выход за пределы экрана нужно всегда, а потому лучшим местом будет игровой цикл — функция, которая вызывается при отрисовке каждого кадра игры.

Шаг 3: Игровой цикл

Добавьте следующий код в gamelevel.lua:

Мы должны вызывать метод gameLoop до тех пор, пока игра запущена. Легче всего это сделать при помощи слушателя события enterFrame:

И, как обычно, не забываем удалять слушателя в методу scene:hide:

Захватчики

Шаг 1: Добавление захватчиков

В первом шаге мы добавим захватчиков на сцену:

В зависимости от того, на каком уровне находится игрок, ряды будут содержать разное количество захватчиков. Переменная rowsOfInvaders отвечает за количество рядов с захватчиками, а invaderNum — за номер текущего уровня (но также используется и в некоторых расчетах).

Мы задаем начальную x-координату захватчиков, затем вычисляем общее их количество и создаем нужное количество врагов. Расчет количества происходит по формуле invaderNum * 2 + 1. При создании захватчиков мы проверяем текущий ряд. Если он последний, то создающийся захватчик вставляется в таблицу invadersWhoCanFire.

Шаг 2: Движение захватчиков

Чтобы заставить захватчиков перемещаться, мы будем использовать функцию gameLoop. Но для начала добавьте следующий код:

Мы в каждой итерации цикла изменяем x-координату всех захватчиков. Причем, если мы вышли за границу экрана (проверяем при помощи leftBounds и rightBounds), то переменной changeDirection присваиваем значение true.

А если же значение переменной changeDirection равно true, то мы умножаем invaderSpeed на -1 и смещаем каждого захватчика вниз.

Таким образом, функция moveInvaders будет вызываться в игровом цикле, т. е. в методе gameLoop:

Обнаружение столкновений

Теперь у нас на сцене есть как герой, так и захватчики. Настало время проверять столкновение каждого из них с выстрелами. Делается это в методе onCollision:

И не забываем удалить его в нужном месте:

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

Вывод

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

Перевод статьи «Create a Space Invaders Game in Corona: Implementing Gameplay»