Android Auto Rotate in Unity

Unity ignores Android’s auto-rotate settings which determine if an app should be able to auto-rotate and change orientation between; Portrait, LandscapeLeft, etc. If your game doesn’t auto-rotate between these settings then it doesn’t matter however, if it does this can really annoy users.

The first script shows how to call into the Android system and check if the auto-rotate setting is enabled on the device, if it fails it just assumes it is enabled by default.

//#undef UNITY_EDITOR // Lets you edit Android code easily with formatting, comment out before going back to editor.
#if UNITY_ANDROID && !UNITY_EDITOR  // stop auto formatter removing unused using.
using UnityEngine;
#endif

public class AndroidDetectAutoRotation {

#if UNITY_ANDROID && !UNITY_EDITOR
  private static AndroidJavaClass androidUnityActivity = null;

  /// <summary>    
  /// <para> Gets the current UnityActivity used on Android. </para>
  /// It will store the AndroidJavaClass for later use ensuring it is not creating a new
  /// class in memory every call.
  /// </summary>
  /// <returns> The AndroidActivity with the UnityPlayer running in it. </returns>
  public static AndroidJavaObject GetUnityActivity() {  // Worth noting I have separated this out into a class for common Android calls :/
    if (androidUnityActivity == null) {
      androidUnityActivity = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    }
    return androidUnityActivity.GetStatic<AndroidJavaObject>("currentActivity");
  }
#endif

  /// <summary>
  /// Checks if Android Device has auto rotation enabled.
  /// </summary>
  /// <returns> True if auto-rotate is enabled, is not Android, or it fails. </returns>
  public static bool IsAutoRotateEnabled() {
    bool isAutoRotateEnabled = true;
#if UNITY_ANDROID && !UNITY_EDITOR
    try {
      // Uses $ as System is subclass of Settings
      using (AndroidJavaClass systemSettings = new AndroidJavaClass("android.provider.Settings$System"))
      using (AndroidJavaObject contentResolver = CommonAndroid.GetUnityActivity().Call<AndroidJavaObject>("getContentResolver")) {
        isAutoRotateEnabled = (systemSettings.CallStatic<int>("getInt", contentResolver, "accelerometer_rotation") == 1);
      }
    } catch (System.Exception e) {
      Debug.LogError(e);
    }
#endif
    return isAutoRotateEnabled;
  }
}

The next script is a wrapper around IsAutoRotateEnabled, it retrieves your current Allowed Orientations for Auto Rotation from the player settings in the editor and stores them. This allows you to modify them at run time but using the IsAutoRotateEnabled function honour the devices auto-rotate settings. This is extended from a Singleton class I use described here, it should be added to a GameObject in your scene.

using System;
using UnityEngine;

/// <summary>
/// Controls the allowed auto-rotate settings, whilst honouring device auto-rotate settings.
/// <para/>
/// Allows the modification of auto-rotate settings exposed in player settings at runtime.
/// </summary>
public class AutoRotationMaintainer : Singleton<AutoRotationMaintainer> {

  /// <summary>
  /// Possible available rotation settings.
  /// Mask values, use | to combine multiple options.
  /// </summary>
  [Flags]
  public enum AllowedOrientation {
    kPortrait = 1 << 0,
    kPortraitUpsideDown = 1 << 1,
    kLandscapeLeft = 1 << 2,
    kLandscapeRight = 1 << 3
  }

  private AllowedOrientation allowedOrientations_;

  /// <summary>
  /// Generates the <seealso cref="AllowedOrientation"/>s from the player settings in the editor.
  /// </summary>
  protected new void Awake() {
    base.Awake();

    if (Screen.autorotateToPortrait) {
      allowedOrientations_ = allowedOrientations_ | AllowedOrientation.kPortrait;
    }
    if (Screen.autorotateToPortraitUpsideDown) {
      allowedOrientations_ = allowedOrientations_ | AllowedOrientation.kPortraitUpsideDown;
    }
    if (Screen.autorotateToLandscapeLeft) {
      allowedOrientations_ = allowedOrientations_ | AllowedOrientation.kLandscapeLeft;
    }
    if (Screen.autorotateToLandscapeRight) {
      allowedOrientations_ = allowedOrientations_ | AllowedOrientation.kLandscapeRight;
    }

    UpdateAutoRotationSettings();
  }

  /// <summary>
  /// Checks the devices current settings when the app has regained focus.
  /// <para/>
  /// This happens when the user changes activity or goes outside the Unity Player i.e when user 
  /// goes to settings bar and changes Auto-rotates.
  /// </summary>
  /// <param name="focusStatus"> From Unity, true if the application has focus. </param>
  private void OnApplicationFocus(bool focusStatus) {
    if (focusStatus) {
      UpdateAutoRotationSettings();
    }
  }

  /// <summary>
  /// The currently allowed orientations, if the device has auto-rotate enabled.
  /// <para/>
  /// This will be the same as the settings in player settings if it has not been modified.
  /// </summary>
  /// <returns> The current allowed orientations for Auto-rotates. </returns>
  public static AllowedOrientation CurrentAllowedOrientations() {
    if (Instance) {
      return Instance.allowedOrientations_;
    }
    return 0;
  }
  
  /// <summary>
  /// Sets the current <seealso cref="AllowedOrientation"/>s mask, this does not force it to a valid orientation.
  /// <para/> 
  /// Use <seealso cref="SetAllowedOrientation(AllowedOrientation,ScreenOrientation)"/> to
  /// force a change in the orientation.
  /// </summary>
  /// <param name="allowedAutoOrientations"> New mask of <seealso cref="AllowedOrientation"/>. </param>
  public static void SetAllowedOrientation(AllowedOrientation allowedOrientations) {
    if (Instance) {
      Instance.allowedOrientations_ = allowedOrientations;
      Instance.UpdateAutoRotationSettings();
    }
  }

  /// <summary>
  /// Sets the current <seealso cref="AllowedOrientation"/>s mask, and forces it to a specified orientation.
  /// <para/>
  /// Worth noting that when auto-rotate is off on the device, <seealso cref="ScreenOrientation.PortraitUpsideDown"/>
  /// will only allow Portrait, but when they re-enable auto-rotate it will jump to upside down, best 
  /// setting newOrientation to standard Landscape or Portrait.
  /// </summary>
  /// <param name="allowedAutoOrientations"> New mask of <seealso cref="AllowedOrientation"/>s. </param>
  /// <param name="newOrientation"> Orientation the device will be forced to</param>
  public static void SetAllowedOrientation(AllowedOrientation allowedOrientations, ScreenOrientation newOrientation) {
    Screen.orientation = newOrientation;
    SetAllowedOrientation(allowedOrientations);
  }

  /// <summary>
  /// Checks if auto rotation is possible then updates the applicable <seealso cref="Screen"/>.autorotate
  /// rotations that are allowed.
  /// </summary>
  private void UpdateAutoRotationSettings() {
    // Disable all the auto-rotate settings
    Screen.autorotateToPortrait = false;
    Screen.autorotateToPortraitUpsideDown = false;
    Screen.autorotateToLandscapeLeft = false;
    Screen.autorotateToLandscapeRight = false;

    if (AndroidDetectAutoRotation.IsAutoRotateEnabled()) {
      // Re enable any allowed auto-rotate settings.

      if (IsOrientationAllowed(AllowedOrientation.kPortrait)) {
        Screen.autorotateToPortrait = true;
      }
      if (IsOrientationAllowed(AllowedOrientation.kPortraitUpsideDown)) {
        Screen.autorotateToPortraitUpsideDown = true;
      }
      if (IsOrientationAllowed(AllowedOrientation.kLandscapeLeft)) {
        Screen.autorotateToLandscapeLeft = true;
      }
      if (IsOrientationAllowed(AllowedOrientation.kLandscapeRight)) {
        Screen.autorotateToLandscapeRight = true;
      }

      // Reset screen orientation to auto-rotate so it can change to an allowed orientation.
      // This does not force it to an allowed orientation if it is not on a correct orientation.
      // Use SetAllowedOrientation(AllowedOrientation,ScreenOrientation) to force a change
      // in the orientation.
      Screen.orientation = ScreenOrientation.AutoRotation;
    }
  }

  /// <summary>
  /// Checks if the passed in orientation is set in the <seealso cref="allowedOrientations_"/> mask.
  /// </summary>
  /// <param name="allowedOrientation"> The orientation to check is set. </param>
  /// <returns> True if it is set in the mask. </returns>
  private bool IsOrientationAllowed(AllowedOrientation allowedOrientation) {
    return ((allowedOrientations_ & allowedOrientation) != 0);
  }
}

You can now use the AutoRotationMaintainer class to change the orientation of the device, whilst still honouring the auto-rotate settings, whether it be allowing the user to enter landscape mode on a button click, or when entering combat.

The last script could be hooked up to a buttons on click event to change it to any set of orientations you wish. It uses a Property Drawer the EnumFlag property drawer to show a proper selection Ui so you can select multiple allowed orientations.

using UnityEngine;

public class AndroidChangeAutoRotation : MonoBehaviour {

	[SerializeField, EnumFlag]
	AutoRotationMaintainer.AllowedOrientation allowedOrientations;

	[SerializeField]
	ScreenOrientation screenOrientationToChangeTo;

	public void ChangeOrientation() {
		AutoRotationMaintainer.SetAllowedOrientation(allowedOrientations, screenOrientationToChangeTo);
	}
}

Related

comments powered by Disqus