СПЕЦИАЛЬНЫЙ ВЫПУСК 2015 О WINDOWS 10

Том 30, номер 11

Разработка игр - Написание игр для Universal Windows Platform с помощью Unity

Хайме Родригес | Windows 2015

Продукты и технологии:

Windows 10, Universal Windows Platform, Unity, Visual Studio

В статье рассматриваются:

  • подготовка среды разработки;
  • ориентация на Windows 10;
  • новые шаблоны проектов;
  • адаптация и оптимизации;
  • передача игры в Windows Store;
  • новые средства для игр с погружением в виртуальную среду (immersive games).

Windows 10 предлагает много новых средств для разработчиков игр и для геймеров. С появлением Universal Windows Platform (UWP) разработчики могут ориентироваться на ПК, планшеты и мобильные устройства с Windows 10 (т. е. смартфоны), Xbox One и HoloLens, используя единую кодовую базу. Кроме того, произошло слияние различных Windows Store в один, и Microsoft сделала доступными Xbox Live Services на платформе Windows 10 наряду с приложением Xbox, которое резко увеличивает степень погружения геймера в игры на всех семействах устройств с Windows.

Для разработчиков игр Unity является одной из самых популярных игровых инфраструктур и движков. Поддерживая 21 платформу, она обладает лучшей поддержкой кросс-платформенной разработки среди всех инфраструктур, доступных сегодня. Ее редактор в сочетании с поддержкой скриптов на C# делает ее в высшей степени продуктивной средой разработки игр. Благодаря Asset Store с сообществом Unity и растущим возможностям Unity в области развлечений и сбора аналитической информации разработка игр с погружением в виртуальную среду (immersive games) еще никогда не была столь легка.

Раз уж все сложилось так удачно, мы обязаны написать об этом! Эта статья даст вам все необходимое для того, чтобы ваши Unity-игры отлично работали в Windows 10. Это не введение ни в Unity, ни в UWP-приложения. Мы предполагаем, что вы уже знакомы с ними, и вместо этого сосредоточимся на разъяснениях того, что изменилось с появлением Windows 10, и советах из серии «Вы должны это знать» по созданию впечатляющих «Универсальных Игр Windows» (Universal Windows Games, UWG). Мы будем делать это, используя практический подход, и проведем вас по нескольким изменениям, внесенным нами в игру-пример Chomp (рис. 1) для ее оптимизации под Windows 10.

Игра Chomp
Рис. 1. Игра Chomp

Chomp начала свою жизнь как игра, написанная с применением Unity для Windows 8.1. Как видно из рис. 1, это довольно простая лабиринтная игра, похожая на знаменитую Pac-Man. Этот пример создан в качестве пособия для разработчиков, демонстрирующего, как писать игры с использованием Unity, поэтому ее простота была ключевым условием. Но теперь, когда появилась Windows 10 и Unity стала поддерживать эту новую ОС, Chomp понадобилось обновить. Исходный код для Chomp см. по ссылке bit.ly/ChompUnity. Скачайте его и следуйте за нами.

Чтобы получить UWP-версию нашей игры, мы могли бы просто экспортировать игру, используя профиль Windows 10 в Unity, но это не дало бы нам приложение, оптимизированное под Windows 10. Она не обрабатывает выполнение в окне, полноэкранный режим, сенсорный ввод и т. д. Итак, давайте посмотрим, что мы сделали для эффективного переноса этой игры с Windows 8.1 на Windows 10.

Приступаем к работе (обязательные требования)

UWP-приложения (и UWG) требуют разработки и тестирования в Windows 10 с помощью Visual Studio 2015. В любой редакции Visual Studio 2015 есть все, что нужно для создания игр, — даже в бесплатном Visual Studio Community 2015!

Также вам понадобится Unity версии 5.2.1p2 или выше. Unity 5.2 теперь устанавливает Visual Studio Community 2015 и Visual Studio 2015 Tools for Unity (VSTU), так что на практике, чтобы приступить к работе, достаточно установить Unity и указать правильные параметры в процессе установки (рис. 2).

Установка Unity с правильными параметрами дает все, что нужно для начала работы
Рис. 2. Установка Unity с правильными параметрами дает все, что нужно для начала работы

Примечание Для разработчиков, использующих Mac, новой альтернативой является применение редактора Visual Studio Code совместно с проектами Unity. Более подробно об этом варианте см. по ссылке bit.ly/UnityVSCode.

Разработка под Windows 10

Разработка под Windows 10 следует точно тому же процессу, с которым вы уже знакомы. Но появился новый SDK для UWP-приложений под платформу Windows Store (рис. 3), который будет экспортировать игру как UWP-приложение.

Ориентация на Windows 10 в Unity
Рис. 3. Ориентация на Windows 10 в Unity

Ниже перечислены некоторые важные элементы, работающие «за кулисами» при экспорте для разработки под Windows 10 и UWP.

  • Новые препроцессоры UNITY_UWP и UNITY_WSA_10_0, с помощью которых можно «подстроить» логику и пользовательскую среду вашей игры под UWP.
  • Unity 5.2 теперь включает новую упрощенную WinRTLegacy DLL, которая содержит меньшее количество типов, чем предыдущие версии. С появлением Windows 10 компания Microsoft вернула несколько типов обратно в профиль .NET Core, сделав некоторые обходные методы, которые включались в WinRTLegacy, больше не нужными.
  • Unity 5.2 использует новую модель плагинов, введенную в Unity 5. Это значительно упрощает плагины, участвующие в вашем рабочем процессе, как вы еще увидите далее в этой статье.
  • Unity 5.2 включает экспериментальную поддержку DirectX 12, который поставляется с Windows 10. Чтобы опробовать эту экспериментальную поддержку, откройте Player Settings в Unity, сбросьте флажок Auto Graphics API и вручную включите поддержку Direct3D12.

Новые шаблоны проектов

Процесс компиляции в Unity теперь генерирует проект Visual Studio 2015, совместимый с UWP. Как вы, вероятно, знаете, в этой новой системе проектов появились некоторые важные изменения.

Например, каждое UWP-приложение теперь поставляется с собственной копией .NET Core внутри приложения, поэтому вы должны всегда получать ту версию .NET, под которую вы проводите тестирование. В связи с этим Unity генерирует соответствующий файл project.json, который будет извлекать необходимые «части» .NET через NuGet.

Кроме того, UWP-приложения используют .NET Native, которая генерирует оптимизированный «родной» машинный код до того, как ваше приложение скачивается на устройства пользователей, что ускоряет время запуска и снижает расход аккумуляторов вашими играми. Влияние этих оптимизаций будет варьироваться в зависимости от сложности игры, но они определенно не повредят производительности. Единственный подвох с .NET Native в том, что она приводит к значительно более длительной компиляции в Visual Studio. Для обычных приложений Visual Studio выполняет компиляцию с .NET Native только при конфигурации Release проекта. В случае генерируемого Unity файла проекта реализуется похожий подход, и только конфигурация Master компилируется с использованием .NET Native. Если вы не знакомы с конфигурациями в Unity, то знайте, что их три:

  • Debug — полностью отладочный проект безо всяких оптимизаций;
  • Release — проект, компилируемый с оптимизациями, но включающий поддержку средства профилирования;
  • Master — конфигурация, в которой игра должна передавать в магазин, поскольку в ней нет отладочного кода, все оптимизации включены, а поддержка профилирования отсутствует.

Вы определенно должны компилировать и тестировать с использованием .NET Native на ранних стадиях цикла разработки. Unity выполняет массу сложных операций над кодом на промежуточном языке (IL), поэтому, если и есть какой-то тип приложения, который нагружает .NET Native по полной, то это Unity-игра. Чтобы убедиться, что все работает корректно, следите за предупреждениями в окне Output при компиляции .NET Native.

Упомянутые выше отличия значимы в период выполнения, но новые шаблоны Unity делают эти изменения прозрачными для разработчика, поэтому давайте сосредоточимся на том, как подстроить и отшлифовать вашу игру под Windows 10.

Подстройка и шлифовка игры под Windows 10

Одна кодовая база для множества форм-факторов — ключевая особенность UWP, но, когда дело доходит до игр, все равно могут потребоваться некоторые оптимизации и адаптация для конкретных форм-факторов. Чаще всего это включает механизмы ввода (например, сенсорный, с клавиатуры, от мыши и геймпада), изменение размеров окна, оптимизация ресурсов и реализация интеграции платформенной функциональности (native integration) (например, использование активных плиток [live tiles], уведомлений или Cortana) для каждого конкретного форм-фактора. Мы исследуем, как обстоит с этим дело в UWG.

Окна Универсальные Windows-приложения теперь размещаются в окнах с изменяемым размером вместо выполнения в полноэкранном режиме, как это было в Windows 8 и 8.1, поэтому масштабирование окна теперь является одним из факторов, который вы должны учитывать в своих играх и приложениях. Большинство из этих отличий прозрачно для Unity-разработчика, потому что свойства Screen.height и Screen.width по-прежнему сообщают о доступном пространстве экрана в пикселях.

Windows 10 также включает новые API для входа в полноэкранный режим и выхода из него, и эти возможности предоставляются через Unity-класс Screen заданием свойства Screen.fullScreen. Рекомендуется реализовать стандартные клавиши-ускорители для входа в полноэкранный режим и выхода из него. Они широко варьируются у разных издателей, но наиболее распространенный ускоритель, который переключает между режимами, — клавиша F11 или комбинация Alt+Enter. В случае Chomp мы хотели дать игрокам возможность играть в полноэкранном режиме, поэтому реализовали клавишу-переключатель:

if (Input.GetKeyUp (KeyCode.F11))
{
  Screen.fullScreen = !Screen.fullScreen;
}

Наличие многооконного режима требует другого обязательного изменения в играх для Windows 10: вы должны обрабатывать смену фокуса ввода. На многооконном рабочем столе, если окно вашей игры вне фокуса ввода, вы должны ставить игру и ее музыкальное сопровождение на паузу, так как пользователь может взаимодействовать с другим окном. Unity абстрагирует это взаимодействие с помощью одинакового на всех платформах API: метода OnApplicationPause. Этот метод вызывается для всех активных MonoBehaviours, когда фокус ввода меняется. Мы обрабатываем это в Chomp, как показано на рис. 4.

Рис. 4. Игра ставится на паузу, когда фокус ввода меняется

public void OnApplicationPause(bool pause)
{
  FocusChanged(!pause);
}

public void FocusChanged(bool isFocused)
{
  if (isFocused)
    musicSource.UnPause();
  else
  {
    musicSource.Pause();
    GameState.Instance.SetState(GameState.GameStatus.Paused);
  }
}

Обратите внимание на асимметрию: когда игра теряет фокус ввода, игра и звук ставятся на паузу, а когда она получает фокус ввода, мы снимаем с паузы только звуковое сопровождение. Дело здесь вот в чем. Когда игра ставится на паузу, мы также показываем диалоговое окно паузы в самой игре, а когда фокус возвращается, игра ждет подтверждения от пользователя, что он действительно хочет возобновить игру. Этот диалог обрабатывает восстановление состояния игры из Pause обратно в Running.

Внеся эти два изменения, теперь мы должным образом обрабатываем возможность входа в полноэкранный режим и выхода из него и ставим игру на паузу, когда наше окно теряет фокус.

Ввод В более ранних выпусках Unity была отличная поддержка ввода в Windows-игры, и с выходом Windows 10 она не изменилось. Ввод от мыши, с геймпада и сенсорный ввод остаются элегантно абстрагированными с помощью класса Input и диспетчера ввода (Input Manager) в Unity.

Самое важное, что нужно помнить в отношении ввода, — убедиться в том, что вы реализуете столько механизмов ввода, сколько имеет смысл в вашей игре. В случае Chomp мы хотим поддерживать клавиатуру, геймпад и сенсорный ввод. Не забывайте, что UWG могут выполняться на любом устройстве, поэтому обеспечьте игрокам максимальные удобства во вводе. Наиболее часто спрашивают, как распознавать, требуется ли показывать сенсорные элементы управления (вроде виртуального джойстика или панели для переключения направлений [D-pad]), когда игра запускается на сенсорном устройстве вроде смартфона.

Один из способов определить, надо ли показывать сенсорный джойстик, — выяснить, выполняется ли игра на смартфоне. Если да, тогда имело бы смысл отображать этот джойстик и включать его по умолчанию. Чтобы определить конкретную платформу, на которой выполняется игра (например, смартфон или Xbox), можно проверить, реализован ли соответствующий контракт. Именно так Chomp распознает, что она выполняется в Windows 10 Mobile:

public static bool IsWindowsMobile
{
  get
  {
    #if UNITY_WSA_10_0 && NETFX_CORE
      return Windows.Foundation.Metadata.ApiInformation.
        IsApiContractPresent("Windows.Phone.PhoneContract", 1);
    #else
      return false;
    #endif
  }
}

Заметьте, что в этом коде мы используем препроцессор UNITY_WSA_10_0, чтобы определить, выполняется ли компиляция для Windows 10. Без этой проверки код не удалось бы скомпилировать в сборках, не предназначенных для Windows 10.

Держать виртуальный джойстик видимым постоянно — это один из подходов, но может оказаться, что лучше отображать его, только когда пользователь реально использует сенсорный ввод на устройстве. Windows 10 включает новый API — Windows.UI.ViewManagement.UIViewSettings.UserInteractionMode, который определяет, в каком режиме выполняется данная копия Windows 10 — планшетном (сенсорный ввод) или традиционном настольном (мышь/клавиатура). Этот API должен работать в UI-потоке Windows, поэтому из Unity вы должны вызов UI-потока для его маршалинга. Мы добавили в Chomp код (рис. 5), чтобы определять, взаимодействует ли пользователь с приложением с помощью сенсорного ввода.

Рис. 5. Код, определяющий, используется ли сенсорный ввод

public static bool IsWindows10UserInteractionModeTouch
{
  get
  {
    bool isInTouchMode = false;
#if UNITY_WSA_10_0 && NETFX_CORE
    UnityEngine.WSA.Application.InvokeOnUIThread(() =>
    {
      isInTouchMode = UIViewSettings.GetForCurrentView().
        UserInteractionMode == UserInteractionMode.Touch;
    }, true);
#endif
    return isInTouchMode;
  }
}

Теперь, реализовав эти два метода, можно обновить Chomp, чтобы эта игра делала обоснованный выбор, когда показывать джойстик. Если она выполняется на мобильном устройстве или если используется сенсорный режим взаимодействия, UseJoystick вернет true и мы покажем джойстик:

public static bool UseJoystick
{
  get
  {
    return (IsWindowsMobile ||
      IsWindows10UserInteractionModeTouch);
  }
}

Закончив с поддержкой масштабируемого окна и обработкой ввода, мы переходим к использованию преимуществ новых «родных» Windows API для расширения функциональности нашей игры.

Интеграция платформенной функциональности с Windows 10 Такая интеграция с ОС из Unity-игры обрабатывается точно так же, как и раньше: если вы компилируете с использованием .NET Core в Unity (применяя препроцессор NETFX_CORE), то может подставлять платформенный код, как показано в нашем предыдущем примере.

Если код, который вы хотите добавить, слишком велик для подстановки или если поведение требуется абстрагировать (между платформами), вы можете по-прежнему использовать плагины. Для Windows 10 группа Microsoft Developer Experience Games Evangelism (мы являемся ее членами) предлагает несколько новых плагинов с открытым исходным кодом, которые упростят вам интеграцию с Windows 10. Вы найдете эти плагины по ссылке bit.ly/Win10UnityPlugins. На сегодняшний день в открытом доступе находятся следующие плагины.

  • Store Плагин для покупок из приложения — все, что нужно для проведения транзакций в магазине Windows Store.
  • AzureMobile Плагин с поддержкой базовых CRUD-операций (Create, Read, Update, Delete) для Azure Storage.
  • Core Плагин, обеспечивающий интеграцию платформенной функциональности с базовыми средствами ОС, такими как активные плитки, локальные уведомления, оповещения (push notifications) и Cortana.
  • Ads Плагин, обертывающий новый Microsoft Ads SDK в Windows 10, который теперь поддерживает рекламные вставки в видео (video interstitials).

Ранее в Chomp не было механизма покупок из приложения, поэтому мы решили добавить его в игру, используя плагин Store. Так что Chomp поддерживает теперь покупку бустеров (boosters) и дополнительных лабиринтов (mazes).

Самый простой способ использовать эти плагины — скачать пакет Unity с GitHub и импортировать его в Chomp через Assets | Import Package | Custom Packages в Unity Editor. После импорта пакета в папке плагинов Unity появятся соответствующие двоичные файлы. После установки вы найдете их в каталоге Plugins\WSA; Unity требует использовать в редакторе подстановочные сборки (placeholder assemblies) (совместимые с Microsoft .NET Framework 3.5), поэтому они тоже включаются и копируются в саму папку Plugins.

Импортировав пакет, мы можем ссылаться на Store API из Chomp. Сначала мы вызываем Store.LoadListingInformation, предоставляя метод обратного вызова, который выполняется при завершении операции; тем самым мы получаем список всех элементов, доступных для покупки. Если вызов был успешным, мы перебираем возвращенные элементы ProductListing в цикле и используем их для заполнения цен в нашем UI (рис. 6).

Рис. 6. Перечисление элементов, доступных для покупки из приложения

void Start()
{
  Store.LoadListingInformation(OnListingInformationCompleted);
  ...
}

void OnListingInformationCompleted(
  CallbackResponse<ListingInformation> response)
{
  if (response.Status == CallbackStatus.Success)
  {
    ListingInformation result = response.Result;
    PopulatePrices(result.ProductListings);
  }
}

После выбора пользователем элемента, который он хочет приобрести, выполнение транзакции требует написания всего нескольких строк кода. На рис. 7 показан добавленный нами код для покупки новых лабиринтов для игры.

Рис. 7. Покупка из приложения

public void OnDurablePurchase(GameObject buttonClicked)
{
  string productId = GetProductId(buttonClicked.name);
  if (!string.IsNullOrEmpty (productId))
  {
    Store.RequestProductPurchase(productId, (response) =>
    {
      if (response.Status == CallbackStatus.Success)
      {
        // response.Status просто сообщает, был ли успешным
        // обратный вызов. CallbackResponse информирует нас
        // о реальном Status, возвращенном магазином.
        // Делайте обе проверки.
        if (response.Result.Status ==
          ProductPurchaseStatus.Succeeded)
        {
          EnableLevels(productId);
          return;
        }
      }
    });
  }
}

Как видите, использовать новые плагины для реализации платформенной функциональности достаточно легко.

Передача в магазин

Передача в Windows Store стала еще проще. Теперь вы можете передать либо один пакет, включающий все двоичные файлы, либо по одному пакету для каждой платформы/архитектуры.

Если вы хотите разделить свои пакеты или поддерживать игру только на определенных платформах, то можете вручную отредактировать файл package.appxmanifest и настроить элемент TargetDeviceFamily:

<Dependencies>
  <TargetDeviceFamily Name="Windows.Universal"
    MinVersion="10.0.10240.0"
    MaxVersionTested="10.0.10240.0" />
</Dependencies>

Три возможных семейства устройства перечислены ниже.

  • Windows.Universal Позволяет развертывать двоичные файлы на любом устройстве, аппаратное обеспечение которого отвечает вашим требованиям.
  • Windows.Mobile Применяется для двоичных файлов, отправляемых в Windows 10 Mobile SKU, а именно Windows Phone, хотя в будущем скорее всего появятся другие малые устройства (шесть дюймов и меньше), которые не являются телефонами и работают под управлением этой SKU, поэтому не полагайтесь на то, что это только смартфоны.
  • Windows.Desktop Следует использовать для игр, которые работают только на настольных компьютерах или планшетах.

Если вы ориентируетесь на мобильную или настольную платформу, но не на консоли и другие семейства Windows, то можете указать в манифесте два семейства устройств (подставьте вместо «x» и «y» нужные цифры):

<Dependencies>
  <TargetDeviceFamily Name="Windows.Mobile"
    MinVersion="10.0.x.0"
    MaxVersionTested="10.0.y.0"/>
  <TargetDeviceFamily Name="Windows.Desktop"
    MinVersion="10.0.x.0"
    MaxVersionTested="10.0.y.0"/>
</Dependencies>

Заметьте, что для каждого семейства устройств могут быть разные MinVersion и MaxVersion. Это станет удобно в будущем, когда Windows 10 Mobile начнет поставляться с более высоким номером версии, чем настольная Windows 10. Однако пока мы советуем оставить версии по умолчанию (10.0.10240.0).

Как упоминалось при обсуждении .NET Native, передавая свои пакеты, убедитесь, что они имеют конфигурацию Master. Кроме того, мы рекомендуем включать полный файл базы данных символов программы (program database, PDB) для анализа аварийных дампов. Новый Store не позволяет скачивать CAB-архивы для их локального анализа. Вместо этого вы должны передавать свои PDB в Windows Store, и тот проделает работу за вас, выдав вам трассировки стека из аварийных дампов (рис. 8).

Включение файлов базы данных символов программы
Рис. 8. Включение файлов базы данных символов программы

Наконец, когда вы передаете продукт в Windows Store через портал разработчика, убедитесь, что вы выбрали правильную аппаратную конфигурацию. Store теперь позволяет указывать аппаратные требования, такие как сенсорный ввод или клавиатура, чтобы ваша игра устанавливалась только на подходящие устройства (рис. 9).

Страница Hardware Preferences на портале разработчика
Рис. 9. Страница Hardware Preferences на портале разработчика

Это лишь начало!

Эта статья — краткий обзор для Unity-разработчиков из серии «что нового там внутри». Мы показали это, перенеся свою простую игру в Windows 10 и добавив несколько новых возможностей. Из-за ограниченного пространства мы сосредоточились на основах и специфических для игр обязательных изменений, не став демонстрировать вам каждое новое средство на платформе Windows 10. Если вы хотите создать игру с погружением в ее виртуальную среду, подумайте об использовании дополнительных средств, таких как новый центр уведомлений на настольных компьютерах, Cortana, новые шаблоны активных плиток и новые Xbox Live Services API. С выпуском Windows 10 компания Microsoft постепенно открывает доступ к Xbox Live Services. Вам не нужно писать консольную игру — у вас просто должна быть отличная игра, работающая в Windows 10 и желание задействовать преимущества сервисов таблиц лидеров, достижений (achievements), игры на разных устройствах (cross-device play) и др. Для доступа к этим сервисам зарегистрируйтесь на программу ID@Xbox по ссылке xbox.com/id.

Visual Studio 2015 Tools for Unity

Starting with the 5.2 release, Unity includes Visual Studio 2015 Tools for Unity (VSTU), making Visual Studio the new default code editor for Unity projects on Windows. This change will bring improved IntelliSense, syntax coloring in the editor and a great C# debugger. To configure your default script editor, select the Edit | Preferences menu option in the Unity Editor. Your choices might include MonoDevelop (built-in), Visual Studio 2015, Visual Studio 2013 or the option to browse for further options.

VSTU also includes a few shortcuts for more easily writing code for Unity. For example, if you right-click within a MonoBehaviour class, the context menu will have a new Implement MonoBehaviours entry, which lets you quickly inject a MonoBehaviour method signature into your class.

In that same context menu, there’s also a Quick MonoBehaviours entry, which performs a similar function but with a less-intrusive dialog where you can type the method name you’re searching for, and the signature is again injected. Both of these methods are accessible from shortcut key accelerators for even more rapid use (Ctrl+Shift+M and Ctrl+Shift+Q).

Beyond the editor enhancements, one of the best features when using VSTU is the streamlined debugger integration with the Unity Editor. When a Unity project is open in Visual Studio, you’ll automatically see an “Attach to Unity” button for debugging in the standard Debug toolbar. Figure A shows where this button is located. This button will automatically find the Unity Editor’s process and attach to it. From there you can seamlessly set breakpoints, analyze variable values and do most everything else you’d expect to do with Visual Studio. VSTU even has experimental support for breaking on exceptions. For even more details on VSTU 2.0 features, check out the blog post, “Visual Studio Tools for Unity 2.0,” at bit.ly/VSTUInfo.

Note: If you aren’t targeting Windows 10 and using Visual Studio 2013, there’s a downloadable package that brings all the goodness we described here to Visual Studio 2013 Community Edition users. You can download this extension at bit.ly/VSTU2013.


Хайме Родригес (Jaime Rodriguez) — руководитель группы Microsoft Developer Experience Games Evangelism. Вы можете найти его в Twitter (@jaimerodriguez) или через его блог на jaimerodriguez.com.

Брайен Пик (Brian Peek) — старший разработчик-идеолог игр в Microsoft. Является «хардкорным» геймером, занимался разработкой игр для Windows, консолей и всего остального, что можно программировать. Часто выступает на конференциях разработчиков. Следите за его заметками в Twitter (@BrianPeek) или читайте его блог на brianpeek.com.

Выражаем благодарность за рецензирование статьи экспертам Microsoft Сандживу Двиведи (Sanjeev Dwivedi) и Адаму Тьюлиперу (Adam Tuliper).