Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

シーン構成

この記事では、シーンコンテンツの効果的な編成に関連するトピックについて説明します。どのノードを使用する必要がありますか?どこに配置すればよいですか?彼らはどのように相互作用する必要がありますか?

個々の結びつきを効果的に構築する方法

Godotユーザーが独自のシーンを作成し始めると、次の問題に遭遇することがよくあります。

They create their first scene and fill it with content only to eventually end up saving branches of their scene into separate scenes as the nagging feeling that they should split things up starts to accumulate. However, they then notice that the hard references they were able to rely on before are no longer possible. Re-using the scene in multiple places creates issues because the node paths do not find their targets and signal connections established in the editor break.

これらの問題を修正するには、環境に関する詳細を要求せずにサブシーンをインスタンス化する必要があります。サブシーンがどのように使われるかを選り好みせずに自分が作成されることを信頼できる必要があります。

One of the biggest things to consider in OOP is maintaining focused, singular-purpose classes with loose coupling to other parts of the codebase. This keeps the size of objects small (for maintainability) and improves their reusability.

These OOP best practices have several implications for best practices in scene structure and script usage.

可能な限り、依存関係を持たないようにシーンを設計する必要があります。 つまり、必要なものすべてを内部に保持するシーンを作成する必要があります。

シーンが外部コンテキストとやり取りする必要がある場合、経験豊富な開発者は、 依存性の注入 <https://en.wikipedia.org/wiki/Dependency_injection> の使用をお勧めします。この手法では、高レベルAPIを使用して低レベルAPIの依存関係を提供します。なぜこれを行うのですか?外部環境に依存するクラスは、誤ってバグや予期しない動作を引き起こす可能性があるためです。

これを行うには、データを公開し、親コンテキストを使用して初期化する必要があります:

  1. Connect to a signal. Extremely safe, but should be used only to "respond" to behavior, not start it. By convention, signal names are usually past-tense verbs like "entered", "skill_activated", or "item_collected".

    # Parent
    $Child.signal_name.connect(method_on_the_object)
    
    # Child
    signal_name.emit() # Triggers parent-defined behavior.
    
  2. メソッドを呼び出します。動作を開始するために使用されます。

    # Parent
    $Child.method_name = "do"
    
    # Child, assuming it has String property 'method_name' and method 'do'.
    call(method_name) # Call parent-defined method (which child must own).
    
  3. Initialize a Callable property. Safer than a method as ownership of the method is unnecessary. Used to start behavior.

    # Parent
    $Child.func_property = object_with_method.method_on_the_object
    
    # Child
    func_property.call() # Call parent-defined method (can come from anywhere).
    
  4. ノードまたはその他のオブジェクト参照を初期化します。

    # Parent
    $Child.target = self
    
    # Child
    print(target) # Use parent-defined node.
    
  5. NodePathを初期化します。

    # Parent
    $Child.target_path = ".."
    
    # Child
    get_node(target_path) # Use parent-defined NodePath.
    

These options hide the points of access from the child node. This in turn keeps the child loosely coupled to its environment. One can reuse it in another context without any extra changes to its API.

注釈

上記の例は親子関係を示していますが、同じ原則がすべてのオブジェクト間の関係に適用されます。兄弟であるノードは、(親やその親などの)祖先が通信と参照を仲介する間のみ、相手の階層を認識する必要があります。

# Parent
$Left.target = $Right.get_node("Receiver")

# Left
var target: Node
func execute():
    # Do something with 'target'.

# Right
func _init():
    var receiver = Receiver.new()
    add_child(receiver)

同じ原則は、他のオブジェクトへの依存関係を維持する非Nodeオブジェクトにも適用されます。オブジェクトを実際に所有するオブジェクトがオブジェクト間の関係を管理する必要があります。

警告

データを組織内(シーンの内部)に保持することをお勧めします。疎結合のコンテキストであっても、外部のコンテキストに依存関係を置くことは、ノードは環境内の何かが実在することを期待することを意味します。プロジェクトの設計哲学は、これが起こらないようにする必要があります。そうでない場合、コード固有の責任により、開発者はドキュメントを使用して、オブジェクトの関係を微視的に追跡する必要があります。これは、開発地獄とも呼ばれます。外部ドキュメントに依存するコードを記述すると、安全のために規定でエラーが発生しやすくなります。

To avoid creating and maintaining such documentation, one converts the dependent node ("child" above) into a tool script that implements _get_configuration_warnings(). Returning a non-empty PackedStringArray from it will make the Scene dock generate a warning icon with the string(s) as a tooltip by the node. This is the same icon that appears for nodes such as the Area2D node when it has no child CollisionShape2D nodes defined. The editor then self-documents the scene through the script code. No content duplication via documentation is necessary.

このようなGUIを使用すると、プロジェクトユーザーにノードに関する重要な情報をより適切に通知できます。外部依存関係はありますか?これらの依存関係は満たされていますか?などです。他のプログラマー、特にデザイナーとライターに対し、それを構成する作業のために必要な指示を、メッセージを通して明確に伝えます。

So, why does all this complex switcharoo work? Well, because scenes operate best when they operate alone. If unable to work alone, then working with others anonymously (with minimal hard dependencies, i.e. loose coupling) is the next best thing. Inevitably, changes may need to be made to a class and if these changes cause it to interact with other scenes in unforeseen ways, then things will start to break down. The whole point of all this indirection is to avoid ending up in a situation where changing one class results in adversely effecting other classes dependent on it.

Scripts and scenes, as extensions of engine classes, should abide by all OOP principles. Examples include...

ノードツリー構造の選択

So, a developer starts work on a game only to stop at the vast possibilities before them. They might know what they want to do, what systems they want to have, but where to put them all? Well, how one goes about making their game is always up to them. One can construct node trees in countless ways. But, for those who are unsure, this helpful guide can give them a sample of a decent structure to start with.

A game should always have a sort of "entry point"; somewhere the developer can definitively track where things begin so that they can follow the logic as it continues elsewhere. This place also serves as a bird's eye view of all of the other data and logic in the program. For traditional applications, this would be the "main" function. In this case, it would be a Main node.

  • Node "Main" (main.gd)

main.gd スクリプトは、ゲームのプライマリコントローラーとして機能します。

次に、実際のゲーム内「ワールド(World)」(2Dまたは3Dのもの)があります。これは、Mainの子にすることができます。さらに、プロジェクトに必要なさまざまなメニューとウィジェットを管理するゲーム用のプライマリGUIが必要になります。

  • Node "Main" (main.gd)
    • Node2D/Node3D "World" (game_world.gd)

    • Control "GUI" (gui.gd)

レベルを変更する場合、"World"ノードの子を入れ替えることができます。シーンを手動で変更 を使用すると、ユーザーはゲームのワールドがどのように遷移するかを完全に制御できます。

次のステップは、プロジェクトに必要なゲームプレイシステムを検討することです。既存のシステムがある場合...

  1. すべてのデータを内部的に追跡する

  2. グローバルにアクセス可能でなければなりません

  3. 独立して存在する必要があります

...次に、自動ロード 'シングルトン' ノード を作成する必要があります。

注釈

For smaller games, a simpler alternative with less control would be to have a "Game" singleton that simply calls the SceneTree.change_scene_to_file() method to swap out the main scene's content. This structure more or less keeps the "World" as the main game node.

Any GUI would need to also be a singleton; be a transitory part of the "World"; or be manually added as a direct child of the root. Otherwise, the GUI nodes would also delete themselves during scene transitions.

If one has systems that modify other systems' data, one should define those as their own scripts or scenes rather than autoloads. For more information on the reasons, please see the Autoloads versus regular nodes documentation.

ゲーム内の各サブシステムには、シーンツリー内に独自のセクションが必要です。ノードが事実上親の一要素である場合にのみ、親子関係を使用する必要があります。親を削除したときに、その子として一緒に削除されてもかまいませんか?そうでない場合は、兄弟または他の関係として、階層内に独自の場所が必要です。

注釈

In some cases, one needs these separated nodes to also position themselves relative to each other. One can use the RemoteTransform / RemoteTransform2D nodes for this purpose. They will allow a target node to conditionally inherit selected transform elements from the Remote* node. To assign the target NodePath, use one of the following:

  1. 割り当てを仲介する信頼できるサードパーティ(おそらく親ノード)。

  2. 目的のノードへの参照を簡単にプルするためのグループ(ターゲットの1つだけが存在すると想定)。

When should one do this? Well, this is subjective. The dilemma arises when one must micro-manage when a node must move around the SceneTree to preserve itself. For example...

  • 「プレイヤー」ノードを「ルーム」に追加する。

  • 部屋を変更する必要があるため、現在の部屋を削除する必要があり。

  • ルームを削除する前に、プレイヤーを保存および/または移動する必要があります。

    記憶は懸念事項ですか?

    • そうでない場合は、2つの部屋を作成し、プレイヤーを移動して古い部屋を削除するだけです。特に心配はいりません。

    もしそうなら、1つは...

    • プレイヤーをツリー内の別の場所に移動します。

    • 部屋を削除します。

    • 新しい部屋をインスタンス化して追加します。

    • そこにプレイヤーを再度追加します。

The issue is that the player here is a "special case"; one where the developers must know that they need to handle the player this way for the project. As such, the only way to reliably share this information as a team is to document it. Keeping implementation details in documentation however is dangerous. It's a maintenance burden, strains code readability, and bloats the intellectual content of a project unnecessarily.

In a more complex game with larger assets, it can be a better idea to simply keep the player somewhere else in the SceneTree entirely. This results in:

  1. より一貫性が高くなります。

  2. どこかに文書化して保守する必要がある「特別なケース」はありません。

  3. これらの詳細を考慮する必要がないため、エラーが発生する可能性はありません。

これに対し、親の幾何学変換を継承しない子ノードが必要な場合、次のオプションがあります:

  1. declarative ソリューション: それらの間に Node を配置します。幾何学変換を行わないノードとして、ノードはそのような情報を子に渡しません。

  2. The imperative solution: Use the top_level property for the CanvasItem or Node3D node. This will make the node ignore its inherited transform.

注釈

ネットワーク化されたゲームを構築する場合、どのノードとゲームプレイシステムがすべてのプレイヤーに関連するのか、権限のあるサーバーに関連するのかを念頭に置いてください。たとえば、ユーザーがすべてのプレイヤーの「PlayerController」ロジックのコピーを持っている必要はありません。代わりに、彼らは自分自身だけが必要です。そのため、これらを「ワールド」とは別のブランチに保持することで、ゲーム接続などの管理を簡素化できます。

The key to scene organization is to consider the SceneTree in relational terms rather than spatial terms. Are the nodes dependent on their parent's existence? If not, then they can thrive all by themselves somewhere else. If they are dependent, then it stands to reason that they should be children of that parent (and likely part of that parent's scene if they aren't already).

これは、ノード自体が構成要素であることを意味しますか? 全く違います。Godotのノードツリーは、構図ではなく集合の関係性を形成します。ただし、ノードを自由に移動できる柔軟性はありますが、デフォルトではそのような移動が不要な場合に最適です。