初めに
始めまして、ゲーム制作をしていたらいつの間にかYoutube初めてVtuberになったえうなひゃという者です。
使用しているゲームエンジンはGodot Engineでこれまでにいくつかゲームを公開しています。
そして現在Godotのソースコードを読む配信をしており、それのまとめ本を何かしらの形で販売する予定です。
今回はその一部をこちらで公開することにいたしました。
なお、執筆したタイミングの都合でGodotのバージョンは4.4となります。
Godot Engineの概要
公式サイトはこちら
知らない方のために解説するとGodot Engineとはオープンソースのゲームエンジンの一つで日本でも最近広まってきております。
似たゲームエンジンで有名なのはUnityですが決定的な違いの一つとしてアカウントの作成を必要としない点と直接インストールしなくてもよく、ダウンロードした瞬間すぐ使える点が挙げられます。
まだ完全とは言えませんが公式ホームページのトップが日本語になったりドキュメントもいくつかが日本語になっており、また、Discordのコミュニティも存在します。
今回閲覧したソースコードは?
今回こちらで公開するのはGodotでの開発ではよく使うNode get_node(path: NodePath) constという関数です。
以下は私の原稿から抜粋いたします。
Godotでよく使うメソッドのソースコード
get_node( name : String )
Godot を使って開発を行う際に最も使う関数の一つがget_nodeである。名前の通り、指定された名前のノードを取得して存在が確認できたらその名前のノードを返す関数だ
UML図にすると以下の通り
ソースコードは以下の通りである。
Node *Node::get_node(const NodePath &p_path) const {
Node *node = get_node_or_null(p_path);
// 失敗した場合
if (unlikely(!node)) {
// 説明を取得
const String desc = get_description();
// 絶対パスと相対パスの2種類メッセージが存在する
if (p_path.is_absolute()) {
ERR_FAIL_V_MSG(nullptr,
vformat(R"(Node not found: "%s" (absolute path attempted from "%s").)", p_path, desc));
} else {
ERR_FAIL_V_MSG(nullptr,
vformat(R"(Node not found: "%s" (relative to "%s").)", p_path, desc));
}
}
return node;
}
get_node()そのものはget_node_or_null(p_path)に追加でうまくいかなかった場合の処理を追加したものだと言える。
ではget_node_or_null(p_path)のアクティビティ図を見てみよう
割と複雑に感じるかもしれないが落ち着いてみてほしい。
非常に長い図になってしまったので current に適用する過程の処理を細かく分解する。
該当するアクティビティ図はこのようになっている。
では unique である場合はどうなるだろうか?
uniqueについて説明するとこれは「僕は自分のことを他のものと共有しないよ」と声高に宣言することである。
unique が null であってはならないので owner の情報を取得している。
では uniqueでない場合はどうなのだろうか?
next に適用した値を current に代入していく、あらゆる方法を使って current に適用したい Node を探していく関数であることがわかる。
ではソースコードを見てみよう。
なお、非常に長いので分割してみていく
Node *Node::get_node_or_null(const NodePath &p_path) const {
ERR_THREAD_GUARD_V(nullptr);
if (p_path.is_empty()) {
return nullptr;
}
ERR_FAIL_COND_V_MSG(!data.inside_tree && p_path.is_absolute(), nullptr, "Can't use get_node() with absolute paths from outside the active scene tree.");
Node *current = nullptr;
Node *root = nullptr;
このメソッドで注目すべき変数は Node *current 次に Node *root 、最終的に current を返すのだがその過程で色々代入している、実際に見てみよう。
これの分岐の条件は以下の通り
# 絶対パス
@onready var absolute = get_node("/root/Node/SomeNode")
# 相対パス
@onready var relative = get_node("SomeNode")
では絶対パス、相対パスそれぞれの処理を見てみよう。
if (!p_path.is_absolute()) {
// 自分自身へのポインタをカレントノードに設定
current = const_cast<Node *>(this); //start from this
} else {
root = const_cast<Node *>(this);
while (root->data.parent) {
root = root->data.parent; //start from root
}
}
相対パスであれば正確なルートを取得するために自分自身の親を遡っていく、次は引数のパスの名前を取得し、rootから初めていく
次に行うのは自分の名前を/または\で分割して走査して条件が合えばcurrentに代入していく処理である。
// get_name_count() == pythonのsplit('\')の後にlen()
// \ という文字を基準に分割して別れた名前の数を数えるメソッド
for (int i = 0; i < p_path.get_name_count(); i++) {
StringName name = p_path.get_name(i);
Node *next = nullptr;
if (name == SNAME(".")) {
next = current;
} else if (name == SNAME("..")) {
if (current == nullptr || !current->data.parent) {
// .. を取得しているのに親がいない、またはcurrentそのものがnullなのはおかしい
return nullptr;
}
next = current->data.parent;
} else if (current == nullptr) {
if (name == root->get_name()) {
next = root;
}
ここからはノードの名前がユニークであった場合の処理を入れている
} else if (name.is_node_unique_name()) {
// オーナーから自分の名前と一緒のユニークノードを取得しようとする。
Node **unique = current->data.owned_unique_nodes.getptr(name);
if (!unique && current->data.owner) {
unique = current->data.owner->data.owned_unique_nodes.getptr(name);
}
if (!unique) {
return nullptr;
}
next = *unique;
ユニークなものではないことが明示されている場合の処理は以下の通りで、とりあえず普通に走査を再開するために変数nextを一旦nullptrにしてから次のノードの取得に入る。
} else {
next = nullptr;
const Node *const *node = current->data.children.getptr(name);
if (node) {
next = const_cast<Node *>(*node);
} else {
return nullptr;
}
}
current = next;
}
// 走査し切った場合、最終的なcurrentを返す。
return current;
}
まとめ
get_node関数はget_node_or_nullにエラーを吐かせる処理を追加した関数である。
なぜこのようにしたのかというとget_nodeでしっかりエラーを吐かせたい場合とget_node_or_nullでnullを取得してしまった場合とを使い分けを行いたいからである。
最後に
同人誌の販売を開始しました!
場所はこちらになります。
Youtubeにて現在こんな感じでソースコードを読み込む配信をしております。
また、マサカリ大歓迎なのでどんどんコメント等で突っ込んでもOKです!
興味が出たらチャンネル登録よろしくおねがします。
Youtubeチャンネルはこちら





