diff --git a/Assets/Tests/InputSystem/CoreTests_Devices.cs b/Assets/Tests/InputSystem/CoreTests_Devices.cs index ed90efe605..08301680ca 100644 --- a/Assets/Tests/InputSystem/CoreTests_Devices.cs +++ b/Assets/Tests/InputSystem/CoreTests_Devices.cs @@ -25,6 +25,7 @@ using Is = NUnit.Framework.Is; using Quaternion = UnityEngine.Quaternion; using TouchPhase = UnityEngine.InputSystem.TouchPhase; +using DeviceOrientation = UnityEngine.InputSystem.DeviceOrientation; using Vector2 = UnityEngine.Vector2; using Vector3 = UnityEngine.Vector3; @@ -2649,6 +2650,7 @@ public void Devices_DeltaControlsResetBetweenUpdates(string layoutName, string c [TestCase("Joystick", typeof(Joystick))] [TestCase("Accelerometer", typeof(Accelerometer))] [TestCase("Gyroscope", typeof(Gyroscope))] + [TestCase("DeviceOrientationSensor", typeof(DeviceOrientationSensor))] public void Devices_CanCreateDevice(string layout, System.Type type) { var device = InputSystem.AddDevice(layout); @@ -3870,6 +3872,18 @@ public void Devices_CanGetLinearAccelerationReading() Assert.That(LinearAccelerationSensor.current, Is.SameAs(sensor)); } + [Test] + [Category("Devices")] + public void Devices_CanGetDeviceOrientationReading() + { + var sensor = InputSystem.AddDevice(); + InputSystem.QueueStateEvent(sensor, new DeviceOrientationState { orientation = (int)DeviceOrientation.LandscapeLeft }); + InputSystem.Update(); + + Assert.That(sensor.orientation.ReadValue(), Is.EqualTo(DeviceOrientation.LandscapeLeft)); + Assert.That(DeviceOrientationSensor.current, Is.SameAs(sensor)); + } + [Test] [Category("Devices")] [TestCase("Accelerometer", "acceleration")] diff --git a/Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs b/Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs index fc2c8d1001..e2eceed858 100644 --- a/Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs +++ b/Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs @@ -7,6 +7,7 @@ using UnityEngine.InputSystem; using UnityEngine.TestTools.Utils; using Gyroscope = UnityEngine.InputSystem.Gyroscope; +using DeviceOrientation = UnityEngine.InputSystem.DeviceOrientation; internal class UnityRemoteTests : CoreTestsFixture { @@ -269,6 +270,42 @@ public void Remote_CanReceiveAccelerometerInputFromUnityRemote() Assert.That(Accelerometer.current, Is.Null); } + [Test] + [Category("Remote")] + public void Remote_CanReceiveDeviceOrientationFromUnityRemote() + { + SendUnityRemoteMessage(UnityRemoteSupport.HelloMessage.Create()); + + // Like the accelerometer, the orientation sensor is assumed present on every device running the + // Unity Remote and does not require explicit enabling. + Assert.That(DeviceOrientationSensor.current, Is.Not.Null); + Assert.That(DeviceOrientationSensor.current.remote, Is.True); + Assert.That(DeviceOrientationSensor.current.enabled, Is.True); + + SendUnityRemoteMessage(new UnityRemoteSupport.DeviceOrientationMessage + { + orientation = (int)DeviceOrientation.LandscapeLeft + }); + InputSystem.Update(); + + Assert.That(DeviceOrientationSensor.current.orientation.ReadValue(), Is.EqualTo(DeviceOrientation.LandscapeLeft)); + + // Disabling it should stop updates. + InputSystem.DisableDevice(DeviceOrientationSensor.current); + + SendUnityRemoteMessage(new UnityRemoteSupport.DeviceOrientationMessage + { + orientation = (int)DeviceOrientation.FaceUp + }); + InputSystem.Update(); + + Assert.That(DeviceOrientationSensor.current.orientation.ReadValue(), Is.EqualTo(DeviceOrientation.LandscapeLeft)); + + SendUnityRemoteMessage(new UnityRemoteSupport.GoodbyeMessage()); + + Assert.That(DeviceOrientationSensor.current, Is.Null); + } + // We don't currently support joystick input coming from the Unity Remote. [Test] [Category("Remote")] diff --git a/Packages/com.unity.inputsystem/InputSystem/Editor/Plugins/UnityRemote/UnityRemoteSupport.cs b/Packages/com.unity.inputsystem/InputSystem/Editor/Plugins/UnityRemote/UnityRemoteSupport.cs index 4dd8cc234e..da927db86d 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Editor/Plugins/UnityRemote/UnityRemoteSupport.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Editor/Plugins/UnityRemote/UnityRemoteSupport.cs @@ -125,6 +125,8 @@ private static unsafe bool ProcessMessageFromUnityRemote(IntPtr messageData) s_State.touchscreen.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; s_State.accelerometer = InputSystem.AddDevice(); s_State.accelerometer.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; + s_State.orientation = InputSystem.AddDevice(); + s_State.orientation.m_DeviceFlags |= InputDevice.DeviceFlags.Remote; // Gryo etc. added only when we receive GyroSettingsMessage. s_State.connected = true; @@ -248,6 +250,17 @@ private static unsafe bool ProcessMessageFromUnityRemote(IntPtr messageData) accelerometerMessage->accelerationZ) }); break; + + case (byte)MessageType.DeviceOrientation: + if (s_State.orientation == null) + break; + var orientationMessage = (DeviceOrientationMessage*)messageData; + // The remote sends the DeviceOrientation enum value directly (same values as ours). + InputSystem.QueueStateEvent(s_State.orientation, new DeviceOrientationState + { + orientation = orientationMessage->orientation + }); + break; } return false; @@ -257,6 +270,8 @@ private static void Disconnect() { InputSystem.RemoveDevice(s_State.touchscreen); InputSystem.RemoveDevice(s_State.accelerometer); + if (s_State.orientation != null) + InputSystem.RemoveDevice(s_State.orientation); if (s_State.gyroscope != null) InputSystem.RemoveDevice(s_State.gyroscope); if (s_State.attitude != null) @@ -287,6 +302,8 @@ private static void OnDeviceChange(InputDevice device, InputDeviceChange change) s_State.touchscreen = null; else if (device == s_State.linearAcceleration) s_State.linearAcceleration = null; + else if (device == s_State.orientation) + s_State.orientation = null; break; case InputDeviceChange.Enabled: @@ -534,6 +551,17 @@ internal struct AccelerometerInputMessage : IUnityRemoteMessage public byte staticType => (byte)MessageType.AccelerometerInput; } + // See HandleOrientationMessage() in Editor/Src/RemoteInput/GenericRemote.cpp: a single int32 holding + // the DeviceOrientation enum value. + [StructLayout(LayoutKind.Explicit)] + internal struct DeviceOrientationMessage : IUnityRemoteMessage + { + [FieldOffset(0)] public MessageHeader header; + [FieldOffset(5)] public int orientation; + + public byte staticType => (byte)MessageType.DeviceOrientation; + } + private struct State { public bool connected; @@ -548,6 +576,7 @@ private struct State // Devices that we create for receiving input from the remote. public Touchscreen touchscreen; public Accelerometer accelerometer; + public DeviceOrientationSensor orientation; public Gyroscope gyroscope; public AttitudeSensor attitude; public GravitySensor gravity; diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/OrientationControl.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/OrientationControl.cs new file mode 100644 index 0000000000..b514866f3a --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/OrientationControl.cs @@ -0,0 +1,44 @@ +using UnityEngine.InputSystem.Layouts; +using UnityEngine.InputSystem.LowLevel; + +namespace UnityEngine.InputSystem.Controls +{ + /// + /// A control reading a value. + /// + /// + /// This is used by to report the physical orientation of the device + /// (see ). It provides feature parity with the legacy + /// UnityEngine.Input.deviceOrientation property. + /// + /// + [InputControlLayout(hideInUI = true)] + public class OrientationControl : InputControl + { + /// + /// Default-initialize the control. + /// + /// + /// Format of the control is + /// by default. + /// + public OrientationControl() + { + m_StateBlock.format = InputStateBlock.FormatInt; + } + + /// + public override unsafe DeviceOrientation ReadUnprocessedValueFromState(void* statePtr) + { + var intValue = stateBlock.ReadInt(statePtr); + return (DeviceOrientation)intValue; + } + + /// + public override unsafe void WriteValueIntoState(DeviceOrientation value, void* statePtr) + { + var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset; + *(int*)valuePtr = (int)value; + } + } +} diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/OrientationControl.cs.meta b/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/OrientationControl.cs.meta new file mode 100644 index 0000000000..d89d235298 --- /dev/null +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/OrientationControl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 92dc56acb38a47d3994fdd8162746807 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Sensor.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Sensor.cs index c4cd9c952d..c1c757fa91 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Sensor.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Sensor.cs @@ -60,6 +60,18 @@ internal struct LinearAccelerationState : IInputStateTypeInfo public FourCC format => kFormat; } + + internal struct DeviceOrientationState : IInputStateTypeInfo + { + public static FourCC kFormat => new FourCC('O', 'R', 'N', 'T'); + + // Note: unlike the other sensors this value is *not* compensated for screen orientation. It reports + // the physical orientation of the device and thus must be independent of how the content is rendered. + [InputControl(name = "orientation", displayName = "Orientation", layout = "Orientation")] + public int orientation; + + public FourCC format => kFormat; + } } namespace UnityEngine.InputSystem @@ -694,4 +706,109 @@ protected override void FinishSetup() base.FinishSetup(); } } + + /// + /// Enum describing the physical orientation of a device as reported by . + /// + /// + /// The values mirror the legacy UnityEngine.DeviceOrientation enum so that content migrating from + /// UnityEngine.Input.deviceOrientation to the Input System observes identical semantics. Note that this + /// is a package-local enum, kept independent of the legacy input module. + /// + /// + public enum DeviceOrientation + { + /// The orientation of the device cannot be determined. + Unknown = 0, + + /// The device is in portrait mode, with the device held upright and the home button at the bottom. + Portrait = 1, + + /// The device is in portrait mode but upside down, with the device held upright and the home button at the top. + PortraitUpsideDown = 2, + + /// The device is in landscape mode, with the device held upright and the home button on the right side. + LandscapeLeft = 3, + + /// The device is in landscape mode, with the device held upright and the home button on the left side. + LandscapeRight = 4, + + /// The device is held parallel to the ground with the screen facing upwards. + FaceUp = 5, + + /// The device is held parallel to the ground with the screen facing downwards. + FaceDown = 6, + } + + /// + /// Input device representing the physical orientation of the device playing the content. + /// + /// + /// The orientation sensor reports the physical orientation of the device (for example, whether it is held in + /// portrait or landscape, or lying face up or face down) as a discrete value. + /// It provides feature parity with the legacy UnityEngine.Input.deviceOrientation property. + /// + /// Unlike the other motion sensors, the reported value is not compensated for screen orientation; it always + /// describes the physical orientation of the hardware. + /// + /// + /// + /// class MyBehavior : MonoBehaviour + /// { + /// protected void OnEnable() + /// { + /// InputSystem.EnableDevice(DeviceOrientationSensor.current); + /// } + /// + /// protected void OnDisable() + /// { + /// InputSystem.DisableDevice(DeviceOrientationSensor.current); + /// } + /// + /// protected void Update() + /// { + /// var orientation = DeviceOrientationSensor.current.orientation.ReadValue(); + /// //... + /// } + /// } + /// + /// + /// + [InputControlLayout(stateType = typeof(DeviceOrientationState), displayName = "Orientation")] + public class DeviceOrientationSensor : Sensor + { + /// + /// The physical orientation of the device. + /// + /// Control reporting the current . + public OrientationControl orientation { get; protected set; } + + /// + /// The orientation sensor that was last added or had activity last. + /// + /// Current orientation sensor or null. + public static DeviceOrientationSensor current { get; private set; } + + /// + public override void MakeCurrent() + { + base.MakeCurrent(); + current = this; + } + + /// + protected override void OnRemoved() + { + base.OnRemoved(); + if (current == this) + current = null; + } + + /// + protected override void FinishSetup() + { + orientation = GetChildControl("orientation"); + base.FinishSetup(); + } + } } diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs index 994538ecc2..daa7815f6f 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/InputManager.cs @@ -2053,6 +2053,7 @@ internal void InitializeData() RegisterControlLayout("Touch", typeof(TouchControl)); RegisterControlLayout("TouchPhase", typeof(TouchPhaseControl)); RegisterControlLayout("TouchPress", typeof(TouchPressControl)); + RegisterControlLayout("Orientation", typeof(OrientationControl)); RegisterControlLayout("Gamepad", typeof(Gamepad)); // Devices. RegisterControlLayout("Joystick", typeof(Joystick)); @@ -2073,6 +2074,7 @@ internal void InitializeData() RegisterControlLayout("HumiditySensor", typeof(HumiditySensor)); RegisterControlLayout("AmbientTemperatureSensor", typeof(AmbientTemperatureSensor)); RegisterControlLayout("StepCounter", typeof(StepCounter)); + RegisterControlLayout("DeviceOrientationSensor", typeof(DeviceOrientationSensor)); RegisterControlLayout("TrackedDevice", typeof(TrackedDevice)); // Precompiled layouts. diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/Android/AndroidSupport.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/Android/AndroidSupport.cs index 1a7619c501..b6d45e7664 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/Android/AndroidSupport.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/Android/AndroidSupport.cs @@ -111,6 +111,13 @@ public static void Initialize() .WithDeviceClass("AndroidSensor") .WithCapability("sensorType", AndroidSensorType.HingeAngle)); + // Device orientation is not an Android hardware sensor; it is reported as its own device + // class and maps directly to the base DeviceOrientationSensor layout. + InputSystem.RegisterLayoutMatcher("DeviceOrientationSensor", + new InputDeviceMatcher() + .WithInterface(kAndroidInterface) + .WithDeviceClass("Orientation")); + InputSystem.onFindLayoutForDevice += OnFindLayoutForDevice; } diff --git a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/iOS/iOSSupport.cs b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/iOS/iOSSupport.cs index 20353ebe9c..93f9dfb813 100644 --- a/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/iOS/iOSSupport.cs +++ b/Packages/com.unity.inputsystem/InputSystem/Runtime/Plugins/iOS/iOSSupport.cs @@ -55,6 +55,10 @@ public static void Initialize() new InputDeviceMatcher() .WithInterface("iOS") .WithDeviceClass("LinearAcceleration")); + InputSystem.RegisterLayoutMatcher("DeviceOrientationSensor", + new InputDeviceMatcher() + .WithInterface("iOS") + .WithDeviceClass("Orientation")); #if UNITY_EDITOR || UNITY_IOS InputSystem.RegisterLayout(); // Don't add devices for InputTestRuntime