Сделайте Arkanoid:

Arkanoid

  • Наверху некоторое количество объектов-кирпичей, у каждого свой цвет и прочность (количество ударов которое кирпич выдерживает до того как исчезнет)
  • Внизу движется платформа, ее можно двигать влево и вправо (клавиатурой и/или мышкой)
  • Игровой шарик в начале игры приклеен к платформе, но как только платформа двигается - шарик начинает лететь
  • Шарик летит с константной скоростью
  • Если шарик врезается в кирпич - прочность кирпича уменьшается на один
  • Если шарик врезается в кирпич или стену с края экрана - шарик рикошетит под углом падения относительно встреченной поверхности (так же как отражается луч света от зеркала)
  • Если шарик врезается внизу в платформу управляемую игроком - шарик рикошетит тем левее чем левее он упал на платформу, или тем правее чем правее упал на платформу
  • Если шарик пролетает вниз мимо платформы - конец игры (здорово если вы добавите поддержку жизней)
  • Здорово если вы добавите бонусы иногда выпадающие из кирпичей при их разрушении - расширение платформы, замедление мячика, раздвоение/растроение/раздесятерение мячика

Идеи про обновление игрового мира

Все объекты вроде платформы, шарика и кирпичей храните во вспомогательном объекте класса World.

Все методы обработки коллизий реализуйте как методы этого класса World:

  • void checkBallBricksCollisions() - проверить мяч на врезание в кирпичи, обновить состояние мяча (например направление его скорости) и состояние кирпича (прочность)
  • void checkBallPlatformCollision() - проверить мяч на врезание в платформу игрока и обновить состояние мяча, возможно хорошо еще обновить очки игрока
  • void checkBallWallsCollisions() - проверить мяч на врезание в границы экрана и обновить состояние мяча
  • void checkBallOutOfScreenCollision() - проверить мяч на вылет за пределы экрана мимо платформы игрока

И соответственно в World будет общий метод обновления состояния всего игрового мира за временной промежуток :

    void update(double dt) {
        checkBallBricksCollisions();
        checkBallPlatformCollision();
        checkBallWallsCollisions();
        checkBallOutOfScreenCollision();

        ball.update(dt); // обновляем положение шарика - т.е. двигает его на вектор v*dt, где v - вектор скорости
    }

Как реализовать checkBallBricksCollisions(), checkBallPlatformCollision(), checkBallWallsCollisions()?

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

Т.е. достаточно реализовать методы (в классе World):

boolean isBallInsideRectangle(Ball ball, Rectangle rectangle) {
    // Мяч внутри прямоугольника если его абсцисcа лежит между левой и правой стороной прямоугольника
    // и его ордината лежит между верхней и нижней стороной прямоугольника
    if (ball.x > ... && ...) {
        return true;
    } else {
        return false;
    }
}

void updateBallSpeed(Ball ball, Rectangle rectangle) {
    if (...) { // Если мяч ударился о нижнюю сторону прямоугольника
        ball.v.y = -ball.v.y;
    } else if (...) { // Если мяч ударился о левую сторону прямоугольника
        ball.v.x = -ball.v.x;
    } else if (...) {
        ...
    } else {
    }
}

И тогда например метод проверяющий а не врезался ли мячик в кирпич, а так же обновляющий скорость шарика и уменьшающий прочность кирпича в случае врезания выглядит в классе World примерно так:

void checkBallBricksCollisions() {
    for (int i = 0; i < bricks.size(); ++i) {
        if (isBallInsideRectangle(ball, bricks.get(i).rectangle)) {
            updateBallSpeed(ball, bricks.get(i).rectangle);
            bricks.get(i).hit(); // уменьшить у кирпича прочность
            break;               // эта строчка не обязательна, но если мы решили что в одном обновлении шарик врезается не больше чем в один кирпич - то можно остальные кирпичи не проверять (прервав пробег по циклу)
        }
    }
}

Рекомендуемый порядок разработки

1) Создайте класс Main и создайте в main-функции окно

2) Создайте класс ArkanoidPanel: унаследуйте его от JPanel и переопределите метод:

@Override
protected void paintComponent(Graphics g) {
    // ...
}

3) Создайте класс Ball: с полями хранящими его местоположение и скорость например

4) Создайте класс Brick: с полями хранящими его прямоугольную фигуру (координаты и размеры), прочность и цвет

5) Создайте класс Platform: с полями хранящими ее координаты и ширину

6) Создайте класс World: с полями хранящими мячик, список кирпичей (ArrayList<Brick>) и платформу игрока

7) Реализуйте метод void draw(Graphics g) в Ball, Brick, Platform

8) Реализуйте метод void draw(Graphics g) в World например так:

void draw(Graphics g) {
    ball.draw(g);
    for (int i = 0; i < bricks.size(); ++i) {
        bricks.get(i).draw(g);
    }
    platform.draw(g);
}

9) Убедитесь что ваш изначальный мир (шарик на платформе, сверху какие-то кирпичи) рисуется так как вы ожидаете (не забудьте вызвать отрисовку мира из метода paintComponent в ArkanoidPanel)

10) Объявите методы про проверку врезаний в World (см. Идеи про обновление мира выше), но пока оставьте их пустыми

11) Реализуйте методы void update(double dt) в классах Ball и World (см. Идеи про обновление мира выше)

12) Вызовите world.update(dt) из вечного цикла в main-функции

13) Убедитесь что ваш мир теперь не только рисуется, но шарик даже начал куда-то лететь

14) Реализуйте методы про проверку врезаний в World

15) Убедитесь что шарик начал ломать кирпичи

16) Добавьте обработку клавиатуры и/или мышки для управления платформой

17) Можете добавить бонусов или чего-то подобного