最終確認日

Godot-iOSキーボードの高さの変化を通知するネイティブプラグインを作る

はじめに

Godot-iOSキーボードの高さの変化を通知するネイティブプラグインを作る-失敗を経て。

このノートからさらに改良した場合はまた新しいノートを書く -> 書いたら貼る

背景

Godotにはキーボードの高さを取得する関数はあるが、キーボードの高さの変化を取得する方法がないため、GodotでiOSのキーボードに追従するツールバーを実装するためにはiOSプラグインの実装が必要だった。

環境

参考リンク

目標

iOS では UIKeyboardWillChangeFrameNotificationでキーボードの高さの変化と、キーボード表示までのアニメーション時間を取得することができる。

今回は Godot 側にこの値を伝える iOS プラグインを作るぞい。

利用するもの

実装手順

  1. SwiftGodotを含むSwiftPackageを作る。
  2. Godot側の res://addons/ 内に プラグイン名.gdextension を作る。
  3. SwiftPackage内にiOS側の処理を書く。
  4. 作ったSwiftPackageから.framework.dylib を作る。
  5. 4で作った .framework.dylibres://addons に配置する。
  6. Godot側でSwiftのコードを使う
  7. iOS にエクスポートする
  8. エクスポートされた .xcodeprojXcodeで開き、実機でビルドを行う

実装

SwiftPackageをつくる

GodotでUIKitを使いたい!の方に書いた。

.gdextensionの書き方と、.framework.dylib の配置方法などもここでは割愛。

キーボードの値を通知する

SwiftGodotドキュメントを参考に。

Godotから見える @Godot をつけた ObjectGodotKeyboardObserver.swift を作り、その中で、 Godotからは見えない UIKitを用いた KeyboardObserver.swift を呼び出す形になる。

Mac でもビルドするためにひどいコードになっているので、あとで直す。

GodotKeyboardObserver.swift
import SwiftGodot

#initSwiftExtension(
    cdecl: "keyboard_observer",
    types: [
        GodotKeyboardObserver.self,
    ]
)

@Godot
public final class GodotKeyboardObserver: Object {

    @Signal
    var changedKeyboardHeight: SignalWithArguments<Float, Double>

    #if os(iOS)
    // このプロパティは Godot から見えないが、Swift 側で保持する
    private var nativeObserver: KeyboardObserver?
    #endif

    @Callable
    func start() {
        #if os(iOS)
        nativeObserver = KeyboardObserver(target: self)
        #endif
    }

    @Callable
    func stop() {
        #if os(iOS)
        nativeObserver?.remove()
        nativeObserver = nil
        #endif
    }

    @Callable
    func notifyKeyboardHeight(_ height: Float, duration: Double) {
        changedKeyboardHeight.emit(max(height, 0), duration)
    }
}

UIKitの処理は @Godot とは分ける必要がある。これはほぼいつも通りかけるね。

#if canImport(UIKit)

import UIKit

final class KeyboardObserver {

    weak var godotTarget: GodotKeyboardObserver?

    init(target: GodotKeyboardObserver) {
        godotTarget = target
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillChangeFrame(_:)),
            name: UIResponder.keyboardWillChangeFrameNotification,
            object: nil
        )
    }

    @MainActor
    @objc func keyboardWillChangeFrame(_ notification: Notification) {
        guard let userInfo = notification.userInfo,
              let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect,
            let duration: TimeInterval = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }

        let screenHeight = UIScreen.main.bounds.height
        let keyboardHeight = Float(screenHeight - keyboardFrame.origin.y)
        godotTarget?.notifyKeyboardHeight(keyboardHeight, duration: duration)
    }

    func remove() {
        NotificationCenter.default.removeObserver(self)
    }
}

#endif

Godotでつかう

Godotから次のようにSwiftのコードを呼び出すことができる。

var _observer: GodotKeyboardObserver


func _ready() -> void:
    if _observer == null && ClassDB.class_exists("GodotKeyboardObserver"):
        _observer = ClassDB.instantiate("GodotKeyboardObserver")
        # Swift で作ったシグナルを使う
        _observer.changed_keyboard_height.connect(_on_changed_keyboard_height)
        # Swift で作った関数を使う
        _observer.start()
        print("GodotKeyboardObserver あるよ", _observer)
    else:
        print("GodotKeyboardObserver ないよ")


func _on_changed_keyboard_height(height: float, duration: float):
    print("キーボードの高さ変わったよ!", height, duration)

_on_changed_keyboard_height(height: float, duration: float) は、キーボードの高さが変わる時に呼ばれる。(名前がちょっと良くないかも)_will_change_keyboard_height とかのがいいのか、シグナルは _on に統一したいような...

座標変換

この height は、iPhone 上での論理ピクセル値になる。(iPhone 13 mini では height は 336 を返す)

つまり、DisplayServer.screen_get_scale() をこの height にかけてあげると、物理ピクセルが取得できる。そのあとさらに Viewport 座標に直す必要がある。

  1. UIKit 上の座標からスクリーン座標に直す
  2. スクリーン座標をViewport座標に直す

座標系の話は Godotでモバイル向けの画面サイズ取得を参考にしてね。

func _on_changed_keyboard_height(height: float, duration: float) -> void:
    # iOS端末の座標 -> スクリーン座標 -> viewport座標への変換が必要.
    var target_position := position
    var screen_size := DisplayServer.screen_get_size()
    var screen_scale := DisplayServer.screen_get_scale()
    var viewport_size := get_viewport().get_visible_rect().size
    var scale_y := screen_size.y / viewport_size.y

    # スクリーン座標に変換
    var keyboard_height_in_screen := height * screen_scale
    # スクリーン座標を Viewport座標に変換
    var keyboard_height := keyboard_height_in_screen / scale_y
    # キーボードの上端の座標を取得
    var keyboard_top_y := viewport_size.y - keyboard_height

使い方

あとはアニメーションを実装するだけ。残りは次のノートを参照してね。

  • GodotでiOSのキーボードに追従するツールバーを実装する

関連ノート

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