Как обрабатывать нажатия на клавиатуре

Раньше мы обрабатывали взаимодействия мышкой. Но для управления в играх часто используется и клавиатура - например для игры вроде Flappy Bird было бы удобно прыгать по пробелу.

Раньше клики мышки были привязаны к панели рисования. Но обрабатывать клавиатуру для игры хотелось бы не зависимо от того где находится мышка и т.п.. Иначе говоря - хотелось бы чтобы обработка клавиатуры была глобальной.

Чтобы это сделать, нужно создать объект, который олицетворяет клавиатуру: KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();

А затем добавить к нему обработчик нажатий: manager.addKeyEventDispatcher(new MyDispatcher()), где в скобочках передается объект вашего нового класса, который должен реализовывать интерфейс KeyEventDispatcher (с единственным методом: public boolean dispatchKeyEvent(KeyEvent e)).

Теперь вы сможете в этом методе обработать нажатия кнопок клавиатуры, например:

@Override
public boolean dispatchKeyEvent(KeyEvent e) {
    System.out.println("Новое событие кнопок клавиатуры!");

    String typeOfEvent = "unknown";
    if (e.getID() == KeyEvent.KEY_PRESSED) {
        typeOfEvent = "pressed";
    } else if (e.getID() == KeyEvent.KEY_RELEASED) {
        typeOfEvent = "released";
    } else if (e.getID() == KeyEvent.KEY_TYPED) {
        typeOfEvent = "typed";
    }

    String key = "unknown";
    if (e.getKeyCode() == KeyEvent.VK_SPACE) {
        key = "space";
    } else if (e.getKeyCode() == KeyEvent.VK_ENTER) {
        key = "enter";
    } else {
        key = "code#" + e.getKeyCode();
    }

    System.out.println("type=" + typeOfEvent + " keyCode=" + key);
    return false;
}

Задание: добавьте в вашу Flappy Bird дополнительное управление - пусть птица машет крыльями и при нажатии на мышку, и при нажатии на пробел. Можете как создать новый класс MyDispatcher, так и можете добавить эту функциональность в вашу панель - достаточно сказать что она implements KeyEventDispatcher (а если она уже implements MouseListener, то эти два интерфейса нужно перечислить через запятую).

Как поворачивать картинку

Такого рода вещи легко найти в интернете, например по запросу “java swing как поворачивать картинку” (напоминаю, что Swing - название пакета который мы используем для создания графического интерфейса).

Но гораздо лучше и всмысле эффективности и всмысле пользы для вас в будущем - привыкать гуглить на английском - “java swing how to rotate image”.

Окажется, что достаточно:

1) Загрузить на диск из интернета какую-нибудь картинку, например эту.

2) Добавить поле BufferedImage unicornImage; в класс MyPanel.

3) Инициализировать его в конструкторе MyPanel, загрузив картинку с диска, например так: this.unicornImage = ImageIO.read(new File("C:\\downloads\\unicorn.png"));

4) Не забыть сделать все импорты всех новых классов через Alt+Enter.

5) Т.к. загрузка картинки может кинуть ошибку (например если файл не будет найден), поэтому вы будете видеть ошибку Unhandled exception: java.io.IOException - поправьте ее кинув ошибку выше через Alt+Enter (Add exception to method signature) - выше появится throws IOException - это означает что теперь текущий метод (или конструктор) может кинуть ошибку связанную с IO - т.е. связанную с Input Output, т.е. связанную с чтением с диска.

6) Теперь надо нарисовать загруженную картинку - достаточно в отрисовке MyPanel (в методе paintComponent) добавить например g.drawImage(this.unicornImage, centerX, centerY, null);.

7) И наконец чтобы рисовать картинку повернутой на некоторый угол:

double angleInDegrees = 45.0; // Угол поворота в градусах
double angleInRadians = Math.toRadians(angleInDegrees);
double locationX = unicornImage.getWidth() / 2;
double locationY = unicornImage.getHeight() / 2;
AffineTransform tx = AffineTransform.getRotateInstance(angleInRadians, locationX, locationY);
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);

g.drawImage(op.filter(unicornImage, null), 100, 100, null);

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

Чтобы такого не случалось - картинка должна быть с достаточным запасом пустых пикселей вокруг объекта изображенного в центре. Грубо говоря объект не должен выходить за пределы окружности вписанной в картинку, чтобы свободно при повороте картинки оставаться в пределах этой окружности - а значит не вылезать за границы картинки и не быть обрезанным по краю.

Задание: попробуйте добавить в вашу игру с летающей птицей следующий спецэффект - пусть она будет наклонена пропорционально текущей скорости:

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

Как обрабатывать жесты мышки

Чтобы обрабатывать движения мышки (например полезно в игре где можно было бы перетаскивать шарики) - достаточно загуглить “java how to handle mouse motion”.

Окажется достаточно подобно тому как мы делали addMouseListener сделать addMouseMotionListener, и теперь нам нужно реализовать другой интерфейс не MouseListener - а как вы уже могли догадаться - MouseMotionListener.

Задание: можете попробовать в Flappy Bird заменить управление по клику мышки на управление по жесту - пусть птица взмывает вверх только если пользователь зажал левую кнопку мышки и расчертил в окошке что-то вроде жеста “взмах вверх”. Можете даже сделать скорость птицы пропорционально тому насколько сильный игрок сделал взмах.