Реализация управления
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)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- идентификатор генерируется автоматически для нового компонента
public class InputController : Component
{
// данный компонент представлен синглтоном для упрощения доступа
private static InputController instance = null;
// определим список типов действий: нажатие на газ, тормоз, поворот, стояночный тормоз и сброс позиции
public enum InputActionType
{
Throttle = 0,
Brake,
WheelLeft,
WheelRight,
HandBrake,
Reset,
}
// интерфейс состояния единицы ввода
private interface IInputState
{
float State { get; }
}
// реализации интерфейса для зажатой и нажатой клавиши
private class InputStateKeyboardPressed : IInputState {
private Input.KEY key = Input.KEY.UNKNOWN;
public InputStateKeyboardPressed(Input.KEY key) { this.key = key; }
float IInputState.State { get { return Input.IsKeyPressed(key) ? 1.0f : 0.0f; } }
}
private class InputStateKeyboardDown : IInputState {
private Input.KEY key = Input.KEY.UNKNOWN;
public InputStateKeyboardDown(Input.KEY key) { this.key = key; }
float IInputState.State { get { return Input.IsKeyDown(key) ? 1.0f : 0.0f; } }
}
// абстракция для действия ввода, принимает список состояний при инициализации
// и обновляет собственное состояние, равное 1.0f, если хотя бы одно состояние из списка выполняется
private class InputAction
{
private IInputState[] states;
private float state = 0.0f;
public InputAction(IInputState[] states) { this.states = states; }
public void Update()
{
float s = float.NegativeInfinity;
foreach(IInputState state in states)
{
s = MathLib.Max(s,state.State);
}
state = s;
}
public float State { get { return state; } }
}
// определим список действий ввода для клавиш управления
private InputAction[] actions =
{
new InputAction(new IInputState[] { new InputStateKeyboardPressed(Input.KEY.W) }),
new InputAction(new IInputState[] { new InputStateKeyboardPressed(Input.KEY.S) }),
new InputAction(new IInputState[] { new InputStateKeyboardPressed(Input.KEY.A) }),
new InputAction(new IInputState[] { new InputStateKeyboardPressed(Input.KEY.D) }),
new InputAction(new IInputState[] { new InputStateKeyboardPressed(Input.KEY.SPACE) }),
new InputAction(new IInputState[] { new InputStateKeyboardDown(Input.KEY.F5) }),
};
// инициализация контроллера ввода будет проведена в первую очередь благодаря явно указанному порядку
[Method(Order=0)]
private void Init()
{
instance = this;
IsEnabled = true;
}
// обновление состояний каждого действия каждый кадр
private void Update()
{
foreach(InputAction action in actions)
{
action.Update();
}
}
public static float GetAction(InputActionType action)
{
if (!IsEnabled)
return 0.0f;
if (instance == null)
return 0.0f;
return instance.actions[(int)action].State;
}
public static bool IsEnabled { get; set; }
}
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:
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;
[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- идентификатор генерируется автоматически для нового компонента
public class CarPlayer : Car
{
protected override void Update()
{
// установка поворота руля
float wheel = 0.0f;
wheel -= InputController.GetAction(InputController.InputActionType.WheelRight);
wheel += InputController.GetAction(InputController.InputActionType.WheelLeft);
SetWheelPosition(wheel);
// тормоз и газ
float brake = InputController.GetAction(InputController.InputActionType.Brake);
float throttle = InputController.GetAction(InputController.InputActionType.Throttle);
// изменение режима движения при смене знака скорости и от состояния тормоза и газа
float velocity = Speed * (CurrentMoveDirection == MoveDirection.Forward ? 1 : -1);
if (velocity > 0.0f)
{
if (velocity < 1.25f && brake > MathLib.EPSILON && throttle < MathLib.EPSILON)
SetMoveDirection(MoveDirection.Reverse);
}
else
{
if (velocity > -1.25f && throttle > MathLib.EPSILON && brake < MathLib.EPSILON)
SetMoveDirection(MoveDirection.Forward);
}
// при заднем движении газ и тормоз меняются местами
if (CurrentMoveDirection == MoveDirection.Reverse)
{
float t = brake;
brake = throttle;
throttle = t;
}
// установка газа и тормоза
SetThrottle(throttle);
SetBrake(brake);
// установка стояночного тормоза клавишей и при неактивном контроллере ввода (после завершения игры)
SetHandBrake(InputController.IsEnabled ? InputController.GetAction(InputController.InputActionType.HandBrake) : 1.0f);
base.Update();
}
}
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.Запустим приложение и насладимся управляемым автомобилем.