// Copyright (c) Wojciech Figat. All rights reserved. #include "Input.h" #include "InputSettings.h" #include "Keyboard.h" #include "Mouse.h" #include "Gamepad.h" #include "Engine/Platform/Window.h" #include "Engine/Engine/Engine.h" #include "Engine/Engine/EngineService.h" #include "Engine/Engine/Screen.h" #include "Engine/Engine/Time.h" #include "Engine/Platform/WindowsManager.h" #include "Engine/Scripting/ScriptingType.h" #include "Engine/Scripting/BinaryModule.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Serialization/JsonTools.h" struct AxisEvaluation { float RawValue; float Value; float PrevKeyValue; bool Used; }; struct ActionData { bool Active = false; uint64 FrameIndex = 0; InputActionState State = InputActionState::Waiting; }; struct AxisData { float Value = 0.0f; float ValueRaw = 0.0f; float PrevValue = 0.0f; float PrevKeyValue = 0.0f; uint64 FrameIndex = 0; }; namespace InputImpl { Dictionary Actions; Dictionary Axes; bool GamepadsChanged = true; Array AxesValues; InputDevice::EventQueue InputEvents; } using namespace InputImpl; class InputService : public EngineService { public: InputService() : EngineService(TEXT("Input"), -60) { } void Update() override; void Dispose() override; }; InputService InputServiceInstance; Mouse* Input::Mouse = nullptr; Keyboard* Input::Keyboard = nullptr; Array> Input::Gamepads; Action Input::GamepadsChanged; Array> Input::CustomDevices; Delegate Input::CharInput; Delegate Input::KeyDown; Delegate Input::KeyUp; Delegate Input::MouseDown; Delegate Input::MouseUp; Delegate Input::MouseDoubleClick; Delegate Input::MouseWheel; Delegate Input::MouseMove; Delegate Input::MouseMoveRelative; Action Input::MouseLeave; Delegate Input::TouchDown; Delegate Input::TouchMove; Delegate Input::TouchUp; Delegate Input::ActionTriggered; Delegate Input::AxisValueChanged; Array Input::ActionMappings; Array Input::AxisMappings; void InputSettings::Apply() { Input::ActionMappings = ActionMappings; Input::AxisMappings = AxisMappings; } void InputSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { const auto actionMappings = stream.FindMember("ActionMappings"); if (actionMappings != stream.MemberEnd()) { auto& actionMappingsArray = actionMappings->value; if (actionMappingsArray.IsArray()) { ActionMappings.Resize(actionMappingsArray.Size(), false); for (uint32 i = 0; i < actionMappingsArray.Size(); i++) { auto& v = actionMappingsArray[i]; if (!v.IsObject()) continue; ActionConfig& config = ActionMappings[i]; config.Name = JsonTools::GetString(v, "Name"); config.Mode = JsonTools::GetEnum(v, "Mode", InputActionMode::Pressing); config.Key = JsonTools::GetEnum(v, "Key", KeyboardKeys::None); config.MouseButton = JsonTools::GetEnum(v, "MouseButton", MouseButton::None); config.GamepadButton = JsonTools::GetEnum(v, "GamepadButton", GamepadButton::None); config.Gamepad = JsonTools::GetEnum(v, "Gamepad", InputGamepadIndex::All); } } else { ActionMappings.Resize(0, false); } } const auto axisMappings = stream.FindMember("AxisMappings"); if (axisMappings != stream.MemberEnd()) { auto& axisMappingsArray = axisMappings->value; if (axisMappingsArray.IsArray()) { AxisMappings.Resize(axisMappingsArray.Size(), false); for (uint32 i = 0; i < axisMappingsArray.Size(); i++) { auto& v = axisMappingsArray[i]; if (!v.IsObject()) continue; AxisConfig& config = AxisMappings[i]; config.Name = JsonTools::GetString(v, "Name"); config.Axis = JsonTools::GetEnum(v, "Axis", InputAxisType::MouseX); config.Gamepad = JsonTools::GetEnum(v, "Gamepad", InputGamepadIndex::All); config.PositiveButton = JsonTools::GetEnum(v, "PositiveButton", KeyboardKeys::None); config.NegativeButton = JsonTools::GetEnum(v, "NegativeButton", KeyboardKeys::None); config.GamepadPositiveButton = JsonTools::GetEnum(v, "GamepadPositiveButton", GamepadButton::None); config.GamepadNegativeButton = JsonTools::GetEnum(v, "GamepadNegativeButton", GamepadButton::None); config.DeadZone = JsonTools::GetFloat(v, "DeadZone", 0.1f); config.Sensitivity = JsonTools::GetFloat(v, "Sensitivity", 0.4f); config.Gravity = JsonTools::GetFloat(v, "Gravity", 1.0f); config.Scale = JsonTools::GetFloat(v, "Scale", 1.0f); config.Snap = JsonTools::GetBool(v, "Snap", false); } } else { AxisMappings.Resize(0, false); } } } void Mouse::OnMouseMoved(const Float2& newPosition) { _prevState.MousePosition = newPosition; _state.MousePosition = newPosition; } void Mouse::OnMouseDown(const Float2& position, const MouseButton button, Window* target) { Event& e = _queue.AddOne(); e.Type = EventType::MouseDown; e.Target = target; e.MouseData.Button = button; e.MouseData.Position = position; } bool Mouse::IsAnyButtonDown() const { // TODO: optimize with SIMD bool result = false; for (auto e : Mouse::_state.MouseButtons) result |= e; return result; } void Mouse::OnMouseUp(const Float2& position, const MouseButton button, Window* target) { Event& e = _queue.AddOne(); e.Type = EventType::MouseUp; e.Target = target; e.MouseData.Button = button; e.MouseData.Position = position; } void Mouse::OnMouseDoubleClick(const Float2& position, const MouseButton button, Window* target) { Event& e = _queue.AddOne(); e.Type = EventType::MouseDoubleClick; e.Target = target; e.MouseData.Button = button; e.MouseData.Position = position; } void Mouse::OnMouseMove(const Float2& position, Window* target) { Event& e = _queue.AddOne(); e.Type = EventType::MouseMove; e.Target = target; e.MouseData.Position = position; } void Mouse::OnMouseMoveRelative(const Float2& positionRelative, Window* target) { Event& e = _queue.AddOne(); e.Type = EventType::MouseMoveRelative; e.Target = target; e.MouseMovementData.PositionRelative = positionRelative; } void Mouse::OnMouseLeave(Window* target) { Event& e = _queue.AddOne(); e.Type = EventType::MouseLeave; e.Target = target; } void Mouse::OnMouseWheel(const Float2& position, float delta, Window* target) { Event& e = _queue.AddOne(); e.Type = EventType::MouseWheel; e.Target = target; e.MouseWheelData.WheelDelta = delta; e.MouseWheelData.Position = position; } void Mouse::ResetState() { InputDevice::ResetState(); _prevState.Clear(); _state.Clear(); } bool Mouse::Update(EventQueue& queue) { // Move the current state to the previous Platform::MemoryCopy(&_prevState, &_state, sizeof(State)); // Gather new events if (UpdateState()) return true; // Handle events _state.MouseWheelDelta = 0; for (int32 i = 0; i < _queue.Count(); i++) { const Event& e = _queue[i]; switch (e.Type) { case EventType::MouseDown: { _state.MouseButtons[static_cast(e.MouseData.Button)] = true; break; } case EventType::MouseUp: { _state.MouseButtons[static_cast(e.MouseData.Button)] = false; break; } case EventType::MouseDoubleClick: { _state.MouseButtons[static_cast(e.MouseData.Button)] = true; break; } case EventType::MouseWheel: { _state.MouseWheelDelta += e.MouseWheelData.WheelDelta; break; } case EventType::MouseMove: { _state.MousePosition = e.MouseData.Position; break; } case EventType::MouseMoveRelative: { _state.MousePosition += e.MouseMovementData.PositionRelative; break; } case EventType::MouseLeave: { break; } } } // Send events further queue.Add(_queue); _queue.Clear(); return false; } void Keyboard::State::Clear() { Platform::MemoryClear(this, sizeof(State)); } Keyboard::Keyboard() : InputDevice(SpawnParams(Guid::New(), TypeInitializer), TEXT("Keyboard")) { _state.Clear(); _prevState.Clear(); } bool Keyboard::IsAnyKeyDown() const { // TODO: optimize with SIMD bool result = false; for (auto e : _state.Keys) result |= e; return result; } void Keyboard::OnCharInput(Char c, Window* target) { // Skip control characters if (c < 32) return; Event& e = _queue.AddOne(); e.Type = EventType::Char; e.Target = target; e.CharData.Char = c; } void Keyboard::OnKeyUp(KeyboardKeys key, Window* target) { if (key >= KeyboardKeys::MAX) return; Event& e = _queue.AddOne(); e.Type = EventType::KeyUp; e.Target = target; e.KeyData.Key = key; } void Keyboard::OnKeyDown(KeyboardKeys key, Window* target) { if (key >= KeyboardKeys::MAX) return; Event& e = _queue.AddOne(); e.Type = EventType::KeyDown; e.Target = target; e.KeyData.Key = key; } void Keyboard::ResetState() { InputDevice::ResetState(); _prevState.Clear(); _state.Clear(); } bool Keyboard::Update(EventQueue& queue) { // Move the current state to the previous Platform::MemoryCopy(&_prevState, &_state, sizeof(State)); // Gather new events if (UpdateState()) return true; // Handle events _state.InputTextLength = 0; for (int32 i = 0; i < _queue.Count(); i++) { const Event& e = _queue[i]; switch (e.Type) { case EventType::Char: { if (_state.InputTextLength < ARRAY_COUNT(_state.InputText) - 1) _state.InputText[_state.InputTextLength++] = e.CharData.Char; break; } case EventType::KeyDown: { _state.Keys[static_cast(e.KeyData.Key)] = true; break; } case EventType::KeyUp: { _state.Keys[static_cast(e.KeyData.Key)] = false; break; } } } // Send events further queue.Add(_queue); _queue.Clear(); return false; } int32 Input::GetGamepadsCount() { return Gamepads.Count(); } Gamepad* Input::GetGamepad(int32 index) { if (index >= 0 && index < Gamepads.Count()) return Gamepads[index]; return nullptr; } void Input::OnGamepadsChanged() { ::GamepadsChanged = true; } StringView Input::GetInputText() { return Keyboard ? Keyboard->GetInputText() : StringView::Empty; } bool Input::GetKey(const KeyboardKeys key) { return Keyboard ? Keyboard->GetKey(key) : false; } bool Input::GetKeyDown(const KeyboardKeys key) { return Keyboard ? Keyboard->GetKeyDown(key) : false; } bool Input::GetKeyUp(const KeyboardKeys key) { return Keyboard ? Keyboard->GetKeyUp(key) : false; } Float2 Input::GetMousePosition() { return Mouse ? Screen::ScreenToGameViewport(Mouse->GetPosition()) : Float2::Minimum; } void Input::SetMousePosition(const Float2& position) { if (Mouse && Engine::HasGameViewportFocus()) { const auto pos = Screen::GameViewportToScreen(position); if (pos > Float2::Minimum) Mouse->SetMousePosition(pos); } } Float2 Input::GetMouseScreenPosition() { return Mouse ? Mouse->GetPosition() : Float2::Minimum; } void Input::SetMouseScreenPosition(const Float2& position) { if (Mouse && Engine::HasFocus) { Mouse->SetMousePosition(position); } } Float2 Input::GetMousePositionDelta() { return Mouse ? Mouse->GetPositionDelta() : Float2::Zero; } float Input::GetMouseScrollDelta() { return Mouse ? Mouse->GetScrollDelta() : 0.0f; } bool Input::GetMouseButton(const MouseButton button) { return Mouse ? Mouse->GetButton(button) : false; } bool Input::GetMouseButtonDown(const MouseButton button) { return Mouse ? Mouse->GetButtonDown(button) : false; } bool Input::GetMouseButtonUp(const MouseButton button) { return Mouse ? Mouse->GetButtonUp(button) : false; } float Input::GetGamepadAxis(int32 gamepadIndex, GamepadAxis axis) { if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count()) return Gamepads[gamepadIndex]->GetAxis(axis); return 0.0f; } bool Input::GetGamepadButton(int32 gamepadIndex, GamepadButton button) { if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count()) return Gamepads[gamepadIndex]->GetButton(button); return false; } bool Input::GetGamepadButtonDown(int32 gamepadIndex, GamepadButton button) { if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count()) return Gamepads[gamepadIndex]->GetButtonDown(button); return false; } bool Input::GetGamepadButtonUp(int32 gamepadIndex, GamepadButton button) { if (gamepadIndex >= 0 && gamepadIndex < Gamepads.Count()) return Gamepads[gamepadIndex]->GetButtonUp(button); return false; } float Input::GetGamepadAxis(InputGamepadIndex gamepad, GamepadAxis axis) { if (gamepad == InputGamepadIndex::All) { float result = 0.0f; for (auto g : Gamepads) { float v = g->GetAxis(axis); if (Math::Abs(v) > Math::Abs(result)) result = v; } return result; } else { const auto index = static_cast(gamepad); if (index < Gamepads.Count()) return Gamepads[index]->GetAxis(axis); } return false; } bool Input::GetGamepadButton(InputGamepadIndex gamepad, GamepadButton button) { if (gamepad == InputGamepadIndex::All) { for (auto g : Gamepads) { if (g->GetButton(button)) return true; } } else { const auto index = static_cast(gamepad); if (index < Gamepads.Count()) return Gamepads[index]->GetButton(button); } return false; } bool Input::GetGamepadButtonDown(InputGamepadIndex gamepad, GamepadButton button) { if (gamepad == InputGamepadIndex::All) { for (auto g : Gamepads) { if (g->GetButtonDown(button)) return true; } } else { const auto index = static_cast(gamepad); if (index < Gamepads.Count()) return Gamepads[index]->GetButtonDown(button); } return false; } bool Input::GetGamepadButtonUp(InputGamepadIndex gamepad, GamepadButton button) { if (gamepad == InputGamepadIndex::All) { for (auto g : Gamepads) { if (g->GetButtonUp(button)) return true; } } else { const auto index = static_cast(gamepad); if (index < Gamepads.Count()) return Gamepads[index]->GetButtonUp(button); } return false; } bool Input::GetAction(const StringView& name) { const auto e = Actions.TryGet(name); return e ? e->Active : false; } InputActionState Input::GetActionState(const StringView& name) { const auto e = Actions.TryGet(name); if (e != nullptr) { return e->State; } return InputActionState::None; } float Input::GetAxis(const StringView& name) { const auto e = Axes.TryGet(name); return e ? e->Value : false; } float Input::GetAxisRaw(const StringView& name) { const auto e = Axes.TryGet(name); return e ? e->ValueRaw : false; } void Input::SetInputMappingFromSettings(const JsonAssetReference& settings) { auto actionMappings = settings.GetInstance()->ActionMappings; ActionMappings.Resize(actionMappings.Count(), false); for (int i = 0; i < actionMappings.Count(); i++) { ActionMappings[i] = actionMappings.At(i); } auto axisMappings = settings.GetInstance()->AxisMappings; AxisMappings.Resize(axisMappings.Count(), false); for (int i = 0; i < axisMappings.Count(); i++) { AxisMappings[i] = axisMappings.At(i); } Axes.Clear(); Actions.Clear(); } void Input::SetInputMappingToDefaultSettings() { InputSettings* settings = InputSettings::Get(); if (settings) { ActionMappings = settings->ActionMappings; AxisMappings = settings->AxisMappings; Axes.Clear(); Actions.Clear(); } } ActionConfig Input::GetActionConfigByName(const StringView& name) { ActionConfig config = {}; for (const auto& a : ActionMappings) { if (a.Name == name) { config = a; return config; } } return config; } Array Input::GetAllActionConfigsByName(const StringView& name) { Array actionConfigs; for (const auto& a : ActionMappings) { if (a.Name == name) actionConfigs.Add(a); } return actionConfigs; } AxisConfig Input::GetAxisConfigByName(const StringView& name) { AxisConfig config = {}; for (const auto& a : AxisMappings) { if (a.Name == name) { config = a; return config; } } return config; } Array Input::GetAllAxisConfigsByName(const StringView& name) { Array actionConfigs; for (const auto& a : AxisMappings) { if (a.Name == name) actionConfigs.Add(a); } return actionConfigs; } void Input::SetAxisConfigByName(const StringView& name, AxisConfig& config, bool all) { for (int i = 0; i < AxisMappings.Count(); ++i) { auto& mapping = AxisMappings.At(i); if (mapping.Name.Compare(name.ToString()) == 0) { if (config.Name.IsEmpty()) config.Name = name; mapping = config; if (!all) break; } } } void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const KeyboardKeys positiveButton, const KeyboardKeys negativeButton, bool all) { for (int i = 0; i < AxisMappings.Count(); ++i) { auto& mapping = AxisMappings.At(i); if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Axis == inputType) { mapping.PositiveButton = positiveButton; mapping.NegativeButton = negativeButton; if (!all) break; } } } void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const GamepadButton positiveButton, const GamepadButton negativeButton, InputGamepadIndex gamepadIndex, bool all) { for (int i = 0; i < AxisMappings.Count(); ++i) { auto& mapping = AxisMappings.At(i); if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Gamepad == gamepadIndex && mapping.Axis == inputType) { mapping.GamepadPositiveButton = positiveButton; mapping.GamepadNegativeButton = negativeButton; if (!all) break; } } } void Input::SetAxisConfigByName(const StringView& name, InputAxisType inputType, const float gravity, const float deadZone, const float sensitivity, const float scale, const bool snap, bool all) { for (int i = 0; i < AxisMappings.Count(); ++i) { auto& mapping = AxisMappings.At(i); if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Axis == inputType) { mapping.Gravity = gravity; mapping.DeadZone = deadZone; mapping.Sensitivity = sensitivity; mapping.Scale = scale; mapping.Snap = snap; if (!all) break; } } } void Input::SetActionConfigByName(const StringView& name, const KeyboardKeys key, bool all) { for (int i = 0; i < ActionMappings.Count(); ++i) { auto& mapping = ActionMappings.At(i); if (mapping.Name.Compare(name.ToString()) == 0) { mapping.Key = key; if (!all) break; } } } void Input::SetActionConfigByName(const StringView& name, const MouseButton mouseButton, bool all) { for (int i = 0; i < ActionMappings.Count(); ++i) { auto& mapping = ActionMappings.At(i); if (mapping.Name.Compare(name.ToString()) == 0) { mapping.MouseButton = mouseButton; if (!all) break; } } } void Input::SetActionConfigByName(const StringView& name, const GamepadButton gamepadButton, InputGamepadIndex gamepadIndex, bool all) { for (int i = 0; i < ActionMappings.Count(); ++i) { auto& mapping = ActionMappings.At(i); if (mapping.Name.Compare(name.ToString()) == 0 && mapping.Gamepad == gamepadIndex) { mapping.GamepadButton = gamepadButton; if (!all) break; } } } void Input::SetActionConfigByName(const StringView& name, ActionConfig& config, bool all) { for (int i = 0; i < ActionMappings.Count(); ++i) { auto& mapping = ActionMappings.At(i); if (mapping.Name.Compare(name.ToString()) == 0) { if (config.Name.IsEmpty()) config.Name = name; mapping = config; if (!all) break; } } } void InputService::Update() { PROFILE_CPU(); const auto frame = Time::Update.TicksCount; const auto dt = Time::Update.UnscaledDeltaTime.GetTotalSeconds(); InputEvents.Clear(); // If application has no user focus then simply clear the state if (!Engine::HasFocus) { if (Input::Mouse) Input::Mouse->ResetState(); if (Input::Keyboard) Input::Keyboard->ResetState(); for (int32 i = 0; i < Input::Gamepads.Count(); i++) Input::Gamepads[i]->ResetState(); Axes.Clear(); Actions.Clear(); return; } // Update input devices state if (Input::Mouse) { if (Input::Mouse->Update(InputEvents)) { Input::Mouse->DeleteObject(); Input::Mouse = nullptr; } } if (Input::Keyboard) { if (Input::Keyboard->Update(InputEvents)) { Input::Keyboard->DeleteObject(); Input::Keyboard = nullptr; } } for (int32 i = 0; i < Input::Gamepads.Count(); i++) { if (Input::Gamepads[i]->Update(InputEvents)) { Input::Gamepads[i]->DeleteObject(); Input::Gamepads.RemoveAtKeepOrder(i); Input::OnGamepadsChanged(); i--; if (Input::Gamepads.IsEmpty()) break; } } for (int32 i = 0; i < Input::CustomDevices.Count(); i++) { if (Input::CustomDevices[i]->Update(InputEvents)) { Input::CustomDevices[i]->DeleteObject(); Input::CustomDevices.RemoveAtKeepOrder(i); i--; if (Input::CustomDevices.IsEmpty()) break; } } // Send gamepads change events if (GamepadsChanged) { GamepadsChanged = false; Input::GamepadsChanged(); } // Pick the first focused window for input events WindowsManager::WindowsLocker.Lock(); Window* defaultWindow = nullptr; for (auto window : WindowsManager::Windows) { if (window->IsFocused() && window->GetSettings().AllowInput) { defaultWindow = window; break; } } WindowsManager::WindowsLocker.Unlock(); // Send input events for the focused window for (const auto& e : InputEvents) { auto window = e.Target ? e.Target : defaultWindow; if (!window || window->IsClosed()) continue; switch (e.Type) { // Keyboard events case InputDevice::EventType::Char: window->OnCharInput(e.CharData.Char); break; case InputDevice::EventType::KeyDown: window->OnKeyDown(e.KeyData.Key); break; case InputDevice::EventType::KeyUp: window->OnKeyUp(e.KeyData.Key); break; // Mouse events case InputDevice::EventType::MouseDown: window->OnMouseDown(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button); break; case InputDevice::EventType::MouseUp: window->OnMouseUp(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button); break; case InputDevice::EventType::MouseDoubleClick: window->OnMouseDoubleClick(window->ScreenToClient(e.MouseData.Position), e.MouseData.Button); break; case InputDevice::EventType::MouseWheel: window->OnMouseWheel(window->ScreenToClient(e.MouseWheelData.Position), e.MouseWheelData.WheelDelta); break; case InputDevice::EventType::MouseMove: window->OnMouseMove(window->ScreenToClient(e.MouseData.Position)); break; case InputDevice::EventType::MouseMoveRelative: window->OnMouseMoveRelative(e.MouseMovementData.PositionRelative); break; case InputDevice::EventType::MouseLeave: window->OnMouseLeave(); break; // Touch events case InputDevice::EventType::TouchDown: window->OnTouchDown(window->ScreenToClient(e.TouchData.Position), e.TouchData.PointerId); break; case InputDevice::EventType::TouchMove: window->OnTouchMove(window->ScreenToClient(e.TouchData.Position), e.TouchData.PointerId); break; case InputDevice::EventType::TouchUp: window->OnTouchUp(window->ScreenToClient(e.TouchData.Position), e.TouchData.PointerId); break; } } // Skip if game has no focus to handle the input if (!Engine::HasGameViewportFocus()) { Axes.Clear(); Actions.Clear(); return; } // Send input events for (const auto& e : InputEvents) { switch (e.Type) { // Keyboard events case InputDevice::EventType::Char: Input::CharInput(e.CharData.Char); break; case InputDevice::EventType::KeyDown: Input::KeyDown(e.KeyData.Key); break; case InputDevice::EventType::KeyUp: Input::KeyUp(e.KeyData.Key); break; // Mouse events case InputDevice::EventType::MouseDown: Input::MouseDown(e.MouseData.Position, e.MouseData.Button); break; case InputDevice::EventType::MouseUp: Input::MouseUp(e.MouseData.Position, e.MouseData.Button); break; case InputDevice::EventType::MouseDoubleClick: Input::MouseDoubleClick(e.MouseData.Position, e.MouseData.Button); break; case InputDevice::EventType::MouseWheel: Input::MouseWheel(e.MouseWheelData.Position, e.MouseWheelData.WheelDelta); break; case InputDevice::EventType::MouseMove: Input::MouseMove(e.MouseData.Position); break; case InputDevice::EventType::MouseMoveRelative: Input::MouseMoveRelative(e.MouseMovementData.PositionRelative); break; case InputDevice::EventType::MouseLeave: Input::MouseLeave(); break; // Touch events case InputDevice::EventType::TouchDown: Input::TouchDown(e.TouchData.Position, e.TouchData.PointerId); break; case InputDevice::EventType::TouchMove: Input::TouchMove(e.TouchData.Position, e.TouchData.PointerId); break; case InputDevice::EventType::TouchUp: Input::TouchUp(e.TouchData.Position, e.TouchData.PointerId); break; } } // Update all actions for (int32 i = 0; i < Input::ActionMappings.Count(); i++) { const auto& config = Input::ActionMappings[i]; const StringView name = config.Name; ActionData& data = Actions[name]; data.Active = false; data.State = InputActionState::Waiting; // Mark as updated in this frame data.FrameIndex = frame; } for (int32 i = 0; i < Input::ActionMappings.Count(); i++) { const auto& config = Input::ActionMappings[i]; const StringView name = config.Name; ActionData& data = Actions[name]; bool isActive; if (config.Mode == InputActionMode::Pressing) { isActive = Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton); } else if (config.Mode == InputActionMode::Press) { isActive = Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton); } else { isActive = Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton); } if (Input::GetKeyDown(config.Key) || Input::GetMouseButtonDown(config.MouseButton) || Input::GetGamepadButtonDown(config.Gamepad, config.GamepadButton)) { data.State = InputActionState::Press; } else if (Input::GetKey(config.Key) || Input::GetMouseButton(config.MouseButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadButton)) { data.State = InputActionState::Pressing; } else if (Input::GetKeyUp(config.Key) || Input::GetMouseButtonUp(config.MouseButton) || Input::GetGamepadButtonUp(config.Gamepad, config.GamepadButton)) { data.State = InputActionState::Release; } data.Active |= isActive; } // Update all axes AxesValues.Resize(Input::AxisMappings.Count(), false); for (int32 i = 0; i < Input::AxisMappings.Count(); i++) { const auto& config = Input::AxisMappings[i]; const StringView name = config.Name; const AxisData& data = Axes[name]; // Get key raw value const bool isPositiveKey = Input::GetKey(config.PositiveButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadPositiveButton); const bool isNegativeKey = Input::GetKey(config.NegativeButton) || Input::GetGamepadButton(config.Gamepad, config.GamepadNegativeButton); float keyRawValue = 0; if (isPositiveKey && !isNegativeKey) { keyRawValue = 1; } else if (!isPositiveKey && isNegativeKey) { keyRawValue = -1; } // Apply keyboard curve smoothing and snapping float prevKeyValue = data.PrevKeyValue; if (config.Snap && Math::NotSameSign(data.PrevKeyValue, keyRawValue)) { prevKeyValue = 0; } float keyValue; if (Math::Abs(prevKeyValue) <= Math::Abs(keyRawValue)) { keyValue = Math::LerpStable(prevKeyValue, keyRawValue, Math::Saturate(dt * config.Sensitivity)); } else { keyValue = Math::LerpStable(prevKeyValue, keyRawValue, Math::Saturate(dt * config.Gravity)); } // Get axis raw value float axisRawValue = 0.0f; switch (config.Axis) { case InputAxisType::MouseX: axisRawValue = Input::GetMousePositionDelta().X * config.Sensitivity; break; case InputAxisType::MouseY: axisRawValue = Input::GetMousePositionDelta().Y * config.Sensitivity; break; case InputAxisType::MouseWheel: axisRawValue = Input::GetMouseScrollDelta() * config.Sensitivity; break; case InputAxisType::GamepadLeftStickX: axisRawValue = Input::GetGamepadAxis(config.Gamepad, GamepadAxis::LeftStickX); break; case InputAxisType::GamepadLeftStickY: axisRawValue = Input::GetGamepadAxis(config.Gamepad, GamepadAxis::LeftStickY); break; case InputAxisType::GamepadRightStickX: axisRawValue = Input::GetGamepadAxis(config.Gamepad, GamepadAxis::RightStickX); break; case InputAxisType::GamepadRightStickY: axisRawValue = Input::GetGamepadAxis(config.Gamepad, GamepadAxis::RightStickY); break; case InputAxisType::GamepadLeftTrigger: axisRawValue = Input::GetGamepadAxis(config.Gamepad, GamepadAxis::LeftTrigger); break; case InputAxisType::GamepadRightTrigger: axisRawValue = Input::GetGamepadAxis(config.Gamepad, GamepadAxis::RightTrigger); break; case InputAxisType::GamepadDPadX: if (Input::GetGamepadButton(config.Gamepad, GamepadButton::DPadRight)) axisRawValue = 1; else if (Input::GetGamepadButton(config.Gamepad, GamepadButton::DPadLeft)) axisRawValue = -1; break; case InputAxisType::GamepadDPadY: if (Input::GetGamepadButton(config.Gamepad, GamepadButton::DPadUp)) axisRawValue = 1; else if (Input::GetGamepadButton(config.Gamepad, GamepadButton::DPadDown)) axisRawValue = -1; break; } // Apply dead zone const float deadZone = config.DeadZone; float axisValue = axisRawValue >= deadZone || axisRawValue <= -deadZone ? axisRawValue : 0.0f; keyValue = keyValue >= deadZone || keyValue <= -deadZone ? keyValue : 0.0f; auto& e = AxesValues[i]; e.Used = false; e.PrevKeyValue = keyRawValue; // Select keyboard input or axis input (choose the higher absolute values) e.Value = Math::Abs(keyValue) > Math::Abs(axisValue) ? keyValue : axisValue; e.RawValue = Math::Abs(keyRawValue) > Math::Abs(axisRawValue) ? keyRawValue : axisRawValue; // Scale e.Value *= config.Scale; } for (int32 i = 0; i < Input::AxisMappings.Count(); i++) { auto& e = AxesValues[i]; if (e.Used) continue; const auto& config = Input::AxisMappings[i]; const StringView name = config.Name; AxisData& data = Axes[name]; // Blend final axis raw value between all entries // Virtual axis with the same name may be used more than once, select the highest absolute value for (int32 j = i + 1; j < Input::AxisMappings.Count(); j++) { auto& other = AxesValues[j]; if (!other.Used && Input::AxisMappings[j].Name == config.Name) { if (Math::Abs(other.Value) > Math::Abs(e.Value)) { e = other; } other.Used = true; } } // Setup axis data data.PrevKeyValue = e.PrevKeyValue; data.PrevValue = data.Value; data.ValueRaw = e.RawValue; data.Value = e.Value; // Mark as updated in this frame data.FrameIndex = frame; } // Remove not used entries for (auto i = Actions.Begin(); i.IsNotEnd(); ++i) { if (i->Value.FrameIndex != frame) { Actions.Remove(i); } } for (auto i = Axes.Begin(); i.IsNotEnd(); ++i) { if (i->Value.FrameIndex != frame) { Axes.Remove(i); } } #if !PLATFORM_SDL // Lock mouse if need to const auto lockMode = Screen::GetCursorLock(); if (lockMode == CursorLockMode::Locked) { const Float2 pos = Screen::ScreenToGameViewport(Screen::GetSize() * 0.5f) - Screen::ScreenToGameViewport(Float2::Zero); Input::SetMousePosition(pos); } #endif // Send events for the active actions and axes (send events only in play mode) if (!Time::GetGamePaused()) { for (auto i = Axes.Begin(); i.IsNotEnd(); ++i) { if (i->Value.Value != i->Value.PrevValue) { Input::AxisValueChanged(i->Key); } } for (auto i = Actions.Begin(); i.IsNotEnd(); ++i) { if (i->Value.State != InputActionState::Waiting) { Input::ActionTriggered(i->Key, i->Value.State); } } } } void InputService::Dispose() { // Dispose input devices if (Input::Mouse) { Input::Mouse->DeleteObject(); Input::Mouse = nullptr; } if (Input::Keyboard) { Input::Keyboard->DeleteObject(); Input::Keyboard = nullptr; } for (int32 i = 0; i < Input::Gamepads.Count(); i++) Input::Gamepads[i]->DeleteObject(); Input::Gamepads.Clear(); for (int32 i = 0; i < Input::CustomDevices.Count(); i++) Input::CustomDevices[i]->DeleteObject(); Input::CustomDevices.Clear(); }