Играем в кубики с Kinect

в 10:09, , рубрики: .net, Kinect, Microsoft Kinect, NUI, Программирование, метки: , ,

Иногда удивительно, как стремительно идет развитие IT-индустрии. Я помню, как еще сравнительно недавно, на одном из семинаров TechDays, Евгений Марченков показывал первые ролики о проекте Natal. Много ли людей помнит это название? Ведь сейчас проект известен как Kinect!

Kinect

Сенсор был разработан для консоли Xbox 360, а с февраля 2012 года доступен для персональных компьютеров. Не так давно мне представилась возможность оценить сенсор и написать немного кода.

В статье я расскажу о том, как можно написать небольшую игру в кубики. И конечно кубики будут двигаться при помощи рук!

Итак, в первую очередь нам понадобится SDK для сенсора, его можно скачать с официального сайта. В SDK вы найдете некоторое количество примеров и документацию. Так же рекомендую посмотреть серию Quick Starts от Coding4Fun. И на закуску Kinect Paint на codeplex, можно скачивать, компилировать, изучать код.

На что точно не хочется отвлекаться так это на физику в игре. Но без неё тоже как-то грустно. Решение очень простое — использовать готовую библиотеку. Для Silverlight и Windows Phone существует хороший проект Physics Helper. К сожалению, текущая версия (4.0) не поддерживает WPF (комментарии автора по этому поводу здесь), но предыдущая версия поддерживала. Поэтому можно взять исходный код той версии, которая поддерживала WPF, обновить проекты до .NET 4.0 и скомпилировать сборки. Ссылки на обновленный проект в конце статьи.

Теперь в Visual Studio 2010 создаем новый проект WPF Application (C#). В примерах SDK есть проект Microsoft.Samples.Kinect.WpfViewers. В нём можно найти готовые элементы управления для работы с камерами и микрофонами сенсора (viewers), а так же элемент инкапсулирующий логику соединения с сенсором (chooser). Добавим этот проект в наш solution. Вот что получилось:

Solution Explorer

В окне Toolbox доступны элементы управления из WpfViewers.

image

Теперь в MainWindows.xaml заменим корневой Grid на Canvas, на нём будем рисовать кубики. Поместим на форму элементы KinectSensorChooser и KinectColorViewer, чтобы соединиться с сенсором и отобразить видеопоток с камеры.

Играем в кубики с Kinect

Обратите внимание на binding. Необходимо связать свойство Kinect класса KinectColorViewer со свойством Kinect класса KinectSensorChooser. Так color viewer будет знать, с каким сенсором мы работаем в данный момент. Остается только добавить инициализацию нужных потоков. Это можно сделать в обработчике события KinectSensorChanged (класс KinectSensorChooser), что позволит инициализировать сенсор при подключении. В общем виде это делается так:

sensor.ColorStream.Enable(); // включаем видеопоток

sensor.SkeletonFrameReady += SkeletonsReady; // подписываемся на событие, чтобы знать когда готов кадр с координатами найденных фигур людей
sensor.SkeletonStream.Enable(); // включаем поток

sensor.Start(); // включаем сенсор
* This source code was highlighted with Source Code Highlighter.

Методы Enable потоков перегружены, вы можете передавать в них параметры, изменяющие поведение по умолчанию. Так перегруженный метод Enable для ColorStream принимает значение перечисления (enum) ColorImageFormat для настройки формата изображения (RGB или YUV), а так же его разрешения.

Технически этого достаточно, чтобы запустить приложение и увидеть видео того, что попадает в поле зрения Kinect.

Для использования физики в игре в первую очередь добавим в проект ссылки на сборки: FarseerPhysics, Spritehand.FarseerHelper и Spritehand.PhysicsBehaviors из библиотеки Physics Helper.Следующее, что необходимо сделать — добавить к корневому элементу Canvas поведение (behavior) PhysicsControllerBehavior. Теперь нарисуем прямоугольник – это будет пол или земля, как угодно. Добавим ему поведение физического объекта PhysicsObjectBehavior и установим свойство IsStatic равным true. Я не буду останавливаться подробно на использовании Physics Helper, вы можете посмотреть видеоурок на странице библиотеки. Кубики можно задать в xaml разметке, а можно в C# коде. Главное не забыть о необходимости физического поведения для свежесозданного кубика.

private void CreateCube()
{
  var cube = new Rectangle
    {
      Name = string.Format("PART_Cube_{0}", Guid.NewGuid().ToString("N")),
      Width = CubeSide,
      Height = CubeSide,
      Fill = new SolidColorBrush(Colors.Red),
      Stroke = new SolidColorBrush(Colors.Black),
      StrokeThickness = 3,
      RadiusX = CubeCornerRadius,
      RadiusY = CubeCornerRadius
    };

  var behavior = new PhysicsObjectBehavior { IsStatic = false };
  behavior.Attach(cube);

  PART_LayoutRoot.Children.Add(cube);
}
* This source code was highlighted with Source Code Highlighter.

Прежде чем двигаться дальше, создадим курсор, который будет отображать движение руки по полю. Можно взять какую-нибудь готовую картинку или красиво нарисовать курсор с помощью элемента Path. Я не стал брать картинку или красиво рисовать Path’ом, а пошел третьим путем и нарисовал Path’ом некрасиво.
Cursor

Зияющая красная дырка вовсе не рана, а просто указатель центра, назначение которого я опишу чуть дальше.

Теперь остается только обрабатывать событие SkeletonFrameReady и перемещать «чудо-руку» по полю. Общий подход обработки события таков: получить кадр, вытащить из него данные по всем человеческим фигурам (skeleton) найденным в кадре и среди этих фигур выбрать первую отслеживаемую (tracked). Kinect может предоставлять данные о шести фигурах в кадре, но отслеживать перемещение может только двух (см. Player Segmentation Data). Для простой игры нам достаточно получить первого отслеживаемого игрока:

private Skeleton GetTrackedSkeleton(SkeletonFrameReadyEventArgs frameReadyEventArgs)
{
  using (SkeletonFrame skeletonFrameData = frameReadyEventArgs.OpenSkeletonFrame())
  {
    if (skeletonFrameData == null)
    {
      return null;
    }

    Skeleton[] allSkeletons = new Skeleton[skeletonFrameData.SkeletonArrayLength];
    skeletonFrameData.CopySkeletonDataTo(allSkeletons);

    return allSkeletons.Where(s => s.TrackingState == SkeletonTrackingState.Tracked).FirstOrDefault();
  }
}
* This source code was highlighted with Source Code Highlighter.

Информация о человеческой фигуре (skeleton) это набор из 20 точек. Изображение витрувианского человека как нельзя лучше подходит для их демонстрации.

Skeleton points

Итак, из skeleton мы можем получить позицию интересующей нас части тела игрока и обновить игровое поле, если необходимо. Примем следующую систему жестов:

  • если левая рука игрока поднята и правая рука находится над кубиком – кубик перемещается вместе с правой рукой (перетаскивается);
  • если левая рука опущена, то правая может спокойно перемещаться над игровым полем.

Очень просто проверить положение левой руки. Достаточно сравнить Y-координаты кисти левой руки и левого плеча:

private bool IsAcionMode(JointCollection joints)
{
  Joint lh = joints[JointType.HandLeft];
  Joint ls = joints[JointType.ShoulderLeft];

  return lh.Position.Y > ls.Position.Y;
}
* This source code was highlighted with Source Code Highlighter.

Для того чтобы определить находиться ли правая рука игрока над кубиком, можно использовать функцию HitTest класса VisualTreeHelper. Помните красный кружок в «чудо-руке»? Именно под этой точкой мы ищем кубик.

Функция обработки жестов:

private void UpdateScenaItems(Skeleton skeleton)
{
  Point currentPos = skeleton.Joints[JointType.HandRight]
    .ScaleTo((int)PART_LayoutRoot.ActualWidth, (int)PART_LayoutRoot.ActualHeight, 0.50f, 0.20f)
    .Position
    .ToPoint2D();

  Rectangle cube = GetCubeUnderCursor(currentPos);

  var isAction = IsAcionMode(skeleton.Joints);

  if (!isAction || (isAction && cube == null))
  {
    UpdateCursorPosition(currentPos);
    return;
  }

  MoveCubeUnderCursor(cube, currentPos);
}
* This source code was highlighted with Source Code Highlighter.

Метод расширения ScaleTo используется для приведения координат к масштабу игрового поля. Это метод входит в состав пакета Coding4Fun Kinect Toolkit (доступен для скачивания через NuGet). ToPoint2D — мой метод расширения использующийся для конвертации SkeletonPoint в Point. Понятно, что в методе UpdateCursorPosition мы перемещаем только курсор, а в методе MoveCubeUnderCursor перетаскиваем кубик.

Теперь можно запустить игру и немного помахать руками!

Ниже вы найдете исходные коды и сборки(assemblies) игры.
P.S. Время от времени кубики слипаются или застревают в полу – забавно.
P.P.S. В следующей статье я расскажу о том, как научить приложение понимать голосовые команды.

Сборки игры
Исходные коды игры
Сборки библиотеки PhysicsHelper
Исходные коды библиотеки PhysicsHelper

Автор: Lardite


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js