Обработка нажатий клавиш: пытаемся управлять
Симулятор любого транспорта обязан давать игроку возможность этим транспортом управлять. Для управления можно использовать различные интерфейсы ввода данных, самым распространенным в большинстве железнодорожных симуляторов является клавиатура и мышь.
Обработка событий пользовательского ввода в RRS устроена достаточно мудрено из-за того что визуализация и прием событий с устройств ввода работает в отдельном от симуляции движения поезда процессе. Не вдаваясь глубоко в механику данного процесса могу сказать только, что получение состояния клавиатуры реализована достаточно прозрачно для разработчика.
Для начала переопределим в нашем классе SimpleLoco метод keysProcess()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#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 описываем реализацию этого метода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#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 сделаем увеличение и уменьшение, соотвественно, уровня заданного тягового усилия. Для этого заведем в классе переменную, которая будет хранить этот уровень
1 2 3 4 5 6 7 8 |
private: /// Заданный уровень тягового усилия double ref_traction_level; /// Обработка нажатия клавиш void keyProcess(); }; |
Обязательно (!) инициализируем эту переменную в конструкторе класса
1 2 3 4 5 |
SimpleLoco::SimpleLoco(QObject *parent) : Vehicle (parent) , ref_traction_level(0.0) { } |
C++ это язык в котором очень легко, что называется, «выстрелить себе в ногу». Если в коде существует переменная, значение которой изначально не задано, это может привести к неопределенному поведению программы. Поэтому возьмем за правило инициализировать все вводимые переменные, особенно локальные, как в данном случае.
Теперь мы можем работать с этой переменной, напишем в методе keyProcess() следующий код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
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 и является шаблонной. Ее назначение — ограничивать подаваемое на вход значение заданными пределами. Прототип этой функции выглядит так
1 |
T cut(T x, T min, Tmax) |
функция возвращает x, если он находится в пределах от min до max, возвращает min если x меньше минимального значения, и max — если x больше максимального. Таким образом, в приведенном коде мы «обрезаем» значение уровня заданной тяги в пределах от 0 до 1. Ведь если мы не сделаем этого, то при долгом удержании клавиши A уровень может дорасти и до 10, и до 100 и до 1000, что нас не устраивает по понятным причинам.
После компиляции и запуска локомотива все это будет происходить, но каким образом мы сможем отследить правильную работу кода? Познакомимся с ещё одним важным инструментом разработчика — отладочной строкой. Эта строка записывается в стандартную переменную класса Vehicle, названную DebugMsg. Допишем в функцию-обработчик клавиш еще немного кода
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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.
Ну так и что, скажете вы, заданная тяга растет, а локомотив не едет. Как добиться того, чтобы он поехал — об этом мы поговорим дальше.