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("OrientationSensor", typeof(OrientationSensor))]
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<OrientationSensor>();
InputSystem.QueueStateEvent(sensor, new OrientationState { orientation = (int)DeviceOrientation.LandscapeLeft });
InputSystem.Update();

Assert.That(sensor.orientation.ReadValue(), Is.EqualTo(DeviceOrientation.LandscapeLeft));
Assert.That(OrientationSensor.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 @@
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(OrientationSensor.current, Is.Not.Null);
Assert.That(OrientationSensor.current.remote, Is.True);
Assert.That(OrientationSensor.current.enabled, Is.True);

SendUnityRemoteMessage(new UnityRemoteSupport.DeviceOrientationMessage
{
orientation = (int)DeviceOrientation.LandscapeLeft
});
InputSystem.Update();

Assert.That(OrientationSensor.current.orientation.ReadValue(), Is.EqualTo(DeviceOrientation.LandscapeLeft));

// Disabling it should stop updates.
InputSystem.DisableDevice(OrientationSensor.current);

SendUnityRemoteMessage(new UnityRemoteSupport.DeviceOrientationMessage
{
orientation = (int)DeviceOrientation.FaceUp
});
InputSystem.Update();

Assert.That(OrientationSensor.current.orientation.ReadValue(), Is.EqualTo(DeviceOrientation.LandscapeLeft));

SendUnityRemoteMessage(new UnityRemoteSupport.GoodbyeMessage());

Check warning on line 304 in Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs#L304

Added line #L304 was not covered by tests

Assert.That(OrientationSensor.current, Is.Null);
}

Check warning on line 307 in Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Assets/Tests/InputSystem/Plugins/UnityRemoteTests.cs#L306-L307

Added lines #L306 - L307 were not covered by tests

// We don't currently support joystick input coming from the Unity Remote.
[Test]
[Category("Remote")]
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 @@
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<OrientationSensor>();
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 @@
accelerometerMessage->accelerationZ)
});
break;

case (byte)MessageType.DeviceOrientation:
if (s_State.orientation == null)
break;

Check warning on line 256 in Packages/com.unity.inputsystem/InputSystem/Editor/Plugins/UnityRemote/UnityRemoteSupport.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/InputSystem/Editor/Plugins/UnityRemote/UnityRemoteSupport.cs#L256

Added line #L256 was not covered by tests
var orientationMessage = (DeviceOrientationMessage*)messageData;
// The remote sends the DeviceOrientation enum value directly (same values as ours).
InputSystem.QueueStateEvent(s_State.orientation, new OrientationState
{
orientation = orientationMessage->orientation
});
break;
}

return false;
Expand All @@ -257,6 +270,8 @@
{
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 @@
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 @@
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 @@
// Devices that we create for receiving input from the remote.
public Touchscreen touchscreen;
public Accelerometer accelerometer;
public OrientationSensor 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="OrientationSensor"/> to report the physical orientation of the device
/// (see <see cref="OrientationSensor.orientation"/>). It provides feature parity with the legacy
/// <c>UnityEngine.Input.deviceOrientation</c> property.
/// </remarks>
/// <seealso cref="OrientationSensor"/>
[InputControlLayout(hideInUI = true)]
public class OrientationControl : InputControl<DeviceOrientation>
Comment thread
MorganHoarau marked this conversation as resolved.
Outdated
{
/// <summary>
/// Default-initialize the control.
/// </summary>
/// <remarks>
/// Format of the control is <see cref="InputStateBlock.FormatInt"/>
/// by default.
/// </remarks>
public OrientationControl()
{
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;
}

Check warning on line 42 in Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/OrientationControl.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

Packages/com.unity.inputsystem/InputSystem/Runtime/Controls/OrientationControl.cs#L39-L42

Added lines #L39 - L42 were not covered by tests
}
}

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 OrientationState : 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 = "Orientation")]
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="OrientationSensor"/>.
/// </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="OrientationSensor"/>
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.

{
/// <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(OrientationSensor.current);
/// }
///
/// protected void OnDisable()
/// {
/// InputSystem.DisableDevice(OrientationSensor.current);
/// }
///
/// protected void Update()
/// {
/// var orientation = OrientationSensor.current.orientation.ReadValue();
/// //...
/// }
/// }
/// </code>
/// </example>
/// </remarks>
[InputControlLayout(stateType = typeof(OrientationState), displayName = "Orientation")]
public class OrientationSensor : Sensor
Comment thread
K-Tone marked this conversation as resolved.
Outdated
{
/// <summary>
/// The physical orientation of the device.
/// </summary>
/// <value>Control reporting the current <see cref="DeviceOrientation"/>.</value>
public OrientationControl 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 OrientationSensor 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<OrientationControl>("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("Orientation", typeof(OrientationControl));
Comment thread
MorganHoarau marked this conversation as resolved.
Outdated

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("OrientationSensor", typeof(OrientationSensor));
RegisterControlLayout("TrackedDevice", typeof(TrackedDevice));

// Precompiled layouts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 OrientationSensor layout.
InputSystem.RegisterLayoutMatcher("OrientationSensor",
new InputDeviceMatcher()
.WithInterface(kAndroidInterface)
.WithDeviceClass("Orientation"));
Comment thread
MorganHoarau marked this conversation as resolved.
Outdated

InputSystem.onFindLayoutForDevice += OnFindLayoutForDevice;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ public static void Initialize()
new InputDeviceMatcher()
.WithInterface("iOS")
.WithDeviceClass("LinearAcceleration"));
InputSystem.RegisterLayoutMatcher("OrientationSensor",
new InputDeviceMatcher()
.WithInterface("iOS")
.WithDeviceClass("Orientation"));
Comment thread
MorganHoarau marked this conversation as resolved.
Outdated
#if UNITY_EDITOR || UNITY_IOS
InputSystem.RegisterLayout<iOSStepCounter>();
// Don't add devices for InputTestRuntime
Expand Down
Loading