Skip to content
14 changes: 14 additions & 0 deletions Assets/Tests/InputSystem/CoreTests_Devices.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<DeviceOrientationSensor>();
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")]
Expand Down
37 changes: 37 additions & 0 deletions Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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")]
Expand Down
4 changes: 4 additions & 0 deletions Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased] - yyyy-mm-dd

### Added

- Added `DeviceOrientationSensor`, a sensor that reports the physical orientation of the device as a discrete value (portrait, landscape, face up or face down), providing parity with the legacy `UnityEngine.Input.deviceOrientation` property. Read it via `DeviceOrientationSensor.current.orientation.ReadValue()`.

### Fixed

- Fixed an `OverflowException` when creating a control scheme (or other named item) whose all-numeric name exceeds `Int32.MaxValue`, which previously discarded the entered name and fell back to the default [UUM-145766](https://issuetracker.unity3d.com/product/unity/issues/guid/UUM-145766)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Note: [`UnityEngine.TouchScreenKeyboard`](https://docs.unity3d.com/ScriptReferen
[`Input.accelerationEventCount`](https://docs.unity3d.com/ScriptReference/Input-accelerationEventCount.html)<br/>[`Input.accelerationEvents`](https://docs.unity3d.com/ScriptReference/Input-accelerationEvents.html)|Acceleration events aren't made available separately from other input events. See the [accelerometer code sample on the Sensors page](query-sensors.md#measure-a-devices-acceleration).
[`Input.compass`](https://docs.unity3d.com/ScriptReference/Input-compass.html)|No corresponding API yet.
[`Input.compensateSensors`](https://docs.unity3d.com/ScriptReference/Input-compensateSensors.html)|[`InputSettings.compensateForScreenOrientation`](xref:UnityEngine.InputSystem.InputSettings).
[`Input.deviceOrientation`](https://docs.unity3d.com/ScriptReference/Input-deviceOrientation.html)|No corresponding API yet.
[`Input.deviceOrientation`](https://docs.unity3d.com/ScriptReference/Input-deviceOrientation.html)|[`DeviceOrientationSensor.current.orientation.ReadValue()`](xref:UnityEngine.InputSystem.DeviceOrientationSensor).
[`Input.gyro`](https://docs.unity3d.com/ScriptReference/Input-gyro.html)|The `UnityEngine.Gyroscope` class is replaced by multiple separate sensor Devices in the new Input System:<br/>[`Gyroscope`](xref:UnityEngine.InputSystem.Gyroscope) to measure angular velocity.<br/>[`GravitySensor`](xref:UnityEngine.InputSystem.GravitySensor) to measure the direction of gravity.<br/>[`AttitudeSensor`](xref:UnityEngine.InputSystem.AttitudeSensor) to measure the orientation of the device.<br/>[`Accelerometer`](xref:UnityEngine.InputSystem.Accelerometer) to measure the total acceleration applied to the device.<br/>[`LinearAccelerationSensor`](xref:UnityEngine.InputSystem.LinearAccelerationSensor) to measure acceleration applied to the device, compensating for gravity.
[`Input.gyro.attitude`](https://docs.unity3d.com/ScriptReference/Gyroscope-attitude.html)|[`AttitudeSensor.current.orientation.ReadValue()`](xref:UnityEngine.InputSystem.AttitudeSensor).
[`Input.gyro.enabled`](https://docs.unity3d.com/ScriptReference/Gyroscope-enabled.html)|Get: `Gyroscope.current.enabled`<br/>Set:<br/>`EnableDevice(Gyroscope.current);`<br/>`DisableDevice(Gyroscope.current);`<br/><br/> **Note:** The new Input System replaces `UnityEngine.Gyroscope` with multiple separate sensor devices. Substitute [`Gyroscope`](xref:UnityEngine.InputSystem.Gyroscope) with other sensors in the sample as needed. See the notes for `Input.gyro` above for details.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Each sensor device implements a single control which represents the data read by
| [`Gyroscope`](xref:UnityEngine.InputSystem.Gyroscope) | Measures the angular velocity of a device. | Yes | Yes | Yes |
| [`GravitySensor`](xref:UnityEngine.InputSystem.GravitySensor) | Determines the direction of the gravity vector relative to the device. | Yes | Yes | Yes |
| [`AttitudeSensor`](xref:UnityEngine.InputSystem.AttitudeSensor) | Determine the orientation of a device. | Yes | Yes | Yes |
| [`DeviceOrientationSensor`](xref:UnityEngine.InputSystem.DeviceOrientationSensor) | Reports the physical orientation of the device as a discrete value (for example portrait, landscape, face up or face down). | Yes | Yes | Yes |
| [`LinearAccelerationSensor`](xref:UnityEngine.InputSystem.LinearAccelerationSensor) | Measures the acceleration of a device unaffected by gravity. | Yes | Yes | Yes |
| [`MagneticFieldSensor`](xref:UnityEngine.InputSystem.MagneticFieldSensor) | Represents the magnetic field that affects the device. | Yes | No | No |
| [`LightSensor`](xref:UnityEngine.InputSystem.LightSensor) | Represents the ambient light measured by the device. | Yes | No | No |
Expand Down
Comment thread
K-Tone marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ private static unsafe bool ProcessMessageFromUnityRemote(IntPtr messageData)
s_State.touchscreen.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
s_State.accelerometer = InputSystem.AddDevice<Accelerometer>();
s_State.accelerometer.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
s_State.orientation = InputSystem.AddDevice<DeviceOrientationSensor>();
s_State.orientation.m_DeviceFlags |= InputDevice.DeviceFlags.Remote;
// Gryo etc. added only when we receive GyroSettingsMessage.

s_State.connected = true;
Expand Down Expand Up @@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;

namespace UnityEngine.InputSystem.Controls
{
/// <summary>
/// A control reading a <see cref="DeviceOrientation"/> value.
/// </summary>
/// <remarks>
/// This is used by <see cref="DeviceOrientationSensor"/> to report the physical orientation of the device

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great it provides parity, but is there any kind of definition of what the reference is? Might be good to define that - if possible - since that would be the first thing I would wonder as a dev using it.

/// (see <see cref="DeviceOrientationSensor.orientation"/>). It provides feature parity with the legacy
/// <c>UnityEngine.Input.deviceOrientation</c> property.
/// </remarks>
/// <seealso cref="DeviceOrientationSensor"/>
[InputControlLayout(hideInUI = true)]
public class DeviceOrientationControl : InputControl<DeviceOrientation>
{
/// <summary>
/// Default-initialize the control.
/// </summary>
/// <remarks>
/// Format of the control is <see cref="InputStateBlock.FormatInt"/>
/// by default.
/// </remarks>
public DeviceOrientationControl()
{
m_StateBlock.format = InputStateBlock.FormatInt;
}

/// <inheritdoc />
public override unsafe DeviceOrientation ReadUnprocessedValueFromState(void* statePtr)
{
var intValue = stateBlock.ReadInt(statePtr);
return (DeviceOrientation)intValue;
}

/// <inheritdoc />
public override unsafe void WriteValueIntoState(DeviceOrientation value, void* statePtr)
{
var valuePtr = (byte*)statePtr + (int)m_StateBlock.byteOffset;
*(int*)valuePtr = (int)value;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

117 changes: 117 additions & 0 deletions Packages/com.unity.inputsystem/InputSystem/Runtime/Devices/Sensor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to provide compensated value as well in the future you think? Generally I would argue that is a binding property - but just curious on your perspective after working with this?

@MorganHoarau MorganHoarau Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// the physical orientation of the device and thus must be independent of how the content is rendered.
[InputControl(name = "orientation", displayName = "Orientation", layout = "DeviceOrientation")]
public int orientation;

public FourCC format => kFormat;
}
}

namespace UnityEngine.InputSystem
Expand Down Expand Up @@ -694,4 +706,109 @@ protected override void FinishSetup()
base.FinishSetup();
}
}

/// <summary>
/// Enum describing the physical orientation of a device as reported by <see cref="DeviceOrientationSensor"/>.
/// </summary>
/// <remarks>
/// The values mirror the legacy <c>UnityEngine.DeviceOrientation</c> enum so that content migrating from
/// <c>UnityEngine.Input.deviceOrientation</c> to the Input System observes identical semantics. Note that this
/// is a package-local enum, kept independent of the legacy input module.
/// </remarks>
/// <seealso cref="DeviceOrientationSensor"/>
public enum DeviceOrientation
Comment thread
K-Tone marked this conversation as resolved.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's just me but I find DeviceOrientation confusing since it's a quantised orientation of the screen more than anything else. Does it translate well to non-mobile sensors - I guess this mimics Input Manager president so fine by all means - just sharing a reaction since device orientation could also be seen as a Vector3 relative to some other reference Vector3 if not quantized.

I would have suspected orientation could just be derived from the Attitude sensor as part of a binding but maybe I am wrong? At least that is how I have implemented orientation based bindings previously. How is this different from quantised Attitude sensor values? Is it due to getting corresponding enum from OS?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it will cover everything, but Tomas wanted it named that way as there is also a concept of screen orientation.

This one allow for value like device face up and down.

Does it translate well to non-mobile sensors

Currently, only mobile and web do have this. for gamepad, they are fed through device extension.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its fine as-is not a strong opinion, just wanted ensure we question it critically before committing to it, e.g. if a raw device orientation Vector3 is added later - it needs a matching name as well - not blocking in anyway - just a reflection

{
/// <summary>The orientation of the device cannot be determined.</summary>
Unknown = 0,

/// <summary>The device is in portrait mode, with the device held upright and the home button at the bottom.</summary>
Portrait = 1,

/// <summary>The device is in portrait mode but upside down, with the device held upright and the home button at the top.</summary>
PortraitUpsideDown = 2,

/// <summary>The device is in landscape mode, with the device held upright and the home button on the right side.</summary>
LandscapeLeft = 3,

/// <summary>The device is in landscape mode, with the device held upright and the home button on the left side.</summary>
LandscapeRight = 4,

/// <summary>The device is held parallel to the ground with the screen facing upwards.</summary>
FaceUp = 5,

/// <summary>The device is held parallel to the ground with the screen facing downwards.</summary>
FaceDown = 6,
}

/// <summary>
/// Input device representing the physical orientation of the device playing the content.
/// </summary>
/// <remarks>
/// 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 <see cref="DeviceOrientation"/> value.
/// It provides feature parity with the legacy <c>UnityEngine.Input.deviceOrientation</c> property.
///
/// Unlike the other motion sensors, the reported value is not compensated for screen orientation; it always
/// describes the physical orientation of the hardware.
///
/// <example>
/// <code>
/// 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();
/// //...
/// }
/// }
/// </code>
/// </example>
/// </remarks>
[InputControlLayout(stateType = typeof(DeviceOrientationState), displayName = "DeviceOrientation")]
public class DeviceOrientationSensor : Sensor
{
/// <summary>
/// The physical orientation of the device.
/// </summary>
/// <value>Control reporting the current <see cref="DeviceOrientation"/>.</value>
public DeviceOrientationControl orientation { get; protected set; }

/// <summary>
/// The orientation sensor that was last added or had activity last.
/// </summary>
/// <value>Current orientation sensor or <c>null</c>.</value>
public static DeviceOrientationSensor current { get; private set; }

/// <inheritdoc />
public override void MakeCurrent()
{
base.MakeCurrent();
current = this;
}

/// <inheritdoc />
protected override void OnRemoved()
{
base.OnRemoved();
if (current == this)
current = null;
}

/// <inheritdoc />
protected override void FinishSetup()
{
orientation = GetChildControl<DeviceOrientationControl>("orientation");
base.FinishSetup();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2053,6 +2053,7 @@ internal void InitializeData()
RegisterControlLayout("Touch", typeof(TouchControl));
RegisterControlLayout("TouchPhase", typeof(TouchPhaseControl));
RegisterControlLayout("TouchPress", typeof(TouchPressControl));
RegisterControlLayout("DeviceOrientation", typeof(DeviceOrientationControl));

RegisterControlLayout("Gamepad", typeof(Gamepad)); // Devices.
RegisterControlLayout("Joystick", typeof(Joystick));
Expand All @@ -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.
Expand Down
Loading