Сдача прошлого домашнего задания: сегодня надо сдать мне домашку - пошарив экран чтобы я посмотрел на результат домашней работы.

Цель - реализовать игру Flappy Bird:

Если вы ее уже делали или это слишком просто - делайте Arkanoid (платформой управление мышкой - какая координата мыши по оси X - такое положение у платформы, блоки должны быть хотя бы двух-трех цветов соответствующие разной прочности, хорошо бы чтобы блоки начинали трескаться после первого удара, хорошо бы чтобы были редкие бонус из разбитых блоков - например дающие расширение платформы если его поймать):

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

Создаем опять окно с панелью

Создайте пустое окно с вашей простой панелью которая рисует эллипс с какими-то координатами (чтобы просто проверить что в целом база работает). Воспользуйтесь подсказками из прошлого урока.

Делаем заготовки

1) Создайте новый проект FlappyBird и в папку проекта src скачайте картинку игрового персонажа - птицы:

Flabby bird

2) Создайте окно, добавьте на него ваш класс BirdPanel наследованный от JPanel с переопределенным методом protected void paintComponent(Graphics g) { ... }.

3) В методе paintComponent нарисуйте картинкой игрового персонажа (про картинки - см. прошлый урок).

Замечание Если ничего не рисуется, то проверьте что:

  • вы создали объект BirdPanel через new BirdPanel()
  • вы добавили этот объект в окно через frame.add
  • вы сделали frame.add(birdPanel) ДО frame.setVisible(true)
  • вы добавили while(true) { frame.repaint(); }
  • в BirdPanel в методе paintComponent вызывается рисование картинки

4) Создайте класс Bird описывающий птицу (ее положение в пространстве - высота - вещественное поле - double, ее скорость по вертикальной оси и т.п.) и реализующий метод void draw(Graphics g) который теперь инкапсулирует (т.е. прячет детали реализации того как нарисовать птицу внутри себя) отрисовку птицы. Иначе говоря тот код который рисовал картинку в paintComponent теперь должен быть перенесен в метод Bird.draw(Graphics g). Обратите внимание что поле с картинкой птицы тоже надо перенести в класс Bird (и соответственно его инициализацию - в конструктор птицы).

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

public static final double G = 0.0001; // не забудьте потом подогнать так чтобы птица вела себя правдоподобно

5) Создайте объект этого класса, и вызовите его отрисовку (т.е. метод draw объекта класса Bird) из метода BirdPanel.paintComponent.

6) Давайте теперь создадим класс описывающий наш мир - World. Он будет хранить птицу и препятствия (трубы). Поэтому теперь пусть в нем хранится наша птица, а значит в World есть поле public Bird bird;. Обратите внимание что у вас должен быть ровно один объект птицы на всю программу, в т.ч. это означает что new Bird() должен быть написан ровно один раз во все программе - в конструкторе класса World.

А это в свою очередь означает что чтобы BirdPanel все еще могла сказать птицы “рисуйся” - из панели должен быть доступ к полю “птица” в объекте “мир”. А значит не забудьте сохранить мир в поле панели - public World world;. И тогда при отрисовке панели вам нужно сказать птице внутри мира чтобы она отрисовалась: this.world.bird.draw(g);.

7) Добавьте в класс Bird метод void update(double dt) который будет обновлять положение птицы (с учетом прошедшего времени \(dt\) и ускорения гравитацией - константа G).

8) Добавьте в main-функции цикл, который будет вызывать frame.repaint() и world.update(dt) (который просто вызывает в свою очередь bird.update(dt)), тем самым постоянно обновляя положение птицы (она должна падать) и отрисовывая ее.

9) Добавьте обработчик мышки, который при клике мышкой будет подкидывать птицу вверх (спойлер: для подкидывания нужно просто в момент клика менять значение вертикальной скорости на скорость “лечу вверх”). Про обработку кликов мышки - см. прошлый урок.

Обратите внимание что нужно изменять скорость птицы хранящейся в нашем мире, т.е. world.bird.

10) Убедитесь что птица ведет себя естественно на ваш взгляд при нажатии на пробел. Если нет - задумайтесь и подгоните константы и метод update.

11) Добавьте класс Wall, описывающий стену (или назовите его Pipe если считаете что это труба), которая движется справа налево. Метод wall.update должен обновлять ее местоположение и соответственно так же должен быть вызван из цикла в main-функции. Метод wall.paint должен рисовать ее и соответственно должен быть вызван из paintComponent. Хранить стены мы тоже хотим ввиде поля в World. Начните пока с одной стены, но затем сделайте так чтобы это был массив препятствий.

12) В цикле добавьте создание стены, ее движение и создание новой случайной стены когда текущая стена ушла за пределы экрана налево.

13) Добавьте метод checkCollision который будет проверять, не врезалась ли птица в стену, и если врезалась - печатайте сообщение в консоль “Game over”

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

15) Можете попробовать добавить промежуточное состояние - показывать на несколько секунд черный экран с надписью “Game over” при врезании с последующим перезапуском (или перезапуск только после клика на пробел).

16) Сделайте три стены а не одну. Храните их в массиве размера 3 в World. И просто проверяйте “если какая-то из них вышла за пределы окна - меняем ее на новую находящуюся на правом краю окна”.

17) ???

18) Вы восхитительны!