最終確認日
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 (横持ち) | 
|---|---|
|   |   | 
| 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.sizeviewport に合わせて再スケールする
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)これで実行すると、正しい位置とサイズで取得ができていることがわかる。
値はネイティブで実行した場合と異なる点に注意。
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.0Autoloadで使いやすくする
Control ノードを作る
Autoloadで SafeAreaGuide を登録する
スクリプトを書く
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関連ノート
- Godotでモバイル向けの画面サイズ取得
- GodotでiOSのキーボードに追従するツールバーを実装する
- Godotでモバイルゲームを作るメモ(Udemy)セクション4

公開日
更新日
 
   
   
  