GodotにPull Requestを提出してみた

# はじめに

どうも、ChocolaMintです。

最近、ゲーム業界の就活に向けて自分のエンジニア力をもっと鍛えることを決めました。普段はUnityを使ってて、ポートフォリオにC#関連の実績が多い一方、ゲーム業界では同じくらい重要なC++に対して大学のグラフィックの宿題しか実績がないことに悩んでいました。

それで丁度世間では「とある事件」でGodotフィーバーが「また」爆発し、それをきっかけにGodotのコードベースを挑戦してみました。Godotのコードベースは他のC++コードベースと比べてかなり分かりやすい方なので、初心者向きとも言えますね。今回は自分が初めてGodotに提出した2つのPull Request（プルリクエスト、略してPR）について、簡単に紹介します。

# PR #1 - ユニットテストを書いてみましょう！

基本的に、PRはIssue（問題、不具合）を解決するため提出するものですから、まずはGodotの[Issue一覧](https://github.com/godotengine/godot/issues)を見ましょう。

Godotのコードベースは他の大型C++リポジトリと比べて小さい一方、なんと、本記事の投稿時間まで**9000**以上の未解決Issueが溜まっています。一体どこから始めるべきでしょうかね。

GitHubでは`good first issue`というラベルが用意されてるけど、[未解決の`good first issue`](https://github.com/godotengine/godot/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)はほんのわずかで、その中でもグラフィックとかの専門知識が必要なケースも多いです。

実は、本当に初心者向けのIssueはこちらの「ユニットテスト」です。

https://github.com/godotengine/godot/issues/43440

## 準備作業

ユニットテストのIssueページを覗いてみると、ユニットテストのタスクリストが見れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629782/9c5b0691-2a29-b63a-714b-930f3e705f5c.png)

スレッド内では「このクラスのユニットテストを書いてます！」みたいな告知コメントが見れます。クラスを選ぶ前にCtrl+Fで確認しましょう。

私の場合は、Camera3Dに決めました。一見テストできなさそうなクラスだけど、少なくともカメラの座標変換メソッド辺りはテストできるでしょう。

テストを書く前に、マニュアルから[Godotのビルド手順](https://docs.godotengine.org/en/latest/contributing/development/configuring_an_ide/visual_studio.html)と[ユニットテストの書き方](https://docs.godotengine.org/en/latest/contributing/development/core_and_modules/unit_testing.html)を確認しましょう。現時点では日本語訳がありませんので、英語が読めない方は翻訳サービスの手を借りましょう。~~でもエンジニアってみんな英語喋れなくても読めるんじゃない？（謎の偏見）~~

:::note info
Godotのテストは：
* [`tests`フォルダー](https://github.com/godotengine/godot/tree/master/tests)に置いてあります
* ヘッダーファイル単体に直接にテストケースを入れています（分かりやすくていいよね！）
    * 例えば[`test_node_2d.h`](https://github.com/godotengine/godot/blob/master/tests/scene/test_node_2d.h)にはNode2Dというクラスのテストケースが入っています
* テスト用の特殊マクロを使っています（一部抜粋）
    * `TEST_CASE`（テスト関数の宣告）
    * `CHECK`（assertと似たような機能が持っている）
    * `CHECK_MESSAGE`（`CHECK`にエラーメッセージを付けれる）
:::

テストの書き方を参考するため、[`test_math_funcs.h`](https://github.com/godotengine/godot/blob/master/tests/core/math/test_math_funcs.h)をちょっとだけ見るのがおすすめです。数学関数なら専門知識がなくても簡単に読めますので。

## Camera3Dのユニットテストを決めましょう

Camera3Dのユニットテストを書くには、まずCamera3Dのパブリック関数を確認しましょう。丁度Camera3Dのパブリック関数は全部GDScriptから呼び出しできるので、直接にGodotのマニュアルを検索すれば各関数のスペックが確認できます。では、Camera3Dのマニュアルページを読んでみましょう。

https://docs.godotengine.org/en/stable/classes/class_camera3d.html#methods

テストのIssueページでは、カメラのFrustum（錐台、つまり見える範囲）に関連する関数をテストしたい、と書かれているので、優先したい関数をまとめてみましょう：
* [`is_position_behind`](https://docs.godotengine.org/en/stable/classes/class_camera3d.html#class-camera3d-method-is-position-behind)
* [`is_position_in_frustum`](https://docs.godotengine.org/en/stable/classes/class_camera3d.html#class-camera3d-method-is-position-in-frustum)
* [`project_local_ray_normal`](https://docs.godotengine.org/en/stable/classes/class_camera3d.html#class-camera3d-method-project-local-ray-normal)
* [`project_position`](https://docs.godotengine.org/en/stable/classes/class_camera3d.html#class-camera3d-method-project-position)
* [`project_ray_normal`](https://docs.godotengine.org/en/stable/classes/class_camera3d.html#class-camera3d-method-project-ray-normal)
* [`project_ray_origin`](https://docs.godotengine.org/en/stable/classes/class_camera3d.html#class-camera3d-method-project-ray-origin)
* [`unproject_position`](https://docs.godotengine.org/en/stable/classes/class_camera3d.html#class-camera3d-method-unproject-position)

これらの関数を中心にユニットテストを書くことを決めました。

## テストを実装しましょう

今回は`project_position`を例として紹介します。`project_position`とは、スクリーン上のポイントをワールドスペースにマップする関数です。`z_depth`を指定すればカメラからの距離も指定できます。

正投影（Orthographic projection）を使ってる場合、カメラの向いてる方向から簡単にこの関数の答えを求めますが、透視投影（パース、Perspective projection）の場合は中学数学で学んだ「相似な三角形」の解き方で求めるのが必要です。

でも本当の問題点は、Godotに定義されてる「もう一つの」投影法：[「フラスタム投影」(Frustum projection)](https://docs.godotengine.org/en/stable/classes/class_camera3d.html#class-camera3d-constant-projection-frustum)です。

これに関して、本当に全く情報がありません。マニュアルの説明だけじゃ数学的な定義がわからないし、単語をそのままググってみると透視投影に関しての結果しか見えませんし、[この機能を実装したPR](https://github.com/godotengine/godot/pull/26064)を特定したけど定義に関する説明もなかったし、仕方なく実装の部分を読むしかありません。

でもソースコードを読んだら更に怖いことが発覚しました：この[「フラスタム投影」のコード](https://github.com/godotengine/godot/blob/master/core/math/projection.cpp#L364)は一見[正投影のコード](https://github.com/godotengine/godot/blob/master/core/math/projection.cpp#L344)に似てるけど、全く違うview matrixを使ってることがわかりました。私一応大学でグラフィックの授業を通ってたけど、このマトリックスのこと全く知りませんでした。

Godotの[コントリビューターチャット](https://chat.godotengine.org/)で聞いても、この謎のコードのことを知ってる人は誰一人もいませんでした。Issueを探ったら[明らかにバグってるケース](https://github.com/godotengine/godot/issues/61174)も見つかりました。正解がわからない以上、「フラスタム投影」のことは一旦そっとしておいた方が賢明ですね。~~正直もうこの謎の機能をdeprecateして削除してくれない？とツッコミたいところですね。~~ まあ大きいコードベースに謎のレガシーコードが潜んでいるのも当たり前のことですからね。

## Pull Requestのワークフロー

:::note warn
PRを提出する前に、いくつかの事項を確認しないといけません：
* 全てのテストにパスしないといけません
    * 自分のPRに付いている新しいユニットテストも含めています。もし自分のユニットテストから新しいバグを発見したら、そのバグをIssueとして投稿して、問題のテストを変えましょう。
    * テストにパスしてないとGitHub Actionsによる自動チェックで却下されます。
* コードをフォーマットしないといけません
    * [コードスタイル](https://docs.godotengine.org/en/stable/contributing/development/code_style_guidelines.html#doc-code-style-guidelines)の自動チェックの一つです。Godotではclang-formatを使って、最低限のコードスタイルを確認しています。Visual Studioの場合は[ClangFormat](https://marketplace.visualstudio.com/items?itemName=LLVMExtensions.ClangFormat)もしくは[Clang Power Tools](https://clangpowertools.com/)（どっちも無料）を使うことがおすすめです。
    * ちゃんとフォーマットしないとGitHub Actionsによる自動チェックで却下されます。
* PRが解決したいことに関連しない変更を入れないでください
    * 手癖で周りのコードを整理する気持ちになりやすいですが、関係ない変更が入ってるとPRの目的と乖離して、「関係ない変更を別のPRの形にして提出してください」と言われるので、PRをレビューするGodotのメンバーさんに迷惑をかけないようにしましょう。
* GitでPRのコミットを一つのコミットにsquashしてください。
    * Gitのコマンドラインを使ってる場合は[こちらの記事](https://deku.posstree.com/git/git-squash/)を参考にしてください。
    * Sourcetree、GitHub DesktopなどのGitのGUIツールではコミットをマウスで選択してsquashする機能もありますので、コマンドラインに自信がない方はそっちのほうがおすすめです。
:::

実際に提出したPRはこちらです：

https://github.com/godotengine/godot/pull/84826

提出したら、まずはレビュアーを待つことです。レビュアーがレビュープロセスを始めたら、GitHub Actionsによる自動チェックが行われます。最悪30分くらいかかることもあるので、ゆっくり待ちましょう。

その間、もしくはその後、レビュアーからPRについて「こうしたほうがいいと思うよ」、「こうしたほうがもっと読めやすいよ」などのコメントがもらったら、レビュアーとやり取りして、コードを改善しましょう。

レビュープロセスにパスしたら、PRの一番下にChanges approvedのメッセージが出るようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629782/ccd3e5f9-9350-d35e-3992-78b0edcce154.png)

パスしたら、アサインされたマイルストーンに応じて次第にマージされます。重要なバグ修正はすぐマージされることが多いですが、ユニットテストの優先度はさすがにそんなに高くないので大人しく待ちましょう。因みにこちらのPRはバージョン4.3のマイルストーンにアサインされたので、マージされるのは4.2の正式リリースのあとのことでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629782/7ab23fd4-ad4d-0ab8-8c14-3ee57a167dfa.png)

# PR #2 - Godotのバグを解決してみましょう！

先ほど紹介したのは「いつでも気軽に始めるPR」だけど、気持ち的にやっぱりユニットテストよりもっとユーザー（ゲーム開発者）に直接に感じれるPRを提出したいですね。

もう一度[Issueページ](https://github.com/godotengine/godot/issues)を見てみましょう。

Issueが提出されたのち、Godotチームのメンバーにラベルが付けられます。
* バグを探したい場合は`bug`のラベルが付いてるIssueを探しましょう。
* でも`bug`のラベルが付いてるIssueの中には、「要確認」という意味の`needs testing`ラベルが付いてるものも多いです。確認のお手伝いもできますが、確認済み（`confirmed`）のバグ、もしくは`needs testing`が付いてないバグを優先して修正しましょう。

とりあえず色々見回って、直せる自信があるIssueから始めましょう。

今回選んだIssueは#85401、コードエディターのUIに関する不具合です。

https://github.com/godotengine/godot/issues/85401

## バグを修正するには

まずはIssueの内容を見てみましょう。

ページ内ではバグの動画が添付されています。まとめると「コードのオートコンプリートと関数の定義のヒント」が同時に表示できないのが問題です。

例えばVisual Studioでは同時に表示することができるけど、Godotの内蔵コードエディターではできません。ちょっとめんどくさいですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629782/e0f2c8cc-3c3a-fe9b-1fb6-bf21c57606c8.png)

GitHubの検索機能を使って、キーワードから問題のコードを特定できました。[`CodeEdit.cpp`](https://github.com/godotengine/godot/blob/eda44bfe109d7f15c5a965db2d65c8a17ce4b7d0/scene/gui/code_edit.cpp#L73)の中にはオートコンプリートと関数の定義のヒントを表示するコードがあるけど、同時に表示できないように設計されています。

よく見ると、オートコンプリートとヒントはどっちも「編集中の行」の上か下かに表示できますし、そのためのコードも見つかりました。一番簡単な修正方法として、ヒントの表示ボックスをオートコンプリートの逆側に表示すればいいのでは？と思って、修正して試しにPRを提出しました。

https://github.com/godotengine/godot/pull/85436

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629782/d05fda6f-2fe3-8dd2-37ce-13925132d31c.png)

ただ、オートコンプリートは関数のパラメーターを入力してる時常にあるわけでもないので、上の場合ではすぐ下のように、関数のヒントが下側に戻って、そして繰り返して、嫌な点滅になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629782/42d7fb0f-a041-e9b0-e805-e5344b4d0573.png)

それでレビュアーからもっといい案をもらいました。レビュアーとやり取りしつつ直したら、Visual Studioのような解決案にたどり着きました：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629782/00055b4e-e063-1176-3166-7a1c7d3d99dc.png)

まだパスしていないけど、かなりいい反響を受けたのでパスできる自信はありますね。


# 最後に

GodotにPull Requestを提出するのは、ゲームエンジンのプログラマーじゃなくても意外と簡単にできることですね。PRを提出することで、直接に応援したいゲームエンジンにコードを貢献できるし、それにエンジニアとしての能力の証にもなれるので、ご興味があれば、GodotにPull Requestを提出してみませんか？