Godot-iOSキーボードの高さの変化を通知するネイティブプラグインを作る
はじめに
Godot-iOSキーボードの高さの変化を通知するネイティブプラグインを作る-失敗を経て。
このノートからさらに改良した場合はまた新しいノートを書く -> 書いたら貼る
背景
Godotにはキーボードの高さを取得する関数はあるが、キーボードの高さの変化を取得する方法がないため、GodotでiOSのキーボードに追従するツールバーを実装するためにはiOSプラグインの実装が必要だった。
環境
- Godot 4.4.stable
- Xcode 16.2
参考リンク
- Creating iOS plugins - Godot Docs- 失敗編で使っていたやり方。そのうちこっちでも再チャレンジする。
 
- zt-pawer/SwiftGodotIosPlugins- 激感謝
 
- migueldeicaza/SwiftGodot
目標
iOS では UIKeyboardWillChangeFrameNotificationでキーボードの高さの変化と、キーボード表示までのアニメーション時間を取得することができる。
今回は Godot 側にこの値を伝える iOS プラグインを作るぞい。
利用するもの
実装手順
- SwiftGodotを含むSwiftPackageを作る。
- Godot側の res://addons/内にプラグイン名.gdextensionを作る。
- SwiftPackage内にiOS側の処理を書く。
- 作ったSwiftPackageから.frameworkと.dylibを作る。
- 4で作った .frameworkと.dylibをres://addonsに配置する。
- Godot側でSwiftのコードを使う
- iOS にエクスポートする
- エクスポートされた .xcodeprojをXcodeで開き、実機でビルドを行う
実装
SwiftPackageをつくる
GodotでUIKitを使いたい!の方に書いた。
.gdextensionの書き方と、.framework や .dylib の配置方法などもここでは割愛。
キーボードの値を通知する
SwiftGodotのドキュメントを参考に。
Godotから見える @Godot をつけた Object の GodotKeyboardObserver.swift を作り、その中で、 Godotからは見えない UIKitを用いた KeyboardObserver.swift を呼び出す形になる。
Mac でもビルドするためにひどいコードになっているので、あとで直す。
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)
    }
}
#endifGodotでつかう
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 座標に直す必要がある。
- UIKit 上の座標からスクリーン座標に直す
- スクリーン座標を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のキーボードに追従するツールバーを実装する
関連ノート
- Godotでモバイル端末のキーボードの高さの取得する
- GodotでiOSのキーボードに追従するツールバーを実装する
- GodotでiOS用プラグインのgodot-ios-pluginsを使いたい
