[Java] Flappy bird (или Arkanoid)
Сдача прошлого домашнего задания: сегодня надо сдать мне домашку - пошарив экран чтобы я посмотрел на результат домашней работы.
Цель - реализовать игру Flappy Bird:
Если вы ее уже делали или это слишком просто - делайте Arkanoid (платформой управление мышкой - какая координата мыши по оси X - такое положение у платформы, блоки должны быть хотя бы двух-трех цветов соответствующие разной прочности, хорошо бы чтобы блоки начинали трескаться после первого удара, хорошо бы чтобы были редкие бонус из разбитых блоков - например дающие расширение платформы если его поймать):
Домашнее задание: доделать Flappy Bird (или Arkanoid если вы делаете его). Пожалуйста не стесняйтесь спрашивать меня в телеграмме если на чем-то застряли, вы в т.ч. поможете другим - т.к. если у вас общий вопрос - то я дополню эту страницу и остальным тоже будет проще.
Создаем опять окно с панелью
Создайте пустое окно с вашей простой панелью которая рисует эллипс с какими-то координатами (чтобы просто проверить что в целом база работает). Воспользуйтесь подсказками из прошлого урока.
Делаем заготовки
1) Создайте новый проект FlappyBird и в папку проекта src скачайте картинку игрового персонажа - птицы:
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) Вы восхитительны!