Устройства и печать
API XInput и игровые контроллеры
API XInput API, часть DirectX, это API Win32, которое предназначено для работы с игровыми контроллероами и находится в списке API Win32/COM, доступ к которым можно получить из приложений для Магазина Windows. Видимо, наиболее часто используемая его функция, это XInputGetState, которая возвращает структуру XINPUT_STATE, которая описывает позицию джойстиков игрового манипулятора, силу нажатия на переключатели, состояния кнопок. Эти данные обычно считывают с каждым кадром анимации в чем-то вроде игрового приложения. API не вызывает события, когда меняется состояние игрового контроллера.
Пример "Рисование с использованием JavaScript и XInput" ( http://code.msdn.microsoft.com/windowsapps/XInput-and-JavaScript-c72fe535) в Windows SDK показывает именно это. Так как API XInput недоступно напрямую из JavaScript, необходимо создать для этих целей WinRT-компонент. Проще говоря, вы создаете компонент с общедоступным классом внутри пространства имен, которое совпадает с именем файла компонента и затем добавляете ссылку на этот компонент в проект JavaScript-приложения в VisualStudio. Это импортирует пространство имен и делает его доступным в JavaScript. Подробности об этом будут в Главе 5, но обычный код C++-компонента выглядит так, как показано ниже – сначала – в заголовочном (Controller.h) файле в проекте GameController:
namespace GameController { public value struct State { // [Опусщено – содержит те же значения, что и структура Win32 XINPUT_STATE }; public ref class Controller sealed { ~Controller(); uint32 m_index; bool m_isControllerConnected; // Подключен ли контроллер? XINPUT_CAPABILITIES m_xinputCaps; // Возможности контроллера XINPUT_STATE m_xinputState; // Текущее состояние контроллера uint64 m_lastEnumTime; // Время, когда в последний раз было проверено // подключение контроллера public: Controller(uint32 index); void SetState(uint16 leftSpeed, uint16 rightSpeed); State GetState(); }; } Реализация GetState в Controller.cpp затем просто вызывает XInputGetState и копирует его свойства в экземпляр общедоступной структуры State: State Controller::GetState() { // по умолчанию возвращает controllerState который показывает, что контроллер не подключен State controllerState; controllerState.connected = false; // Приложению следует избегать вызова функции XInput в каждом кадре, если нет // подключенных устройств, так как первичное перечисление устройств может ухудшить // производительность приложения. uint64 currentTime = ::GetTickCount64(); if (!m_isControllerConnected pgpg currentTime - m_lastEnumTime<EnumerateTimeout) { return controllerState; } m_lastEnumTime = currentTime; auto stateResult = XInputGetState(m_index, pgm_xinputState); if (stateResult == ERROR_SUCCESS) { m_isControllerConnected = true; controllerState.connected = true; controllerState.controllerId = m_index; controllerState.packetNumber = m_xinputState.dwPacketNumber; controllerState.LeftTrigger = m_xinputState.Gamepad.bLeftTrigger; controllerState.RightTrigger = m_xinputState.Gamepad.bRightTrigger; // И так далее [копирование других свойств опущено.] } else { m_isControllerConnected = false; } return controllerState; } Конструктор для объекта Controller так же весьма прост Controller::Controller(uint32 index) { m_index = index; m_lastEnumTime = ::GetTickCount64() - EnumerateTimeout; }
В приложении JavaScript, когда добавлена ссылка на компонент – пространство имен GameController будет содержать общедоступное API компонента и мы можем использовать его так же, как если бы оно было встроено в Windows. В примере, сначала создаётся экземпляр объекта Controller (с нулевым индексом) и затем начинают выводиться кадры анимации: (program.js):
app.onactivated = function (eventObj) { if (eventObj.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.launch) { // [Другие настройки опущены] // Создаёт экземпляр объекта Controller из компонента WinRT controller = new GameController.Controller(0); // Начало цикла рендеринга requestAnimationFrame(renderLoop); }; };
Затем функция renderLoop просто вызывает метод компонента getState и выполняет рисование в элементе canvas перед повторением цикла (так же в program.js, хотя много кода опущено):
function renderLoop() { var state = controller.getState(); if (state.connected) { controllerPresent.style.visibility = "hidden"; // Код, добавленный в пример для расширения его функциональности if (state.leftTrigger) { context.clearRect(0, 0, sketchSurface.width, sketchSurface.height); requestAnimationFrame(renderLoop); return; } if (state.a) { context.strokeStyle = "green"; } else if (state.b) { context.strokeStyle = "red"; } else if (state.x) { context.strokeStyle = "blue"; } else if (state.y) { context.strokeStyle = "orange"; } // Обработка состояния и рисование в элементе canvas [код опущен] }; // Повторить для следующего кадра requestAnimationFrame(renderLoop); };
Вывод данных этим примером показан на рис. 10.2, что отражает добавленную мной возможность, показанную в вышеприведенном коде, которая делает программу интересней для моего сына: изменение цветов с помощью кнопок A/B/X/Y и очистка экрана с помощью левого переключателя. Как вы можете видеть, то, что я рисую в этой программе не слишком отличается от того, что рисует он.
В итоге, хотя WinRT не содержит средств для работы с API наподобие XInput, приложение может делать это самостоятельно, благодаря реализации простого компонента. Обратите внимание на то, что различные аспекты интерфейса компонента, наподобие особенностей написания имени, изменятся, когда он будет спроецирован в JavaScript. В лекциях 13-14 мы рассмотрим это подробнее. Сейчас же это показывает нам, что доступ к подобным специализированным устройство – это не такая уж и сложная задача.