最終確認日

Godotでモバイル向けのセーフエリア対応

要点

  • DisplayServer.get_display_safe_area() を使う
  • viewport に基づいて画面ピクセル ÷ viewport サイズ でスケーリングする。
    • DisplayServer.screen_get_scale() は使わない
var screen_size := DisplayServer.screen_get_size()
var safe_area := DisplayServer.get_display_safe_area()
var viewport_size := get_viewport().get_visible_rect().size

var scale_x := screen_size.x / viewport_size.x
var scale_y := screen_size.y / viewport_size.y
var screen_size_in_ui := Vector2(
    screen_size.x / scale_x,
    screen_size.y / scale_y
)
# 実ピクセル -> UI座標へ
var safe_area_in_ui := Rect2(
    Vector2(safe_area.position.x / scale_x, safe_area.position.y / scale_y),
    Vector2(safe_area.size.x / scale_x, safe_area.size.y / scale_y)
)

# セーフエリア上部の余白
var safe_area_top_inset := safe_area_in_ui.position.y

# セーフエリア下部の余白
var safe_area_bottom_inset := screen_size_in_ui.y - safe_area_in_ui.end.y

# セーフエリア左部分の余白
var safe_area_left_inset := safe_area_in_ui.position.x

# セーフエリア右部分の余白
var safe_area_right_inset := screen_size_in_ui.x - safe_area_in_ui.end.x

画面サイズの取得方法についてはGodotでモバイル向けの画面サイズ取得にて。

調査

Swiftの場合

まずは目標の値を知る。iPhone 13 mini 実機で、Swiftで調査する。

import SwiftUI

struct ContentView: View {

    private var safeAreaInsets: UIEdgeInsets {
        let scenes = UIApplication.shared.connectedScenes
        let windowScene = scenes.first as? UIWindowScene
        return windowScene?.windows.first?.safeAreaInsets ?? .zero
    }
    
    var body: some View {
        ZStack {
            GeometryReader { geometry in
                Color.yellow
                VStack {
                    Text("画面サイズ")
                    Text("幅:\(UIScreen.main.bounds.width) 高さ:\(UIScreen.main.bounds.height)")
                    
                    Text("SafeArea")
                    Text("上:\(safeAreaInsets.top)")
                    Text("下:\(safeAreaInsets.bottom)")
                    Text("左:\(safeAreaInsets.left)")
                    Text("右:\(safeAreaInsets.right)")
                    
                    Text("SafeAresサイズ(黄色部分)")
                    Text("幅:\(geometry.size.width) 高さ:\(geometry.size.height)")
                }
            }
        }
        .background(Color.blue)
    }
}
portrait (縦持ち) landscape (横持ち)
Godotでモバイル向けのセーフエリア対応-1753334050876 Godotでモバイル向けのセーフエリア対応-1753334102953
portrait (縦持ち)の場合
  • 画面サイズは (width: 375, height: 812)
  • SafeArea insets は (top: 50, left: 0, bottom: 34, right: 0)
  • SafeArea size は (width: 375, height: 728)

landscape (横持ち)の場合

  • 画面サイズは (width: 812, height: 375)
  • SafeArea insets は (top: 0, left: 50, bottom: 21, right: 50)
  • SafeArea size は (width: 712, height: 354)

Godotで確認してみる

目標値はSwiftで確認した値。

func _ready():
    var screen_size = DisplayServer.screen_get_size()
    var safe_area = DisplayServer.get_display_safe_area()
    var screen_scale = DisplayServer.screen_get_scale()
    var screen_size_in_ui = screen_size / screen_scale
    var safe_area_in_ui = Rect2(
        safe_area.position / screen_scale,
        safe_area.size / screen_scale
    )

    # セーフエリア上部の余白
    var safe_area_top_inset = safe_area_in_ui.position.y

    # セーフエリア左部分の余白
    var safe_area_left_inset = safe_area_in_ui.position.x

    # セーフエリア下部の余白
    var safe_area_bottom_inset = screen_size_in_ui.y - safe_area_in_ui.end.y

    # セーフエリア右部分の余白
    var safe_area_right_inset = screen_size_in_ui.x - safe_area_in_ui.end.x
    
    print("Screen Size: ", screen_size)
    print("Screen Size(ui): ", screen_size_in_ui)
    print("Screen Scale: ", screen_scale)
    print("Safe Area position(display): ", safe_area.position)
    print("Safe Area size(display): ", safe_area.size)
    print("Safe Area position(ui): ", safe_area_in_ui.position)
    print("Safe Area size(ui): ", safe_area_in_ui.size)
    print("Safe Area end: ", safe_area_in_ui.end)

    print("Safe Area top inset: ", safe_area_top_inset)
    print("Safe Area left inset: ", safe_area_left_inset)
    print("Safe Area bottom inset: ", safe_area_bottom_inset)
    print("Safe Area right inset: ", safe_area_right_inset)

出力

Screen Size:                   (1125, 2436)
Screen Size(ui):               (375.0, 812.0)
Screen Scale:                  3.0
Safe Area position(display):   (0, 150)
Safe Area size(display):       (1125, 2184)
Safe Area position(ui):        (0.0, 50.0)
Safe Area size(ui):            (375.0, 728.0)
Safe Area end:                 (375.0, 778.0)
Safe Area top inset:           50.0
Safe Area left inset:          0.0
Safe Area bottom inset:        34.0
Safe Area right inset:         0.0

一致することがわかる。

ただし、この値をそのままUIに反映させてみるとうまく表示されないことがわかる。

@onready var screen_size_rect = $ScreenSize
@onready var safe_area_rect = $SafeArea

func _ready():
    var screen_size = DisplayServer.screen_get_size()
    var safe_area = DisplayServer.get_display_safe_area()
    var screen_scale = DisplayServer.screen_get_scale()
    var screen_size_in_ui = screen_size / screen_scale
    var safe_area_in_ui = Rect2(
        safe_area.position / screen_scale,
        safe_area.size / screen_scale
    )

    
    screen_size_rect.position = Vector2.ZERO
    screen_size_rect.size = screen_size_in_ui
    
    safe_area_rect.position = safe_area_in_ui.position
    safe_area_rect.size = safe_area_in_ui.size

Godotでモバイル向けのセーフエリア対応-1753345623339

viewport に合わせて再スケールする

DisplayServer.screen_get_scale() を使わずに、screen_size.x / viewport_size.x のように、画面ピクセル ÷ viewport サイズ にすることで、正しい位置に配置することができる。

extends Control

@onready var screen_size_rect = $ScreenSize
@onready var safe_area_rect = $SafeArea

func _ready():
    var screen_size = DisplayServer.screen_get_size()
    var safe_area = DisplayServer.get_display_safe_area()
    # スクリーンスケールは使わない
    var screen_scale = DisplayServer.screen_get_scale()
    
    var viewport_size = get_viewport().get_visible_rect().size
    
    var scale_x = screen_size.x / viewport_size.x
    var scale_y = screen_size.y / viewport_size.y
    var screen_size_in_ui := Vector2(
        screen_size.x / scale_x,
        screen_size.y / scale_y
    )
    # 実ピクセル -> UI座標へ
    var safe_area_in_ui := Rect2(
        Vector2(safe_area.position.x / scale_x, safe_area.position.y / scale_y),
        Vector2(safe_area.size.x / scale_x, safe_area.size.y / scale_y)
    )

    # セーフエリア上部の余白
    var safe_area_top_inset = safe_area_in_ui.position.y

    # セーフエリア下部の余白
    var safe_area_bottom_inset = screen_size_in_ui.y - safe_area_in_ui.end.y

    # セーフエリア左部分の余白
    var safe_area_left_inset = safe_area_in_ui.position.x

    # セーフエリア右部分の余白
    var safe_area_right_inset = screen_size_in_ui.x - safe_area_in_ui.end.x
    
    # UIに反映
    screen_size_rect.position = Vector2.ZERO
    screen_size_rect.size = screen_size_in_ui
    
    safe_area_rect.position = safe_area_in_ui.position
    safe_area_rect.size = safe_area_in_ui.size
    
    print("Screen Size: ", screen_size)
    print("Screen Size(ui): ", screen_size_in_ui)
    print("Screen Scale: ", screen_scale)
    print("Scale x: ", scale_x)
    print("Scale y: ", scale_y)
    print("Safe Area position(display): ", safe_area.position)
    print("Safe Area size(display): ", safe_area.size)
    print("Safe Area position(ui): ", safe_area_in_ui.position)
    print("Safe Area size(ui): ", safe_area_in_ui.size)
    print("Safe Area end: ", safe_area_in_ui.end)

    print("Safe Area top inset: ", safe_area_top_inset)
    print("Safe Area bottom inset: ", safe_area_bottom_inset)
    print("Safe Area left inset: ", safe_area_left_inset)
    print("Safe Area right inset: ", safe_area_right_inset)

これで実行すると、正しい位置とサイズで取得ができていることがわかる。

Godotでモバイル向けのセーフエリア対応-1753346565213

値はネイティブで実行した場合と異なる点に注意。

Screen Size:                   (1125, 2436)
Screen Size(ui):               (540.0, 1169.0)
Screen Scale:                  3.0
Scale x:                       2.08333333333333
Scale y:                       2.08383233532934
Safe Area position(display):   (0, 150)
Safe Area size(display):       (1125, 2184)
Safe Area position(ui):        (0.0, 71.98276)
Safe Area size(ui):            (540.0, 1048.069)
Safe Area end:                 (540.0, 1120.052)
Safe Area top inset:           71.9827575683594
Safe Area bottom inset:        48.9482421875
Safe Area left inset:          0.0
Safe Area right inset:         0.0

Autoloadで使いやすくする

Control ノードを作る

Godotでモバイル向けのセーフエリア対応-1753351678232

AutoloadSafeAreaGuide を登録する

Godotでモバイル向けのセーフエリア対応-1753351668299

スクリプトを書く

safe_area_guide.gd
extends Control

var safe_area_insets: SafeAreaInsets

func _ready():
    if not (OS.get_name() == "iOS" or OS.get_name() == "Android"):
        # モバイル端末でのみセーフエリアを考慮する.
        safe_area_insets = SafeAreaInsets.new(0, 0, 0, 0)
        return
    var screen_size := DisplayServer.screen_get_size()
    var safe_area := DisplayServer.get_display_safe_area()
    var screen_scale := DisplayServer.screen_get_scale()
    
    var viewport_size := get_viewport().get_visible_rect().size
    
    var scale_x := screen_size.x / viewport_size.x
    var scale_y := screen_size.y / viewport_size.y
    var screen_size_in_ui := Vector2(
        screen_size.x / scale_x,
        screen_size.y / scale_y
    )
    # 実ピクセル -> UI座標へ
    var safe_area_in_ui := Rect2(
        Vector2(safe_area.position.x / scale_x, safe_area.position.y / scale_y),
        Vector2(safe_area.size.x / scale_x, safe_area.size.y / scale_y)
    )

    # セーフエリア上部の余白
    var safe_area_top_inset := safe_area_in_ui.position.y

    # セーフエリア下部の余白
    var safe_area_bottom_inset := screen_size_in_ui.y - safe_area_in_ui.end.y

    # セーフエリア左部分の余白
    var safe_area_left_inset := safe_area_in_ui.position.x

    # セーフエリア右部分の余白
    var safe_area_right_inset := screen_size_in_ui.x - safe_area_in_ui.end.x
    
    safe_area_insets = SafeAreaInsets.new(
        safe_area_top_inset,
        safe_area_left_inset,
        safe_area_bottom_inset,
        safe_area_right_inset
    )

SafeAreaInsets というクラスも作った。(safe_area_insets.gd )

safe_area_insets.gd
extends RefCounted
class_name SafeAreaInsets

var top: float = 0
var left: float = 0
var bottom: float = 0
var right: float = 0

func _init(top: float, left: float, bottom: float, right: float):
    self.top = top
    self.left = left
    self.bottom = bottom
    self.right = right

つかう

SafeAreaGuide.safe_area_insets からアクセスする。

# 上部の余白
SafeAreaGuide.safe_area_insets.top

関連ノート

サイトアイコン
公開日
更新日