Поддержите двух человек, играющих лицом к лицу на iPad с разделенным экраном

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

Проект нужно было завершить как можно скорее, поэтому мы использовали технологии, которые знаем лучше всего. Мы решили создать приложение в React и обернуть его Cordova. Как правило, мы предпочитаем React-фреймворк, и Cordova предоставит нам встроенную возможность изменять ориентацию приложения по запросу.

Компонент разделения экрана

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

Как только они перейдут на первый этап игры в приложении, им будет предложено положить iPad на специально разработанный стол, над которым есть разделительная панель, которую можно снять, как только iPad окажется на месте. С этого момента player1 будет видеть одну половину экрана, а player2 - другую половину.

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

Нашим решением для разделения экрана было создание компонента более высокого порядка, который инкапсулирует логику разделения экрана. Мы пришли к идее создания компонента SplitScreen, который имеет два узла - один для верхней части экрана, а другой - для нижней.

С помощью CSS мы задаем каждой стороне высоту 50% и поворачиваем верхнюю половину (topPane) на 180 градусов.

Теперь, когда мы визуализируем SplitScreen компонент и предоставляем ему два узла:

<SplitScreen
  topPane={(
    <div>
      <h1>Player1</h1>
    </div>
  )}
  bottomPane{(
    <div>
      <h1>Player2</h1>
    </div>
  )}
/>

Это даст нам следующее:

Ориентация

Мы хотим, чтобы этот разделенный экран всегда был в портретной ориентации, а некоторые другие экраны (например, экран входа в систему) всегда были в альбомной ориентации. Для этого мы используем плагин ориентации экрана Кордовы.

Мы ведем учет того, какие страницы в нашем приложении предназначены для разделения экрана, а какие нет. На самом высоком уровне нашего приложения мы проверяем с помощью маршрутизатора, предназначена ли наша текущая отображаемая страница для разделения экрана. Если это так, и текущая ориентация еще не портретная, мы переключаемся в портретный режим. То же самое и с пейзажем.

Обратите внимание, что мы фиксируем ориентацию в режимах landscape-primary и portrait-primary. Таким образом мы гарантируем, что player1 и player2 всегда находятся на одной стороне iPad при смене ориентации.

Перспектива

Все это было довольно просто. Мы можем просто отрендерить компонент SplitScreen и предоставить ему два узла - по одному для каждого игрока, и один из них будет повернут на 180 градусов.

Следующая проблема заключается в том, что анимации, такие как теги присужденных очков, не поворачиваются для player1 в режиме разделенного экрана. Это заставит этого игрока увидеть их вверх ногами со своей точки зрения. Эти анимированные элементы визуализируются с использованием порталов React, чтобы они отображались вне иерархии DOM и помещались над всеми другими элементами.

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

Нам нужно убедиться, что компонент AwardedScore знает, отображается ли он в topPane компонента SplitScreen, и соответствующим образом повернуть элемент. Для этого мы использовали Контекст React.

Сначала мы создаем контекст.

export const SplitPaneContext = React.createContext('default');

Значение default - это наш идентификатор для ландшафтного режима. Мы используем этот контекст, чтобы предоставить дочерним компонентам нашей верхней и нижней панели информацию о том, где они отображаются.

Когда компонент отображается в topPane, значение контекста равно top. Если он отображается в bottomPane, значение равно bottom. И когда он отображается за пределами этих двух узлов, значение будет default.

Теперь мы можем использовать это значение контекста в нашем AwardedScore компоненте

Когда компонент AwardedScore отображается в topPane, значение контекста равно top и применяется класс .upsideDown. Когда он отображается в bottomPane, значение равно bottom, и класс не применяется.

В awardedScore.scss мы хотим повернуть только содержимое компонентаAwardedScore, отображаемого на верхней панели. Если бы мы повернули весь элемент .upsideDown, оси x и y нашей анимации тоже были бы перевернуты, в результате чего была бы перевернута сама анимация, а не только ее содержимое.

Это происходит потому, что наш AwardedScorePortal визуализируется за пределами корня DOM, таким образом имея window в качестве ссылки для его координат x и y.

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

Модальные окна в перспективе

Для получения оценок мы используем портал для визуализации вне иерархии DOM компонента SplitScreen. Таким образом, функция разделения экрана не будет влиять на координаты x и y нашей анимации на основе координат.

У нас также есть модальные окна в нашем приложении, и мы хотим, чтобы они отображались поверх всего в SplitScreen компоненте. Итак, здесь мы используем портал для рендеринга модального окна в SplitScreen иерархии DOM.

Обратите внимание, как мы создали пустые div элементы в topPane и bottomPane с уникальным id. Они содержат наши модальные окна при рендеринге на нижней или верхней панели. У нас также есть еще один <div id="modal-root-default" /> на уровне тела нашего приложения на самом высоком уровне. Это для модальных окон в режиме без разделения экрана.

Теперь, где бы мы ни рендерили наш Modal компонент, он будет иметь правильную перспективу. Обратите внимание, что мы добавляем дополнительный .split класс к Modal, когда он находится в режиме разделенного экрана - мы хотим изменить стиль модального окна в этом режиме, так как у нас меньше места на экране.

Теперь модальное окно будет отображаться в modal-root-default, modal-root-bottom или modal-root-top и при необходимости будет повернуто вместе со своим родителем.

Заключение

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

У нас возникли некоторые проблемы с правильной перспективой для анимации, поскольку она была отрисована в портале на верхнем уровне DOM, но решили это с помощью контекста.

Таким образом, мы могли сохранить анимацию на верхнем уровне DOM и правильные оси x и y для нашей анимации. Наши модальные окна не полагались на эти оси, поэтому мы могли создать портал внутри повернутой части DOM разделенного экрана и снова использовать контекст, чтобы отобразить его в правильном корне.

Отказ от ответственности: поскольку мы разработали только одно конкретное устройство, iPad Pro 12.9, я не могу гарантировать, что это решение будет работать на любых других устройствах.