Итак, к этому уроку, опираясь на ваши домашние задания, вы уже должны быть готовы чтобы сделать это:
Что тут есть:
- Рельеф, который при движении влево или вправо никогда не кончается; он подобно Земному шару, если идти по экватору – придёшь в ту же точку, откуда вышел.
- Сначала генерирует точка сбора каждого инопланетянина. После его группировки он появляется, а затем через несколько секунд он самовзрывается.
- Движение всех объектов на экране согласовано в относительном движении игрока
- Движение игрока выполнено с учетом физики движение (есть скорость движения, есть учёт ускорения/торможения, есть учет сопротивления воздуха). Это позволяет создавать эффект инерционного движения, когда при смене направления движения корабль игрока не мгновенно начинает лететь в обратную сторону, а сначала замедляется, и лишь потом разгоняется в обратную сторону.
То, что мы в прошлых уроках использовали для тестирования геймплея (игрового процесса), было удобно и эффективно, однако, с новыми реалиями и физикой некоторые моменты нужно будет упростить, изменить и добавить новые. Начнём.
Инициализацию параметров игрока делаем так:
К координатам игрока я добавил переменные, которые будут хранить его скорость и ускорение, а также их предельные значения (у игрока не должна бесконечно возрастать скорость, иначе это будет противоречить канонам физики). Заметьте, предел ускорения по X и Y различны, оно и понятно – двигатели нашего корабля развивают разную мощность по вертикали и горизонтали))). Тут ещё не мешало бы вспомнить школьный курс физики и математики, что
, где:
L – перемещение объекта
L0 – начальная координата
V0 – начальная скорость
a – ускорение
t – время
Однако, увы, эта формула будет для нас не пригодна, т.к. у нас нет показателя времени – t. Связано это как раз с тем, что обновление экрана мы выполняем квантами времени, как помните,- 25 раз в секунду. Т.е. с точки зрения математики мы обладаем не параметром t, а параметром ∂t (дифференциал t). Исходя из этого и перемещение объекта будем определять через совершенное за единицу времени перемещение. Так и поступим. Изменим для начала метод onButtonDown() как и было это в домашнем задании – он теперь будет определять не какие-то тестовые координаты, а именно само ускорение. С точки зрения моделирования это так и есть – как в автомобиле, чем сильнее нажимаем на педаль газа, тем с большим ускорением разгоняемся. Добавим ещё один параметр с радиусом кнопки, чтобы более универсально можно было настраивать параметры размеров интерфейса.
Разумеется при вызовах тоже нужно это учесть:
Сам расчет расстояния перемещения выведен туда, где он более всего логичен – в метод вызываемый 25 раз в секунду:
Сначала мы находим текущую скорость от ускорения. Затем изменение расстояние от этой скорости, тут вроде должно быть понятно. Подчеркиваю, метод вызывается 25 раз в секунду, по этому с точки зрения динамического моделируемого процесса, мы имеем расчет параметров за единицу времени ∂t. Просто она в математике стремится к нулю, а в жизни для игрока уже всё что чаще 24 кадров в секунду уже незаметно и кажется непрерывным движением, и этого оказывается достаточным. С таким ∂t идеально точный математический расчет траектории мы конечно же не получим, но визуально мы разницы не заметим. Далее в коде идут условия, по которым я ограничиваю и текущую скорость и координаты чтобы корабль игрока не вышел за пределы игрового поля или не развил скорость, при которой уже станет не понятным что на экране вообще происходит. После этого я умножаю на числовую константу ускорение и скорость. Это таким образом я создал эффект сопротивления воздуха, т.е. если игрок отпустит кнопку управления, то через какое-то время его корабль сам плавно остановится. Если бы это был космос, где нет сопротивления воздуха, то корабль летел бы сколь угодно долго пока не столкнулся бы с каким-либо объектом, но в нашей игровой реальности, пардон, виртуальности, условия другие.
Но с этим методом ещё не всё, давайте сразу же добавим в него и ещё кое что:
Основное предназначение этой части кода – управление трансформацией инопланетных объектов. Для каждого объекта выполняется сначала метод run(), далее посмотрим что он делает. Затем определяется тип объекта, что это ещё только группирующийся/взрывающийся объект или уже готовый инопланетянин. Тут же определяется, что если группировка уже завершена, то нужно на этом месте создать инопланетянина, а сам группирующийся объект удалить из списка игровых объектов. Если инопланетянин “прожил” некоторый лимит времени, то должен удалиться из списка игровых объектов, но в этот же момент на этом же месте нужно создать взрыв ( метод explosion() ).
Заметьте, я не сразу прямо в цикле объектов gameObjects удаляю “старый” объект из списка и добавляю туда новый, а изначально создал два временных списка, один для удаления, другой для добавления, и делаю работу над ними уже после выполнения основного цикла. Делается это для того, чтобы не получилось исключительной ситуации. Если циклом перебираем список, и сам цикл зависим от этого массива, то менять состав этого списка внутри этого цикла строго нельзя (иначе результат такого функционала может стать не предсказуемым) !
Завершает метод всё тот же invalidate(), который запускает цепочку системных вызовов по обновлению экрана и запуску метода onDraw().
Претерпел изменения класс GameObject (изменил немного формат некоторых методов). Конструктор теперь не содержит начальных координат, т.к. они генерятся в burn() дочернего класса GroupingParts и больше генерить их пока негде; в конструктор добавлен тип:
Дочерние классы:
Заметьте выполнения класса explosion() также как и burn() только координаты при этом не генерятся и лимиты наоборот, а так всё тоже самое.
Инициализация местности немного изменена (понижена), а инициализация объектов теперь использует тип:
Заметьте, инициализируется группирующийся объект, который (как я вначале написал) после схлопывания удаляется и создаётся одновременно новый уже типа Alien. Сгенерировал сразу 100 объектов чтобы оценить будет ли хромать производительность.
Батон, пардон, кнопку увеличил в размерах и перенёс выше и правее, т.к. испытав на своём телефоне с разрешением 1920 х 1080 понял, что кнопка для управления для пальца слишком мала. Также изменил и настройки виртуального мобильника на аналогичные:
В изображение кнопки добавил функционал делающие её слегка прозрачной, это позволяет и видеть что находится за ней и при этом видеть саму кнопку (если делали домашнее задание в одном из уроков то поняли, что я о setAlpha():
Рельеф Земли теперь изображается с учетом того, что она якобы круглая, т.е. летим в одну сторону, вылетаем из другой (циклически повторяется). Для этого пришлось сделать её отображение с учетом этого:
Тут было два очевидных варианта решения, либо оставить всё как есть и адаптировать под существующий функционал, либо математически представить её действительно круглой (ввести полярную систему координат и измерять не координатами по x и y, а углами alpha и beta. Но расчеты в полярной системе потребовали бы вычисления косинусов и синусов в вещественных числах, а это существенно забрало бы вычислительных ресурсов. В связи с этим, если вы ещё раз посмотрите на код объектов, то увидите, что вычисление координат при отрисовке претерпело явные изменения. Для рельефа же потребовалось сделать не только цикл его отрисовки, но и два цикла дорисовки на случай, если мы подлетаем к краю рельефа (слева или справа) – визуально он не должен обрываться, а должен дорисовываться его началом до границ экрана, создавая иллюзию циклической поверхности, вроде экватора планеты. Но делать три цикла подряд тут как-то некрасиво, по этому я сделал цикл в цикле.
По факту получилось даже лучше, чем планировалось:
Задание по уроку:
1. Снова оптимизируйте отрисовку рельефа так, чтобы его куски за пределами экрана не отрисовывались. Аналогично оптимизируйте отрисовку объектов и частей группировки / взрыва.
2. Самостоятельно разберите вопрос рисования текста на холсте.
3. Сделайте чтобы корабли инопланетян случайно двигались тоже с учетом физики
Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.
Урок 15 Урок 17
El Vinto, 2023 (Copyright)