【Godot Engine】画面遷移時のシーンのローディングのやり方を検討してみる

公式ドキュメント

SceneTree — Godot Engine latest documentation

Background loading — Godot Engine latest documentation

公式ドキュメントを読んで ResourceLoader を使用すれば実現できるのはなんとなく理解できたけど、いまいちコレと言った決定的なやり方が示されていない印象。

読み込みスクリプト

公式ドキュメントの Background loading の章で説明されているサンプルスクリプトをベースにしています。

SceneLoader.gd

extends Node

# 読込進捗通知シグナル
signal _scene_loading(percent)
# 読込完了シグナル
signal _loaded_scene(scene)
# 読込失敗シグナル
signal _scene_load_error

# ローディング実行時間
const limit_msec = 100
# ローダー
var loader

# 対象シーンパス設定
func set_target_scene(path):
    # ローダー作成
    loader = ResourceLoader.load_interactive(path)
    if loader == null:
        return FAILED
    return OK

# ローディング処理
func process(delta):
    if loader == null:
        return
    var time = OS.get_ticks_msec()
    while OS.get_ticks_msec() < time + limit_msec:
        var ret = loader.poll()
        if ret == OK:
            # 読み込み実行
            continue
        elif ret == ERR_FILE_EOF:
            # 読み込み完了
            emit_signal('_loaded_scene', loader.get_resource())
            return
        else:
            # 読み込み失敗
            emit_signal('_scene_load_error')
            loader = null
            return
    # 進捗通知
    var p = float(loader.get_stage() + 1) / float(loader.get_stage_count())
    emit_signal('_scene_loading', p)

自動ロード

作成したスクリプトを自動読み込みに登録します。

f:id:erudoru:20180326232018p:plain

これで全てのシーンから SceneLoader という名称でアクセスできます。

サンプルシーン作成

3つのシーン(画面)を作成します。

  • FromScene.tscn ・・・ 遷移元画面
  • Loading.tscn ・・・ ローディング画面
  • ToScene.tscn ・・・ 遷移先画面

遷移元画面

ボタン一つだけのシンプルな画面です。

f:id:erudoru:20180326232538p:plain

スクリプト

extends Node

func _on_Button_pressed():
    # ターゲットに遷移先のパスを設定
    SceneLoader.set_target_scene('res://ToScene.tscn')
    # ローディング画面に遷移(軽量な画面なのでchange_sceneで遷移)
    get_tree().change_scene('res://Loading.tscn')

ローディング画面

ローディング中画面は背景を黒く塗りつぶして、ローディングの進捗をテキストで表示

f:id:erudoru:20180326232829p:plain

スクリプト

extends Control

func _enter_tree():
    SceneLoader.connect("_scene_loading", self, "_on_scene_loading")
    SceneLoader.connect("_loaded_scene", self, "_on_loaded_scene")
    SceneLoader.connect("_scene_load_error", self, "_on_scene_load_error")

func _process(delta):
    SceneLoader.process(delta)

func _exit_tree():
    SceneLoader.disconnect("_scene_loading", self, "_on_scene_loading")
    SceneLoader.disconnect("_loaded_scene", self, "_on_loaded_scene")
    SceneLoader.disconnect("_scene_load_error", self, "_on_scene_load_error")

func _on_scene_loading(p):
    # ローディング中の画面表示を更新
    $Label.text = "Loading... %d" % [int(p * 100)]

func _on_loaded_scene(scene):
    # 画面遷移
    get_tree().change_scene_to(scene)

func _on_scene_load_error(error_type):
    # エラーの場合の表示
    print('error:' + str(error_type))

遷移先画面

遷移元画面とほぼ同じ

f:id:erudoru:20180326232538p:plain

extends Node

func _on_Button_pressed():
    # ターゲットに遷移先のパスを設定
    SceneLoader.set_target_scene('res://FromScene.tscn')
    # ローディング画面に遷移(軽量な画面なのでchange_sceneで遷移)
    get_tree().change_scene('res://Loading.tscn')

動作確認

画面自体が軽量なのでほぼ一瞬で遷移してしましますが一応ローディング中の表示が行われます。

f:id:erudoru:20180326233718p:plain

【Godot Engine】TileMap の使い方

素材

使用させていただく素材はこちら。

opengameart.org

公式ドキュメント

Using tilemaps — Godot Engine latest documentation

基本的には公式ドキュメント通りの手順でタイルが作成できますが、別の方法を紹介したいと思います。

Region を使用して Sprite を追加

一つ一つ Sprite を追加して Texture に設定するのは骨が折れるため、少しだけ楽になる方法です。

そのためには、タイル系の素材の中には切り出された画像ではなく全タイルを1枚の画像に固めた画像が必要になります。

今回使用した素材ではBasePack > Tiles > tiles_spritesheet.png がそれになります。

Node に Sprite を一つ追加して、Texture に画像を設定し Region を ON に設定します。

f:id:erudoru:20180325121658p:plain

f:id:erudoru:20180325122030p:plain

そこから画面下部に表示される「Texture Region」をクリックします。「Texture Region」が見当たらない場合 Scene タブで Sprite が選択されているかを確認してください。

f:id:erudoru:20180325122202p:plain

ウィンドウが開いたら Snap モードを「Grid Snap」にします。

f:id:erudoru:20180325122456p:plain

Godot のイマイチなところとしてエディタの UI がかなり固定的なため、PC の画面が小さいとウィンドウの右側が見切れてしまい、画像拡大縮小や縦スクロールができなくなってしまいます。

f:id:erudoru:20180325122925p:plain

操作できなくなってしまった場合は、一時的にファイルシステムタブを他のタブに統合して領域を広げる事で対応できます。(タブの右にある︙から移動させます)

f:id:erudoru:20180325123237p:plain

今回使用した素材は 70px のタイルが 2px 区切りになっているためそれぞれ設定します。これで準備が完了しました。

f:id:erudoru:20180325123509p:plain

ここからタイル Sprite の追加作業になります。

Scene タブでもう一度 Sprite をクリックして選択状態にします。

f:id:erudoru:20180325123758p:plain

Texture Region ウィンドウ上で1タイル分を選択します(クリック選択でなく、ドラッグで領域選択)

f:id:erudoru:20180325124010p:plain

この状態で Sprite を複製します。ショートカットは Ctrl + D (Command + D) ですが、環境によって異なるかもしれないため Sprite を右クリックして複製の箇所のショートカットを確認してください。

f:id:erudoru:20180325124235p:plain

これで Node ツリーに Sprite が追加されていきます。

必要な分コピーできたら公式ドキュメントの手順に従い TileSet をエクスポートします。

あとは TileMap に設定して使うだけです。

f:id:erudoru:20180325124854p:plain

プラグインを使用する

タイル数が少なければ手動でもいいのですが、数が多いとどうしてもしんどいのでプラグインを使用すると更に楽ができます。

AssetLib からカテゴリーを 2D Tools で絞り込むと幾つかツールが表示されます。

今回は TileSetCutter を使用します。

github.com

ただしこのプラグイン addons ディレクトリにインストールされずプロジェクト直下にインストールされてしまうため、プロジェクトのアイコンを上書きしてしまいます。addons 直下に強制しないところが Godot の自由さの良いところかもしれませんが、せめてインストール時にインストール先ディレクトリを指定したいなぁ。

手間ですが一時的に icon.png を他の名称に変更してからプラグインをインストールします。その後、プロジェクト直下に置かれたファイルを addons/TileSetCutter に移動させます。最後に icon.png を元の名称に戻します。

インストール完了後 Project Settings のプラグインタブに TileSetCutter が追加されているため、ステータスを Active に変更します。

f:id:erudoru:20180325130626p:plain

Node 一覧に TileSetCutter が表示されるようになるため、新しく作成した TileSet 作業用のシーンに追加します。

f:id:erudoru:20180325130830p:plain

f:id:erudoru:20180325130835p:plain

TileSize に区切り線込のサイズを指定します。更に Separation に区切り線のサイズを指定し、Texture に画像をドラッグするだけで自動で区切られた Sprite が Node に追加されます。とっても楽です。

f:id:erudoru:20180325131555p:plain

f:id:erudoru:20180325131715p:plain

後は各 Sprite の名称変えて、必要に応じて当たり判定を追加すれば完了です。

画像側にズレがあると対応できないケースもありますが、シンプルなプラグインなので自ら修正して調整を入れるのもいいかもしれません。

【Godot Engine】作成したゲームを公開しました

Godot Engine 3 をさわり始めて1ヶ月。Android 版を公開してみました。

f:id:erudoru:20180322234322g:plain

play.google.com

リソースリンク

画像

opengameart.org

opengameart.org

Platformer Art Complete Pack (often updated) | OpenGameArt.orgopengameart.org

opengameart.org

フォント

opengameart.org

サウンド

opengameart.org

opengameart.org

【Godot Engine】一時停止・終了確認ポップアップ

ポップアップ作成

Godot には何種類かポップアップが用意されていますが、自分でオリジナルのポップアップを作るのも簡単にできます。

f:id:erudoru:20180321005749p:plain

シーンを新規作成し、ざっくりこんな感じのポップアップダイアログを作成します。

f:id:erudoru:20180321000656p:plain

ポップアップスクリプト

ポップアップに以下のスクリプトを追加します。

func _enter_tree():
    # ツリーに追加された際に、「閉じる」で終了させない
    get_parent().get_tree().set_auto_accept_quit(false)

# Popup の about_to_show を connect
func _on_Popup_about_to_show():
    # ポップアップ表示時にシーンツリーを停止
    get_parent().get_tree().paused = true

# Cancel ボタンの pressed を connect
func _on_Cancel_pressed():
    # シーンツリーを再開
    get_parent().get_tree().paused = false
    hide()

# Ok ボタンの pressed を connect
func _on_Ok_pressed():
    # アプリを終了
    get_parent().get_tree().quit()

paused 中のボタン操作

SceneTree.paused に true を設定するとザ・ワールド並に全てが停止します。ポップアップもその例に漏れず、一切の操作を受け付けなくなっていまいます。そのため paused の影響を受けずにポップアップの操作を行えるように追加で設定が必要です。

ポップアップの Cancel ボタンと OK ボタンを押せる様に、それぞれのボタンのインスペクターから Pause に Process を設定して paused 対象外に設定します。

f:id:erudoru:20180321002119p:plain

アプリに組み込み

確認用アプリはシンプルに背景色 (ColorRect) を設定して、先に作成したポップアップシーンを追加します。

f:id:erudoru:20180320235536p:plain

アプリのルート Node に以下のスクリプトを追加します。

閉じる、バックグラウンド、バックキー押下 (Android) 時にポップアップを表示するようにします。

func _notification(what):
    var quit_type = [
        # 閉じる
        MainLoop.NOTIFICATION_WM_QUIT_REQUEST,
        # バックグラウンド
        MainLoop.NOTIFICATION_WM_FOCUS_OUT,
        # バックキー
        MainLoop.NOTIFICATION_WM_GO_BACK_REQUEST
    ]
    # ポップアップ表示
    if quit_type.has(what):
        $Popup.popup()

ポイントは $Popup.show ではなく $Popup.popup でポップアップを表示すること。show を使用すると ポップアップの about_to_show シグナルが呼ばれません。

バックキーの設定

このまま Android アプリをエクスポートすると、バックキー押下時に NOTIFICATION_WM_GO_BACK_REQUEST が呼ばれず、そのままアプリが終了してしまいます。

追加で Project > Project Settings > Application > Config から Quit On Go Back をオフにします。

f:id:erudoru:20180321004452p:plain

これで、Android でバックキー押下時に NOTIFICATION_WM_GO_BACK_REQUEST が呼ばれます。

設定オンでアプリが終了するのはいいのですが、せめて NOTIFICATION_WM_QUIT_REQUEST を呼んでほしいのですが、これが仕様なのかもしれませんが現在のところ何も呼ばれずそのままアプリが終了してしまいます。

動作確認

アプリ実行後、バツボタンやアプリをバックグラウンド、Androidであれば更にバックキーを押下するとアプリ終了確認ダイアログが表示されます。

f:id:erudoru:20180321004616p:plain

公式ドキュメント

Handling quit requests — Godot Engine latest documentation

Pausing games — Godot Engine latest documentation

追記

popup で表示すると、ポップアップの周辺をタップした際にポップアップが閉じられてしまう様です。モーダル表示にするためには show を使用する必要がありそうです。

そうなると about_to_show シグナルが呼ばれないため独自に表示メソッドを作成した方がいいですね。

# これは disconnect してしまう
func _on_Popup_about_to_show():
    get_parent().get_tree().paused = true

# 新規作成
# popup の代わりにこれを呼ぶ
func show_popup():
    get_parent().get_tree().paused = true
    show()

【Godot Engine】サウンドファイルのループ設定

音声ファイルのループ設定をしようとしたときに少しハマったのでメモ

AudioStreamPlayer などをツリーに追加して音声ファイルを設定しますが、node の設定を行うインスペクター上にループ再生か1回のみ再生かを指定する項目がありません。

ループに関する設定はリソースファイルの設定の一つという扱いらしく、インポートタブから設定を行う必要があるようです。

ogg ファイルの場合

f:id:erudoru:20180318172942p:plain

wav ファイルの場合

f:id:erudoru:20180318173006p:plain

ということが公式ドキュメントにはちゃんと書いていましたが、まったく見落としていました。

Importing audio samples — Godot Engine latest documentation

【Godot Engine】2Dキャラクターアニメーション

素材

今回使用させて頂いた素材はこちら。

Platformer Art Deluxe | OpenGameArt.org

AnimatedSprite

複数の画像をパラパラ漫画のように表示するアニメーションです。

使用は非常に簡単で、Node に AnimationSprite を追加します。Frames を追加するまでは警告が表示されます。

f:id:erudoru:20180313224017p:plain

インスペクターから New Sprite Frames を選択

f:id:erudoru:20180313224121p:plain

追加した Frames をもう一度クリクして SpriteFrames ウィンドウを開きます。

f:id:erudoru:20180313224953p:plain

一つデフォルトのアニメーションを追加されているので名前を "walk" に変更して、ファイルシステムから画像を枠内にドラッグ&ドロップすれば完了です。

f:id:erudoru:20180313224152p:plain

インスペクターから Animation で walk を選択して Playing をチェックするか、

f:id:erudoru:20180313224604p:plain

もしくはスクリプトで play メソッドを実行すれば再生されます。

func _ready():
    play("walk")

基本ループ再生のようです。

f:id:erudoru:20180313224852p:plain

Sprite + AtlasTexture + AnimationPlayer

アトラス画像(1枚絵)を切り取って表示するアニメーションです。

Node に Sprite を追加して、Texture に AtlasTexture を追加します。

f:id:erudoru:20180313225628p:plain

f:id:erudoru:20180313225645p:plain

追加した AtlasTexture をクリックして、Atlas に アニメーションさせたい画像をドラッグ&ドロップします。

f:id:erudoru:20180313225716p:plain

Region にアニメーション対象にしたい画像の座標を指定します。この画像では全体を指定しています。

f:id:erudoru:20180313225947p:plain

Sprite のインスペクターの Animation のVframes、Hframes で縦横の画像枚数を指定します。

f:id:erudoru:20180313230108p:plain

次に Sprite の子 Node に AnimationPlayer を追加します。Sprite の下に追加することは強制ではありませんが、AnimationPlayer のデフォルト設定が親 Node をアニメーションさせるようになっているため、この形が面倒なくていいかと思います。別の場所に追加する場合はインスペクターの Root Node から対象の Node を選択し直します。

f:id:erudoru:20180313230238p:plain

手順が多いので番号付きで記載します。

f:id:erudoru:20180313230644p:plain

  1. アニメーションを追加します。アニメーション名が聞かれるため "Walk" で決定します。

  2. アニメーションの長さと、ステップ数を入力します。長さは後で簡単に縮める事ができるため、長さがわからない場合は適当に大きな数字を入れておきます。

  3. Frame の行に表示されている鍵マークをクリックします。初回はキーフレームを追加するための確認ダイアログが表示されますが OK を押し、鍵を画像のコマ数分クリックするとフレームが追加されます。

  4. ループ再生したい場合は ON にします。

AnimationPlayer のインスペクターの Current Animation で "Walk" を選択すると再生されます。

f:id:erudoru:20180313231518p:plain

ただ、バグ(?)なのかアプリとして実行するとアニメーションされないため、スクリプト上から play メソッドを実行する必要があるようです。

func _ready():
    $AnimationPlayer.play("Walk")

これでアニメーションが実行されます。

f:id:erudoru:20180313231342p:plain

Cutout animation

より細かなキャラクターのアニメーションを実行したい場合は、公式ドキュメントのこちらを参照してください。

Cutout animation — Godot Engine latest documentation

【Godot Engine】ブロック画像を繰り返し表示する

タイルを使用せずに画像を繰り返し表示する場合 Sprite の Region を使用します。

f:id:erudoru:20180310130443p:plain

Region.Enabled をオンにして、Rect に「画像サイズ x 繰り返し回数」を設定します。

繰り返し表示させるため、テクスチャ画像をクリックして設定を開き Repeat フラグを立てます。

f:id:erudoru:20180310130553p:plain

指定したサイズ分繰り返し表示されます。

f:id:erudoru:20180310130924p:plain

ただし、現在 Texture にバグがあるらしく、フラグが反映されないようです。Github の Issue を見る限り修正されていて最新版にも取り込まれているようなんですが。。。うーん。。

(この記事の時点での最新版は v3.0.2 です)

texture.set_flags does not work · Issue #14526 · godotengine/godot · GitHub

該当の修正を細かく見ているわけではないですが、もしかしたらこの修正はエディタ側の挙動には影響していないのかもしれません。。

プレビューでは繰り返し表示されていても、実際に実行すると Repeat フラグが反映されないため正しく表示されません。

f:id:erudoru:20180310131046p:plain

その為、当面の暫定処置としてスクリプトを追加して動的に Repeat フラグを設定します。

func _ready():
    $Sprite.texture.flags = Texture.FLAG_REPEAT

これで正しく繰り返し表示されます。

f:id:erudoru:20180310131334p:plain

動的にサイズを変更する

動的に大きさを変更する場合、Region.Rect を書き換えます

func _ready():
    var w = 4
    var h = 3
    var texture_size = $Sprite.texture.get_size()
    $Sprite.region_rect = Rect2(0, 0, texture_size.x * w, texture_size.y * h)
    $Sprite.texture.flags = Texture.FLAG_REPEAT

f:id:erudoru:20180310131830p:plain