godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Add Notification that is sent whenever device screen orientation is changed #5971

Open David1Socha opened 1 year ago

David1Socha commented 1 year ago

Describe the project you are working on

A 2D game that plays in landscape mode on several platforms including Android and iOS

Describe the problem or limitation you are having in your project

As far as I can tell, there is currently no way for a Godot game to handle changes in device orientation. One use-case where this can be problematic: in my game, I want to ensure that UI controls are not inside the bezel/notch of an Android/iPhone's screen. OS.get_window_safe_area() works well for this purpose; however, if the game is in sensor_landscape mode, upon rotating the device 180 degrees to the opposite landscape mode, OS.get_window_safe_area() needs to be called again so the UI safe area can be recalculated. Without a notification/callback/signal on orientation change, I don't see a good way to accomplish this. To avoid this issue, I'm currently keeping orientation mode as landscape (device can only turn one way), but I would prefer to support both possible landscape orientations.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Add a new Notification code called something like NOTIFICATION_OS_ORIENTATION_CHANGED that is similar to many of the OS-level notifications listed here. This notification would be triggered whenever an Android or iOS device changes its orientation.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Not 100% sure on what implementation would look like, but Android and iOS both expose callbacks for device orientation change, so I think Godot could essentially listen to the appropriate device-specific callbacks for orientation change, then fire a notification whenever the callbacks are hit.

If this enhancement will not be used often, can it be worked around with a few lines of script?

From searching Godot documentation, Godot source code/issues, and searching online, I haven't been able to find any existing callback or notification that would allow a user to respond to changes in device screen orientation.

Is there a reason why this should be core and not an add-on in the asset library?

I think this would require updates in the Android/iOS-specific Godot wrappers, so I don't think an add-on could really achieve this.

Calinou commented 1 year ago

For portrait-to-landscape rotation (or vice versa), you should be able to detect whether the root's viewport size has changed, since it will change from width × height to height × width:

func _ready():
    get_viewport().connect("size_changed", self, "_root_viewport_size_changed")

func _root_viewport_size_changed():
    print("Size changed")

I don't know of a workaround for 180-degree rotations though (maybe using the accelerometer, but this will ignore user-defined rotation locks).

ProFiLeR4100 commented 2 weeks ago

Not sure if it is broken in 4.3.beta1.mono, but on my device (Android, Poco F5, MIUI 14.0.6) "size_changed" signal is not being triggered when I rotate my phone from landscape to portrait and vice versa, though the viewport is rotating and scale is applied.

@Calinou which version of engine you were using when you wrote a comment?

Here the code that I was using, I've tried string literal and cached SignalName, no success.

public partial class Core : Node {
    private Vector2I _targetViewportSize = new(1280, 704);
    private Window? _window;

    public override void _Ready() {
        _window = GetWindow();

        GetViewport().Connect("size_changed", Callable.From(CheckAndUpdateScreenViewport));
        // next code also don't work
        // GetViewport().Connect(
        //  Viewport.SignalName.SizeChanged,
        //  Callable.From(CheckAndUpdateScreenRotation)
        // );
        CheckAndUpdateScreenViewport();
    }

    private void CheckAndUpdateScreenViewport() {
        _window.ContentScaleSize = _window.Size.X > _window.Size.Y
            ? _targetViewportSize // Landscape
            : new Vector2I(_targetViewportSize.Y, _targetViewportSize.X); // Portrait
    }
}
Calinou commented 1 week ago

@Calinou which version of engine you were using when you wrote a comment?

I didn't test the particular code sample for the use case of changing device orientation, but it did work on desktop at least (I think this was 4.0.beta). Which stretch mode and stretch aspect do you use in the Project Settings? If using the viewport stretch mode and keep stretch aspect, the viewport's size won't change when the window is resized or the device orientation changes.

ProFiLeR4100 commented 11 hours ago

Yep, I'm using viewport and keep values, like as you said. As a workaround now I'm checking GetWindow().Size in _PhysicsProcess, this is unoptimized but I haven't found any signals on Window and WindowWrapper classes that could be used for this.

If someone want to repeat this, here is the code that I'm using:

public partial class Core : Node {
    private Window? _window;
    private Vector2I _previousResolution = Vector2I.Zero;
    private Vector2I _targetViewportSize = new(1280, 704);

    [Signal]
    public delegate void OrientationChangedEventHandler(DisplayServer.ScreenOrientation screenOrientation, Vector2I resolution);

    public override void _Ready() {
        _window = GetWindow();
        CheckAndUpdateScreenRotation();
    }

    public override void _PhysicsProcess(double delta) {
        base._PhysicsProcess(delta);
        CheckAndUpdateScreenRotation();
    }

    private void CheckAndUpdateScreenRotation() {
        if (_window is null) {
            return;
        }

        Vector2I currentResolution = _window.Size;

        if (_previousResolution.Equals(currentResolution)) {
            return;
        }
        _previousResolution = currentResolution;

        Vector2I newViewportSize = currentResolution.X > currentResolution.Y
            ? _targetViewportSize // Landscape
            : new Vector2I(_targetViewportSize.Y, _targetViewportSize.X); // Portrait

        if (_window.ContentScaleSize.Equals(newViewportSize)) {
            return;
        }

        _window.ContentScaleSize = newViewportSize;

        EmitSignal(
            SignalName.OrientationChanged,
            (long) (currentResolution.X > currentResolution.Y
                ? DisplayServer.ScreenOrientation.Landscape
                : DisplayServer.ScreenOrientation.Portrait),
            currentResolution
        );
    }
}