UnityエンジニアがGodot Engineを触ってみた（２）

[PONOS Advent Calendar 2023](https://qiita.com/advent-calendar/2023/ponos)の25日目の記事です。

# はじめに
先日は初めて[UnityエンジニアがGodot Engineを触ってみた](https://qiita.com/bluenova1221/items/fec2e25f2b550a4763af)わけですが、
今回はチュートリアルを覗きながら引き続き触ってみました。

日本語のドキュメントも充実していますので、お勉強するに困らないですよね。
- [Godot Engine 4.2の日本語のドキュメント](https://docs.godotengine.org/ja/4.x/index.html)
- [最初の2Dゲーム](https://docs.godotengine.org/ja/4.x/getting_started/first_2d_game/index.html)



# テスト環境
以下の環境やバージョンでテストしました。
- MacBook Pro (13インチ, 2020, Thunderbolt 3ポート x 4)
    - macOS Monterey (バージョン 12.5.1)
- Godot Engine 4.2
    - https://godotengine.org/download/macos/



# デモプロジェクト
お役に立つデモプロジェクトが沢山あります。
- Godot demo projects
    - https://github.com/godotengine/godot-demo-projects
- Pong
    - https://github.com/godotengine/godot-demo-projects/tree/master/mono/pong

その中でPongゲームに決定！



# プロジェクトのインポート
Godot Engineを起動しましょう。
- [Pong with C#](https://godotengine.org/asset-library/asset/535)ページからプロジェクトをダウンロードします。
- それからプロジェクトをインポートしましょう。
![Godot import pong.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/587afc7d-5dd2-245f-b213-386e45308bf9.png)

- デモプロジェクトの作成バージョンが古いため、変換作業を行います。
![Godot import pong alert.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/35c248b7-907c-4d1c-a531-ab5f124c6818.png)

- 警告のポップアップが出てもOK押しちゃいます！
![Godot import pong alert popup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/7e29f3af-52f6-bd7c-15b5-ab2d6ff34361.png)

- プロジェクトのインポートが完了しました。
![Godot pong project.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/78029665-c998-3df5-1241-45ae930c98f0.png)



# プロジェクトの実行
早速プロジェクトを動かしてみたいのですが…
- 実行するボタンを押したところ…ビルド時にエラーが発生！
![GodotSharp 4.2.0 Error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/f784b230-08cd-b11b-a0af-6133bcfd38dd.png)
どうやらGodot Engineの内製のC#エディターはバージョンが古いようですね…
- Visual Studio Codeと連携しましょうか。
    - [外部エディタの設定](https://docs.godotengine.org/ja/4.x/tutorials/scripting/c_sharp/c_sharp_basics.html#configuring-an-external-editor)
    - トップバーのメニューから「エディター→エディター設定」を開きます。
    - .NETの外部エディターをVisual Studio Codeに設定し、再起動します。 
![Godot Editor Settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/dda5cab0-b1e7-b9ac-6a18-8b2b912a2d1e.png)
- 改めて実行してみます。
    - 残念ながらデモプロジェクトはGodot 4.2に対応してないみたい（涙）
![Godot Build Error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/b01c2b01-95f8-bfb7-7b80-ce25301f53b3.png)

エラーを一つずつ解決していきましょう。



# エラーの修正
- なんと_Processのパラメーターがfloatからdoubleに変わったみたいですね…
    - float型にキャストして、エラーを解消します。
```c#
// エラーログ
CS0266: 型 'double' を 'float' に暗黙的に変換できません。

// Godot 3.5
public override void _Process(float delta)

// Godot 4.2 - Ball.csの修正
public override void _Process(double delta)
{
    var fDelta = (float)delta;
    _speed += (fDelta * 2);
    Position += _speed * fDelta * direction;
}

// Godot 4.2 - Paddle.csの修正
public override void _Process(double delta)
{
    var fDelta = (float)delta;
    position += new Vector2(0, input * MoveSpeed * fDelta);
}
```
- 変なエラーに遭遇したと思ったら、アクセス可能な拡張メソッドが大文字になっていました（罠）
- 大文字に修正することで解消できます。
```c#
// エラーログ
CS1061: 'Vector2' に 'y' の定義が含まれておらず、型 'Vector2' の最初の引数を受け付けるアクセス可能な拡張メソッド 'y' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください

// Vector2のプロパティを小文字から大文字に修正
public override void _Process(double delta)
{
    var fDelta = (float)delta;
    // Move up and down based on input.
    float input = Input.GetActionStrength(_down) - Input.GetActionStrength(_up);
    Vector2 position = Position; // Required so that we can modify position.y.
    position += new Vector2(0, input * MoveSpeed * fDelta);
    position.Y = Mathf.Clamp(position.Y, 16, GetViewportRect().Size.Y - 16);
    Position = position;
}
```
- また文字列のToLower()で訳のわからないエラーが発生。
    - おそらく親クラスであるNodeのNameプロパティの扱いが変わったと思いますが
    - 今回は別の方法で回避しましょう。
```c#
// エラーログ
CS7036: 'MemoryExtensions.ToLower(ReadOnlySpan<char>, Span<char>, CultureInfo?)' の必要な仮パラメーター 'destination' に対応する特定の引数がありません

// 左右パドル名をインスペクターから設定できるようにする。
[Export]
private string _name;

public override void _Ready()
{
    // エラーになるコード
    string name = Name.ToLower();

    // 修正したコード
    string name = _name;
```
- 上記のコードに記述したようにフィールドに[[Export]](https://docs.godotengine.org/ja/4.x/tutorials/scripting/c_sharp/c_sharp_differences.html#export-annotation)属性を付けることができます。
    - Export属性を付けることでプライベートのデータもインスペクターから設定することができます。
    - Unityの[SerializeField]といったものでしょう。
    - 左側のパドルには left と設定します。
![Godot Export.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/f4ef0da0-b2f0-fdb8-0221-58fad9db6855.png)
    - 右側のパドルには right と設定します。
![Godot Right Paddle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/6b58eafe-2567-ea1a-7dc2-95a063119fe3.png)

これで左右パドルを動かす準備ができました。

# Inputについて
- パドルを動かす前に以下のページも一読し、より深く理解しましょう。
    - [Input Event](https://docs.godotengine.org/ja/4.x/tutorials/inputs/inputevent.html)
    - [Controllers, gamepads, and joysticks](https://docs.godotengine.org/ja/4.x/tutorials/inputs/controllers_gamepads_joysticks.html)
- インプットマップは「プロジェクト→プロジェクト設定ウィンドウ」を開くことで確認できます。
![Godot Input Settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/5a405f8a-5b13-edd1-0d6c-a699651f0c1d.png)
- 左パドルは「left_move_down, left_move_up」キーで動かします。
- 右パドルは「right_move_down, right_move_up」キーで動かします。
- パドルのスクリプトはこちら↓
```c#
// Input.GetActionStrength() メソッドを使って上下の入力を判定します。
float input = Input.GetActionStrength(_down) - Input.GetActionStrength(_up);
```
Inputの情報についてわかりました。



# Area2D
- Pong ゲームは「ボール・天井・床・パドル・壁」で構成されていて、ボールと衝突することでインタラクションが発生します。
- その衝突処理をするために全てのオブジェクトはArea2Dを継承しています。
![Godot Area2D.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/72d6e0ff-22d6-3b69-a451-871fb3004af2.png)
- Area2Dは、2D空間の領域を定義していて、他のCollisionObject2D ノードが進入、オーバラップ、および退出することを検出できます。
    - UnityだとOnCollisionEnter2D, OnCollisionStay2D, OnCollisionExit2Dのことでしょう。
- 詳しくはこちらの一読をおすすめ。
    - https://docs.godotengine.org/ja/4.x/tutorials/physics/using_area_2d.html
    - https://docs.godotengine.org/ja/4.x/classes/class_area2d.html
- スクリプトに書いてメソッドと「接続・切断」することで衝突時の処理を制御できます。
- ノードのタブからシグナルの一覧が確認できますので、自由に設定しましょう。
![Godot Area2D Remove.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/ae8a8d4e-149b-73a6-1341-75930b618efa.png)
- スクリプトの振る舞いに沿って命名し、Area2Dのシグナルと接続すれば衝突した時に呼ばれるはずです。
![Godot Area2D Add.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/3fbaa7d6-4343-8b64-9b6e-acf16a19b7bd.png)



# 衝突処理の実装
- パドルはボールと衝突した時に反対側に飛ばします。
- バリエーションのため、Yの方向はランダムに！
```c#
public void OnAreaEntered(Area2D area)
{
    if (area is Ball ball)
    {
        // Assign new direction
        ball.direction = new Vector2(_ballDir, ((float)new Random().NextDouble()) * 2 - 1).Normalized();
    }
}
```
- 天井と床はボールと衝突した時に反転の方向に飛ばします。
```c#
public void OnAreaEntered(Area2D area)
{
    if (area is Ball ball)
    {
        ball.direction = (ball.direction + new Vector2(0, _bounceDirection)).Normalized();
    }
}
```
- 壁と衝突したら、最初の位置に設定します。
```c#
public void OnWallAreaEntered(Area2D area)
{
    if (area is Ball ball)
    {
        // Ball went off the side of the screen, reset it.
        ball.Reset();
    }
}
```
スクリプトの内容は少ないですが、これでも立派なゲームとして動作します。
スコアの加算やエフェクトとゲームオーバーまで入れると最高ですよね！



# プレイしてみる！
- おお…動く…！
![Godot_Pong.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2156003/e2ff7062-6c87-d048-6afc-3243bf7aec01.gif)



# 最後に
プロジェクトを動かしてみながら、より広く勉強することができました。
やっぱりバージョンアップは簡単ではないですよね〜（最新バージョンでデモがコケたらユーザーは離れてしまうし）
エンジンの方がどんどん進化していっても、チュートリアルが追いついてこないことがよくありますよね。

でもプロジェクトのインポートから外部エディタの設定、Export属性とInput機能、Area2Dの衝突処理まで触ってみることで、Godot Engineの知識が増えた実感がします。
エディタが軽いのがお気に入りかもと。
今後ともGodot Engineの活躍を応援しながら見守ってみようかと思います。

さて、本日をもって [PONOS Advent Calendar 2023](https://qiita.com/advent-calendar/2023/ponos) もおしまいです。
ここまでご覧いただいた方、誠にありがとうございました。