1) Создайте пустой проект

2) На базе недавней статьи:

  • Создайте новый класс MyPanel, удалите человечков (поля-объекты Human), удалите из метода отрисовки все кроме super.paintComponent(g);
  • Создайте класс Main с main функцией - в ней создается окно, создается панель, человечков оттуда тоже удалите

3) Посмотрите на оставшийся код, он должен компилироваться и запускаться - пустое окно. Этот код вам должен быть понятен, если есть по нему непонятки что где - спросите.

4) Создайте класс Ball:

  • Целочисленные поля x, y - координаты мячика в данный момент
  • Конструктор который инициализирует эти поля в переданные значения
  • Метод void paint(Graphics g), который отрисовывает мячик по его координатам - и в этом методе нарисуйте мячик

5) В main функции создайте объект класса Ball и передайте его как аргумент в конструктор MyPanel (чтобы панель знала про существование этого мяча и смогла его отрисовать)

6) В MyPanel:

  • Добавьте поле которое будет хранить ссылку на мячик (объект класса Ball, как было с человечками)
  • Добавьте в конструкторе аргумент типа Ball, сохраните этот аргумент в только что созданное поле
  • В методе paintComponent сразу после super.paintComponent(g) мы хотим вызвать у нашего мячика метод “нарисуй себя”

7) Запустите программу и убедитесь что мячик нарисовался

8) Добавьте в мячик метод update(int width, int height) - который будет обновлять положение мячика и какого размера окошко в данный момент (для этого в MyPanel в вызове ball.update(...) нажмите this. начните печатать width и выберите самый подходящий метод - он даст вам ширину панели, то же самое - с height, результаты работы этих методов надо передать в метод обновления положения мячика)

9) В этом методе просто двигайте его строго горизонтально или строго вертикально пока что на \(1\), с проверкой - что если он выйдет за пределы окна - пусть телепортируется в начало

10) В MyPanel в методе отрисовки перед тем как рисовать мячик добавьте вызов обновления его положения (тогда каждый кадр отрисовки будет двигать мяч чуть дальше)

11) Запустите программу и убедитесь что мячик летит как надо и не исчезает за пределами окна, попробуйте изменить размеры окна - правда ли что мяч долетает как раз до нового края

12) Попробуйте теперь замедлить мячик - сдвигать не на \(1\) а на \(0.1\) - запустите программу, что будет с мячиком? Почему так?

13) Подумайте как это исправить? Если ничего не приходит в голову - обратите внимание что координаты мячика целочисленные, какое будет там значение после прибавления \(0.1\) после округления? Т.е. достаточно перейти на double. А при отрисовке (в самый последний момент) преобразовывать число в целое округлением, см. пример: int xInt = (int) (x + 0.12412); - т.е. дописав в круглых скобках тип к которому нужно преобразовать.

14) Сделайте так чтобы мячиков было много - вспомните про динамический список - ArrayList<Ball> (см. статью) - в main-функции его нужно будет создать и наполнить шариками - создайте хотя бы штук 10-100 случайных шариков:

// Это пример как создать динамический список из 10 случайных целых чисел
// С мячиками почти то же самое - только нужно создать два случайных числа - x, y
// И создав очередной случайный мячик - добавить его в список (который будет хранить не Integer, а Ball)
ArrayList<Integer> values = new ArrayList<>();
Random r = new Random(); // этот объект генерирует случайные числа
int n = 10;
for (int i = 0; i < n; i = i + 1) {
    Integer value = r.nextInt(100); // r.nextInt(X) создаст случайное число от 0 (включительно) до X=100 (исключительно, т.е. до 99 включительно)
    values.add(value);
}

15) Храните теперь в панели не один мячик - а этот динамический список (т.е. замените поле с мячика на динамический список, поправьте конструктор, и теперь надо вызывать обновление состояния каждого мячика, и отрисовывать тоже каждый мячик - пробежав по ним в for-цикле)

16) Запустите программу и убедитесь что мячиков теперь много и они все вместе движутся. Укажите диапазон случайных значений такой чтобы мячики заполнили все окно.

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

17) Сделайте мячики двигающимися в разных направлениях - для этого надо создать еще два поля в классе Ball - vx и vy - скорости по обеим осям (кроме обновления конструктора нужно так же обновить метод обновления состояния мячика). Запустите программу.

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

18) Сделайте так чтобы некоторые мячики двигались в т.ч. налево или вверх. Запустите программу.

Подсказка: Как создать отрицательное случайное число от \(-10\) до \(10\)? Давайте создадим число от \(0\) до \(20\) (включительно, поэтому r.nextInt(21)) и вычтем \(10\).

19) Сделайте так чтобы у шариков был случайный радиус и скорость была обратно пропорциональна радиусу (большие - медленные, маленькие - быстрые). Запустите программу.

Пример красоты:

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

21) Сделайте так чтобы при залетании за стенку мячик не телепортировался на другой край - а отражался как бывает по законам физики (угол падения равен углу отражения). Запустите программу.

22) Добавьте в панели рассчет сколько прошло времени с отрисовки предыдущего кадра (достаточно использовать System.currentTimeMillis() и хранить в поле панели время предыдущей отрисовки), передавайте это число в метод update - чтобы мяч двигался на скорость перемноженную с этим прошедшим временем (движение будет плавнее)

23) Режим УльтраХардкор++: сделайте рассчет физики столкновения мячиков (считая их абсолютно упругими телами). Запустите программу.