背景
SceneSemanticAPIというARCoreの機能を利用する際に、端末の横持ちを検知する必要があり、調査しました。
関連リンク:【Unity】SceneSemanticAPIを使ってオクルージョン処理を実現する
結論
横持ちを厳密に検知することはUnityのAPIでは不可能なのではないか?という仮説を立てて調査したところ、予想通り不可能でした。(もし良い方法ご存知であればコメントで教えてください)
そこでジャイロによる横持ち検知を用いるという結論に至りました。
調査した内容
調べると色々と横持ちに関する情報が出てきます。主に以下の3つの手法が候補に挙がりました。
- Input.deviceOrientation
- Screen.orientaion
- Screen.width / heght による検知
Input.deviceOrientation
最初にたどり着いたAPIで、端末の向きを検知するために存在しているものです。
参考リンク:Input.deviceOrientation
ただ、 Android、iOS共に端末の回転設定をOSレベルで設定することができるので、この機能とコンフリクトします。具体的には、端末の回転をOSでロックしているとInput.deviceOrientationは反応しません。
Screen.orientaion
次にPlayerSettingsに存在する設定値でもある、Screen.orientaionを調べました。
この値は端末の向きに応じて変化するものではなく、端末の回転を制限するための値を持っているだけで判定には使えませんでした。
参考リンク:Screen.orientaion
Screen.width / heght
画面の縦幅と横幅が画面の向きに応じて変わることを利用する手法です。
この方法もダメで、以下条件の時にしか値が変化しませんでした。
- ネイティブの画面の回転設定がAutoかつ、Screen.orientaionがAutoRotationである
サンプルコード
以上の理由からジャイロによる物理的な検知を独自実装しました。以下がサンプルです。
using UnityEngine;
public class CheckOrientation : MonoBehaviour
{
[SerializeField] private GameObject _warningMessage;
private void Awake()
{
Input.gyro.enabled = true;
}
private void Update()
{
_warningMessage.SetActive(IsLandscape());
}
private bool IsLandscape()
{
bool isLandscape;
if (Application.isEditor)
{
isLandscape = Screen.width > Screen.height;
}
else
{
// Z軸方向だけの判定だと、縦持ちの状態でカメラを上下に傾けた場合に横持ちと判定されるため、X軸方向も判定に含める。
var gyro = GetCameraGyroEuler(Input.gyro.attitude);
var landScapeThresholdL = (gyro.z > 45 && gyro.z < 135) && (gyro.x < 45 || gyro.x > 315);
var landScapeThresholdR = (gyro.z > 225 && gyro.z < 315) && (gyro.x < 45 || gyro.x > 315);
isLandscape = landScapeThresholdL || landScapeThresholdR;
}
return isLandscape;
}
/// <summary>
/// カメラのジャイロを取得する。
/// カメラの正面方向をZ軸とし、Unityの座標系に合わせた変換を行なった上でオイラー角を返す。
/// </summary>
private Vector3 GetCameraGyroEuler(Quaternion gyro)
{
var gyroAsUnity = new Quaternion(gyro.x, gyro.y, -gyro.z, -gyro.w);
var calculatedRotation = Quaternion.Euler(90f, 0f, 0f) * gyroAsUnity;
return calculatedRotation.eulerAngles;
}
}