Реализация управления
Let's create another component named InputController that implements keyboard event handling. We'll make it universal to ensure simple extensibility, for example, it will be easy to add processing of input events from joysticks and other devices.Создадим еще один компонент InputController, реализующий обработку событий с клавиатуры. Сделаем его универсальным, чтобы обеспечить простоту расширения, к примеру, можно будет легко добавить обработку событий ввода с джойстиков и других устройств.
Let's define a list of events that can occur and their corresponding keys:Определим список событий, которые могут произойти, и соответствующих им клавиш:
- Hitting the gas (W)Нажатие на газ (клавиша W)
- Brake (S)Тормоз (S)
- Turn (A and D)Поворот (A и D)
- Hand brake (Spacebar)Стояночный тормоз (Пробел)
- Position reset (F5)И сброс позиции (F5)
#pragma once
// include the header file of the Component System
#include <UnigineComponentSystem.h>
#include <UnigineInput.h>
#include <UniginePhysics.h>
#include <UniginePhysics.h>
class InputController : public Unigine::ComponentBase
{
private:
static InputController* instance;
static bool enabled;
public:
// конструктор компонента и список методов
COMPONENT_DEFINE(InputController, ComponentBase);
// -------------------------------
COMPONENT_INIT(init, 0);
COMPONENT_UPDATE(update,0);
// определим список действий: газ, тормоз, поворот, ручноый тормоз и сброс положения
enum INPUT_ACTION_TYPE
{
THROTTLE = 0,
BRAKE,
WHEEL_LEFT,
WHEEL_RIGHT,
HAND_BRAKE,
RESET,
};
static float getAction(InputController::INPUT_ACTION_TYPE action);
static InputController* getInstance();
static bool isEnabled();
// интерфейс состояния единицы ввода
class IInputState
{
public:
virtual ~IInputState() {}
virtual float getState() { return 0; }
};
// реализации интерфейса для зажатой и нажатой клавиши
class InputStateKeyboardPressed : public IInputState {
private:
Unigine::Input::KEY key = Unigine::Input::KEY::KEY_UNKNOWN;
public:
InputStateKeyboardPressed(Unigine::Input::KEY key) { this->key = key; }
float getState() {
return Unigine::Input::isKeyPressed(key) ? 1.0f : 0.0f;
};
};
class InputStateKeyboardDown : public IInputState {
private:
Unigine::Input::KEY key = Unigine::Input::KEY::KEY_UNKNOWN;
public:
InputStateKeyboardDown(Unigine::Input::KEY key) { this->key = key; }
float getState() {
return Unigine::Input::isKeyDown(key) ? 1.0f : 0.0f;
};
};
// абстракция для действия ввода, принимает список состояний при инициализации
// и обновляет собственное состояние, равное 1.0f, если хотя бы одно состояние из списка выполняется
private: class InputAction
{
private:
Unigine::Vector <IInputState*> states;
public:
float state = 0.0f;
InputAction(Unigine::Vector <IInputState*> states) { this->states = states; }
void update()
{
float s = -Unigine::Math::Consts::INF;
for(int i=0; i < states.size();i++)
{
s = Unigine::Math::max(s, states[i]->getState());
}
state = s;
}
};
// определим список действий ввода для клавиш управления
InputAction actions[6] =
{
InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_W) })),
InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_S) })),
InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_A) })),
InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_D) })),
InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardPressed(Unigine::Input::KEY::KEY_SPACE) })),
InputAction(Unigine::Vector<IInputState*>({new InputStateKeyboardDown(Unigine::Input::KEY::KEY_F5) })),
};
protected:
// методы основного цикла
void init();
void update();
};
#include "InputController.h"
REGISTER_COMPONENT(InputController);
InputController* InputController::instance = nullptr;
bool InputController::enabled;
void InputController::init()
{
instance = this;
enabled = true;
}
// обновление состояний каждого действия каждый кадр
void InputController::update()
{
for(InputAction action : actions)
{
action.update();
}
}
InputController* InputController::getInstance()
{
return instance;
};
float InputController::getAction(InputController::INPUT_ACTION_TYPE action)
{
if (!enabled)
return 0.0f;
if (instance == nullptr)
return 0.0f;
instance->actions[(int)action].update();
return instance->actions[(int)action].state;
}
bool InputController::isEnabled()
{
return enabled == true;
}
Save all the files that we modified and then build and run the application by hitting Ctrl + F5 to make the Component System generate a property to be used to assign the components to nodes. Close the application after running it and switch to UnigineEditor.Сохраните все файлы, которые мы изменили, а затем пересоберите и запустите приложения, нажав Ctrl + F5, чтобы Компонентная система сгенерировала свойство (property), которое будет использоваться для связи компонент с объектами. После запуска закройте приложение и переключитесь в UnigineEditor.
Create a new Node Dummy and name it Input. This node will be responsible for processing input events, so assign the InputController component to it.Создайте новую ноду Node Dummy и назовите её Input. Именно эта нода будет отвечать за обработку событий ввода, поэтому назначьте на нее компонент InputController.
Now we need to combine the processing of input events and car control, so create the CarPlayer component inherited from Car:Теперь нам требуется объединить обработку событий ввода и управление автомобилем, поэтому создадим компонент CarPlayer, унаследованный от Car:
#pragma once
#include "Car.h"
#include "InputController.h"
#include <UnigineComponentSystem.h>
class CarPlayer :
public Car
{
public:
// конструктор компонента и список методов
COMPONENT_DEFINE(CarPlayer, Car);
// -------------------------------
COMPONENT_UPDATE(update,2);
protected:
// методы основного цикла
void update();
};
#include "CarPlayer.h"
#include "InputController.h"
#include <UnigineComponentSystem.h>
using namespace Unigine;
REGISTER_COMPONENT(CarPlayer);
void CarPlayer::update()
{
// установка поворота руля
float wheel = 0.0f;
wheel -= InputController::getAction(InputController::INPUT_ACTION_TYPE::WHEEL_RIGHT);
wheel += InputController::getAction(InputController::INPUT_ACTION_TYPE::WHEEL_LEFT);
setWheelPosition(wheel);
// тормоз и газ
float brake = InputController::getAction(InputController::INPUT_ACTION_TYPE::BRAKE);
float throttle = InputController::getAction(InputController::INPUT_ACTION_TYPE::THROTTLE);
// изменение режима движения при смене знака скорости и от состояния тормоза и газа
float velocity = getSpeed() * (getCurrentMoveDirection() == Car::MOVE_DIRECTION::FORWARD ? 1 : -1);
if (velocity > 0.0f)
{
if (velocity < 1.25f && brake > Math::Consts::EPS && throttle < Math::Consts::EPS)
setMoveDirection(Car::MOVE_DIRECTION::REVERSE);
}
else
{
if (velocity > -1.25f && throttle > Math::Consts::EPS && brake < Math::Consts::EPS)
setMoveDirection(Car::MOVE_DIRECTION::FORWARD);
}
// при заднем движении газ и тормоз меняются местами
if (getCurrentMoveDirection() == Car::MOVE_DIRECTION::REVERSE)
{
float t = brake;
brake = throttle;
throttle = t;
}
// установка газа и тормоза
setThrottle(throttle);
setBrake(brake);
// установка стояночного тормоза клавишей и при неактивном контроллере ввода (после завершения игры)
setHandBrake(InputController::isEnabled() ? InputController::getAction(InputController::INPUT_ACTION_TYPE::HAND_BRAKE) : 1.0f);
Car::update();
}
Save all the files that we modified and then build and run the application by hitting Ctrl + F5 to make the Component System generate a property to be used to assign the components to nodes. Close the application after running it and switch to UnigineEditor.Сохраните все файлы, которые мы изменили, а затем пересоберите и запустите приложения, нажав Ctrl + F5, чтобы Компонентная система сгенерировала свойство (property), которое будет использоваться для связи компонент с объектами. После запуска закройте приложение и переключитесь в UnigineEditor.
Assign this component to the pickup_frame node and customize the car parameters as you need, for example use the following values:Полученный компонент назначим на ноду pickup_frame и настроим параметры авто по вкусу, к примеру, с такими значениями:
Make sure to specify the wheel nodes from the wheels group by dragging each node to the corresponding field. Do the same with the nodes for the brake light and reverse light. The Car component is responsible for turning these lights on and off, so all the lights except the daytime running lights (the light node) should be turned off.Не забудьте указать ноды колес из группы wheels, перетаскивая каждую ноду в соответствующий слот. Также поступите с нодами стоп-сигнала и фонаря заднего хода. Компонент Car отвечает за включение и выключение этих фар, поэтому всю светотехнику, кроме дневных ходовых огней (нода light), нужно отключить.
Let's launch the application and enjoy driving.Запустим приложение и насладимся управляемым автомобилем.