この記事は Google 翻訳による英語翻訳の結果であり、これに DrGraph がいくつかの修正を加えたものです。英語のオリジナル ページ:ベスト プラクティス — Godot Engine (安定版) ドキュメント (英語)
はじめに¶ _
このシリーズは、Godot を効率的に使用するためのベスト プラクティスを集めたものです。
Godot は、プロジェクトのコード ベースを構造化し、それをシーンに分割する際に優れた柔軟性を提供します。それぞれのアプローチには長所と短所があり、エンジンを十分に長く使用するまで、それらをすべて比較検討するのは難しい場合があります。
コードを構造化し、特定のプログラミングの問題を解決するには、常に多くの方法があります。ここですべてを網羅することは不可能です。
だからこそ、すべての記事は現実世界の問題から始まります。それぞれの基本的な問題を分析し、解決策を提案し、各オプションの長所と短所を分析し、当面の問題に対する最適な行動方針を強調します。
まず、 「Godot でのオブジェクト指向原則の適用」を読む必要があります。Godot のノードとシーンが他のオブジェクト指向プログラミング言語のクラスやオブジェクトにどのように関連するかを説明します。このシリーズの残りの部分を理解するのに役立ちます。
Godot でのオブジェクト指向の原則の適用¶
このエンジンは、再利用可能なオブジェクトを作成する 2 つの主な方法、スクリプトとシーンを提供します。これらはいずれも、内部でクラスを技術的に定義するものではありません。
ただし、Godot を使用するためのベスト プラクティスの多くには、ゲームを構成するスクリプトやシーンにオブジェクト指向プログラミングの原則を適用することが含まれます。だからこそ、それらをクラスとしてどのように考えるかを理解することが有益です。
このガイドでは、エンジンのコアでスクリプトとシーンがどのように動作するかを簡単に説明し、内部でどのように動作するかを理解するのに役立ちます。
エンジン内でのスクリプトの動作¶
このエンジンは、 Nodeのような組み込みクラスを提供します。これらを拡張して、スクリプトを使用して派生型を作成できます。
これらのスクリプトは技術的にはクラスではありません。代わりに、これらはエンジンの組み込みクラスの 1 つで一連の初期化を実行するようにエンジンに指示するリソースです。
Godot の内部クラスには、クラスのデータをClassDBに登録するメソッドがあります。このデータベースは、クラス情報への実行時アクセスを提供します。ClassDB
クラスに関する次のような情報が含まれます。
-
特性。
-
方法。
-
絶え間ない。
-
信号。
これは、ClassDB
プロパティへのアクセスやメソッドの呼び出しなどを行うときにオブジェクトがチェックするものです。データベースのレコードとオブジェクトの基本型のレコードをチェックして、オブジェクトがその操作をサポートしているかどうかを確認します。
オブジェクトにスクリプトClassDB
を添付することは拡張可能です。
注: キーワードを使用しないスクリプトでも、extends
エンジンの基本 RefCountedクラスから暗黙的に継承されます。したがって、 extends
code キーワードを使用せずにスクリプトをインスタンス化できます。展開するため、 NodeRefCounted
にアタッチすることはできません。
シーン¶ _
シーンはさまざまな方法でクラスのように動作するため、シーンをクラスとして考えるのが自然です。シーンは、再利用可能、インスタンス化可能、継承可能なノードのグループです。シーンの作成は、ノードを作成し、それらを子として追加するスクリプトに似ていますadd_child()
。
多くの場合、シーン ノードを使用して、シーンをスクリプト可能なルート ノードと組み合わせます。したがって、スクリプトは命令型コードを介して動作を追加することでシーンを拡張します。
シーンの内容は、以下を定義するのに役立ちます。
-
スクリプトが使用できるノード
-
どのように組織されているか
-
どのように初期化されるか
-
それらの間の信号接続は何ですか
これらがシーンの構成にとって重要なのはなぜですか? シーンのインスタンスはオブジェクトであるためです。したがって、コードの記述に適用されるオブジェクト指向の原則の多くは、単一責任、カプセル化などのシナリオにも適用されます。
シーンは常にルート ノードにアタッチされたスクリプトの拡張であるため、クラスの一部として解釈できます。
このベスト プラクティス シリーズで紹介されているテクニックのほとんどは、この点に基づいています。
シーンの構成¶
この記事では、シーン コンテンツの効果的な構成に関連するトピックについて説明します。どのノードを使用する必要がありますか? どこに配置すればよいでしょうか? 彼らはどのように対話すべきでしょうか?
人間関係を効率的に構築する方法¶
Godot ユーザーが独自のシーンを作成し始めると、多くの場合次の問題に遭遇します。
彼らは最初のシーンを作成し、そこにコンテンツを埋め込みましたが、物事を分けておかなければならないというしつこい感覚が蓄積し始めたため、最終的にシーンのブランチを別のシーンに保存することになりました。しかし、彼らは以前は信頼できたハードリファレンスがもはや不可能であることに気づきました。シーンを複数の場所で再利用すると、ノード パスがターゲットを見つけられず、エディターで作成された信号接続が切断されるため、問題が発生します。
これらの問題を解決するには、環境に関する詳細な情報を持たずにサブシーンをインスタンス化する必要があります。人々がサブシーンをどのように使用するかを気にすることなく、サブシーンが自動的に作成されると信頼できる必要があります。
OOP で考慮すべき最も重要なことの 1 つは、コードベースの残りの部分と疎結合された集中化された単一目的のクラスを維持することです。これにより、オブジェクトのサイズが (保守性のために) 小さく保たれ、再利用性が向上します。
これらの OOP のベスト プラクティスは、シーンの構造とスクリプトの使用におけるベスト プラクティスにいくつかの影響を与えます。
シナリオは、可能であれば依存関係を持たないように設計する必要があります。 つまり、人々は必要なものをすべて自分の中に収めるシナリオを作成する必要があります。
シーンが外部コンテキストと対話する必要がある場合、経験豊富な開発者は、 依存関係の注入を使用することをお勧めします。この手法には、高レベル API に低レベル API の依存関係を提供させることが含まれます。なぜこれをしたいのですか?外部環境に依存するクラスは、誤ってバグや予期しない動作を引き起こす可能性があるためです。
これを行うには、データを公開し、親コンテキストに依存してデータを初期化する必要があります。
-
信号に接続されています。非常に安全ですが、動作を開始するのではなく、「応答」動作にのみ使用してください。シグナル名は通常、「entered」、「skill_activated」、「item_collected」などの過去形の動詞であることに注意してください。
# 親 $Child.signal_name.connect(method_on_the_object) # 子 signal_name.emit() # 親定義の動作をトリガーします。
-
メソッドを呼び出します。動作を開始するために使用されます。
# 親 $Child.method_name = "do" # 子。文字列プロパティ「method_name」とメソッド「do」があると仮定します。 call(method_name) # 親定義のメソッド (子が所有する必要がある) を呼び出します。
-
Callableプロパティを初期化します。メソッドの所有権が必要ないため、メソッドより安全です。動作を開始するために使用されます。
# 親 $Child.func_property = object_with_method.method_on_the_object # 子 func_property.call() # 親定義のメソッドを呼び出します (どこからでも呼び出すことができます)。
-
ノードまたは他のオブジェクト参照を初期化します。
# 親 $Child.target = self # 子 print(target) # 親定義のノードを使用します。
-
ノードパスを初期化します。
# 親 $Child.target_path = ".." # 子 get_node(target_path) # 親定義の NodePath を使用します。
これらのオプションは、子ノードのアクセス ポイントを非表示にします。これにより、子供は環境と疎結合になります。API に追加の変更を加えることなく、別のコンテキストで再利用できます。
注: 上記の例は親子関係を示していますが、同じ原則がすべてのオブジェクト関係に適用されます。兄弟であるノードはその階層についてのみ知っている必要がありますが、祖先は通信と参照を仲介します。
# 親 $Left.target = $Right.get_node("Receiver") # Left var target: Node func use(): # 'target' で何かを実行します。# 右関数func _init(): varレシーバ = Receiver.new() add_child(receiver)
同じ原則が、他のオブジェクトへの依存関係を維持する非ノード オブジェクトにも当てはまります。これらのオブジェクトを実際に所有しているオブジェクトは、オブジェクト間の関係を管理する必要があります。
注: データを内部 (シーン内) に保持することを優先する必要がありますが、外部コンテキスト (たとえ疎結合コンテキストであっても) に依存するということは、ノードがその環境内の何かが true であると期待することを意味します。プロジェクトの設計哲学は、このようなことが起こらないようにする必要があります。そうしないと、コードに固有の責任があるため、開発者はドキュメントを使用してオブジェクトの関係を微視的なスケールで追跡する必要が生じ、これは開発地獄とも呼ばれます。デフォルトでは、安全に使用するために外部ドキュメントに依存するコードを作成すると、エラーが発生しやすくなります。
このようなドキュメントの作成と維持を避けるには、依存ノード (上記の「子」) を実装に変換します。 _get_configuration_warning()
これから空ではない文字列を返すと、シーン ドックはその文字列をツールヒントとして受け取るアラート アイコンを生成します。Area2Dノードが子CollisionShape2Dノードを定義していない 場合、このアイコンは Area2D ノードや他のノードで表示されるアイコンと同じになります。次に、編集者はスクリプト コードを介してシーンを自己記録します。ドキュメントの内容をコピーする必要はありません。
このような GUI を使用すると、プロジェクト ユーザーにノードに関する重要な情報をより適切に通知できます。外部依存関係はありますか? これらの依存関係は満たされていますか? 他のプログラマー、特にデザイナーやライターは、メッセージ内に構成方法を示す明確な指示が必要になります。
では、なぜこれらの複雑なスイッチが機能するのでしょうか? そうですね、シーンは個別に実行したときに最も効果的に機能するからです。単独での作業が不可能な場合は、匿名で他のユーザーと作業する (ハードな依存関係、つまり疎結合を最小限に抑えて) ことが次善の選択肢です。必然的に、クラスに変更を加える必要がある場合があります。その変更によってクラスが予期しない方法で他のシーンと相互作用する場合、問題が発生し始めます。このすべての間接化の要点は、1 つのクラスを変更すると、それに依存する他のクラスに悪影響を与える状況を回避することです。
スクリプトとシーンは、エンジン クラスの拡張として、すべてのOOP 原則に従う必要があります。例としては...
-
ノードツリー構造の選択¶
その結果、開発者はゲームの開発に取り組み始めましたが、目の前にある巨大な可能性に気づいて立ち止まってしまいます。彼らはおそらく、自分たちが何をしたいのか、どのようなシステムが欲しいのかを知っていますが、それをすべてどこに配置すればよいでしょうか? まあ、ゲームをどのように作るかは常に彼ら次第です。ノード ツリーは無限の方法で構築できます。ただし、よくわからない人のために、この役立つガイドは、開始するための適切な構造サンプルを提供します。
ゲームには常に、ある種の「入口点」が必要です。これは、開発者がどこから物事が始まるのかを明確に追跡できるため、別の場所で続行するときにロジックに従うことができるためです。この場所は、プログラム内の他のすべてのデータとロジックの鳥瞰図としても機能します。レガシー アプリケーションの場合、これが「メイン」機能になります。この場合、それはマスターノードになります。
ノード「メイン」(main.gd)
スクリプトはmain.gd
ゲームのメイン コントローラーとして機能します。
そして、実際のゲームの「世界」(2D または 3D 世界) ができます。これは Main の子である可能性があります。さらに、彼らのゲームには、プロジェクトに必要なさまざまなメニューやウィジェットを管理するためのメイン GUI が必要です。
ノード「メイン」(main.gd)
Node2D/Node3D“世界”(game_world.gd)
コントロール「GUI」(gui.gd)
レベルを変更すると、「ワールド」ノードの子を入れ替えることができます。 手動でシーンを変更すると、ユーザーはゲーム世界がどのように変化するかを完全に制御できます。
次のステップは、プロジェクトに必要なゲーム システムの種類を検討することです。システムがあれば...
-
すべてのデータを内部で追跡する
-
世界中からアクセスできる必要がある
-
孤立して存在すべきである
...そして、自動ロード「シングルトン」ノードを作成する必要があります。
ノート
小規模なゲームの場合、制御の少ない、よりシンプルな代替手段は、SceneTree.change_scene_to_file ( )メソッドを呼び出してメイン シーンのコンテンツを交換するだけの「ゲーム」シングルトンを使用することです。この構造により、多かれ少なかれ「世界」がメインのゲーム ノードとして維持されます。
また、GUI はシングルトンであるか、「ワールド」の一時的な部分であるか、ルートの直接の子として手動で追加される必要があります。それ以外の場合、GUI ノードはシーン遷移中にそれ自体も削除します。
あるシステムが他のシステムのデータを変更する場合、それらのシステムは自動ロードではなく、独自のスクリプトまたはシーンとして定義する必要があります。 その理由の詳細については、自動ロードと通常のノードのドキュメントを参照してください 。
ゲーム内のすべてのサブシステムには、SceneTree 内に独自のセクションが必要です。親子関係は、ノードがその親の有効な要素である場合にのみ使用する必要があります。親を削除するということは、子も削除する必要があることを合理的に意味するのでしょうか? そうでない場合は、兄弟またはその他の関係として階層内に独自の場所を設ける必要があります。
ノート
場合によっては、これらの個別のノードも相互に相対的に配置することが望ましい場合があります。このために、 RemoteTransform / RemoteTransform2D ノードを使用できます。これにより、ターゲット ノードが選択された変換要素を Remote* ノードから条件付きで継承できるようになります。target
NodePath を割り当てるには、次のいずれかを使用します。
-
信頼できる第三者 (おそらく親ノード) が割り当てを仲介します。
-
目的のノードへの参照を簡単に抽出できるグループ (ターゲットが 1 つだけであると仮定)。
これはいつ行うべきですか? まあ、これは主観的なものです。ノードが自身を保護するために SceneTree 内を移動しなければならない場合や、微細な管理を行う必要がある場合に、ジレンマが発生します。例えば...
-
「ルーム」に「プレイヤー」ノードを追加します。
-
ルームを変更する必要があるため、現在のルームを削除する必要があります。
-
ルームを削除する前に、プレーヤーを保持または移動する必要があります。
記憶力が問題なのでしょうか?
-
そうでない場合は、2 つの部屋を作成し、プレーヤーを移動して、古い部屋を削除するだけです。問題ない。
もしそうなら、次のことを行う必要があります...
-
プレーヤーをツリー内の別の場所に移動します。
-
部屋を削除します。
-
新しいルームをインスタンス化して追加します。
-
プレーヤーを再度追加します。
-
問題は、ここでのプレーヤーが「特殊なケース」であることです。開発者は、プロジェクトではプレーヤーをこの方法で処理する必要があることを理解しておく必要があります。したがって、この情報をチームとして確実に共有する唯一の方法は、それを文書化することです。ただし、実装の詳細をドキュメントに残しておくのは危険です。これはメンテナンスの負担となり、コードの可読性に影響を及ぼし、プロジェクトの知的内容を不必要に肥大化させます。
より大きなアセットを持つより複雑なゲームでは、プレーヤーを完全に SceneTree 内の別の場所に保持する方が良い場合があります。その結果、次のような結果が得られます。
-
より一貫性を高めます。
-
どこかに文書化して維持する必要がある「特別なケース」はありません。
-
これらの詳細は考慮されないため、エラーが発生する可能性はありません。
代わりに、親のトランスフォームを継承しない子ノードが必要な場合は、次のオプションがあります。
-
宣言的な解決策:それらの間にノードを置きます。遷移のないノードとして、ノードはそのような情報を子に渡しません。
-
強制的な解決策: CanvasItemまたは Node3Dノードのプロパティを使用します。これにより、ノードは継承された変換を無視します。
top_level
ノート
オンライン ゲームを構築する場合は、どのノードとゲーム システムがすべてのプレイヤーに関連し、どれが権威サーバーにのみ関連するかを念頭に置いてください。たとえば、ユーザー全員が各プレーヤーの「PlayerController」ロジックのコピーを持っている必要はありません。代わりに、彼らは自分自身のものを必要とするだけです。したがって、それらを「ワールド」とは別のブランチに保持すると、ゲーム接続などの管理が簡素化されます。
シーンを構成する鍵は、SceneTree を空間的な観点ではなく関係的な観点から考えることです。ノードは親ノードの存在に依存しますか? そうでない場合は、彼らは別の場所で独自に繁栄することができます。彼らが扶養家族である場合、おそらく彼らはその親の子供であるべきです(まだそうでない場合は、おそらくその親のシーンの一部です)。
これはノード自体がコンポーネントであることを意味しますか? 全くない。Godot のノード ツリーは、合成関係ではなく、集合関係を形成します。ただし、ノードを移動する柔軟性はまだありますが、デフォルトでそのような移動が不要になるのは望ましいことです。
シーンとスクリプトを使用する場合¶
シナリオとスクリプトの違いについて説明しました。スクリプトは、命令型コードを使用してエンジン クラス拡張を定義し、宣言型コードを使用してシナリオを定義します。
したがって、各システムの機能は異なります。シーンでは拡張クラスの初期化方法を定義できますが、実際の動作は定義できません。シーンはスクリプトと組み合わせて使用されることが多く、シーンはノードの組み合わせを宣言し、スクリプトは命令型コードを使用して動作を追加します。
匿名型¶
シーンのコンテンツは、スクリプトのみを使用して完全に定義できます。基本的に、これは Godot エディタのオブジェクトの C++ コンストラクター内で実行されることです。
ただし、どれを使用するかを選択するのはジレンマになる可能性があります。スクリプト インスタンスの作成はエンジン内クラスの作成と同じですが、シナリオの処理には API の変更が必要です。
const MyNode = preload("my_node.gd") const MyScene = preload("my_scene.tscn") var node = Node.new() var my_node = MyNode.new() # 同じメソッド呼び出し var my_scene = MyScene.instantiate() # 別のメソッド呼び出し var my_inherited_scene = MyScene.instantiate(PackedScene.GEN_EDIT_STATE_MAIN) # MyScene を継承してシーンを作成
また、エンジンとスクリプト コードの速度差により、スクリプトの実行はシーンよりも少し遅くなります。ノードが大きく複雑になればなるほど、それをシーンに構築する理由が増えます。
名前付き型¶
スクリプトはエディタ自体に新しいタイプとして登録できます。これにより、ノードまたはリソースの作成ダイアログに新しいタイプとしてオプションのアイコンとともに表示されます。このようにして、ユーザーによるスクリプトの使用能力が大幅に簡素化されます。そうする必要があるのではなく...
-
使用するスクリプトの基本的な種類を理解します。
-
この基本タイプのインスタンスを作成します。
-
スクリプトをノードに追加します。
登録されたスクリプトでは、システム内の他のノードやリソースと同様に、スクリプト タイプが作成オプションになります。作成ダイアログには、タイプを名前で検索するための検索バーもあります。
登録システムには次の 2 種類があります。
-
-
編集者のみ。型名は実行時にアクセスできません。
-
継承されたカスタム タイプはサポートされていません。
-
初期化ツール。スクリプトを使用してノードを作成します。それで全部です。
-
エディターは、スクリプトのタイプや、他のエンジン タイプやスクリプトとの関係を認識しません。
-
ユーザーがアイコンを定義できるようにします。
-
スクリプト リソースを抽象的に処理するため、すべてのスクリプト言語で動作します。
-
-
-
エディターとランタイムにアクセス可能。
-
継承関係の完全な表示。
-
ノードはスクリプトを使用して作成されますが、タイプはエディターから変更または拡張することもできます。
-
エディターは、スクリプト、スクリプト クラス、エンジン C++ クラス間の継承関係を認識しています。
-
ユーザーがアイコンを定義できるようにします。
-
エンジン開発者は、言語のサポート (名前の公開と実行時のアクセシビリティ) を手動で追加する必要があります。
-
Godot 3.1 以降のみ。
-
エディターはプロジェクト フォルダーをスキャンし、すべてのスクリプト言語の公開された名前を登録します。各スクリプト言語は、この情報を公開するための独自のサポートを実装する必要があります。
-
これらのメソッドはどちらも作成ダイアログに名前を追加しますが、特にスクリプト クラスを使用すると、ユーザーはスクリプト リソースをロードせずに型名にアクセスできます。インスタンスの作成と定数または静的メソッドへのアクセスはどこからでも可能です。
これらの機能を使用すると、ユーザーが使いやすくなるために、そのタイプをシナリオのないスクリプトにしたいと思う人もいるかもしれません。プラグインを開発している人や、デザイナーが使用する社内ツールを作成している人は、この方法が簡単になるでしょう。
マイナス面としては、命令型プログラミングを頻繁に使用する必要があることも意味します。
スクリプトとPackedSceneのパフォーマンス¶
シーンとスクリプトを選択するときに考慮すべき最後の側面は、実行速度です。
オブジェクトのサイズが大きくなるにつれて、オブジェクトの作成と初期化に必要なスクリプトのサイズも大きくなります。ノード階層を作成すると、これがわかります。各ノードのロジックは数百行のコードになる場合があります。
次のコード例では、新しいノードを作成しNode
、その名前を変更し、スクリプトを割り当て、将来の親を所有者として設定してノードと一緒にディスクに保存し、最後にノードの子として追加しますMain
。
# main.gd extends Node func _init(): var child = Node.new() child.name = "Child" child.script = preload("child.gd") child.owner = self add_child(child)
このようなスクリプト コードは、エンジン側の C++ コードよりもはるかに遅くなります。各命令はスクリプト API を呼び出します。これにより、バックエンドで複数の「検索」が実行され、実行するロジックが検索されます。
シーンは、このパフォーマンスの問題を回避するのに役立ちます。PackedScene は、Scene の継承元の基本タイプであり、シリアル化されたデータを持つオブジェクトを作成するリソースを定義します。このエンジンはバックエンドでシーンをバッチ処理し、スクリプトよりも優れたパフォーマンスを提供します。
結論¶ _
最終的に、最善のアプローチは次のことを考慮することです。
-
いくつかの異なるプロジェクトで再利用され、あらゆるスキル レベルの人々 (自分を「プログラマー」と称していない人々も含む) が使用する可能性が高い基本的なツールを作成したい場合は、おそらくスクリプトにすることが可能です。 、カスタム名/アイコンが付いている可能性があります。
-
誰かが自分のゲームに特有のコンセプトを作成したい場合、それは常にシーンである必要があります。シナリオはスクリプトよりも追跡/編集が簡単で、セキュリティが強化されます。
-
シーンに名前を付けたい場合は、3.1 でも、スクリプト クラスを宣言し、シーンを定数として指定することでそれを行うことができます。スクリプトは実際には名前空間になります。
# game.gd class_name Game # extends RefCounted なので、ノード作成ダイアログには表示されません extends RefCounted const MyScene = preload("my_scene.tscn") # main.gd extends Node func _ready(): add_child(Game. MyScene.instantiate())
オートローディングと通常のノード¶
Godot はプロジェクトのルート ノードを自動的にロードする機能を提供し、グローバルにアクセスできるようにし、シングルトンの役割を完了できます: シングルトン ( 自動ロード)。これらの自動ロードされたノードは、 SceneTree.change_scene_to_file を使用してコードからシーンを変更しても解放されません。
このガイドでは、自動ロードを使用するタイミングと、それを回避するために使用できるトリックについて説明します。
オーディオの問題をカットする¶
他のエンジンは、グローバルにアクセス可能なオブジェクト内のシングルトンに大量の機能を編成するマネージャー クラスの作成の使用を奨励する場合があります。Godot は、ノード ツリーとシグナルのおかげで、グローバル状態を回避する多くの方法を提供します。
たとえば、プラットフォーマーを構築していて、効果音を再生するコインを収集したいとします。AudioStreamPlayerという 1 つのノードがあります。しかし、すでにサウンドを再生しているときにそれを呼び出すとAudioStreamPlayer
、新しいサウンドが最初のサウンドを中断します。
解決策の 1 つは、グローバルな自動ロードされたサウンド マネージャー クラスを作成することです。AudioStreamPlayer
新しい効果音リクエストが受信されるたびに循環するノードのプールを生成します。このクラスを呼び出すとSound
、プロジェクト内のどこからでも呼び出すことでそのクラスを使用できますSound.play("coin_pickup.ogg")
。これにより問題は短期的には解決されますが、さらなる問題が発生します。
-
グローバル状態: 1 つのオブジェクトがすべてのオブジェクトのデータを担当するようになりました。このクラスに
Sound
バグがある場合、または利用可能な AudioStreamPlayer がない場合、このクラスを呼び出すすべてのノードが壊れる可能性があります。 -
グローバル アクセス: どこからでも任意のオブジェクトを呼び出すことができるようになったため
Sound.play(sound_path)
、エラーの原因を簡単に見つける方法はなくなりました。 -
グローバル リソース割り当て
AudioStreamPlayer
: 最初から保存されているノードのプールでは、メモリが少なすぎてエラーが発生するか、メモリが多すぎて必要以上に多くのメモリを使用します。
注: グローバル アクセスの問題は、Sound
この例では、コードがどこにいても間違ったデータを自動ロードに渡す可能性があることです。したがって、バグを修正するために調査する領域はプロジェクト全体に及びます。
シーンにコードを保存する場合、オーディオに含まれるスクリプトは 1 つまたは 2 つだけである場合があります。
これを、AudioStreamPlayer
各シーン内にできるだけ多くのノードを保持することと比較すると、これらの問題はすべて解決されます。
-
各シーンは独自の状態情報を管理します。データに問題がある場合、そのシーンで問題が発生するだけです。
-
各シーンは独自のノードにのみアクセスします。これで、エラーが発生した場合に、どのノードに問題があるのかを簡単に見つけることができます。
-
各シーンは、必要な量のリソースを正確に割り当てます。
共有機能またはデータの管理¶
自動読み込みを使用するもう 1 つの理由は、複数のシナリオで同じメソッドまたはデータを再利用したい場合です。
関数の場合、Node
GDScript でclass_name キーワードを使用して、単一のシーンにその機能を提供する新しいタイプを作成できます。
データ側では、次のことができます。
-
データを共有するための新しいタイプのリソースを作成します。
-
owner
たとえば、プロパティを使用してシーンのルート ノードにアクセスするなど、すべてのノードがアクセスできるオブジェクトにデータを保存します。
自動ロードを使用する必要がある場合¶
ノードを自動ロードすると、場合によってはコードを簡素化できます。
-
静的データ: データベースなど、クラスに固有のデータが必要な場合、自動読み込みは優れたツールです。Godot には、静的データを作成および管理するためのスクリプト API はありません。
-
静的関数: 値を返すだけの関数のライブラリを作成します。
-
幅広いシステム: シングルトンが他のオブジェクトのデータを侵害することなく独自の情報を管理する場合、これは幅広いタスクを処理するシステムを作成する良い方法です。たとえば、クエストや対話システムなどです。
Godot 3.1 より前は、別の使用法が単に便宜上行われていました。オートロードには、GDScript で生成された名前を持つグローバル変数があり、プロジェクト内の任意のスクリプト ファイルから呼び出すことができました。ただし、class_name
キーワードを使用して、プロジェクト全体の型のオートコンプリートを取得できるようになりました。
注: Autoload は正確にはシングルトンではありません。自動ロード ノードのコピーをインスタンス化することを妨げるものは何もありません。これは、ゲームのノード構造やどのシーンが実行されているかに関係なく、たとえば F6 キーを押すことによって、ノードをシーン ツリー ルートの子として自動的にロードさせるための単なるツールです。
したがって、 Sound
たとえば、名前付きget_node("/root/Sound")
autoload を呼び出すことで、自動ロードされたノードを取得できます。
すべてにノードの使用を避ける時期と方法¶
ノードは安価に製造できますが、それでも限界があります。プロジェクトには、何万ものノードが処理を実行している場合があります。ただし、その動作が複雑になればなるほど、それぞれがプロジェクトのパフォーマンスに与えるプレッシャーも大きくなります。
Godot は、ノードで使用される API を作成するためのより軽量なオブジェクトを提供します。プロジェクトの機能をどのように構成するかを設計する際には、オプションとしてこれらを念頭に置くことが重要です。
-
オブジェクト : 究極の軽量オブジェクト。元のオブジェクトは手動メモリ管理を使用する必要があります。そうは言っても、独自のカスタム データ構造を作成するのはそれほど難しいことではありません。また、ノード構造ですら Nodeクラスよりも軽量です。
-
例:ツリーノードを表示します。任意の数の行と列を含むディレクトリの高度なカスタマイズをサポートします。ビジュアライゼーションの生成に使用されるデータは、実際にはTreeItemオブジェクトのツリーです。
-
長所: API を簡素化してオブジェクトの範囲を小さくすると、アクセシビリティが向上し、反復時間が短縮されます。ノード ライブラリ全体を使用するのではなく、ノードが適切な子ノードを生成および管理できる単純化されたオブジェクトのセットを作成します。
注: 取り扱いには注意が必要です。オブジェクトは変数に格納できますが、これらの参照は警告なしに無効になる可能性があります。たとえば、オブジェクトの作成者が不可解にもオブジェクトを削除することを決定した場合、次回オブジェクトにアクセスしたときにエラー状態がトリガーされます。
-
-
RefCounted : Object よりも少し複雑です。これらは自分自身への参照を追跡し、自分自身への参照がなくなった場合にのみロードされたメモリを削除します。これらは、カスタム クラスのデータが必要なほとんどの場合に役立ちます。
-
例: FileAccessオブジェクトを参照してください。自分で削除する必要がない点を除けば、通常のオブジェクトと同じように機能します。
-
利点:オブジェクトと同じ。
-
-
リソース: RefCounted よりもわずかに複雑なだけです。これらには、Godot リソース ファイルとの間でオブジェクト プロパティをシリアル化/逆シリアル化 (つまり、保存およびロード) する機能が備わっています。
-
例: Script、PackedScene (シーン ファイル用)、および各AudioEffectクラスなどのその他のタイプ。これらはそれぞれ保存およびロードできるため、リソースから拡張されます。
-
利点:従来のデータ ストレージ方法に対するリソースの利点 については、多くのことが言われています。ただし、ノードではなくリソースを使用するコンテキストでは、その主な利点はインスペクターの互換性です。Object/RefCounted とほぼ同じくらい軽量ですが、インスペクターでプロパティを表示およびエクスポートできます。これにより、可用性の点で子ノードと同様の目的を果たすことができますが、シーン内にそのようなリソース/ノードを多数持つ予定の場合はパフォーマンスも向上します。
-
Godotインターフェース¶
プロパティを取得するには、他のオブジェクトのスクリプトに依存する必要があることがよくあります。このプロセスには 2 つの部分があります。
-
これらの特性を持つ可能性のあるオブジェクトへの参照を取得します。
-
オブジェクトからデータまたはロジックにアクセスします。
このチュートリアルの残りの部分では、これらすべてを行うためのさまざまな方法の概要を説明します。
オブジェクト参照の取得¶
すべてのObjectと同様、それらを参照する最も基本的な方法は、取得した別のインスタンスから既存のオブジェクトへの参照を取得することです。
var obj = node.object # プロパティへのアクセス。 var obj = node.get_object() # メソッドへのアクセス。
同じ原則がRefCountedオブジェクトにも当てはまります。ユーザーはこの方法でノードと リソースにアクセスすることがよくありますが、代替手段も利用できます。
プロパティやメソッドにアクセスする代わりに、アクセスをロードすることでリソースを取得できます。
var preres = preload(path) # シーンの読み込み中にリソースを読み込みます var res =load(path) # プログラムがステートメントに到達したときにリソースを読み込み ます # ユーザーは慣例により、PascalCase の 名前 (タイプ名など) を使用してシーンとスクリプトを読み込むことに注意してください # 多くの場合、定数。 const MyScene : = preload("my_scene.tscn") as PackedScene # 静的ロード const MyScript : = preload("my_script.gd") as Script # この型の値は変化します。つまり、変数なので、snake_case を使用します。 import(Script) var script_type: Script # 「export const var」(存在しない) が必要な場合は、条件を使用します。 # エディターで実行されているかどうかをチェックするツール スクリプトのセッター。 ツール# ファイルの先頭に配置する必要があります。 # エディターから設定する必要があります。デフォルトは null です。 export(Script) var const_script setget set_const_script func set_const_script(value): if Engine.is_editor_hint(): const_script = value # 値が設定されていない場合はユーザーに警告します。func _get_configuration_warning(): const_scriptでない場合: 「プロパティ 'const_script' を初期化する必要があります。」を 返します。「」 を返す
次の点に注意してください:
-
言語は、いくつかの方法でそのようなリソースをロードできます。
-
オブジェクトがデータにアクセスする方法を設計するときは、リソースを参照として渡すこともできることを忘れないでください。
-
リソースをロードすると、エンジンによって維持されるキャッシュされたリソース インスタンスが取得されることに注意してください。新しいオブジェクトを取得するには、既存の参照 をコピーするか使用する必要があります
new()
。
ノードには、代替アクセス ポイントである SceneTree もあります。
extendsノード番号が遅い。func Dynamic_lookup_with_dynamic_nodepath(): print(get_node("Child")) # 高速化。GDScript のみ。func Dynamic_lookup_with_cached_nodepath(): print($Child) # 最速。ノードが後で移動しても壊れません。# `@onready` アノテーションは GDScript のみであることに注意してください。# 他の言語でも行う必要があります... # var child # func _ready(): # child = get_node("Child") @onready var child = $Child func lookup_and_cache_for_future_access(): print(child) # 外部ソースへの参照割り当てを委任する。 # 短所: 検証チェックを実行する必要がある。 # 長所: ノードはその外部構造を必要としません。 # 'prop' はどこからでも取得できます。 var prop func call_me_after_prop_is_initialized_by_parent(): # 3 つの方法のいずれかで prop を検証します。 # 通知なしで失敗します。 propでない場合 : return # エラー メッセージが表示されて失敗します。propでない場合: printerr("'prop' が初期化されていませんでした") return # 失敗して終了します。# 注: リリース エクスポート テンプレートから実行されるスクリプトは、# `assert` ステートメントを実行しません。 assert(prop, "'prop' が初期化されていませんでした") # オートロードを使用します。 # 一般的なノードにとっては危険ですが、 # 独自のデータを管理し、他のオブジェクトに干渉しない真のシングルトン ノードには役立ちます。 func Reference_a_global_autoloaded_variable(): print(globals) print(globals.prop) print(globals.my_getter())
オブジェクトからのデータまたはロジックへのアクセス¶
Godot のスクリプト API は DUCK です。これは、スクリプトが操作を実行する場合、Godot はその型操作をサポートしているかどうかを検証しないことを意味します。代わりに、オブジェクトが個々のメソッドを実装しているかどうかをチェックします。
たとえば、CanvasItemクラスにはプロパティがありますvisible
。スクリプト API に公開されるすべてのプロパティは、実際には名前にバインドされたセッターとゲッターのペアです。誰かが CanvasItem.visibleにアクセスしようとすると、Godot は次のチェックを順番に実行します。
-
オブジェクトにスクリプトがアタッチされている場合、オブジェクトはスクリプトを介してプロパティの設定を試みます。これにより、スクリプトは、プロパティのセッター メソッドをオーバーライドすることによって、基本オブジェクトで定義されたプロパティをオーバーライドする機会が得られます。
-
スクリプトにその属性がない場合は、ClassDB で CanvasItem クラスとそのすべての継承型に対して HashMap ルックアップを実行して、「visible」属性を見つけます。見つかった場合は、バインドされたセッターまたはゲッターを呼び出します。HashMap の詳細については、 データ設定のドキュメントを参照してください。
-
見つからない場合は、ユーザーが「スクリプト」属性または「メタ」属性にアクセスすることを意図していたかどうかを明示的にチェックします。
-
そうでない場合は、CanvasItem の /implementation とその派生型
_set
(アクセス タイプに応じて) がチェックされます。_get
これらのメソッドは、オブジェクトにプロパティがあるかのような印象を与えるロジックを実行できます。メソッドについても同様です_get_property_list
。-
これは、 TileSetの "1/tile_name" 属性などの不正なシンボル名でも発生することに注意してください。これは、ID 1 のタイルの名前、つまり を指します
TileSet.tile_get_name(1)
。
-
その結果、この DUCK タイプのシステムは、スクリプト内、オブジェクトのクラス内、またはオブジェクトの継承元のクラス内のプロパティを見つけることができますが、オブジェクトを拡張するものに限定されます。
Godot は、これらのアクセスに対して実行時チェックを実行するためのさまざまなオプションを提供します。
-
アヒルのプロパティへのアクセス。これらは物件検査になります(前述)。オブジェクトが操作をサポートしていない場合、実行は停止します。
# すべてのオブジェクトには、アヒル型の get、set、および call ラッパー メソッドがあります。 get_parent()。set ("visible", false) # メソッド呼び出しで文字列ではなくシンボル アクセサーを使用すると、# 暗黙的に `set` メソッドが呼び出され、# プロパティ ルックアップを通じてプロパティにバインドされたセッター メソッドが呼び出されます。 # 順序。 get_parent().visible = false # プロパティの存在を記述する _set と _get を # 定義しても、そのプロパティがどの _get_property_list メソッドでも # 認識されない場合、set() メソッドと get() メソッドは機能することに注意してください。しかし、シンボル# access はプロパティが見つからないと主張します。
-
メソッドチェック。CanvasItem.visible の場合、これらのメソッドは他のメソッド
set_visible
と同様にアクセスできます。is_visible
var child = get_child(0) # 動的検索。 child.call("set_visible", false) # シンボルベースの動的検索。# GDScript は、これを舞台裏で「call」メソッドにエイリアスします。 child.set_visible(false) # 動的検索。最初にメソッドの存在を確認します。if child.has_method("set_visible"): child.set_visible(false) # キャストチェックに続いて動的検索。# クラスがそれらすべてを実装していることが分かっている場合に、複数の「安全な」呼び出しを行う場合に便利です。繰り返しのチェックは必要ありません。# ユーザー定義型のキャスト チェックを実行すると、# より多くの依存関係が強制されるため、注意が必要です。子がCanvasItemの場合: child.set_visible(false) child.show_on_top = true # ユーザーに通知せずにこれらのチェックに失敗したくない場合は、# 代わりにアサートを使用できます。これらは、真でない場合、 # すぐに実行時エラーを引き起こします。 assert(child.has_method("set_visible")) assert(child.is_in_group("offer")) assert(child is CanvasItem) # オブジェクトラベルを使用してインターフェイスを暗示することもできます。つまり、インターフェイスが# 特定のメソッドを実装していると仮定します。# 名前とグループという 2 つのタイプがあり、どちらもノードに対してのみ存在します。 # # 仮定します... # 「Quest」オブジェクトが存在し、1) それが「完了」または「失敗」する可能性がある、そして # 各状態の前後にテキストが表示されるようにします... # 1. 名前を使用します。 var Quest = $Quest print(quest.text) Quest.complete() # または Quest.fail() print(quest.text) # 暗黙の新しいテキスト コンテンツ # 2. グループを使用します。 for a_child in get_children(): if a_child.is_in_group("quest"): print(quest.text) Quest.complete() # または Quest.fail() print(quest.text) # 暗黙の新しいテキスト内容 # これらに注意してくださいインターフェースは、チームが # 定義するプロジェクト固有の規則です (これはドキュメントを意味します! しかし、それだけの価値はあるでしょうか?)。 # 文書化された名前またはグループの「インターフェース」に準拠する任意のスクリプトで、 # 名前またはグループを埋めることができます。
-
アクセスを Callable にアウトソーシングします。これらは、依存関係から最大限の自由が必要な状況で役立ちます。この場合、外部コンテキストに依存してメソッドを設定します。
# child.gd extends Node var fn = null func my_method(): if fn: fn.call() #parent.gd extends Node @onready var child = $Child func _ready(): child.fn = print_me child.my_method( ) func print_me(): print(名前)
これらの戦略は、Godot の柔軟な設計に貢献しています。これらの間に、ユーザーは特定のニーズを満たす幅広いツールを持っています。
Godot通知¶
Godot のすべてのオブジェクトは _notificationメソッドを実装します。その目的は、オブジェクトが、関連付けられている可能性のあるさまざまなエンジン レベルのコールバックに応答することです。たとえば、エンジンが CanvasItemに「描画」するように指示すると、 を呼び出します _notification(NOTIFICATION_DRAW)
。
これらの通知の一部 (ペイントなど) は、スクリプトでオーバーライドすると便利です。そのため、Godot は専用の関数を備えた多数の関数を公開しています。
-
_ready()
:NOTIFICATION_READY -
_enter_tree()
:NOTIFICATION_ENTER_TREE -
_exit_tree()
:NOTIFICATION_EXIT_TREE -
_process(delta)
:NOTIFICATION_PROCESS -
_physics_process(delta)
:NOTIFICATION_PHYSICS_PROCESS -
_draw()
:NOTIFICATION_DRAW
ユーザーが気づいていない可能性があるのは、ノード以外の他のタイプの通知も存在することです。たとえば、次のとおりです。
-
Object::NOTIFICATION_POSTINITIALIZE : オブジェクトの初期化中に発生するコールバック。スクリプトにアクセスできません。
-
Object::NOTIFICATION_PREDELETE : エンジンがオブジェクト (「デストラクター」) を削除する直前に起動されるコールバック。
ノードに存在するコールバックの多くには専用のメソッドがありませんが、それでも非常に便利です。
-
Node::NOTIFICATION_PARENTED : 子ノードが別のノードに追加されるたびにコールバックが発生します。
-
Node::NOTIFICATION_UNPARENTED : 子ノードが別のノードから削除されるたびにコールバックが発生します。
_notification
これらすべてのカスタム通知には、一般的なメソッドからアクセスできます 。
注: ドキュメントで「仮想」とマークされているメソッドも、スクリプトによってオーバーライドされることを目的としています。
典型的な例は、 Object の_initメソッドです。同等のものはありませんが NOTIFICATION_*
、エンジンは依然としてメソッドを呼び出します。ほとんどの言語 (C# を除く) はコンストラクターとしてこれに依存します。
では、これらの通知や仮想機能はいつ使用する必要があるのでしょうか?
_process と _physics_process と * _input¶
_process
フレームレートに依存するフレーム間のデルタ時間が必要な場合に使用されます。オブジェクトのデータを更新するコードをできるだけ頻繁に更新する必要がある場合、これは適切な場所です。通常、ループ ロジック チェックとデータ キャッシュがここで実行されますが、最終的には更新を評価する必要がある頻度によって決まります。すべてのフレームを実行する必要がない場合は、timer-yield-timeout ループを実装することも別のオプションです。
# 無限ループしますが、タイマーが起動するたびにのみ実行されます。 # スクリプト ロジックをトリガーしない繰り返し操作が可能 # フレームごと (または固定フレームごと)。 trueの場合: my_method() $Timer.start() yield($Timer, "タイムアウト")
_physics_process
フレームレートに依存しないフレーム間のデルタ時間が必要な場合に使用します。時間の進み方が速いか遅いかに関係なく、時間の経過とともにコードを一貫して更新する必要がある場合は、これが適切な場所です。サイクリック キネマティクスとオブジェクトの変換操作はここで実行する必要があります。
可能な限り、最高のパフォーマンスを得るには、これらのコールバック中の入力チェックを避ける必要があります。_process
そして _physics_process
あらゆる機会に発火します(デフォルトでは「休止」しません)。代わりに、*_input
コールバックは、エンジンが実際に入力を検出したフレームでのみ起動されます。
入力コールバック内の入力アクションも同様に確認できます。デルタ時間を使用する場合は、必要に応じて関連するデルタ時間メソッドから取得できます。
# エンジンが入力を検出しない場合でも、フレームごとに呼び出されます。 func _process(delta): if Input.is_action_just_pressed("ui_select"): print(delta) # すべての入力イベント中に呼び出されます。func _unhandled_input(event): match event.get_class(): "InputEventKey": if Input.is_action_just_pressed("ui_accept"): print(get_process_delta_time())
_init と初期化とエクスポート¶
スクリプトがシーンなしで独自のノード サブツリーを初期化する場合、そのコードはここで実行される必要があります。SceneTree から独立した他のプロパティまたは初期化もここで実行する必要があります。_ready
これは、 または の前_enter_tree
、ただしスクリプトが作成され、そのプロパティが初期化された後に起動されます。
スクリプトのインスタンス化中に、次の 3 種類のプロパティ割り当てが発生する可能性があります。
# 「1」は「初期化された値」です。これらはセッターをトリガーしません。 # 誰かがインスペクターから値を「2」に設定した場合、これは # 「エクスポートされた値」になります。これらはセッターをトリガーします。 export(String) var test = "one" setget set_test func _init(): # "three" は "初期化割り当て値" です。# これらはセッターをトリガーしませんが... test = "three" # これらはセッターをトリガーします。「self」接頭辞に注意してください。 self.test = "three" func set_test(value): test = value print("設定: ", test)
シーンをインスタンス化するとき、プロパティ値は次の順序で設定されます。
-
初期値の割り当て:インスタンス化により、初期化値または初期割り当て値が割り当てられます。初期化割り当ては、初期化値よりも優先されます。
-
エクスポート値の割り当て:スクリプトではなくシーンからインスタンス化された場合、Godot はスクリプトで定義された初期値を置き換えるエクスポート値を割り当てます。
したがって、スクリプトとシーンのインスタンス化は、初期化とエンジンがセッターを呼び出す回数の両方に影響します。
_ready と _enter_tree とNOTIFICATION_PARENTED¶
最初に実行中のシーンに接続されたシーンをインスタンス化するとき、Godot はツリーの下位にあるノードをインスタンス化し (呼び出しを行い_init
)、ルートから下にツリーを構築します。これにより、_enter_tree
呼び出しがツリーの下にカスケードされます。ツリーが完了するとリーフ ノードによって呼び出されます_ready
。すべての子ノードがメソッドの呼び出しを完了すると、ノードはこのメソッドを呼び出します。これにより、ツリーのルートに戻る逆カスケードが発生します。
スクリプトまたはスタンドアロン シーンをインスタンス化する場合、作成時にノードが SceneTree に追加されないため、_enter_tree
コールバックは起動されません。代わりに、 を_init
呼び出すだけです。シーンが SceneTree に追加されるときに発生し_enter_tree
、呼び出されます_ready
。
別のノードの親として発生する動作をトリガーする必要がある場合は、それがメイン/アクティブ シーンの一部として発生するかどうかに関係なく、 PARENTED通知 を使用できます。たとえば、ノードのメソッドを親ノードのカスタム信号に失敗せずに接続するスニペットを次に示します。実行時に作成される可能性のあるデータ中心のノードに役立ちます。
extends Node varparent_cache func connection_check(): returnparent_cache.has_user_signal ("interacted_with") func _notification(what): 一致するもの: NOTIFICATION_PARENTED: parent_cache = get_parent() if connection_check(): parent_cache.interacted_with.connect (_on_parent_interacted_with) NOTIFICATION_UNPARENTED: if connection_check(): parent_cache.interacted_with.disconnect(_on_parent_interacted_with) func _on_parent_interacted_with(): print("私は親のやりとりに反応しています!")
データ設定¶
問題 X を解決するためにデータ構造 Y と Z のどちらを使用すべきか考えたことはありますか? この記事では、これらのジレンマに関連するさまざまなトピックについて説明します。
注: この記事では、「[something]-time」操作について説明します。この用語は、アルゴリズム分析の Big O Notationに由来しています。
簡単に言うと、これは実行時間の長さに関する最悪のケースを説明しています。平たく言えば:
「問題領域のサイズが大きくなると、アルゴリズムの実行にかかる時間も長くなります...」
-
一定時間、
O(1)
「・・・増えない。」 -
対数時間: 「...ゆっくりとした速度で増加します。」
O(log n)
-
線形時間、
O(n)
「...同じ割合で増加します。」 -
等。
1 つのフレームで 300 万のデータ ポイントを処理する必要がある場合を想像してください。データのサイズが膨大になると実行時間が割り当て時間を大幅に超えてしまうため、線形時間アルゴリズムを使用して特徴を作成することはできません。対照的に、定数時間アルゴリズムを使用すると、この操作は問題なく処理できます。
一般に、開発者は線形時間操作への関与をできる限り避けたいと考えています。ただし、線形時間演算のサイズが小さく保たれ、演算を頻繁に実行する必要がない場合は、許容できる場合があります。これらの要件のバランスをとり、仕事に適したアルゴリズム/データ構造を選択することが、プログラマーのスキルの価値の一部となります。
配列 vs 辞書 vsオブジェクト¶
Godot は、スクリプト API のすべての変数を Variantクラスに保存します。バリアントには、 Array、Dictionary、およびObjectなどの Variant 互換のデータ構造を格納できます 。
Godot は配列を として実装しますVector<Variant>
。エンジンは配列の内容をメモリの連続した部分に保存します。つまり、配列は連続して並べられます。
注: C++ に詳しくない方のために説明すると、Vector は従来の C++ ライブラリの配列オブジェクトの名前です。これは「テンプレート化された」タイプであり、そのレコードには特定のタイプ (山かっこで示される) のみを含めることができることを意味します。したがって、たとえば、 PackedStringArrayは に似ていますVector<String>
。
連続したメモリ ストレージとは、次の動作パフォーマンスを意味します。
-
反復:最速。ループに最適です。
-
Op: 次のレコードを取得するためにカウンターをインクリメントするだけです。
-
-
挿入、消去、移動:位置に依存します。一般的には遅くなります。
-
Op: コンテンツの追加/削除/移動には、隣接するレコードの移動が含まれます (スペースを作る/スペースを埋めるため)。
-
末尾に素早く追加/削除します。
-
任意の位置にゆっくりと追加/削除します。
-
前からの追加/削除は最も遅くなります。
-
正面から何度も抜き差しをすると…
-
配列を反転します。
-
ループを実行し、最後に配列の変更を実行します。
-
配列を反転します。
これにより、配列のコピーは 2 つだけ作成されます (それでも一定時間ですが、速度は遅くなります)。一方、配列の約 1/2 は、平均で N 回 (線形時間) コピーされます。
-
-
-
取得、設定:場所による最速。たとえば、レコード 0、2、10 などをリクエストできますが、どのレコードが必要かを指定することはできません。
-
Op: 配列の先頭から目的のインデックスまでの 1 回の加算演算。
-
-
ルックアップ:最も遅い。ID 値のインデックス/位置。
-
操作: 配列を反復処理し、一致が見つかるまで値を比較する必要があります。
-
パフォーマンスは、徹底的な検索が必要かどうかによっても異なります。
-
-
カスタム検索操作は、順序を守って実行すれば、対数時間で (比較的高速に) 実行できます。ただし、一般ユーザーはこれに満足しないでしょう。これを行うには、各編集後に配列の順序を変更し、順序を意識した検索アルゴリズムを作成します。
-
Godot は、エンジンがキーと値のペアを格納する小さな配列 (2^3 または 8 レコードに初期化) として Dictionary を実装します。ユーザーが値にアクセスしようとするとき、値にキーを提供します。次に、キーをハッシュ化します。つまり、キーを数値に変換します。「ハッシュ」は配列内のインデックスを計算するために使用されます。次に、OHM は値にマップされたキーの「テーブル」を配列として簡単に検索します。HashMap がいっぱいになると、次の 2 のべき乗 (つまり、16 レコード、次に 32 など) に増分し、構造を再構築します。OrderedHashMap<Variant, Variant>
ハッシュはキーの衝突の可能性を減らすために行われます。これが発生した場合、テーブルは前の位置を考慮して値を取得するために別のインデックスを再計算する必要があります。全体として、これにより、メモリと若干の操作効率が犠牲になって、すべてのレコードに常時アクセスすることになります。
-
各キーは何度でもハッシュ化されます。
-
ハッシュは一定時間であるため、アルゴリズムを複数回実行する必要がある場合でも、ハッシュの数がテーブルの密度にあまり依存しない限り、処理は高速に行われます。これはにつながります...
-
-
テーブルのサイズを拡大し続けます。
-
HashMap は、ハッシュの衝突を減らし、アクセス速度を維持するために、未使用のメモリのギャップをテーブル全体に意図的に分散させます。そのため、そのサイズは 2 乗に応じて増加し続けます。
-
辞書は、配列が苦手なタスクに特化しているとも言えるでしょう。それらの操作の詳細は次のとおりです。
-
反復:速い。
-
Op: 反復マップの内部ハッシュ ベクトル。各キーを返します。その後、ユーザーはキーを使用して目的の値にジャンプし、その値に戻ります。
-
-
挿入、消去、移動:最速。
-
Op: 指定されたキーをハッシュします。1 回の加算を実行して、適切な値 (配列の開始 + オフセット) を見つけます。移動はそのうちの 2 つです (挿入 1 つ、消去 1 つ)。マップの機能を維持するには、マップのメンテナンスを行う必要があります。
-
レコードの順序付きリストを更新します。
-
テーブル密度要件に拡張テーブル容量が必要かどうかを判断します。
-
-
辞書は、ユーザーがキーを挿入した順序を記憶します。これにより、信頼性の高い反復を実行できるようになります。
-
-
取得、設定:最速。キーによる検索と同じです。
-
Op: 挿入/消去/移動と同じです。
-
-
ルックアップ:最も遅い。値を識別するキー。
-
操作: 一致するものが見つかるまでレコードを反復処理し、値を比較する必要があります。
-
Godot はこの機能をそのままでは提供していないことに注意してください (この機能はこのタスクには適していないため)。
-
Godot はオブジェクトを、愚かではあるが動的なデータ コンテンツ コンテナとして実装します。オブジェクトは質問するときにデータ ソースをクエリします。たとえば、「'position' というプロパティはありますか?」という質問に答えるために、そのスクリプトまたはClassDBに質問することがあります。オブジェクトとは何か、そしてそれらがどのように機能するかについては、「Godot でのオブジェクト指向の原則の適用」の記事で詳しく知ることができます。
ここで重要なのは、オブジェクト タスクの複雑さです。これらのマルチソース クエリのいずれかが実行されるたびに、複数の 反復ループと HashMap ルックアップが実行されます。また、クエリはオブジェクトの継承階層のサイズに応じて線形時間操作です。Object によってクエリされたクラス (その現在のクラス) で何も見つからなかった場合、リクエストは次の基本クラス、さらには元の Object クラスまで延期されます。これらは単独では高速な操作ですが、非常に多くのチェックを実行する必要があるため、データを検索する両方の方法よりも遅くなります。
注: 開発者がスクリプト API の遅さに言及するときは、このクエリ チェーンのことを指します。スクリプト API 操作は、アプリケーションがどこを探すべきかを正確に認識しているコンパイル済み C++ コードよりも必然的に時間がかかります。関連するデータにアクセスする前に、そのソースを見つける必要があります。
GDScript は、実行されるすべての操作がこのシステムを経由するため、低速になります。
C# は、より最適化されたバイトコードを使用して、一部の処理を高速に処理できます。ただし、C# スクリプトがエンジン クラスのコンテンツを呼び出す場合、またはスクリプトがエンジン クラスの外部のコンテンツにアクセスしようとする場合は、このパイプを経由します。
NativeScript C++ はさらに一歩進んで、デフォルトですべてを内部に保持します。外部構造への呼び出しは、スクリプト API を通じて行われます。NativeScript C++ では、メソッドを登録してスクリプト API に公開するのは手動のタスクです。この時点で、外部の非 C++ クラスは API を使用してクラスを見つけます。
では、参照から拡張して配列や辞書のようなデータ構造を作成すると仮定すると、なぜ他の 2 つのオプションではなくオブジェクトを選択するのでしょうか?
-
コントロール:オブジェクトを使用すると、より複雑な構造を作成できるようになります。データを階層的に抽象化して、内部データ構造が変化しても外部 API が変化しないようにすることができます。さらに、オブジェクトは信号を持つことができるため、反応的な動作が可能になります。
-
明確さ:スクリプトおよびエンジン クラスがオブジェクトに対して定義するデータに関しては、オブジェクトは信頼できるデータ ソースです。プロパティには予期した値が含まれていない可能性がありますが、プロパティが存在するかどうかを心配する必要はありません。
-
利便性:同様のデータ構造をすでに念頭に置いている場合は、既存のクラスを拡張することで、データ構造を構築するタスクがはるかに簡単になります。対照的に、配列と辞書は、考えられるすべてのユースケースを満たしているわけではありません。
オブジェクトにより、ユーザーはより特殊なデータ構造を作成する機会も得られます。これを使用すると、独自のリスト、二分探索ツリー、ヒープ、スプレイ ツリー、グラフ、素セット、その他のオプションを設計できます。
「なぜツリー構造に Node を使用しないのですか?」と疑問に思う人もいるかもしれません。Node クラスには、カスタム データ構造とは何の関係もないものが含まれています。したがって、ツリー構造を構築するときに独自のノード タイプを構築すると便利です。
extends Object class_name TreeNode var _parent: TreeNode = null var _children: = [] setget func _notification(p_what): match p_what: NOTIFICATION_PREDELETE: # デストラクター。_childrenのa_childの場合: a_child.free()
ここから、人々は想像力だけを頼りに、特定の機能を備えた独自の構造を作成できるようになります。
列挙型: int とstring¶
ほとんどの言語には、列挙型のオプションが用意されています。GDScript も例外ではありませんが、他のほとんどの言語とは異なり、整数または文字列を列挙値として使用できます (後者はexport
GDScript でキーワードを使用する場合のみ)。そこで問題になるのが、「どれを使用すべきか?」ということです。
端的に言えば、「どちらが自分にとって快適か」です。これは GDScript に固有の機能であり、Godot スクリプト一般ではありません。これらの言語はパフォーマンスよりも使いやすさを優先します。
技術レベルでは、整数比較 (定数時間) は文字列比較 (線形時間) よりも高速に行われます。他の言語の規則を維持したい場合は、整数を使用する必要があります。
整数の使用に関する主な問題は、列挙値を出力する場合に発生します。整数として、MY_ENUM を印刷しようとすると、次 5
のようなものではなく、または何が表示されますか?"MyEnum"
整数の列挙型を印刷するには、各列挙型の対応する文字列値をマップする辞書を作成する必要があります。
列挙型を使用する主な目的が値を出力することであり、それらを関連する概念としてグループ化したい場合は、列挙型を文字列として使用するのが合理的です。このようにして、印刷を実行するために別のデータ構造は必要ありません。
AnimatedTexture 対 AnimatedSprite2D 対アニメーションプレーヤー対アニメーションツリー¶
Godot の各アニメーション クラスはいつ使用する必要がありますか? Godot の新規ユーザーには、その答えがすぐには分からないかもしれません。
AnimatedTextureは、エンジンが静止画像ではなくアニメーション ループとして描画するテクスチャです。ユーザーが操作できるのは...
-
テクスチャの各部分を移動する速度 (fps)。
-
テクスチャ(フレーム)に含まれる領域の数。
Godot のRenderingServer は、指定されたレートで領域を順番にレンダリングします。良いニュースは、これにはエンジン側に追加のロジックが必要ないことです。悪いニュースは、ユーザーがほとんど制御できないことです。
ここで説明する他のノードオブジェクトとは異なり、AnimatedTexture はリソースであることにも注意してください。AnimatedTexture をテクスチャとして使用する Sprite2D ノードを作成することができます。または (他の人にはできないことですが) AnimatedTexture をタイルとして TileSet に追加し、それを TileMapと統合して、自動的にアニメーション化される多くの背景を 1 回のバッチ描画呼び出しですべてレンダリングすることもできます。
AnimatedSprite2D ノードと SpriteFramesアセットを組み合わせると、スプライトシートを通じてさまざまなアニメーション シーケンスを作成し、アニメーション間で反転し、速度、領域オフセット、方向を制御できます。これにより、2D フレームベースのアニメーションの制御に最適になります。
アニメーションの変更に関連する他のエフェクト (パーティクル エフェクトの作成、関数の呼び出し、フレームベースのアニメーション以外の他の周辺要素の操作など) をトリガーする必要がある場合は、 AnimatedSprite2Dで AnimePlayer ノードを使用する必要があります。
たとえば、より複雑な 2D アニメーション システムを設計する場合は、AnimationPlayer も使用する必要があるツールです。
-
アニメーションのカット:実行時にスプライトの変換を編集します。
-
2D メッシュ アニメーション:領域を定義し、スプライトのテクスチャのスケルトンをリグします。次に、ボーン間の関係に従ってテクスチャを伸縮させて、スケルトンをアニメートします。
-
上記の混合物。
AnimationPlayer はゲーム用に設計された個別のアニメーション シーケンスごとに必要ですが、アニメーションを組み合わせてブレンドする、つまりアニメーション間のスムーズな移行を実現することも役立ちます。オブジェクト用に設計されたアニメーション間に階層が存在する場合もあります。ここで、 AnimationTree が威力を発揮します。AnimationTree の使用に関する詳細なガイドは、ここにあります。
論理設定¶
問題 X を解決するために戦略 Y を使用すべきか、戦略 Z を使用すべきか考えたことはありますか? この記事では、これらのジレンマに関連するさまざまなトピックについて説明します。
ノードの追加とプロパティの変更: どちらが先ですか? ¶
実行時にスクリプトからノードを初期化する場合、ノード名や場所などのプロパティの変更が必要になる場合があります。よくあるジレンマは、これらの値をいつ変更する必要があるかということです。
ノードをシーン ツリーに追加する前に、ノードの値を変更することがベスト プラクティスです。一部のプロパティのセッターには、他の対応する値を更新するコードがあり、そのコードが遅くなる可能性があります。ほとんどの場合、このコードはゲームのパフォーマンスに影響を与えませんが、手続き型生成などの頻繁な使用例では、ゲームの速度が低下する可能性があります。
これらの理由から、ノードをシーン ツリーに追加する前にノードの初期値を設定することが常にベスト プラクティスです。
ロードとプリロード¶
GDScript には、グローバルプリロードメソッドがあります 。リソースを早期にロードして「ロード」操作を進め、パフォーマンス重視のコードの途中でリソースをロードすることを回避します。
これに対応するロードメソッドは、load ステートメントに到達した場合にのみリソースをロードします。つまり、リソースがその場で読み込まれるため、機密性の高いプロセスの途中で発生すると速度が低下する可能性があります。この関数は、すべてのスクリプト言語でアクセスできるResourceLoader.load(path)load
のエイリアスでもあります。
では、プリロードとロードは正確にいつ行われ、それらのいずれかをいつ使用する必要があるのでしょうか? 例を見てみましょう:
# my_buildings.gd extends Node # 定数スクリプト/シーンの命名スキームが# プロパティのバリアントとは異なることに注意してください。# この値は定数であるため、Script オブジェクトのロード時に生成されます。# スクリプトは値をプリロードしています。ここでの利点は、エディターが静的パスである必要があるため、 # オートコンプリートを提供できることです。const BuildingScn = preload("res://building.tscn") # 1. スクリプトは値をプリロードするため、# 'my_buildings.gd' スクリプト ファイルの依存関係としてロードされます。ただし、これは定数ではなく # プロパティであるため、オブジェクトは、スクリプトがインスタンス化されるまで、プリロードされた # PackedScene リソースをプロパティにコピーしません。 # .new() を使用します。 # # 2. プリロードされた値には、Script オブジェクトだけからはアクセスできません。# そのため 、ここで値をプリロードしても実際には誰にも利益がありません。 # # 3. ユーザーが値をエクスポートするため、このスクリプトが # シーン ファイル内のノードに保存されている場合、シーンのインスタンス化コードは # プリロードされた初期値を上書きします (無駄になります)。通常は、 # エクスポートには null、空、またはその他の無効なデフォルト値を指定することをお勧めします 。 # # 4. .new() を使用してこのスクリプトを独自にインスタンス化するとき、 # エクスポートされた値ではなく「office.tscn」がロードされます。 import(PackedScene) var a_building = preload("office.tscn") # ええとああ!これによりエラーが発生します。 # 定数値を定数に割り当てる必要があります。# `load` は その性質上ランタイム ルックアップを実行するため、これを使用して # 定数を初期化することはできません。 const OfficeScn =load("res://office.tscn") # スクリプトをインスタンス化した場合にのみ、正常にロードされます。わーい!var office_scn =load("res://office.tscn")
プリロードにより、スクリプトのロード時にスクリプトがすべてのロードを処理できるようになります。プリロードは便利ですが、望ましくない場合もあります。これらの状況を区別するには、次の点を考慮してください。
-
スクリプトがいつロードされるかを判断する方法がない場合、アセット (特にシーンまたはスクリプト) をプリロードすると、予想以上のロードが発生する可能性があります。これにより、元のスクリプトのロード操作に加えて、意図せずに可変長のロード時間が発生する可能性があります。
-
他の何かで置き換えることができる場合(シーンのエクスポートの初期化など)、値をプリロードしても意味がありません。スクリプトを常に自分で作成する場合、これは重要な要素ではありません。
-
別のクラス アセット (スクリプトまたはシーン) を「インポート」するだけの場合は、通常、プリロード定数を使用することがベスト プラクティスです。ただし、これを実行したくない特殊なケースもあります。
-
export
「インポートされた」クラスが変更される可能性がある場合、それはan または a で初期化されたプロパティである必要がありますload
(おそらく、後になるまで初期化さえされないかもしれません)。 -
スクリプトに多くの依存関係が必要で、メモリをあまり消費したくない場合は、状況の変化に応じて実行時にさまざまな依存関係をロードおよびアンロードすることができます。リソースが定数にプリロードされている場合、それらのリソースをアンロードする唯一の方法は、スクリプト全体をアンロードすることです。それらがロードされたプロパティである場合は、それらを設定して
null
、リソースへのすべての参照を完全に削除できます ( RefCounted拡張タイプとして、これにより、リソース自体がメモリから削除されます)。
-
大きなレベル: 静的対動的¶
大規模なレベルを作成したい場合、どの状況が最も適切ですか? レベルを静的スペースとして作成する必要がありますか? それとも、レベルを分割してロードし、必要に応じてワールド コンテンツを移動する必要がありますか?
そうですね、簡単に言うと、「パフォーマンスが要求される場合」です。これら 2 つのオプションに関連するジレンマは、古くからのプログラミングの選択です。速度よりメモリを最適化するのはどちらか、それともその逆ですか?
単純な答えは、すべてを一度にロードする静的レベルを使用することです。ただし、プロジェクトによっては、大量のメモリを消費する可能性があります。ユーザーの RAM を無駄にすると、プログラムの実行が遅くなったり、コンピュータが同時に実行しようとしている他のすべての処理が完全にクラッシュしたりする可能性があります。
いずれにしても、(アセットの再利用を容易にするために) 大きなシーンは小さなシーンに分割する必要があります。開発者は、リソースとノードの作成/ロード、削除/アンロードをリアルタイムで管理するノードを設計できます。大規模で多様な環境や手続き的に生成された要素を含むゲームでは、メモリの無駄を避けるためにこれらの戦略が実装されることがよくあります。
一方、動的システムはコードがより複雑です。つまり、より多くのプログラミング ロジックを使用するため、バグやエラーの可能性が生じます。注意しないと、アプリケーションの技術的負債を肥大化させるシステムを開発してしまう可能性があります。
したがって、最善の策は...
-
小規模なゲームには静的レベルを使用します。
-
中規模/大規模ゲームをプレイする時間とリソースがある人は、ノードとリソースの管理をコード化するためのライブラリまたはプラグインを作成してください。時間をかけて改良して使いやすさと安定性を向上させれば、プロジェクト全体にわたって信頼できるツールに進化することができます。
-
中規模/大規模ゲーム用の動的ロジック コードを作成します。コーディング スキルはありますが、コードを完成させるための時間やリソースがありません (ゲームを終了する必要があります)。コードをプラグインにアウトソーシングするために、後でリファクタリングされる可能性があります。
実行時にシーンを交換するさまざまな方法の例については、「手動でシーンを変更する」ドキュメントを参照してください 。
プロジェクトの組織¶
はじめに¶ _
Godot にはプロジェクトの構造やファイル システムの使用法に制限がないため、エンジンを学習するときにファイルを整理するのは難しいように思えるかもしれません。このチュートリアルで提案されているワークフローは、良い出発点となるはずです。Godot をバージョン管理に使用する方法についても説明します。
組織¶ _
Godot は本質的にシーンベースであり、ファイルシステムをそのまま使用し、メタデータやアセット データベースは使用しません。
他のエンジンとは異なり、多くのアセットがシーン自体に含まれるため、ファイル システム内のファイルの数ははるかに少なくなります。
これを念頭に置いて、最も一般的なアプローチは、アセットをできる限り現場に近いものにグループ化することです。これにより、プロジェクトが成長するにつれて維持が容易になります。
たとえば、多くの場合、基本的なアセット (スプライト イメージ、3D モデル メッシュ、マテリアル、音楽など) をフォルダーに入れることができます。その後、別のフォルダーを使用して、それらを使用するビルド レベルを保存できます。
/project.godot /docs/.gdignore # 以下の「特定のフォルダーの無視」を参照 /docs/learning.html /models/town/house/house.dae /models/town/house/window.png /models/town/house/ door.png /characters/player/cubio.dae /characters/player/cubio.png /characters/enemies/goblin/goblin.dae /characters/enemies/goblin/goblin.png /characters/npcs/suzanne/suzanne.dae /キャラクター/npcs/スザンヌ/suzanne.png /レベル/リバーデイル /リバーデイル.scn
ファッションガイド¶
プロジェクト間の一貫性を保つために、次のガイドラインに従うことをお勧めします。
-
フォルダー名とファイル名には、 snake_caseを使用します(C# スクリプトを除く)。これにより、Windows でプロジェクトをエクスポートした後に発生する可能性のある大文字と小文字の区別の問題が回避されます。C# スクリプトは、PascalCase にあるクラス名に基づいて名前を付けるのが慣例であるため、この規則の例外です。
-
ノード名にはPascalCaseを使用します。これは、組み込みのノードの大文字と小文字が一致するためです。
-
addons/
一般に、サードパーティのリソースは、エディター プラグインでない場合でも、最上位のフォルダーに保持します。これにより、どのファイルがサードパーティのファイルであるかを追跡しやすくなります。このルールには例外がいくつかあります。たとえば、キャラクターにサードパーティのゲーム アセットを使用している場合、それらをキャラクターのシーンやスクリプトと同じフォルダーに含めるほうが合理的です。
入力¶ _
Godot の 3.0 より前のバージョンでは、プロジェクト外部のファイルからインポート プロセスが実行されました。これは大規模なプロジェクトでは便利ですが、ほとんどの開発者にとって組織上の悩みの種になります。
その結果、アセットをプロジェクト フォルダーから透過的にインポートできるようになりました。
特定のフォルダーを無視する¶
Godot が特定のフォルダーに含まれるファイルをインポートしないようにするには、そのフォルダーに という名前の空のファイルを作成します (.gdignore
先頭が必要です)。.
これは、最初のプロジェクトのインポートを高速化するのに役立ちます。
ノート
Windows でドットで始まる名前のファイルを作成するには、Notepad++ などのテキスト エディターを使用するか、コマンド プロンプトで次のコマンドを使用します。type nul > .gdignore
フォルダーが無視されると、そのフォルダー内のリソースは使用できなくなりload()
、preload()
メソッドが読み込まれます。フォルダーを無視すると、そのフォルダーはファイル システム ドックからも自動的に非表示になり、煩雑さを軽減するのに役立ちます。
.gdignore
ファイルの内容は無視されるため、ファイルは空でなければならないことに注意してください。.gitignore
ファイルとは異なり、スキーマはサポートされません。
大文字と小文字の区別¶
Windows および最近の macOS バージョンはデフォルトで大文字と小文字を区別しないファイル システムを使用しますが、Linux ディストリビューションはデフォルトで大文字と小文字を区別するファイル システムを使用します。Godot の PCK 仮想ファイル システムでは大文字と小文字が区別されるため、プロジェクトのエクスポート後に問題が発生する可能性があります。snake_case
これを回避するには、プロジェクト内のすべてのファイルに必ず名前を付けることをお勧めします (通常は小文字を使用します)。
注: スタイル ガイド (C# スタイル ガイドなど) に別の記載がある場合、このルールに違反する可能性があります。それでも、間違いを避けるために一貫性を保つようにしてください。
Windows 10 では、大文字と小文字の区別に関連するエラーをさらに回避するために、プロジェクト フォルダーで大文字と小文字を区別するようにすることもできます。Windows Subsystem for Linux 機能を有効にした後、PowerShell ウィンドウで次のコマンドを実行します。
# 大文字と小文字の区別を有効にするには: fsutil file setcasesensitiveinfo <プロジェクト フォルダーへのパス> enable # 大文字と小文字の区別を無効にするには: fsutil file setcasesensitiveinfo <プロジェクト フォルダーへのパス> disable
Linux 用の Windows サブシステムを有効にしていない場合は、管理者として実行する PowerShell ウィンドウに次の行を入力し、要求されたら再起動します。
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
バージョン管理システム¶
はじめに¶ _
Godot は VCS に優しく、ほとんどの場合読み取り可能でマージ可能なファイルを生成することを目指しています。Godot はエディターでのバージョン管理システムもサポートしています。ただし、エディターの VCS には、使用している特定の VCS 用のプラグインが必要です。VCS は、エディターの [プロジェクト] > [バージョン管理] で設定またはオフにすることができます。
公式 Gitプラグイン¶
公式プラグインは、エディター内からの Git の使用をサポートしています。最新バージョンはここで見つけることができます。Git プラグインの使用方法に関するドキュメントは、ここにあります。
VCSから除外するファイル¶
Godot はいくつかのファイルとフォルダーを自動的に作成します。これらを VCS 無視に追加する必要があります。
-
.godot/
: さまざまなプロジェクトのキャッシュ データが保存されるフォルダーです。.godot/imported/
すべてのファイルは、ソース アセットとそのインポート フラグに基づいて、ストレージ エンジンによって自動的にインポートされます。.godot/editor/
現在開いているスクリプト ファイルや最近使用したノードなど、エディタの状態に関するデータを保持します。 -
*.translation
: これらのファイルは、CSV ファイルから生成されたバイナリ インポート翻訳です。 -
export_presets.cfg
: このファイルには、Android キーストア認証情報などの機密情報を含む、プロジェクトのすべてのエクスポート プリセットが含まれています。 -
.mono/
: 自動生成されたモノラルファイルが保存されるフォルダーです。これは、Godot の Mono バージョンを使用するプロジェクトにのみ存在します。
注:ファイルの除外を自動的に設定するには、この .gitignore ファイルをプロジェクトのルート フォルダーに保存します。
Windows でのGit の使用¶
ほとんどの Git for Windows クライアントはcore.autocrlf
で構成されてtrue
おり、行末が自動的に変換されるため、ファイルが不必要に Git によって変更済みとしてマークされる可能性があります。このオプションを次のように設定することをお勧めします。
git config --global core.autocrlf 入力
既知の問題¶
実行するgit pull
前に必ずエディタを閉じてください。そうしないと、 エディターを開いているときにファイルを同期すると、データが失われる可能性があります。