Java Android. Урок 14 (“Делаем игру Salvador”)

Продолжаем практическое изучение языка программирование Java под Android. Мой курс немного не обычен и ориентирован не на теорию, закреплённую практикой, а на практику подкреплённую теорией. Я когда учил языки программирования сам, именно таким и нашёл самый оптимальным метод изучения. Я пришёл к выводу, что максимальная эффективность в обучении программированию есть лишь тогда, когда есть определённые задачи и вы стараетесь их решить. Вот для этого и вступает в ход изучение теории. Но как только какая-то часть задачи решена, следует переключиться на другие. После того как вы объёмно представляете задачу, она решена до какой-то детализации, и стоит вникать во все аспекты. Т.е. иными словами, использовать в изучении программирования метод дедукции.

Рассмотрим домашнее задание по добавлению инопланетян на экран. Понятное дело, что вариантов исполнения этого функционала тысячи. Небольшое отступление – чем более ответственно вы будете подходить к выполнению этих домашних заданий, чем более тщательно будете разбираться в предлагаемом мной коде, тем прогрессивнее будет ваш результат в программировании. Здесь вас никто проверять не будет и оценку ставить некому, а обмануть учителя не получится – любой обман здесь – это обман самого себя))).

Итак, сравните мой вариант со своим, и попытайтесь понять почему есть отличия, а они будут, ведь вероятность того, что они полностью совпадут стремится к нулю)).

Для инопланетян я первым делом создал специальный класс для хранения данных этого юнита (юнит – персонаж игры). Я предполагаю, что с одним типом юнитов будет скучновато, по этому типов будет несколько.

Отличаться они будут в основном:

  • Визуально выглядеть по разному
  • Разное поведение
  • Разное вооружение

Однако у них будут одинаковые переменные:

  • Координаты
  • Тип юнита
  • Переменная(-ые) типа Paint

Но для начала я сделаю так:

Само хранение множества экземпляров пришельцев, уже наверно догадываетесь, будет так:

Генерация начальных координат инопланетян будет такой:

Их отображение:

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

Вы заметили наверно, что я просто откопипастил этот класс с урока про игру “Быки и коровы” и только поменял название класса. Вся остальная часть почти такая же, кроме старта и остановки процесса. Дело в том, что в нашем классе GameEngine нет наследованных методов onStart() и onStop(), по этому мы должны создать методы для аналогичного функционала, а выполнять их уже из Активити. Добавим в GameEngine ещё два метода:

А теперь просто их вызовите в нужный момент, но уже из класса Активити:

Проверим, таймер работает, кстати вызов я его настроил через каждые 40 миллисекунд. Догадываетесь почему? Правильно, потому что это 1/25 секунды, чтобы обновление динамических сцен шло со скоростью 25 кадров в секунду. Проверим, правильно ли работает таймер:

Да, действительно, таймер отрабатывает корректно, но на разницу во времени в миллисекундах (где-то действительно около 40 мс) особо не обращайте внимания, т.к. ещё достаточно ресурсов отнимает сам процесс отладки и вынесение данных в консоль отладчика. По этому время может немного “гулять” туда-сюда.

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

Это будет родительский класс для всех объектов игры (инопланетяне, частицы группировок и взрывов, ракеты и т.п.). Создавать мы их тоже будем теперь не через ключевое слово “new”, а через статический метод CreateObject().

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

Далее переделаем и сам класс Alien:

Заметьте, у этого класса теперь нет данных координат – ведь он же наследуется от GameObject (в котором и есть эти координаты), а значит наследует и все переменные и методы отмеченные как public или protected (не наследуются только private). Однако, конструктор родительского класса – класса GameObject, принимает два параметра координат:

По этому в дочернем классе мы обязаны вызывать его конструктор с этими параметрами (если бы параметров не было, super можно было бы не вызывать совсем):

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

Однако ему мы сразу же добавим его уникальные свойства (не свойственные GameObject или другим дочерним классам):

Класс Cords, к сожалению, морально устарел – теперь для хранения координат потребуется хранить их не как целое число, а как вещественное:

После этого Андроид Студио попросит принудительно привести эти координаты к целому числу, там где она, видимо, боится сделать это сама)). По этому где в коде начнёт подчёркивать красным эти координаты, явно укажите, мол да, я настаиваю убрать дробную часть и оставить только целую, например вот так:

Т.е. если говорить по феншую, то мы тип double приводим к типу int.

Но с классом GroupingParts ещё далеко не всё, теперь нам нужны методы управления этим объектом, например, метод, который запустит процесс группировки объекта и ещё некоторые вспомогательные:

Метод burn() как раз инициализирует конечную точку сбора частей (cords.x, cords.y), шаг конструкции (dx, dy) и количество шагов сборки (n).

Теперь ещё нужно добавить две заглушки в родительский класс – метод отображения объекта и его и метод его рождения. Дело в том, что даже если дочерний метод не будет реализовывать какой-то из этих методов, их вызов не должен приводит к ошибки (чтобы сделать их вызов универсальным и работающим при любых условиях реализации):

А теперь реализуем этот метод в дочерних объектах, для класса Alien и класса GroupingParts. Для Alien я фактически просто его перенесу код отрисовки из GameEngine:

Давайте, поскольку теперь у нас игровые объекты – это уже не только Alien, то и их ArrayList переименуем, но чтобы не лазить по коду и в везде править, делаем это так. Выделите переменную aliens и при помощи ПКМ вызываем контекстное меню:

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

В конструкторе GameEngine генерацию инопланетян заменим на генерацию объектов. Причём генерировать сначала будем не Alien, а GroupingParts, да и само создание теперь, как говорили ранее, не через “new”, а через CreateObject(). Получается так:

Отображение тоже изменится, а также переименуем drawAliens() на drawObjects():

Добавим функционал тестового рождения объектов:

Отображение GroupingParts будет таким:

Перезапустим приложение и посмотрим что получилось:

Все точки группировки действительно нарисовались, теперь нужно добавить только динамическую сцену. Для этого надо просто вызывать недостоверность изображения каждые 40 мс (25 кадров в секунду). Делается это так:

Перезапустим снова и вот что происходит:

Задание по уроку:

1. Сделайте ещё один Активити, назовите его PreActivity, который будет запускаться в начале игры вместо MainActivity, отображать надпись названия игры на весь экран, а уж по прошествии 5 секунд будет запускать сам игровой процесс (наш MainAcrivity).

2. Сделайте так чтобы после группирования запускался процесс разгруппирования (взрыва), и процесс циклически повторялся вот так:

3. Самостоятельно изучите метод setAlpha() класса Paint. Сделайте так чтобы в конечных стадиях взрыва зеленые точки выглядели более тускло за счет изменения этого параметра Alpha.

Если всё получилось как нужно – молодцы! Если нет, давайте разберёмся что не так.

Урок 13 Урок 15

El Vinto, 2023 (Copyright)

Добавить комментарий