🎯 Цель работы:
Освоить событийно-ориентированный подход в разработке программ на пример создания click-игры
📋 Задание
Разработать игру “Лови шарик”
Минимальные требования
- Шарик движется случайным образом по экрану
- При клике на шарик игрок получает очки
- Шарик “убегает” от курсора мыши
- Управление паузой двойным кликом
🏗️ Событийно-ориентированное программирование
Событие (Event) - это действие пользователя или изменение состояния системы.
Например, Клик мыши, Нажатие клавиши, Изменение размера окна, уведомление от таймера
JavaFX использует событийную модель, в которой
- Источник события (Source) - компонент, генерирующий событие
- Обработчик события (Handler) - код, реагирующий на событие (Event Handler - объект, реализующий интерфейс EventHandler
)
java
// 1. Анонимный класс
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Кнопка нажата");
}
});
// 2. Лямбда-выражение (Java 8+)
button.setOnAction(event -> {
System.out.println("Кнопка нажата");
});
// 3. Ссылка на метод
button.setOnAction(this::handleButtonClick);
- Событие (Event) - объект с информацией о произошедшем действии
MOUSE_CLICKED // Клик (нажатие + отпускание)
MOUSE_PRESSED // Нажатие кнопки мыши
MOUSE_RELEASED // Отпускание кнопки мыши
MOUSE_MOVED // Движение мыши
MOUSE_DRAGGED // Перетаскивание
MOUSE_ENTERED // Курсор вошёл в компонент
MOUSE_EXITED // Курсор вышел из компонента
Архитектура MVVM (Model-View-ViewModel) как автоматизация обработки событий
Модель (Model) — данные и бизнес-логика
Представление (View) — UI, отображение данных
Модель представления (ViewModel) — посредник между Model и View
MVVM дает автоматическое обновление UI через binding, т.е. НЕ нужно вручную (писать код) обновлять каждый элемент.
Этот механизм основан на Привязках (Binding) - объектах, реализующих интерфейс Рroperty.
Как работают привязки
// Создаём зависимость: A зависит от B
A.bind(B);
// Теперь при изменении B автоматически меняется A
B.set(10); // A автоматически становится 10
A.set(5); // ОШИБКА! Нельзя менять связанное свойство
Используйте события для:
- Пользовательских действий (клики, нажатия клавиш)
- Обработки конкретных ситуаций
- Запуска сложных последовательностей действий
Используйте привязки для:
- Отображения данных
- Синхронизации состояния UI
- Автоматического обновления значений
Последовательность разработки
Подзадача 1 Прыгающий шарик и обработка событий
- Создайте новый JavaFX проект, в View создайте панель gamePane
<Pane fx:id="gamePane" prefWidth="800" prefHeight="600">и шарик<Circle fx:id="ball" radius="30" fill="red"/> - В контроллере GameController.java установите шарик в середину панели
public class GameController { @FXML private Pane gamePane; @FXML private Circle ball; public void setGame() { // Шарик в центре панели ball.setCenterX(gamePane.getWidth() / 2); ball.setCenterY(gamePane.getHeight() / 2); } } - Добавьте обработку нажатий на шарик в контроллер:
@FXML private void handleMouseClick(MouseEvent event) { // Проверка попадания double dx = event.getX() - ball.getCenterX(); double dy = event.getY() - ball.getCenterY(); double distance = Math.sqrt(dx * dx + dy * dy); if (distance <= ball.getRadius()) { // При попадании перемещение шарика moveBallToRandomPosition(); } } private void moveBallToRandomPosition() { double newX = ball.getRadius() + random.nextDouble() * (gamePane.getWidth() - 2 * ball.getRadius()); double newY = ball.getRadius() + random.nextDouble() * (gamePane.getHeight() - 2 * ball.getRadius()); ball.setCenterX(newX); ball.setCenterY(newY); } - Добейтесь, чтобы при клике на шарик он перемещался в случайное место
Подзадача 2 Отображаемый счетчик состояний
Добавить счётчик, который увеличивается при попадании и отражается. Заменить обычную переменную-счетчик score на специальный объект IntegerProperty
В контроллер включите:
public class GameController {
// ... другие поля
// Вместо: private int score = 0;
private IntegerProperty score = new SimpleIntegerProperty(0);
public void initialize() {
// ОДИН РАЗ устанавливаем привязку
scoreLabel.textProperty().bind(
javafx.beans.binding.Bindings.concat("Счет: ", score)
);
}
@FXML
private void handleMouseClick(MouseEvent event) {
if (distance <= ball.getRadius()) {
// Просто меняем значение - UI обновится САМ
score.set(score.get() + 1);
moveBallToRandomPosition();
}
}
}
Текстовая метка scoreLabel должна быть добавлена во View для отображения счета(объект Label)
Подзадача 3 Автоматизация движения шарика
Что сделать
-
Использовать таймер для обновления положения шарика и его отображения
-
Привязать шарик к координатам, отделив модель игры от контроллера
Как сделать
Чтобы обновлять картинку, надо знать когда это делать, т.е. нужен объект, который “кликает по шарику” Без таймера шарик стоит на месте. С таймером каждые 0.05 секунды:
- Сдвигаем шарик на некоторое расстояние
Как сделать таймер
Timer/TimerTask
// создали таймер
timer = new Timer("GameTimer", true);
// создали задачу для таймера
TimerTask task = new TimerTask() {
// метод run() вызывается каждые delay мс для перемещния шарика
public void run() {
if(gameActive.get()) {
moveBallRandomly();
}
}
};
// создали расписание запуска задачи
timer.schedule(task, delay, period);
Варианты индивидуальных заданий
- Сделайте так, чтобы шарик “убегал” от курсора мыши. Когда мышь приближается к шарику на расстояние меньше 100 пикселей, шарик должен двигаться в противоположную сторону. Подсказка: Используй setOnMouseMoved и меняй направление скорости.
- При попадании по шарику он должен менять цвет на случайный. Используй Color.rgb() для генерации случайного цвета.
- Каждые 5 попаданий увеличивайте скорость шарика на 10%. Сделай так, чтобы игрок видел текущую скорость на экране.
- Если игрок попадает по шарику несколько раз подряд (без промахов), давай дополнительные очки
- Добавьте таймер на 60 секунд. Игрок должен набрать как можно больше очков за это время. Когда время закончится, покажи надпись “Игра окончена!”.
- При наведении мыши на шарик (MOUSE_ENTERED без нажатия) замедляйте его движение в 2 раза. Когда мышь выходит из компонента - скорость возвращается.
- Сделайте так, чтобы раз в 10 секунд шарик становится полупрозрачным на 3 секунды. Попасть по нему в это время сложнее, но даёт в 2 раза больше очков.
- Добавьте по двойному клику смену траектории движения шарика со случайной на волнообразную и обратно. Формула: y = 300 + 100 * Math.sin(x/50)
- Сделайте так, чтобы при зажатой левой кнопке мыши шарик притягивался к курсору. Отпустил кнопку - шарик оттолкнётся
- Сделай возможность остановить и возобновить игру
Результаты работы программы (Работоспособность)
- Автоматическое движение шарика и обновление счета при попадании по шарику
- Остановка игры при двойном клике на шарик (игра останавливается, но не перезапускается, счет не сбрасывается)
- Отображение счета и статуса состояния игры (активна/неактивна), возможность начать новую игру в UI (изменить игровые параметры).
- Отражение от границ поля (реагирует на изменение поля), не попадает на элементы UI
- Модель игры включает игровую механику (правила движения шарика), правила игры Gameplay (система очков, уровень сложности), состояние игры (Данные и Свойства)
Структура проекта
GameApplication (extends Application)
├── GameModel (данные игры)
│ ├── scoreProperty: IntegerProperty
│ ├── gameActiveProperty: BooleanProperty
│ ├── ballSpeedProperty: IntegerProperty (интервал в мс)
│ └── ballPosition: Point2D.DoubleProperty
│ └── timer: Timer
│ └── gameLogic методы
├── GameView (UI)
│ ├── gamePane: Pane (игровое поле)
│ ├── ball: Circle (Shape компонент)
│ │ ├── fill: RadialGradient (для объема)
│ │ ├── stroke: цвет обводки
│ │ └── centerX/Y: привязка к свойствам модели
│ ├── scoreLabel: Label (привязка к scoreProperty)
│ └── statusLabel: Label (привязка к gameActiveProperty)
└── GameController (логика и обработка)
│ ├── eventHandlers
│ ├── gameModel: GameModel
└──
Чек-лист верификации кода проекта заданной архитектуре MVVM
Model:
- Есть отдельный класс GameModel
- В нём только позиция шарика и логика игры
- Нет упоминаний JavaFX (Circle, Label и т.д.)
- Используются Property (IntegerProperty, DoubleProperty)
View:
- Есть FXML файл
- В нём только описание интерфейса
- Есть обработчики событий (начало игры, клик по шарику, пауза)
Controller:
- Он только связывает Model и View
- Не содержит механики игры, только обработку событий и обновление Model и View шариком
- Использует binding для автоматического обновления статуса и счета
✅ Критерии оценивания
| Критерий | Балл | Описание | |
|---|---|---|---|
| Работоспособность | 1 | см.Результаты работы | |
| Правильность | 1 | Модель игры выделена в отдельный класс и не связана с view, Обработка не менее 2 событий мыши, Связывание данных через Property и Binding, таймер корректно остановлен | |
| Механика движения | 1 | Шарик “убегает”/ имеет траекторию / изменяет внешний вид по событию / индивидуальный вариант | |
| Индивидуальность | 1 | включена дополнительная опция механики, уровня сложности или других правил игры (не UI и клики) | |
| Соблюдение дедлайна | 1 | Сдача вовремя |