Обработка нажатий клавиш: пытаемся управлять
Симулятор любого транспорта обязан давать игроку возможность этим транспортом управлять. Для управления можно использовать различные интерфейсы ввода данных, самым распространенным в большинстве железнодорожных симуляторов является клавиатура и мышь.
Обработка событий пользовательского ввода в RRS устроена достаточно мудрено из-за того что визуализация и прием событий с устройств ввода работает в отдельном от симуляции движения поезда процессе. Не вдаваясь глубоко в механику данного процесса могу сказать только, что получение состояния клавиатуры реализована достаточно прозрачно для разработчика.
Для начала переопределим в нашем классе SimpleLoco метод keysProcess()
#ifndef SIMPLE_LOCO_H
#define SIMPLE_LOCO_H
#include "vehicle-api.h"
//-----------------------------------------------------------------------
//
//-----------------------------------------------------------------------
class SimpleLoco : public Vehicle
{
public:
/// Конструктор класса
SimpleLoco(QObject *parent = Q_NULLPTR);
/// Деструктор класса
~SimpleLoco();
private:
/// Обработка нажатия клавиш
void keyProcess();
};
#endif // SIMPLE_LOCO_H
В файле simple-loco.cpp описываем реализацию этого метода
#include "simple-loco.h" //------------------------------------------------------------------------ // //------------------------------------------------------------------------ SimpleLoco::SimpleLoco(QObject *parent) : Vehicle (parent) { } //------------------------------------------------------------------------ // //------------------------------------------------------------------------ SimpleLoco::~SimpleLoco() { } //------------------------------------------------------------------------ // //------------------------------------------------------------------------ void SimpleLoco::keyProcess() { } GET_VEHICLE(SimpleLoco)
Метод keyProcess() вызывается симулятором периодически. Реализовав в нем процесс обработки клавиш, можно добиться реакции дополнение на прикладываемые управляющие сигналы. Вопрос только в том, как отслеживать состояние клавиш.
Для этой цели базовый класс Vehicle предоставляет ряд методов
- bool getKeyState(int key) — возвращает состояние клавиши (нажата или отпущена) по переданному в качестве параметра коду клавиши;
- bool isShift() — возвращает истину, если нажат хотя бы один Shift
- bool isControl() — возвращает истину, если нажат хотя бы один Control;
- bool isAlt() — возвращает истину, если нажат хотя бы один Alt
Коды клавиш задаются с помощью перечислителей, определенных в файле sdk/include/key-symbols.h. Остается только определится с тем, что мы в первую очередь реализуем в нашем учебном проекте.
Отставив в сторону реализм, сделаем простейшую операцию — по нажатию клавиш A и D сделаем увеличение и уменьшение, соотвественно, уровня заданного тягового усилия. Для этого заведем в классе переменную, которая будет хранить этот уровень
private:
/// Заданный уровень тягового усилия
double ref_traction_level;
/// Обработка нажатия клавиш
void keyProcess();
};
Обязательно (!) инициализируем эту переменную в конструкторе класса
SimpleLoco::SimpleLoco(QObject *parent) : Vehicle (parent)
, ref_traction_level(0.0)
{
}
C++ это язык в котором очень легко, что называется, «выстрелить себе в ногу». Если в коде существует переменная, значение которой изначально не задано, это может привести к неопределенному поведению программы. Поэтому возьмем за правило инициализировать все вводимые переменные, особенно локальные, как в данном случае.
Теперь мы можем работать с этой переменной, напишем в методе keyProcess() следующий код
void SimpleLoco::keyProcess() { if (getKeyState(KEY_A)) { ref_traction_level += 0.01; } if (getKeyState(KEY_D)) { ref_traction_level -= 0.01; } ref_traction_level = cut(ref_traction_level, 0.0, 1.0);
}
Все очень просто — по нажатию клавиши A заданный уровень тяги увеличивается, по нажатию клавиши D — уменьшается.
Отдельно следует упомянуть вызов функции cut(). Эта функция описана в модуле physics.dll и является шаблонной. Ее назначение — ограничивать подаваемое на вход значение заданными пределами. Прототип этой функции выглядит так
T cut(T x, T min, Tmax)
функция возвращает x, если он находится в пределах от min до max, возвращает min если x меньше минимального значения, и max — если x больше максимального. Таким образом, в приведенном коде мы «обрезаем» значение уровня заданной тяги в пределах от 0 до 1. Ведь если мы не сделаем этого, то при долгом удержании клавиши A уровень может дорасти и до 10, и до 100 и до 1000, что нас не устраивает по понятным причинам.
После компиляции и запуска локомотива все это будет происходить, но каким образом мы сможем отследить правильную работу кода? Познакомимся с ещё одним важным инструментом разработчика — отладочной строкой. Эта строка записывается в стандартную переменную класса Vehicle, названную DebugMsg. Допишем в функцию-обработчик клавиш еще немного кода
void SimpleLoco::keyProcess()
{
if (getKeyState(KEY_A))
{
ref_traction_level += 0.01;
}
if (getKeyState(KEY_D))
{
ref_traction_level -= 0.01;
}
ref_traction_level = cut(ref_traction_level, 0.0, 1.0);
DebugMsg = QString("Зад. тяга: %1")
.arg(ref_traction_level, 4, 'f', 2);
}
Как мы можем увидеть эту отладочную информацию? Для этого запускаем симулятор, жмем F1 и видим внизу экрана эту самую строку, где отображается уровень заданной тяги
![](http://rusrailsim.org/wp-content/uploads/2019/08/p038-1.png)
Нажимая клавиши мы увидим, что переменная таки действительно изменяется, что не может нас не радовать, ведь мы освоили еще одну базовую возможность API RRS.
Ну так и что, скажете вы, заданная тяга растет, а локомотив не едет. Как добиться того, чтобы он поехал — об этом мы поговорим дальше.