Godot4勉強ノート（自分用まとめ）

# まえがき
自分用のメモです。
わかりにくい記述があってもご容赦ください。

2025/03/08 追記
Claude Sonnet 3.7 凄いよこれ
Godot4について質問するとちゃんとしたものを出してくれる
まずは彼に聞いてみてくれ


### 環境
Windows11
~~Windows10~~

Steam版 4.4.1
~~Steam版 4.2~~
~~Steam版 4.1.2~~

# 本題

## 参考にしたサイトなど
[UIのレイヤーを最前面にする方法](https://qiita.com/2dgames_jp/items/f72896469a519115f308)
[CharacterBody2Dの衝突判定](https://scrapbox.io/kidaaam-92022284/Godot:_CharacterBody2D%E5%90%8C%E5%A3%AB%E3%81%AE%E7%A7%BB%E5%8B%95%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E8%A1%9D%E7%AA%81%E5%88%A4%E5%AE%9A)
[2Dオブジェクトの移動（公式Docs）](https://docs.godotengine.org/ja/4.x/tutorials/2d/2d_movement.html)
[2Dアニメーションの設定(公式Docs)](https://docs.godotengine.org/ja/4.x/tutorials/2d/2d_sprite_animation.html)
[コリジョンレイヤーの使い方](https://qiita.com/2dgames_jp/items/062ab83ea1fcdb2c7f5e)
[Nodeオブジェクトで使える関数など](https://docs.godotengine.org/ja/4.x/classes/class_node.html)
[ポーズ機能の実装1](https://zenn.dev/saitos/articles/godot-engine-pause-mode-customize)
[ポーズ機能の実装2](https://zenn.dev/slm/articles/54ad795561afab#pause_mode%E3%81%8Cprocess_mode%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%AB)
[特定のキー入力を検知（公式Docs）](https://docs.godotengine.org/ja/4.x/tutorials/inputs/inputevent.html)


## 一定間隔で処理を繰り返す
:::note
結論
awaitは待機しないでreturnする
JSとかのawaitとは別物
待ち合わせではなく、実行の予約である
:::

検索して出てくる記事を見ると、

```GDScript
await get_tree().create_timer(1.0).timeout
```

という記述がよく出てくる。
のだが、ここがUnityとかと扱いが違った。
awaitの挙動としては、それ以降の処理を予約するイメージ。

```GDScript
var count = 0
func _process(delta):
    count += 1
    await get_tree().create_timer(1.0).timeout
    print("now count = %s" % count)
```

というプログラムの場合、_process()が呼ばれるたびにcountが+1される。
その後、awaitで1秒待つ処理に移るが、これ以降の処理は`続きは1秒後に実行するで。今回の_process()の処理はここで一旦終わる(returnする)で。`となる。
_process()の処理が一旦終わって、また先頭に戻り+1。そして処理の予約だけされて次へ。
というのが最初の1秒の間に複数繰り返されるので、1秒後に表示される値は1ではなく、64とか123とかの全然違う値になる。

ということで、1秒待って処理したい場合は以下のように書く必要がある。
（_process関数の中でこの待ち方をすべきではない）

```GDScript
var count = 0
var elapsed = 0
func _process(delta):
    elapsed += delta
    if elapsed >= 1.0:
        count += 1
        print("now count = %s" % count)
        elapsed = 0
```

応用すると、こんな書き方もできる。

```GDScript
func Func1():
    pass  # 好きな処理

func Func2():
    pass  # 好きな処理

var count = 0
var elapsed = 0
func _process(delta):
    elapsed += delta
    if elapsed >= 1.0:
        Func1()

    # 別の処理

    if elapsed >= 1.0:
        Func2()

    # 別の処理

    if elapsed >= 1.0:
        elapsed = 0
```


……ところで、1秒間隔の処理と2秒間隔の処理を扱おうとしたらどう書くんだろうね。
1秒用の`elapsed1`と2秒用の`elapsed2`を用意して回すことになる？
なんか気持ち悪いなぁ。
~~良い書き方を思いついたら追記する。~~
次の節で説明する。


## 一定間隔で実行したい処理を複数扱う
:::note
結論
Timerオブジェクト使おう
```gdscript
timer1.timeout.connect(func1)
timer1.one_shot = true  # 1回実行すると終わり
timer1.start(0)  # 何秒後に開始するか
```
:::

周期が違うときの話。
シーンにタイマーオブジェクトを追加し、それを変数に入れておく。
~~同一オブジェクトでもOK。~~
勘違いだった。周期が上書きされるので、別々でタイマーオブジェクトを用意する必要がある。
(下記スクリプトは修正済み)
```GDScript
var timer1 = $Timer1
var timer2 = $Timer2
```

あとは、_ready関数の中でタイマーに関数を割り当てて起動するだけ。
```GDScript
# 1秒周期で処理する関数
func func1():
    while true:
        await get_tree().create_timer(1.0).timeout
        print("func1")

# 0.5秒周期で処理する関数
func func2():
    while true:
        await get_tree().create_timer(0.5).timeout
        print("func2")

func _ready():
    var timer1 = $Timer1
    var timer2 = $Timer2
    
    timer1.timeout.connect(func1)
    timer1.one_shot = true  # 1回実行すると終わり
    timer1.start(0)

    timer2.timeout.connect(func2)
    timer2.one_shot = true
    timer2.start(0)
```



## 関数の引数に関数を渡す
:::note
結論
引数の型として`Callable`を指定するだけ
```gdscript
func Caller(f: Callable):
    f.call()
```
:::

今どきの言語ならたいていできるアレ。
Pythonで書くとこんな感じ。

```Python
def SayHello():
    print("Hello")

def Caller(f):
    f()

if __name__ == "__main__":
    Caller(SayHello)
```

これをGDScriptでやろうとすると、ちょっと面倒だった。（公式ドキュメントにはちゃんと書いてある）
忘れそうだった＆英語だったのでここにメモ

```GDScript
func SayHello():
    print("hello")

func Caller(f: Callable):
    f.call()

func _ready():
    Caller(SayHello)
```

こんな感じで、引数を書くときに`Callable`だと明示した上で、使うときは`call`メソッドを呼ぶ必要がある。


## オブジェクト生成
:::note
結論
```GDScript
var obj = preload("res://<目的のプレハブ.tscn>")
func _pressed():
    var _obj : Node2D = obj.instantiate()
    _obj.position.x = 100
    _obj.position.y = 100
```
:::


基本的にはUnityと同じ。
シーンで組み立てて、ドラッグアンドドロップでファイルシステムに放り込んでプレハブ作成。
それをプログラムで呼び出す。

そのときのスクリプトは以下のようになる。
（ボタンを押したらオブジェクト生成）

```GDScript
var obj = preload("res://<目的のプレハブ.tscn>")
func _pressed():
    var _obj : Node2D = obj.instantiate()
    _obj.position.x = 100
    _obj.position.y = 100
```

こう書けば、指定した座標にオブジェクトが生成される。
あとは基本的にUnityと同じ感じ。


## オブジェクトにアタッチした関数を実行
:::note
結論
```GDScript
var obj = preload("res://<目的のプレハブ.tscn>")
func _pressed():
    var _obj : Node2D = obj.instantiate()
    _obj.setPos(100, 100)
```
:::

Unityより簡潔に書けるので最高。

以下のように、直接メソッド呼び出しできる。
上記の座標指定であれば、こんな感じ。

```GDScript
var obj = preload("res://<目的のプレハブ.tscn>")
func _pressed():
    var _obj : Node2D = obj.instantiate()
    _obj.setPos(100, 100)
```

ただ、こう書く場合は、目的のスクリプトはそのオブジェクトのルートノードにアタッチしておく必要がある。

```
StaticBody2D
└－MeshInstance2D
```

みたいな構成の場合、一番上の`StatiBody2D`にアタッチする。


## シーンツリーにあるオブジェクトを絶対パスで取得
:::note
結論
正式な最上位ノードは`root`なので、絶対パスではこう書く
```GDScript
var Balls = get_node("/root/Root/Balls")
```
:::

Unityで言う`GameObject.Find`とかの処理。
Godot4では`get_node("<object path>")`と書くらしい。

絶対パスで指定しようとして詰まったのでメモ。
正しく記述してるはずなのに「そんなものは無い」と言われた。

で、Godot4におけるシーンツリーの全貌がこれ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/382806/a96c8d8e-92ee-d05a-0758-977e13bfe4f3.png)

ルートノードは`Root`ではなく`root`。
ずっとローカルツリー（`Root`以下のツリー）しか見えてなかったので混乱した。
ローカルツリーだけ見ると`Root`が最上位に見えるが、実はもっと上があるって話。
なお、このリモートやローカルの切り替えは**実行中**でないと行えない。

実際のプログラムとしては以下のような記述になる。
```GDScript
var Balls = get_node("/root/Root/Balls")
```

[グローバル変数の設定](https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html)をしてやれば、ローカルルートの名前を変えたりしても対応が楽。
```GDScript
var Balls = get_node("%s/Balls" % Global.LocalRootPath)
```

しかし、この仕様についてドキュメントに記載が無いように思うんだが、調査不足だろうか。



自身の直下の子要素を取得するなら`$`マークを使うことで取得可能。
上記ツリーにおいて、`Root`から子要素の`Camera2D`を参照したいなら以下のように書ける。
```GDScript
var camera = $Camera2D
```



## UIを最前面に置く
:::note
結論
`CanvasLayer`を親にして、`z_index`を大きくする
:::

（3Dの場合は知らん。必要になったら調べる）
2Dの場合、`z_index`なるもので描画順を管理してるらしい。
最前面に近いほど値が大きい。
ということで、CanvasLayerノードを作成し、それの`z_index`を大きくしておく。
そのノードの中にUI部品を入れれば、これでボタンなどが最前面に表示されるってﾜｹ。
[参考URL](https://qiita.com/2dgames_jp/items/f72896469a519115f308)


## 接触した相手を調べる(PhsyicsBody2D同士)
※PhysicsBody: CharacterBody, StaticBody, Rigidbody

自機をPlayerグループに追加しておく。

以下のスクリプトを、敵機、敵弾、…のどれかに追加する。
```GDScript
for i in get_slide_collision_count():
    var collider = get_slide_collision(i).get_collider()
    if collider.is_in_group("Player"):
    	queue_free()  # 自分を(シーンツリーから)消す
```


## 接触した相手を調べる(Area2Dを使用)
[公式Docs](https://docs.godotengine.org/en/stable/classes/class_area2d.html#method-descriptions)

Area2Dの中に接触している（入っている）相手を取得する

**相手がPhysicsBody**の場合は`get_overlapping_bodies()`でまとめて取得できる。
**相手がArea2D**の場合は`get_overlappingareas()`でまとめて取得できる。

使用例
```GDScript
for body in get_overlapping_bodies():
    if body.is_in_group("Enemy"):
        body.doSomething()
```


## アニメーションのコントロール
詳細は[公式Docs](https://docs.godotengine.org/ja/4.x/tutorials/2d/2d_sprite_animation.html)を参照。

単に割り当てるだけでは再生されない。
設定したアニメーションを名指しで再生/停止を制御する必要がある。

```GDScript
$CharaAnime.play("run")
```

また、左右反転や上下反転も同様に制御できる。
```GDScript
$CharaAnime.flip_h = true  # 左右反転
$CharaAnime.flip_v = true  # 上下反転
```


## オブジェクトを回転させる
発射体とかで向きを進行方向に合わせたい時がある。
（弾丸とかをちゃんと進行方向に向ける、など）

直接`rotation`パラメータをイジってたら沼ったのでメモ。

向きを合わせるには`rotate`関数を使う。
引数として回転角度[rad]を渡す。
回転角は上向きを0[deg]として時計回り(正方向)に0～180[deg]、反時計回り(負方向)に0～180[deg]の範囲。
必要に応じて矯正する。

使用例
```GDScript
var dir = Input.get_vector("left", "right", "up", "down")
var rad = atan(dir[1] / dir[0])

# 左向きのときは追加で回転させて頭を進路方向に向ける
if dir[0] < 0:
	rad += deg_to_rad(180)
bullet.rotate(rad)
```


## ポーズ機能の実装
基本的には下記記事２つのとおり。
[ポーズ機能の実装1](https://zenn.dev/saitos/articles/godot-engine-pause-mode-customize)
[ポーズ機能の実装2](https://zenn.dev/slm/articles/54ad795561afab#pause_mode%E3%81%8Cprocess_mode%E3%81%AB%E5%A4%89%E6%9B%B4%E3%81%AB)

その他のことも含めて少しメモ。

いわゆるポーズ状態は、UIとかBGMとか以外が止まる状態のこと（再確認）
それを実装するには、とりあえずシーンツリーをまとめて止めてやれば良い。
```GDScript
get_tree().paused = true
```

ただ、この状態ではあらゆるオブジェクトの時が止まってしまうので復帰できない。
そこで、オブジェクトごとにポーズ時の挙動を決めておく。
インスペクターのNode → Process → Modeから設定できるが、スクリプトでも制御できる。
例えば、常に動作してて欲しい場合は以下のように書く。
```GDScript
process_mode = Node.PROCESS_MODE_ALWAYS
```

とりあえずボタンとかをこの設定にしておけば、いつでもポーズから復帰できる。

なお、ポーズ状態でも止まらない部分がある。
それが_ready関数（というか実行中の関数）


## ユニークネームの扱い
グローバルな扱いになるかと思ったが、そうではなかった。
（ユニークネームにすると上下関係を無視してどこからでもアクセスできると思ってた）

詳細は[公式Docs](https://docs.godotengine.org/en/stable/tutorials/scripting/scene_unique_nodes.html)を読んでくれ。

同じシーンのオブジェクトに対しては短縮形でアクセス可能らしい。
同じシーンとは、例えば「Node2D」オブジェクトの中が一つのシーンになる（と解釈した）

つまり、以下の形式の場合`Player`から見ると`Equips`と`Enemies`は同一シーンに存在してることになるが、`Enemy`は別のシーンに存在しているのでユニークにしても参照できない。
```
Main(Node2D)
├─Player(CharaBody2D)
│ └─Equips(Node2D)
└─Enemies(Node2D)
  └─Enemy(CharaBody2D)
```

逆も同じで、`Enemy`から見ると`Player`は別のシーンに属しているので、ユニークにしても参照できない。
参照したい場合は、公式Docsに書いてるように`get_node("Enemies/%Enemy")`などと書く必要がある。

この機能要る？（純粋な疑問）


## WhileUntilの実装
「ある条件が成立するまで待機する」処理。
UnityのUniTaskで使えるやつとか、そういう機能。

<br>

例：選択肢を出している間は止める
```GDScript
while Sentakushi.visible == true:
    await get_tree().create_timer(0.01).timeout
doSomething()
```

:::note warn
awaitを使わないとプロジェクト全体が止まるので注意。
:::

公式がサポートしてくれねえかな。


## Random.choices(重複なし複数選択)
pythonで言う`random.choices(list)`な処理
GDScriptにはまだ無いようなので。

```GDScript
var items = [...]
items.shuffle()  # 直接書き換わる
for i in range(3):
    print(items[i])
```

シャッフルしたあと、先頭からｎ個取り出してるだけ。

## オブジェクトのイージング(Tween)
ノベルゲームとかでよくある、立ち絵が増えたらヌルっと横にずれたりするアレを実装するのに使えるのがTween。
ちょっとハマったので残しておく。

:::note
結論
`create_tween().bind_node(NODE)`しよう
:::

[公式ドキュメント](https://docs.godotengine.org/en/latest/classes/class_tween.html)には以下のような記載がある。
```gdscript
var tween = get_tree().create_tween()
tween.tween_property($Sprite, "modulate", Color.RED, 1)
tween.tween_callback($Sprite.queue_free)
```

関数化し、`$Sprite`を引数で渡すことを考えると、以下のような記述になる。
```gdscript
func move(object, new_color, duration):
    var tween = get_tree().create_tween()
    tween.tween_property(object, "modulate", new_color, duration)
    tween.tween_callback(object.queue_free)
```

これが罠だった。
これを試しても反応しない。
[create_tweenのドキュメント](https://docs.godotengine.org/en/latest/classes/class_node.html#class-node-method-create-tween)には
> The Tween will start automatically on the next process frame or physics frame (depending on TweenProcessMode).

とあり、要は`get_tree().create_tween()`した次のフレームから再生されると書いてある。（誤読してれば指摘ください）
しかし再生されない。

ではどうすれば良いか。
`create_tween().bind_node(NODE)`すれば良かった。
以下のように書くことで、問題なく再生された。
```gdscript
func move(object, new_color, duration):
    var tween = get_tree().create_tween().bind_node(object)
    tween.tween_property(object, "modulate", new_color, duration)
    tween.tween_callback(object.queue_free)
```