Godot: GDScript特性システムを追加します。

作成日 2018年10月18日  ·  93コメント  ·  ソース: godotengine/godot

(編集:
さらにXY問題の問題を最小限に抑えるには:
ここで対処する問題は、GodotのNode-Sceneシステム/スクリプト言語が、1)ルートノードの機能に固有であり、2)交換および/または組み合わせることができる、再利用可能なグループ化された実装の作成をまだサポートしていないことです。 静的メソッドを含むスクリプトまたはスクリプトを含むサブノードを後者のビットに使用でき、多くの場合、これは機能します。 ただし、Godotは通常、子ノードによって計算されたデータを使用するか、大幅に逸脱したサブタスクを子ノードに委任する間、シーンの全体的な動作のロジックをルートノードに保存することを好みます。たとえば、KinematicBody2Dはアニメーションを管理しないため、それをAnimationPlayerに委任します。

「コンポーネント」の子ノードを使用してその動作を駆動する、より薄いルートノードを持つことは、比較すると弱いシステムです。 すべての動作を子ノードに委任するだけの大部分が空のルートノードを持つことは、このパラダイムに反します。 子ノードは、それ自体でタスクを実行する自給自足のオブジェクトではなく、ルートノードの動作拡張になります。 これは非常に不格好であり、ルートノードのすべてのロジックを統合できるようにすることで設計を簡素化/改善できますが、ロジックをさまざまな構成可能なチャンクに分割することもできます。

この問題のトピックは、特にGDScriptに関するものではなく、上記の問題に対処する方法に関するものだと思いますが、GDScriptトレイトは、問題を解決するための最も単純で最も簡単なアプローチであると思います。
)。

知らない人にとって、特性は本質的に2つのクラスを1つにブレンドする方法(ほとんどコピー/貼り付けメカニズム)であり、ファイルのテキストを文字通りコピー/貼り付けするのではなく、キーワードステートメントを使用して2つのファイルをリンクします。 (編集:トリックは、スクリプトが継承できるクラスは1つだけですが、複数の特性を含めることができるということです)

トレイトタイプがマージされたスクリプトによって継承されたクラスを拡張する限り、つまり、スプライト拡張GDScriptがリソースGDScriptを使用できない限り、任意のGDScriptファイルを別のGDScriptファイルのトレイトとして使用できるものを想像しています。特性として、ただしNode2DGDScriptを使用できます。 私はこれに似た構文を想像します:

# move_right_trait.gd
extends Node2D
class_name MoveRightTrait # not necessary, but just for clarity
func move_right():
    position.x += 1

# my_sprite.gd
extends Sprite
is MoveRightTrait # maybe add a 'use' or 'trait' keyword for this instead?
is "res://move_right_trait.gd" # alternative if class_name isn't used
func _physics_process():
    move_right() # MoveRightTrait's content has been merged into this script
    if MoveRightTrait in self:
        print("I have a MoveRightTrait")

私はこれを行う2つの方法を見ることができます:

  1. "^ trait \の正規表現を介してスクリプトを事前解析しますリロードプロセス)。 特性のネストをサポートしたり、反復のたびに生成されたソースコードを継続的に再検査して、さらに特性が挿入されているかどうかを確認する必要はありません。
  2. スクリプトを通常どおりに解析しますが、パーサーにキーワードを認識し、参照されるスクリプトをロードし、そのスクリプトを解析してから、そのClassNodeのコンテンツを現在のスクリプトの生成されたClassNodeに追加するように指示します(事実上、1つのスクリプトの解析結果を取得して追加します)他のスクリプトの解析結果に)。 これにより、特性タイプのネストが自動的にサポートされます。

一方、トレイトGDScriptに名前を付けたいが、そのGDScriptのclass_nameをCreateDialogに表示したくない場合があります(独自に作成することを意図していないため)。 この場合、スクリプトでサポートさせるは実際には良い考えではないかもしれません。 特別にマークされたものだけ(おそらくファイルの先頭に「trait」と書くことによって?)。 とにかく、考えるべきこと。

考え?

編集:いくつか熟考した後、オプション2は、1)スクリプトセグメントがどのスクリプトからのものであるかを知っていて(エラー報告を改善するため)、2)エラーが発生したときにエラーを特定できるため、はるかに優れていると思います。インクルードされたスクリプトは、最後にすべてを解析するのではなく、順番に解析する必要があります。 これにより、解析プロセスに追加される処理時間が短縮されます。

archived discussion feature proposal gdscript

最も参考になるコメント

@aaronfranke Traitsは、基本的にMixinsと同じものですが、メソッドの実装が含まれているという理由だけで、インターフェースとはまったく異なるユースケースを持っています。 インターフェイスがデフォルトの実装を提供した場合、それは実際はもはやインターフェイスではありません。

トレイト/ミックスインは、PHP、Ruby、D、Rust、Haxe、Scala、およびその他の多くの言語(リンクされたWikiで詳しく説明されています)に存在するため、プログラミング言語に精通している幅広いレパートリーを持つ人々にすでに広く精通している必要があります。

インターフェイスを実装する場合(特にオプションの静的型付けが来る場合は、どちらにも反対しません)、それは事実上、関数シグネチャを指定し、関連するGDScriptスクリプトがそれらの関数シグネチャを特性とともに実装することを要求する方法になります。含まれています(その時点までに存在していた場合)。

全てのコメント93件

代わりにどのようなメリットがありますか: extends "res://move_right_trait.gd"

@MrJustreborn 1つのクラスに複数の特性を含めることができますが、継承できるスクリプトは1つだけだからです。

私が正しく理解していれば、これは基本的にC#が「インターフェース」と呼んでいるものですが、非抽象メソッドではどうでしょうか。 プログラマーに馴染みのある特性ではなく、機能インターフェースを呼び出す方がよい場合があります。

@aaronfranke Traitsは、基本的にMixinsと同じものですが、メソッドの実装が含まれているという理由だけで、インターフェースとはまったく異なるユースケースを持っています。 インターフェイスがデフォルトの実装を提供した場合、それは実際はもはやインターフェイスではありません。

トレイト/ミックスインは、PHP、Ruby、D、Rust、Haxe、Scala、およびその他の多くの言語(リンクされたWikiで詳しく説明されています)に存在するため、プログラミング言語に精通している幅広いレパートリーを持つ人々にすでに広く精通している必要があります。

インターフェイスを実装する場合(特にオプションの静的型付けが来る場合は、どちらにも反対しません)、それは事実上、関数シグネチャを指定し、関連するGDScriptスクリプトがそれらの関数シグネチャを特性とともに実装することを要求する方法になります。含まれています(その時点までに存在していた場合)。

たぶんincludesのようなキーワード?

extends Node2D
includes TraitClass

trait、mixin、hasなどの他の名前も確かに問題ありませんが。

追加メニューからclass_nameタイプを除外するオプションがあるというアイデアも気に入っています。 それ自体がノードとして機能しない小さなタイプでは、非常に雑然となる可能性があります。

それはそれ自体の機能トピックでさえあるかもしれません。

(誤って私のコメントを削除しました、うわー!また、必須の「複数のスクリプトを許可しないのはなぜですか、Unityがそれを行います」

仮にあったとしても、これはVisualScriptでどのように機能しますか?

また、トレイトが実装されている場合、トレイトのインスペクターインターフェイスを含めることは有益でしょうか? トレイトのいくつかのユースケースには、トレイトのみがあり、スクリプトがない(少なくとも、トレイトファイルを含むスクリプト以外のスクリプトはない)ユースケースが含まれる場合があると思います。 しかし、もっと考えてみると、トレイトファイルを含むスクリプトを作成するだけの場合と比べて、そのようなインターフェイスを作成するための努力はそれだけの価値があるのではないかと思います。

@ LikeLakers2

仮にあったとしても、これはVisualScriptでどのように機能しますか?

私が提案した方法で実行した場合、VisualScriptではまったく発生しません。 GDScriptのみ。 VisualScriptは解析された言語ではないため、VisualScript用に実装されたトレイトシステムはまったく異なる設計になります。 ただし、その可能性をまったく排除するものではありません(別の方法で実装する必要があるだけです)。 さらに、VisualScriptの継承サポートを最初に取得することを検討する必要がありますか? 笑

また、トレイトが実装されている場合、トレイトのインスペクターインターフェイスを含めることは有益でしょうか?

あまり意味がありません。 トレイトは、GDScriptに詳細を伝えるだけで、トレイトによって定義されたプロパティ、定数、シグナル、およびメソッドをGDScriptに渡します。

トレイトのいくつかのユースケースには、トレイトのみがあり、スクリプトがない(少なくとも、トレイトファイルを含むスクリプト以外のスクリプトはない)ユースケースが含まれる場合があると思います。

トレイトは他の言語で表されているため、単独で使用することはできませんが、使用するには別のスクリプトに含める必要があります。

そのようなインターフェースを作るために費やされた努力はそれだけの価値があるのではないかと思います

何らかの方法でインスペクターインターフェイスを作成することは、GDScriptだけではあまり意味がありません。 トレイトを追加または削除するには、スクリプトリソースのsource_codeプロパティのソースコードを直接編集する必要があります。つまり、スクリプト自体のプロパティではありません。 したがって、どちらか...

  1. これを行うには、GDScriptファイルのソースコードの適切な編集を具体的に処理する方法(エラーが発生しやすい)をエディターに教える必要があります。
  2. GDScriptLanguageが特性を追加および削除するための独自の内部プロセスを提供できるように、すべてのスクリプトが特性をサポートする必要があります(ただし、すべての言語が特性をサポートしているわけではないため、すべての場合にプロパティが意味を持つわけではありません)。

そのような機能の必要性は何ですか? これで今できないことはありますか? それとも、一部のタスクの処理が大幅に高速化されますか?

GDscriptは、ほとんど使用されない複雑な機能を追加するよりも、単純な言語のままにしておきたいと思います。

これは、この男が問題を抱えていたChild-Nodes-As-Script-Dependenciesの問題を解決しますが、単一の言語に制限されているため、MultiScriptが持っていたのと同じ種類の手荷物は付属していません。 GDScriptモジュールは、特性が相互にどのように関連しているか、およびメインスクリプトに関するロジックを分離できますが、異なる言語間の違いを解決することははるかに複雑になります。

複数のインポート/複数の継承がない場合、スクリプト依存関係としての子ノードは、コードA LOTの繰り返しを回避する唯一の方法であり、これは間違いなく問題をうまく解決します。

@groud @ Zireael07つまり、より急進的な言語横断的なアプローチは、1)ScriptStackを使用してスタックされたスクリプトを単一のスクリプト表現に統合するようにオブジェクトを完全に再設計すること、2)MultiScriptを再導入し、自動的に変換するエディターサポートを作成することです。マルチスクリプトへのスクリプトの追加(または、簡単にするためにすべてのスクリプトを完全にマルチスクリプトにします。この場合、MultiScriptの実装は基本的にScriptStackになります)、または3)オブジェクトタイプの一種のクロスランゲージ特性システムを実装します。特性としての参照拡張スクリプト。通常のスクリプトと同じようにコンテンツを組み込みます。 ただし、これらのオプションはすべて、エンジンに対してはるかに侵襲的です。 これにより、すべてがシンプルになります。

形質は必要ないと思います。 私たちが最も必要としているのは、エンジンのリリースサイクルが速いことです。 つまり、ほとんどのIDEのプラグインスタイルのように、新しいdllなどのファイルを追加するだけで、エンジンがそれ自体と自動的に統合されるため、新しい機能を追加するためにエンジンをより柔軟にする必要があります。 たとえば、WebSocketが機能する必要があるので、3.1がリリースされるまで待つ必要はありません。 3.1は今、非常に多くのバグで壊れすぎています。 この機能があれば素晴らしいと思います。 新しいクラスは、任意のパスのランダムな.dllまたは.soからGDScriptに自動注入できます。 これがC ++でどれだけの努力をするかはわかりませんが、これがそれほど難しくないことを願っています😁

@ fian46ええと、誰かがダウンロード可能なGDNativeプラグインとしてWebSocketを実装した場合、そうです、あなたが説明たのはワークフローです。 代わりに、彼らはそれをバニラエンジンで利用可能な統合機能にすることを選択しました。 人々がそのように機能を作成することを妨げるものは何もないので、あなたのポイントはこの問題のトピックとは本当に無関係です。

おっと、GDNativeが存在することを知りません😂😂😂。 トレイトは素晴らしいですが、偽のトレイトクラスを作成してインスタンス化し、後で基本的なスクリプトのように関数を呼び出す方が簡単ですか?

Godotスクリプトが名前のないクラスである場合、「my_sprite.gd」に「move_right_trait.gd」をインスタンス化してみませんか?
問題がわからない場合は、無知でごめんなさい。

Rustや(のインターフェース)C ++などのより強く型付けられた言語での特性の使用を理解していますが、ダックされた型付けされた言語では、それは少し不必要ではありませんか? 同じ関数を実装するだけで、タイプ間で統一されたインターフェイスを実現できます。 GDScriptがインターフェースを処理する方法の正確な問題や、トレイトシステムが実際にどのように役立つかについては少しわかりません。

preload( "Some-other-behavior.gd")を使用して結果を変数に格納し、基本的に同じ効果を実現することもできませんか?

@ fian46 @DriNeoええ、はい、いいえ。 スクリプトのロードとスクリプトクラスの使用はすでにそれを処理しますが、問題はそれを超えて広がります。

@TheYokai

同じ関数を実装すると、タイプ間で統一されたインターフェイスを実現できるはずです。

問題は、あなたが正しい統一されたインターフェースを実現することではなく、ダックタイピングはうまく解決しますが、関連する実装のグループを効率的に編成(結合/交換)します。


3.1では、スクリプトクラスを使用して、参照スクリプト(または実際には任意のタイプ)で静的関数を定義し、そのスクリプトを名前空間として使用して、GDScriptでそれらの関数にグローバルにアクセスできます。

extends Reference
class_name Game
static func print_text(p_text):
    print(p_text)
# can even add inner classes for sub-namespaces

extends Node
func _ready():
    Game.print_text("Hello World!")

ただし、非静的コンテンツ、特にノード固有の機能を使用するコンテンツに関しては、ロジックを分割するのは面倒です。

たとえば、KinematicBody2Dがあり、「ジャンプ」動作と「実行」動作が必要な場合はどうなりますか? これらの各動作には、KinematicBody2Dの入力処理とmove_and_slide機能へのアクセスが必要です。 理想的には、各動作の実装を個別に交換し、各動作のすべてのコードを別々のスクリプトに保持できるようになります。

現在、これについて私が精通しているすべてのワークフローは、単に最適ではありません。

  1. すべての実装を同じスクリプトに保持し、使用されている関数を入れ替えるだけの場合...

    1. 「動作」の変更には、いくつかの機能をセットとして交換することが含まれる場合があるため、実装の変更を効果的にグループ化することはできません。

    2. すべての動作(X * Y)のすべての関数が単一のスクリプトに含まれているため、スクリプトはすぐに肥大化する可能性があります。

  2. スクリプト全体を置き換えることもできますが、その場合は、動作のすべての組み合わせ加えて、それらの動作を使用するロジックに対して新しいスクリプトを作成する必要があります。
  3. スクリプトの依存関係として子ノードを使用する場合、それは、親を取得してmove_and_slideメソッドを呼び出すこれらの奇妙な「コンポーネント」Node2Dノードがあることを意味します。これは、比較的不自然です。

    • 親がそのメソッドを実装することを想定するか、ロジックを実行してメソッドがあることを確認する必要があります。 また、チェックを行うと、サイレントに失敗してゲームにサイレントバグが発生する可能性があるか、ノードに構成警告を設定してエディターで視覚的に通知できるように、それを不必要にツールスクリプトに変換することができます。問題があること。

    • また、ノードはNode2Dから派生しており、重要なのはKinematicBody2Dの親の動作を駆動することであるため、ノードの意図された操作に対して適切なコード完了が得られません。

ここで、オプション3が現在最も効果的なワークフローであり、3.1のGDScriptに便利でダンディな静的型付けを使用することでその問題を大幅に解決できることを認めます。 ただし、より根本的な問題があります。

Godotのノードシーンシステムは、一般に、ユーザーが独自の囲まれたシステムで特定のジョブを実行するノードまたはシーンを作成する形式を持っています。 これらのノード/シーンを別のシーン内にインスタンス化して、親シーンで使用されるデータを計算させることができます(Area2DとCollisionShape2Dの関係の場合など)。

ただし、バニラエンジンの使用法と一般的なベストプラクティスの推奨事項は、シーンの動作をルートノードやそのスクリプトにロックしたままにすることです。 ルートに実際に何をすべきかを指示する「動作コンポーネント」ノードはほとんどありません(そして、そこにあると、非常に不格好に感じます)。 AnimationPlayer / Tweenノードは私が考えることができる唯一の議論の余地のある例外ですが、それらの操作でさえルートによって指示されます(それは事実上一時的にそれらに制御を委任しています)。 (編集:この場合でも、アニメーションとトゥイーンはKinematicBody2Dの仕事ではないので、これらのタスクを委任するのは理にかなっています。ただし、実行やジャンプなどの移動その責任です)許可する方が簡単で自然ですノード間の関係を厳密にデータアップ/動作ダウンに保ち、コードを独自のスクリプトファイルにさらに分離しておくため、コードを整理するための特性の実装。

ええと、自分自身を「インターフェース/特性の実装」としてマークすることも、 * is *テストを満たす必要があります。これは、何かの機能をテストするのに便利です。

@ OvermindDL1つまり、そのようなテストを行う例を示しましたが、継承と特性の使用を区別したかったので、代わりにinを使用しました。

私はここでXY問題に少し踏み込んだと思います。 私は、このトピックに何らかの形で対処し、提案を提出すると考えた他の2つの問題(#23052、#15996)から来たばかりでしたが、実際にはすべてのコンテキストを提供していませんでした。

@groudこのソリューションは、#19486に対して提起された問題の1つを解決します。

@willnationsdev素晴らしいアイデア、私はそれを楽しみにしています!

私の限られた理解から、このトレイトシステムが達成したいことは、このビデオに示されているワークフローと同様の何かを可能にすることです: https ://www.youtube.com/watch?v = raQ3iHhE_Kk
(考慮に入れてください、私は示されている_ワークフロー_について話しているのであって、使用されている機能ではありません)

ビデオでは、他の種類のワークフローと比較され、長所と短所があります。

少なくとも私の知る限り、この種のワークフローは、継承がどのように機能するかにより、現在GDScriptでは不可能です。

@AfterRebelion彼がコードベースのモジュール性、編集可能性、およびデバッグ可能性(およびそれらの属性の関連する詳細)を分離するそのビデオの最初の数分間、確かにこの機能を持つことの追求です。

少なくとも私の知る限り、この種のワークフローは、継承がどのように機能するかにより、現在GDScriptでは不可能です。

Godotは実際には、ノード階層とシーンの設計に関してこれを非常にうまく行っているため、このビットは完全には真実ではありません。 シーンは本質的にモジュール式であり、プロパティはコードを処理せずにエディターから直接エクスポート(およびアニメーション化)できます。シーンは独立して実行できるため、すべてを個別にテスト/デバッグできます。

難しいのは、ロジックがルートノードの継承された機能に依存しているため、通常は子ノードにアウトソーシングされるロジックをルートノードで実行する必要がある場合です。 このような場合、作文を使用する唯一の方法は、親が使用している間、子供たちに自分のビジネスを気にさせるのではなく、親に何をすべきかを伝え始めることです。

GameObjectにはユーザーが利用できる実際の継承がないため、これはUnityでは問題になりません。 Unrealでは、アクターのノード/コンポーネントベースの内部階層が類似しているため、少し(?)問題になる可能性があります。

さて、ここで悪魔の代弁者を少しプレイしてみましょう( @MysteryGM 、これからキックを得るかもしれません)。 Unrealでそのようなシステムをどのように作成するかを考えるのに時間を費やしました。それは、私に新しい視点を与えてくれます。 これは良いアイデアだと思っていた/興奮していた人々に申し訳ありません:

トレイトシステムを導入すると、言語としてGDScriptに複雑さの層が追加され、保守がより困難になる可能性があります。

その上、機能としての特性により、変数、定数、シグナル、メソッド、さらにはサブクラスが実際にどこから来ているのかを特定することがより困難になります。 ノードのスクリプトに突然12の異なる特性がある場合、すべてがどこから来ているのかを必ずしも知ることはできません。 何かへの参照を見つけた場合は、コードベースのどこにあるかを知るために、12の異なるファイルを調べる必要があります。

これにより、 GDScriptのデバッグ可能性が言語として実際に低下します。これは、特定の問題では、コードベース内の平均2つまたは3つの異なる場所を選択する必要がある場合があるためです。 これらの場所のいずれかが1つのスクリプト内にあるが、実際には別の場所にあると言うためにそれらの場所を見つけるのが難しい場合、およびコードの可読性がデータ/ロジックの原因を明確に示していない場合は、それらの2つまたは3つステップは、任意の大きさで非常にストレスの多い一連のステップに乗算されています。

プロジェクトのサイズと範囲が拡大することで、これらの悪影響がさらに拡大し、特性の使用がかなり受け入れがたい品質になります。


しかし、問題を解決するために何ができるでしょうか? 子の「コンポーネントロジック」ノードがシーンのルートに何をすべきかを指示することは望ましくありませんが、問題を解決するために継承やスクリプト全体の変更に依存することもできません。

さて、この状況で非ECSエンジンはをしますか? 構成は依然として答えですが、この場合、所有権階層の動的化をスケーリング/複雑化する場合、完全なノードは不合理です。 代わりに、動作の具体的な実装を抽象化するが、すべてルートノードによって所有されている非ノード実装オブジェクトを定義することができます。 これは、 Referenceスクリプトを使用して実行できます。

# root.gd
extends KinematicBody2D

export(Script) var jump_impl_script = null setget set_jump_impl_script
var jump_impl
func set_jump_impl_script(p_script):
    jump_impl = p_script.new() if p_script else null

export(Script) var move_impl_script = null setget set_move_impl_script
var move_impl
func set_move_impl_script(p_script):
    move_impl = p_script.new() if p_script else null

func _physics_process():
    # use logic involving these...
    move_impl.move(...)
    jump_impl.jump(...)

エクスポートが、新しいResourceインスタンスの場合と同様に、特定のタイプを派生させるクラスの列挙型としてインスペクターで編集できるように機能する場合、それはすばらしいことです。 これを行う唯一の方法は、リソーススクリプトのエクスポートを修正してから、実装スクリプトにリソースを拡張させることです(他の理由はありません)。 ただし、実装自体にインスペクターから定義できるパラメーターが必要な場合は、リソースを拡張することをお勧めします。 :-)

さて、これを簡単にするのは、スニペットシステムまたはマクロシステムを使用して、開発者がこれらの再利用された宣言型コードセクションを簡単に作成できるようにすることです。

とにかく、ええ、私はちょっと、特性システムと問題を解決するためのより良いアプローチで明白な問題を特定したと思います。 XY問題の問題にご期待ください。 / s

編集:

したがって、上記の例のワークフローには、実装スクリプトを設定し、実行時にスクリプトのインスタンスを使用して動作を定義することが含まれます。 しかし、実装自体がインスペクターから静的に設定したいパラメーターを必要とする場合はどうでしょうか? 代わりに、リソースに基づくバージョンがあります。

# root.gd
extends KinematicBody2D

# if you use a Resource script AND had a way of specifying that the assigned Resource 
# must extend that script, then the editor would automatically assign an instance of 
# that resource script to the var. No separate instancing or setter necessary.

export(Resource) var jump_impl = null # set jump duration, max height, tween easing via Inspector
export(Resource) var move_impl = null # similarly customize movement from Inspector

# can then create different Resources as different implementations. Because they are resources,
# one can edit them even outside of a scene!
func _physics_process():
    move_impl.move(...)
    jump_impl.jump(...)

関連:#22660

@AfterRebelion

考慮に入れてください、私は示されているワークフローについて話しているのであって、使用されている機能ではありません

皮肉なことに、これを明確にして、最適なワークフローに同意し、コメントの後半部分に同意しない場合は、基本的に、そのビデオで使用されている「機能」が実際に取り組む理想的な方法であると言ってフォローアップします。とにかくGodotのこの問題。 はは。

# root.gd
extends KinematicBody2D

export(Script) var jump_impl_script = null setget set_jump_impl_script
var jump_impl
func set_jump_impl_script(p_script):
jump_impl = p_script.new() if p_script else null
...

うわー、輸出がとても強力だとは知りませんでした。 プリミティブや他のデータ構造とのみ対話できることが期待されていました。

これにより、以前のコメントは無効になります。
あなたが言ったように、その実装を容易にするためにある種のマクロが実装されているなら、それはMultiScriptを必要とせずにそのワークフローを実装するための最良の方法でしょう。 Unityほど用途が広いわけではないかもしれません。なぜなら、可能なすべてのスクリプトを事前に宣言する必要があるからですが、それでも良いオプションです。

@AfterRebelion

あなたが言ったように、その実装を容易にするためにある種のマクロが実装されているなら、それはMultiScriptを必要とせずにそのワークフローを実装するための最良の方法でしょう。

同じコメントで述べたResourceベースのアプローチと、#22660からのより優れたエディターのサポートを組み合わせると、Unityで可能な品質に匹敵する品質になります。

Unityほど用途が広いわけではないかもしれません。なぜなら、可能なすべてのスクリプトを事前に宣言する必要があるからですが、それでも良いオプションです。

3.2で配列タイプのヒントが修正された場合は、スクリプトを拡張して独自の配列を効果的に追加する必要があるファイルパスのエクスポートされた配列を定義できます。 これは、3.1のプラグインを介して、 EditorInspectorPluginクラスを使用して、特定のリソースまたはノードのインスペクターにカスタムコンテンツを追加することで実行することもできます。

つまり、「Unity」のようなシステムが必要な場合は、ルートに何をすべきかを指示するサブノードが実際にあり、手動で宣言したり追加したりせずに、名前を参照するだけでそれらをフェッチする必要があります。ルートノードのスクリプトから。 Resourceメソッドは、一般的にはるかに効果的であり、よりクリーンなコードベースを維持します。

トレイトシステムはGDScriptコードの使いやすさに過度の負担をかけるため、概説した理由に基づいて、この問題を解決します。 その追加の落とし穴は、そのようなシステムから得られる可能性のある比較的わずかな利点をはるかに上回り、それらの同じ利点は、より明確で使いやすい方法でさまざまな方法で実装できます。

さて、私は外出中にこの議論を逃しました。 まだすべてを読んでいませんが、コードの再利用の問題を解決するためにGDScriptに特性を追加するというアイデアがありました。これは、派生型にスクリプトをアタッチするという偽の多重継承よりもはるかにエレガントで明確です。 私がやったことは、特定のトレイトファイルを作成することでしたが、クラスファイルをトレイトにすることはできませんでした。 悪くないと思います。

しかし、私は、複数のノードタイプでコードを再利用するという主要な問題を解決するための提案を受け入れています。

@vnen私が思いついた最後の項目である解決策は、再利用可能なセクションをリソーススクリプトにアウトソーシングすることです。

  • それらは、ノードのメンバー変数であるかのように、インスペクターで引き続き公開およびプロパティの編集を行うことができます。

  • これらは、データとロジックがどこから来ているのかを明確に追跡します。これは、1つのスクリプトに多くの特性が含まれていると簡単に危険にさらされる可能性があります。

  • 言語としてのGDScriptに過度の複雑さを加えることはありません。 たとえば、それらが存在する場合、共有品質(プロパティ、定数、シグナル)がメインスクリプトにマージされる方法を解決する必要があります(または、マージされない場合は、ユーザーに依存関係の競合に対処するように強制します)。

  • リソーススクリプトは、インスペクターから割り当てたり変更したりできるため、優れています。 設計者、作成者などは、エディタから直接実装オブジェクトを変更できます。

@willnationsdevなるほど(「リソーススクリプト」という名前は奇妙に聞こえますが、すべてのスクリプトがリソースであるため)。 このソリューションの主な問題は、継承アプローチで人々が期待するものを解決しないことです。つまり、エクスポートされた変数と信号をシーンのルートノードに追加します(特にシーンを他の場所でインスタンス化する場合)。 サブリソースからエクスポートされた変数を編集することはできますが、実用的ではなくなります(編集できるプロパティが一目でわかりません)。

もう1つの問題は、ボイラープレートコードを何度も繰り返す必要があることです。 また、関数を呼び出す前に、リソーススクリプトが実際に設定されていることを確認する必要があります。

利点は、何もする必要がないことです。すでに利用可能です。 これは文書化する必要があると思います。現在の「派生ノードにスクリプトをアタッチする」方法を使用している人は、ソリューションについてコメントして、それがどれほど実行可能かを確認できます。

@vnen

ただし、実用性は低くなります(編集できるプロパティが一目でわかりません)。

ここで何を意味するのか詳しく説明していただけますか? 特に#22660のようなものがマージされた場合、どのプロパティにアクセスできるかについて、どのように明確性が実質的に欠如しているのかわかりません。

  1. シーンをインスタンス化し、編集方法を知りたいので、そのシーンのルートノードのスクリプトを確認します。
  2. スクリプトの中には、わかります...

    • export(MoveImpl) var move_impl = FourWayMoveImpl.new()

    • use FourWayMoveTrait

  3. スクリプトを開くために識別子(実際には3.1.1の機能であるはずです!)をクリックトレースする手段があると仮定すると、関連するスクリプトを開いて、そのプロパティを表示できます。

私が何かを逃していない限り、私には同じステップ数のように思えます。

さらに、どのプロパティが編集可能であるかは、実際にはリソースを使用するとさらに明確になります。実装に固有のプロパティがある場合、データにアクセスする前に、実装インスタンス、つまりmove_impl.<property>

もう1つの問題は、ボイラープレートコードを何度も繰り返す必要があることです。 また、関数を呼び出す前に、リソーススクリプトが実際に設定されていることを確認する必要があります。

これは真実ですが、初期化を伴うエクスポートであるという利点は、追加された言語のコストを上回っていると思います。

(「ハイレベルチームメイト」、HLT、デザイナー、ライター、アーティストなど)

  • スクリプトを開いて変更する正しい行を見つけてから変更するのではなく、インスペクターから直接値を割り当てることができます(すでに説明しましたが、...につながります)。

  • エクスポートされたコンテンツに基本タイプの要件があることを指定できます。 その後、インスペクターは、許可される実装の列挙リストを自動的に提供できます。 HLTは、そのタイプの派生のみを安全に割り当てることができます。 これは、浮かんでいるすべての異なる特性スクリプトの影響を知る必要があるという選択肢から彼らを隔離するのに役立ちます。 また、GDScriptのオートコンプリートを変更して、 useキーワードの表示に応じて、名前付きおよび名前なしのトレイトファイルの検索をサポートする必要があります。

  • 実装の構成を* .tresファイルとしてシリアル化できます。 HLTは、ファイルシステムドックからドラッグアンドドロップしたり、インスペクターで独自の権利を作成したりすることができます。 トレイトで同じことをしたい場合は、デフォルトのコンストラクターをオーバーライドするカスタムコンストラクターを提供する派生トレイトを作成する必要があります。 次に、その特性を、必須にコード化されたコンストラクターを介した「事前構成」として代わりに使用します。

    1. 宣言的ではなく命令的であるため、より弱い。
    2. コンストラクターはスクリプトで明示的に定義する必要があるため、弱くなります。
    3. トレイトに名前が付いていない場合、ユーザーは、デフォルトの基本トレイトの代わりにトレイトを適切に使用するために、トレイトがどこにあるかを知る必要があります。 トレイトに名前が付けられている場合、グローバル名前空間が不必要に詰まります。
    4. スクリプトをuse MoveTrait $ではなくuse FourWayMoveTraitと言うように変更した場合、スクリプトがベースのMoveTraitと互換性があることを示す永続的な兆候はなくなります。 それは、 FourWayMoveTraitが物事を壊すことなく別のMoveTraitに変更できるかどうかについてのHLTの混乱を可能にします。
    5. HLTがこの方法で新しいトレイトの実装を作成している場合、基本トレイトから設定できる/設定する必要のあるすべてのプロパティを必ずしも知っているとは限りません。 これは、インスペクターで作成されたリソースの問題ではありません。
  • 同じタイプの複数のリソースを持つこともできます(その理由がある場合)。 トレイトはこれをサポートしませんが、代わりに解析の競合をトリガーします。

  • 2D / 3Dビューポートを離れることなく、構成や個々の値を変更できます。 これはHLTにとってはるかに快適です(コードを見なければならないときに完全にイライラする人はたくさんいます)。

そうは言っても、ボイラープレートが煩わしいことに同意します。 ただし、これに対処するには、マクロまたはスニペットシステムを追加して単純化します。 スニペットシステムは、GDScriptだけでなく、ScriptEditorで編集できるすべての言語をサポートできると考えられるため良いアイデアです。

約:

ただし、実用性は低くなります(編集できるプロパティが一目でわかりません)。

私は検査官について話していたので、ここでは「HLT」を意味します。 コードを調べない人。 トレイトを使用して、新しいエクスポートされたプロパティをスクリプトに追加できます。 リソーススクリプトでは、変数をリソース自体にエクスポートすることしかできないため、サブリソースを編集しない限り、変数はインスペクターに表示されません。

私はあなたの議論を理解していますが、それは元の問題を超えています:コードを繰り返さないでください(継承なしで)。 特性はプログラマー向けです。 多くの場合、再利用したい実装は1つだけであり、それをインスペクターで公開したくない場合があります。 もちろん、コードで直接割り当てるだけでエクスポートせずにリソーススクリプトを使用することはできますが、エクスポートされた変数と共通の実装からのシグナルを再利用する問題は解決されません。これは、人々がしようとしている主な理由の1つです。継承を使用します。

つまり、人々は現在、関数だけでなく、エクスポートされたプロパティ(通常はそれらの関数に関連している)とシグナル(UIに接続できる)にも汎用スクリプトからの継承を使用しようとしています。

これは解決するのが難しい問題です。 GDScriptだけでそれを行う方法はいくつかありますが、やはりボイラープレートコードをコピーする必要があります。

外部スクリプトをメインスクリプト自体に直接記述されているかのように動作させるために必要な前後の操作を想像することしかできません。

それを達成するために天と地を動かす必要がなければ、持っているといいでしょう。 バツ)

@vnenあなたが今言っていることがわかります。 さて、この号にはまだまだ生命があるようですので、次回機会があれば再開します。

これを再開することについて何かニュースはありますか? GodotCon 2019が発表され、Godot Sprintが登場した今、これはそこで話す価値があるかもしれません。

@AfterRebelion戻ってきて、もう一度開くのを忘れていました。 思い出させてくれてありがとう。 XD

@willnationsdev EditorInspectorPluginに関して読んだものが気に入りました! 簡単な質問です。これは、データ型のカスタムインスペクターを作成できることを意味します...たとえば...インスペクターにボタンを追加します。
(これを実行したかったのでかなり時間がかかりました。インスペクターのボタンを使用してデバッグ目的でイベントをトリガーする方法を使用するために、ボタンを押してスクリプトを実行させることができました)

@xDGameStudiosうん、それだけでなく、もっとたくさんのことが可能です。 カスタムコントロールは、上部、下部、プロパティの上、またはカテゴリの下のいずれかでインスペクターに追加できます。

@willnationsdevプライベートメッセージで連絡してもいいかわかりません!! しかし、EditorInspectorPluginについてもっと知りたいです(サンプルコードのように)。カスタムリソースタイプの行にあるもの(たとえば)プロパティexport "name"と "name"を出力するインスペクターボタンを持つMyResource (エディター内またはデバッグ中に)押すと変数になります! ドキュメントはこの問題で大きな時間が不足しています...これを使用する方法を知っていれば、私は自分でドキュメントを書くでしょう! :Dありがとう

それについてももっと知りたいです。 バツ)

それで、これは静的関数を含むサブクラスを持つ自動ロードスクリプトと同じですか?

たとえば、あなたのケースはTraits.MoveRightTrait.move_right()になります

またはさらに単純で、特性クラスごとに異なる自動ロードスクリプトがあります。

Movement.move_right()

いいえ、トレイトは言語固有の機能であり、あるスクリプトのソースコードを別のスクリプトにコピーして貼り付けるのと同じです(ファイルマージのようなものです)。 したがって、 move_right()の特性があり、2番目のスクリプトがその特性を使用することを宣言すると、静的でなくても、アクセスする場合でも、 move_right()を使用できます。クラスの他の場所のプロパティ。 プロパティが2番目のスクリプトに存在しなかった場合は、解析エラーが発生します。

多重継承ができないため、コードが重複している(関数が小さい、たとえばアークを作成する)か、ノードが不要であることがわかりました。

これは素晴らしいことです。まったく同じ機能を備えたスクリプトを作成する必要があり、異なるノードタイプで使用すると、異なるextendが発生するため、基本的に1行のコードで発生します。 ちなみに、現在のシステムでそれを作る方法を誰かが知っているなら、私に知らせてください。

extends Nodeのスクリプトの機能がある場合、ソースファイルを複製して適切なextendに置き換えることなく、同じ動作を別のノードタイプにアタッチする方法はありますか?

何か進展はありますか? 前に言ったように、コードを複製したり、ノードを追加したりする必要があります。 3.1では実行されないことはわかっていますが、3.2を目指しているのでしょうか。

ああ、私はこれにまったく取り組んでいません。 実際、私はこれよりも拡張メソッドの実装について進んでいます。 私は彼と最高の構文/実装の詳細を理解したいので、私はそれらの両方についてvnenと話す必要があります。

編集:他の誰かがトレイトの実装にひびを入れたい場合は、それに飛び込むことを歓迎します。 開発者と調整する必要があります。

「拡張メソッドの実装」?

@ Zireael07 #15586
これにより、エンジンクラスの新しい「組み込み」関数を追加できるスクリプトを作成できます。 構文の私の解釈は次のようになります。

static Array func sum(p_self: Array):
    if not len(p_self):
        return 0
    var value = p_self[0]
    for i in range(1, len(p_self)):
        value += p_self[i]
    return value

次に、どこか別の場所で、次のことができるようになります。

var arr = [1, 2, 3]
print(arr.sum()) # prints 6

常にロードされる拡張スクリプトを密かに呼び出し、Arrayクラスにバインドされている同じ名前の静的関数を呼び出し、インスタンスを最初のパラメーターとして渡します。

私はこれについて@vnen@ jahd2602といくつか話をしました。 頭に浮かぶことの1つは、ポリモーフィズムに対するJaiのソリューションです。プロパティの名前空間をインポートすることです。

このようにして、次のようなことを行うことができます。

class A:
    var a_prop: String = "Hello"
    func foo():
        print("A's a_prop: ", a_prop)
    func bar():
        print("A's bar()")

class B:
    using var a: A = A.new()
    var a_prop: String = "World" # Overriding A's a_prop

    func bar():  # Overriding A's bar()
        print("B's bar()")

func main():
    var b: B = B.new()
    b.foo() # output: "A's a_prop: World"
    b.bar() # output: "B's bar()"

重要なのは、 usingキーワードがプロパティの名前空間をインポートするため、 b.foo()は実際にはb.a.foo()の構文糖衣構文にすぎないということです。

次に、 b is A == trueとBが、Aも受け入れる型付きの状況で使用できることを確認します。

これには、物事を特性として宣言する必要がないという利点もあります。これは、一般的な品質名を持たないものすべてに対して機能します。

1つの問題は、これが現在の継承システムとうまくかみ合わないことです。 AとBの両方がNode2Dであり、Aで関数を作成する場合: func baz(): print(self.position)b.baz()を呼び出すと、どちらの位置が出力されますか?
1つの解決策は、発信者にselfを決定させることです。 b.foo()を呼び出すと、bをselfとしてfoo()が呼び出され、bafoo()を呼び出すとaをselfとしてfoo()が呼び出されます。

Python( x.f(y)f(x,y)の砂糖)などの独立したメソッドがある場合、これは非常に簡単に実装できます。

別の無関係なアイデア:

独立した関数、JavaScriptスタイルのみに焦点を当てます。

静的関数にx.f(y) == f(x,y)規則を採用すると、非常に簡単に次のようになります。

class Jumper:
    static func jump(_self: KinematicBody2D):
        # jump implementation

class Runner:
    static func run(_self: KinematicBody2D, direction: Vector2):
        # run implementation

class Character:
    extends KinematicBody2D
    func run = Runner.run       # Example syntax
    func jump = Jumper.jump

func main():
    var character = Character.new()
    character.jump()
    character.run(Vector2(1,0))

これは実際にはメソッドにのみ影響するため、クラスシステムへの影響は最小限に抑えられます。 ただし、これを本当に柔軟にしたい場合は、完全なJavaScriptを使用して、関数定義を割り当て可能で呼び出し可能な値にすることができます。

@jabcrossいいですね、私はある種のオプションの名前空間の概念が好きで、関数のアイデアは興味深いものです。

名前空間の1つに関しては、なぜ単純にusing Aではないのか、他の宣言的なものは無関係に見えるのではないかと思います。

多重継承でどのように解決する必要があるのか​​も不思議です。 オプションAは、両方のスクリプトに同じ型を強制的に継承させることであると思います。したがって、特別なマージを行わずに、両方を同じクラスの上に拡張するだけです。

オプションB、特性クラスを指定するための追加のGDScriptキーワード、およびヒントを取得するクラス。 それは同じ考えですが、より明確に見えるようにするための追加の手順だけです。

A.gdの上部:

extends Trait as Node2D
is Trait as Node2D
is Trait extends B
extends B as Trait

ああ、私は名前空間のインポートの概念が本当に好きです。 特性の問題だけでなく、エンジンタイプにコンテンツを追加するための「拡張メソッド」の概念も解決する可能性があります。

class_name ArrayExt
static func sum(_self: Array) -> int:
    var sum: int = 0
    for a_value in _self:
        sum += a_value
    return sum

using ArrayExt
func _ready():
    var a = [1, 2, 3]
    print(a.sum())

@jabcross次に、lambasを追加したり、オブジェクトに呼び出し演算子を実装させたり(互換性のある値に対してcallable型を使用したり)すると、GDScriptコード(これは素晴らしいアイデアだと思います)。 確かに、その時点で@vnenの#18698の領域にさらに足を踏み入れましたが、...

GDScriptは依然として動的言語であり、これらの提案の一部では、呼び出しを正しくディスパッチするためにランタイムチェックが必要であり、パフォーマンスが低下します(すべての関数呼び出しをチェックする必要があるため、機能を使用しない人にも)そして多分プロパティルックアップも)。 これが、拡張機能を追加することが良い考えかどうかわからない理由でもあります(特に、拡張機能は本質的にシンタックスシュガーであるため)。

私は、特性がクラスではなく独自のものである純粋な特性システムを好みます。 このようにして、コンパイル時に完全に解決でき、名前が競合する場合にエラーメッセージを表示できます。 これにより、追加の実行時オーバーヘッドなしで問題が解決すると思います。

@vnenああ、ランタイムコストについては気づいていませんでした。 そして、それが拡張メソッドの実装に当てはまるのであれば、それも理想的ではないと思います。

純粋な特性システムを使用した場合、他のスクリプトの拡張機能の下にあるusing TraitName $と組み合わせてextends trait TraitNameを作成することを考えていましたか? そして、これを自分で実装しますか、それとも委任されますか?

純粋な特性システムを使用した場合、他のスクリプトの拡張機能の下にあるusing TraitName $と組み合わせてextends trait TraitNameを作成することを考えていましたか?

それが私の考えです。 それは十分に単純で、コードの再利用のほとんどすべてのユースケースをカバーしていると思います。 トレイトを使用するクラスがトレイトメソッドをオーバーライドすることも許可します(コンパイル時に実行できる場合)。 特性は他の特性を拡張することもできます。

そして、これを自分で実装しますか、それとも委任されますか?

私はそれをやっている他の誰かにその仕事を与えてもかまいません。 とにかく時間が足りない。 ただし、事前に設計について合意する必要があります。 私は詳細に非常に柔軟性がありますが、1)ランタイムチェックで発生しない(GDScriptはコンパイル時に理解できないものに適していると信じています)、2)比較的単純であり、3)あまり追加しないでくださいコンパイル時間。

@vnenこれらのアイデアが好きです。 トレイトが、それを含むクラスのオートコンプリートなどを実行できると想像するのか、それとも不可能なのか、疑問に思っていましたか?

トレイトが、それを含むクラスのオートコンプリートなどを実行できると想像するのか、それとも不可能なのか、疑問に思っていましたか?

私の見解では、特性は本質的に「インポート」です。 メンバーの完了が機能すると仮定すると、完了を表示するのは簡単なはずです。

@vnen基本的に、 traitフラグが設定されたClassNodeに解析されると思います。 次に、 usingステートメントを実行すると、すべてのプロパティ/メソッド/シグナル/定数/サブクラスを現在のスクリプトにマージしようとします。

  1. メソッドが衝突した場合、継承されたメソッドをオーバーライドするのと同じように、現在のスクリプトの実装が基本メソッドをオーバーライドします。

    • しかし、基本クラスにすでに「マージされた」メソッドがある場合はどうすればよいでしょうか。

  2. プロパティ、シグナル、および定数が重複している場合は、それが同じタイプ/シグナルシグネチャであるかどうかを確認してください。 不一致がない場合は、プロパティ/信号/定数の「マージ」と見なしてください。 それ以外の場合は、型/署名の競合(解析エラー)をユーザーに通知します。

悪いアイデア? それとも、メソッド以外の競合を完全に禁止する必要がありますか? サブクラスはどうですか? 私はそれらを対立にすべきだと感じています。

@willnationsdev 「菱形継承問題」(別名「死の致命的なダイヤモンド」)のように聞こえます。これは、さまざまな一般的なプログラミング言語ですでに適用されているさまざまなソリューションを使用した、十分に文書化されたあいまいさです。

それは私に思い出させます:
@vnen特性は、他の特性を拡張できるようになりますか?

@ jahd2602彼はすでに可能性としてそれを提案しました

特性は他の特性を拡張することもできます。

@ jahd2602 Perl / Pythonソリューションに基づくと、基本的に各クラスのコンテンツを含むレイヤーの「スタック」を形成しているため、最後に使用された特性との競合が他のバージョンの上に残り、上書きされるようです。 これは、このシナリオにはかなり良い解決策のように思えます。 あなたや@vnenが別の考えを持っていない限り。 ソリューションjahdのリンクされた概要をありがとう。

いくつかの質問。

まず、usingステートメントをどのようにサポートする必要がありますか?

usingステートメントには定数値のGDScriptが必要だと思います。

using preload("res://my_trait.gd") # a preloaded expression
using ScriptClass.MyTrait # a const resource
using Autoload.MyTrait # a const resource
using MyTrait # a regular script class

私は上記のすべてを考えています。

第二に:特性および/またはその名前を定義するために許可された構文は何と言うべきですか?

誰かが必ずしもその特性にスクリプトクラスを使用したいとは限らないので、 trait TraitNameの要件を強制する必要はないと思います。 traitは一列に並んでいるに違いないと思います。

したがって、ツールスクリプトの場合は、もちろん上部にツールが必要です。 次に、それが特性である場合、それが次の行の特性であるかどうかを定義する必要があります。 必要に応じて、誰かが同じ行の特性宣言の後にスクリプトクラス名を記述できるようにします。その場合は、 class_nameも使用できないようにします。 特性名を省略した場合は、 class_name <name>で問題ありません。 次に、別の型を拡張するときに、特性宣言の後にextendsを挿入したり、特性宣言の後に独自の行に挿入したりできます。 したがって、これらのそれぞれを有効と見なします。

# Global name from trait keyword.
trait MyTrait extends BaseTrait

# Global name from class_name keyword, but is still a trait and also happens to be a tool script.
tool
trait
extends BaseTrait
class_name MyTrait

# A trait with no global name associated with it. Does not extend anything.
trait

第三に、オートコンプリートの目的および/または意図/要件の宣言のために、トレイトが拡張する必要のある基本タイプを定義できるようにする必要がありますか?

特性は他の特性の継承をサポートする必要があることはすでに説明しました。 ただし、TraitAにextend Nodeを許可する場合は、TraitAスクリプトがノードのオートコンプリートを取得できるようにしますが、現在のクラスがノードを拡張しないときにusing TraitAステートメントを実行すると、解析エラーもトリガーされます。またはその派生タイプのいずれか?

4番目:トレイトに他のトレイトを拡張させるのではなく、単にextendsステートメントをクラス拡張用に予約したままにして、トレイトがこのステートメントをまったく必要としないようにすることはできませんが、基本トレイトを拡張するのではなく、それらの特性をサブインポートする独自のusingステートメントを単純に持つ特性?

# base_trait.gd
trait
func my_method():
    print("Hello")

# derived_trait.gd
trait
using preload("base_trait.gd")
func my_method():
   print("World") # overrides previous method, will only print "World".

もちろん、ここでの利点は、他のいくつかのクラスを含むC ++インクルードファイルと同様に、複数のusingステートメントを使用して、単一のトレイト名で複数のトレイトをバッチ処理できることです。

5番目:トレイトがあり、メソッドにusingまたはextendsがあり、それ自体を実装する場合、その関数.<method_name>内で呼び出したときに何をしますか

cc @vnen

まず、usingステートメントをどのようにサポートする必要がありますか?

usingステートメントには定数値のGDScriptが必要だと思います。

using preload("res://my_trait.gd") # a preloaded expression
using ScriptClass.MyTrait # a const resource
using Autoload.MyTrait # a const resource
using MyTrait # a regular script class

私はそれらすべてで大丈夫です。 しかし、パスには文字列を直接使用します: using "res://my_trait.gd"

第二に:特性および/またはその名前を定義するために許可された構文は何と言うべきですか?

誰かが必ずしもその特性にスクリプトクラスを使用したいとは限らないので、 trait TraitNameの要件を強制する必要はないと思います。 traitは一列に並んでいるに違いないと思います。

したがって、ツールスクリプトの場合は、もちろん上部にツールが必要です。 次に、それが特性である場合、それが次の行の特性であるかどうかを定義する必要があります。 必要に応じて、誰かが同じ行の特性宣言の後にスクリプトクラス名を記述できるようにします。その場合は、 class_nameも使用できないようにします。 特性名を省略した場合は、 class_name <name>で問題ありません。 次に、別の型を拡張するときに、特性宣言の後にextendsを挿入したり、特性宣言の後に独自の行に挿入したりできます。 したがって、これらのそれぞれを有効と見なします。

# Global name from trait keyword.
trait MyTrait extends BaseTrait

# Global name from class_name keyword, but is still a trait and also happens to be a tool script.
tool
trait
extends BaseTrait
class_name MyTrait

# A trait with no global name associated with it. Does not extend anything.
trait

トレイトのtoolは直接実行されないため、違いはありません。

私は、特性が必ずしもグローバルな名前を持っているとは限らないことに同意します。 tool traitを使用します。 スクリプトファイルの最初のものである必要があります(コメントを除く)。 オプションで、キーワードの後に​​特性名を続ける必要があります。 それらはクラスではないので、私はそれらにclass_nameを使用しません。

第三に、オートコンプリートの目的および/または意図/要件の宣言のために、トレイトが拡張する必要のある基本タイプを定義できるようにする必要がありますか?

私は正直なところ、編集者のためにその言語で機能を追加するのは好きではありません。 そこで、注釈が役に立ちます。

特性を特定のタイプ(およびその派生物)にのみ適用したい場合は、問題ありません。 実際、これは静的チェックのために優れていると思います。これにより、トレイトがクラスからのものを使用できるようになり、コンパイルでは、それらが正しいタイプで使用されているかどうかを実際にチェックできます。

4番目:トレイトに他のトレイトを拡張させるのではなく、単にextendsステートメントをクラス拡張用に予約したままにして、トレイトがこのステートメントをまったく必要としないようにするのではなく、基本トレイトを拡張するのではなく、 _those_特性をサブインポートする独自のusingステートメントを単純に持つ特性?

まあ、それは主にセマンティクスの問題です。 特性が別の特性を拡張できると述べたとき、私は実際にはextendsキーワードを使用するつもりはありませんでした。 主な違いは、 extendsでは1つしか拡張できないことですが、 usingでは他の多くの特性を1つに埋め込むことができます。 usingで大丈夫です、サイクルがない限り、それは問題ではありません。

5番目:トレイトがあり、メソッドにusingまたはextendsがあり、それ自体を実装する場合、その関数.<method_name>内でそれが呼び出されたときに何をしますか

それは難しい質問です。 トレイトにはクラスの継承を扱うビジネスはないと思います。 したがって、ドット表記は、親トレイトにメソッドがある場合はそのメソッドを呼び出す必要があり、そうでない場合はエラーをスローします。 トレイトは、それらが属するクラスを認識してはなりません。

OTOH、トレイトはほとんど「インクルード」に似ているため、クラスに逐語的に適用されるため、親実装を呼び出します。 しかし、正直なところ、メソッドが親トレイトに見つからない場合は、ドット表記を禁止します。

クラスに1つ以上の他の特性が必要な特性はどうですか? たとえば、トレイトDoubleJumperは、トレイトJumper 、トレイトUpgradable 、およびKinematicBody2Dを継承するクラスの両方を必要とします。

たとえば、Rustを使用すると、このような型署名を使用できます。 KinematicBody2D: Jumper, Upgradableのようなもの。 ただし、タイプに注釈を付けるために:を使用しているため、 KinematicBody2D & Jumper & Upgradableなどを使用できます。

ポリモーフィズムの問題もあります。 トレイトの実装がクラスごとに異なるが、同じインターフェースを公開している場合はどうなりますか?

たとえば、トレイトJumperにメソッドkill()が必要です。これは、 EnemyPlayerの両方で使用されます。 同じJumper型シグネチャとの互換性を維持しながら、ケースごとに異なる実装が必要です。 これを行う方法?

ポリモーフィズムの場合は、 kill()のトレイトを含む別のトレイトを作成し、メソッドの独自の特定のバージョンを実装するだけです。 以前に含まれていたトレイトのメソッドをオーバーライドするトレイトを使用することは、それを処理する方法です。

また、タイプヒントに特性要件を持たせる計画は(まだ)なかったと思います。 それは私たちがやりたいことですか?

別の特性を作成する

それは一回限りの特性ファイルの束を生成しませんか? ネストされたトレイト宣言( classキーワードと同様)を実行できれば、より便利になる可能性があります。 特性を使用しているクラスのメソッドを直接オーバーライドすることもできます。

強力な型署名システム(おそらくブール構成とオプション/非null)に感謝します。 特性はぴったり合うでしょう。

議論されたかどうかはわかりませんが、トレイト固有のバージョンの関数を呼び出すことは可能であると思います。 例えば:

trait A
func m():
  print("A")

trait B
func m():
  print("B")

class C
using A
using B

func c():
  A.m()
  B.m()
  m()

これは次のように出力します: ABB


また、「ランタイムコストなし」については完全にはわかりません。 エクスポート前に定義された特性を使用しているクラスで動的にロードされたスクリプト(エクスポート中は使用できません)をどのように処理しますか? 私は何かを誤解していますか? または、その場合は「ランタイム」とは見なされませんか?

議論されたかどうかはわかりませんが、トレイト固有のバージョンの関数を呼び出すことは可能であると思います。

私はすでにこれを検討していましたが、クラスが競合するトレイト(つまり、同じ名前のメソッドを定義するトレイト)を使用できるようにするかどうかはわかりません。 usingステートメントの順序に違いはありません。

また、「ランタイムコストなし」については完全にはわかりません。 エクスポート前に定義された特性を使用しているクラスで動的にロードされたスクリプト(エクスポート中は使用できません)をどのように処理しますか? 私は何かを誤解していますか? または、その場合は「ランタイム」とは見なされませんか?

エクスポートではありません。 コンパイルはロード時に行われるため(それほど重要ではないと思いますが)、ロード時間には間違いなく影響しますが、スクリプトの実行時には影響しないはずです。 理想的には、スクリプトはエクスポート時にコンパイルする必要がありますが、それは別の議論です。

こんにちは、みんな。

私はGodotに不慣れで、過去数日間それに慣れてきました。 再利用可能なコンポーネントを作成するために使用するベストプラクティスを理解しようとしたときに、パターンを決定しました。 私は常に、別のシーンでインスタンス化されることを意図したサブシーンのルートノードを作成し、外部から設定することを意図したすべてのプロパティをエクスポートします。 可能な限り、インスタンス化されたブランチの内部構造の知識をシーンの残りの部分で不要にしたかったのです。

これを機能させるには、ルートノードがプロパティを「エクスポート」してから、値を_readyの適切な子にコピーする必要があります。 したがって、たとえば、子タイマーを持つ爆弾ノードを想像してみてください。 サブシーンのルート爆弾ノードは「detonation_time」をエクスポートし、_readyで$Timer.wait_time = detonation_timeを実行します。 これにより、子を編集可能にしてタイマーにドリルダウンしなくても、インスタンス化するたびにGodotのUIで適切に設定できます。

でも
1)これは非常に機械的な変換であるため、同様の何かがシステムによってサポートされる可能性があるようです
2)子ノードに直接適切な値を設定するよりも、おそらくわずかな非効率性が追加されます。

先に進む前に、これは、ある種の「プライベート」継承(C ++用語で)を許可することを含まないため、議論されていることに接しているように見えるかもしれません。 しかし、私は実際には、継承のようなエンジニアリングではなく、シーン要素を構成することによって動作を構築するGodotのシステムが好きです。 これらの「書き込まれた」関係は不変で静的です。 OTOH、シーン構造は動的であり、実行時に変更することもできます。 ゲームロジックは開発中に変更される可能性が非常に高いため、Godotの設計はユースケースに非常によく適合していると思います。

子ノードがルートノードの動作拡張として使用されることは事実ですが、それによって子ノードが自給自足、IMOを欠くことはありません。 タイマーは完全に自己完結型であり、時間に使用されるものに関係なく、動作を予測できます。 スプーンを使ってスープを飲んだり、アイスクリームを食べたりする場合でも、手の延長として機能しますが、適切に機能します。 私はルートノードを子ノードの動作を調整するマエストロと見なしているので、子ノードはお互いを直接知る必要がなく、したがって自己完結型であり続けることができます。 親/ルートノードは、責任を委任するデスクバウンドのマネージャーですが、直接的な作業はあまり行いません。 それらは薄いので、わずかに異なる動作のために新しいものを簡単に作成できます。

ただし、ルートノードは、インスタンス化されたブランチ全体の機能に対するプライマリインターフェイスとしても機能する必要があると思います。 プロパティの最終的な所有者が子ノードであっても、インスタンスで調整できるすべてのプロパティは、ブランチのルートノードで「設定可能」である必要があります。 何かが足りない場合を除いて、これは現在のバージョンのGodotで手動で調整する必要があります。 動的システムの利点とより簡単なスクリプトを組み合わせるために、これを何らかの方法で自動化できれば便利です。

私が考えていることの1つは、Nodeのサブクラスで使用できる「動的継承」のシステムです。 このようなスクリプトには、プロパティ/メソッドの2つのソースがあります。それは、スクリプトが拡張するスクリプトのものと、シーン構造内の子から「バブルアップ」されたものです。 したがって、Bombを使用した私の例は、bomb.gdスクリプトのメンバー変数セクション内でexport lifted var $Timer.wait_time [= value?] as detonation_timeのようになります。 システムは基本的に_readyコールバックで$Timer.wait_time = detonation_timeを生成し、Bombノードの親からの$Bomb.detonation_time = 5$Timer.wait_time = 5を設定できるようにするゲッター/セッターを生成します。

MoveRightTraitを使用したOPの例では、mysprite.gdがアタッチされているノードにMoveRightTraitが子ノードとして含まれています。 次に、mysprite.gdにlifted func $MoveRightTrait.move_right [as move_right]のようなものがあります(名前が同じ場合は、おそらく 'as'はオプションである可能性があります)。 mysprite.gdから作成されたスクリプトオブジェクトでmove_rightを呼び出すと、適切な子ノードに自動的に委任されます。 おそらく、信号をバブリングして、ルートから子ノードに接続できるようにすることができますか? おそらく、ノード全体が、func、signal、またはvarなしでlifted $MoveRightTrait [as MvR]だけでバブル化される可能性があります。 この場合、MoveRightTraitのすべてのメソッドとプロパティには、myspriteから直接mysprite.move_rightとしてアクセスするか、「asMvR」が使用されている場合はmysprite.MvR.move_rightを介してアクセスできます。

これは、インスタンス化されたブランチのルートにあるシーン構造へのインターフェイスの作成を簡素化し、「ブラックボックス」の特性を高め、Godotの動的シーンシステムのパワーとともにスクリプトの利便性を高める方法の1つのアイデアです。 もちろん、考慮すべき多くの側面の詳細があります。 たとえば、基本クラスとは異なり、子ノードは実行時に削除できます。 そのエラーの場合に呼び出されたりアクセスされたりした場合、バブル/リフトされた関数とプロパティはどのように動作する必要がありますか? 正しいNodePathを持つノードが追加された場合、持ち上げられたプロパティは再び機能し始めますか? [はい、IMO]また、ノードから派生していないクラスで「リフト」を使用するとエラーになります。その場合、バブル/リフトする子は存在しないためです。 さらに、ノードが同じ名前のプロパティ/メソッドを持っている場合、重複した「as {name}」または「lifted $ Timer1lifted $ Timer2」で名前の衝突が発生する可能性があります。 スクリプトインタプリタは、理想的にはそのような論理的な問題を検出します。

これにより、転送関数や初期化を作成する必要がなくなるのは、実際には単なる構文糖衣であるにもかかわらず、必要なものがたくさん提供されると思います。 また、概念的には基本的に単純なので、実装や説明はそれほど難しくありません。

とにかく、あなたがこれまで読んでくれてありがとう!

私はどこでも「リフト」を使用しましたが、それは単なる例示です。
using var $Timer.wait_time as detonation_timeusing $Timerのようなものは明らかに同じくらい良いです。 いずれの場合も、子ノードから便利に疑似継承し、インスタンス化するブランチのルートに必要な機能への一貫した単一のアクセスポイントを作成できます。 再利用可能な機能の要件は、Nodeまたはそのサブクラスを拡張して、より大きなコンポーネントに子として追加できるようにすることです。

別の見方をすれば、ノードから継承するスクリプトの「extends」キーワードは、スクリプトの「using」または「lifted」キーワードを使用して「バブルアップ」するときに「is-a」関係を与えるということです。子孫ノードのメンバーは、単一の継承を持つが複数の「インターフェース」(Javaなど)を持つ言語に存在する「実装」[ねえ、可能なキーワード]に似たものを提供します。 無制限の多重継承(c ++など)では、基本クラスは[静的な書き込み済み]ツリーを形成します。 類推すると、私は、Godotの既存のノードツリー上に便利な構文と定型文を階層化することを提案しています。

これが調査する価値のあるものであると判断された場合は、考慮すべき設計スペースの側面があります。
1)「使用」の直接の子供のみを許可する必要があります。 IOW using $Timer 、ただしusing $Bomb/Timer'? This would be simpler but would force us to write boilerplate in some cases. I say that a full NodePath ROOTED in the Node to which the script is attached should be legal [but NO references to parents/siblings allowed]. 2) Should there be an option thatではないfind_nodeのthe "using"-ed node instead of following a written in NodePath? For example using "Timer" with a string for the pattern would be slower but the forwarding architecture would continue to work if a referenced node's position in the sub-tree changes at run time. This could be used selectively for child nodes that we expect to move around beneath the root. Of course syntax would have to be worked out especially when using a particular member (eg. using var "Timer" .wait_time as detonation_time is icky). 3) Should there be a way query for certain functionality [equivalent to asking if an interface is implemented or a child node is present]? Perhaps "using" entire nodes with aliases should allow testing the alias to be a query. So using MoveRightTrait as DirectionalMover in a script would result in node.DirectionalMover returning the child MoveRightTrait. This is logical because node.DirectionalMover.move_right() calls the method on the child MoveRightTrait. Other nodes without that statement would return null. So the statement if node.DirectionalMover: `は、慣例により機能のテストになります。
4)状態パターンは、「using」されたノードを、「using」ステートメントで参照されているものと同じインターフェース[ダックタイピング]および同じNodePathを持つ、動作が異なる別のノードに置き換えることで実装可能である必要があります。 シーンツリーの動作方法を使用すると、これはほぼ無料で機能します。 ただし、システムは、親を介して接続された信号を追跡し、置き換えられた子の接続を復元する必要があります。

私はしばらくの間GDScriptを使用してきましたが、同意する必要があります。ある種のトレイト/ミックスインおよびプロキシ/委任機能が切実に必要です。 プロパティを接続したり、シーンのルートにある子のメソッドを公開したりするためだけに、このボイラープレートをすべてセットアップしなければならないのは非常に面倒です。

または、コンポーネントをシミュレートするためだけにツリーのレベルを追加します(新しいコンポーネントごとにすべてのノードパスを壊すため、かなりすぐに面倒になります)。 たぶん、1つのノードで複数のスクリプトを許可するメタ/マルチスクリプトのような、より良い方法がありますか? 慣用的な解決策がある場合は、共有してください...

C ++(GDNative)をミックスに投入すると、状況がさらに悪化します。これは、 _ready_initの動作が異なるためです(デフォルト値での初期化は機能しないか、まったく機能しません)。

これは、GDScriptで回避する必要がある主なことです。 多くの場合、ノード全体の継承構造を構築せずにノード間で機能を共有する必要があります。たとえば、プレーヤーとショップキーパーには在庫があり、プレーヤー+アイテム+敵には統計があり、プレーヤーと敵にはアイテムが装備されています。

現在、これらの共有された「コンポーネント」を、それらを必要とする「エンティティ」にロードされたクラスまたはノードとして実装していますが、面倒であり(ノードの検索が多くなり、ダックタイピングがほぼ不可能になるなど)、代替アプローチには独自の欠点があります。私はより良い方法を見つけていません。 特性/ミックスインは絶対に私の命を救うでしょう。

結局のところ、継承を使用せずにオブジェクト間でコードを共有できるということです。これは、現在Godotで必要であり、クリーンに実行することは不可能だと思います。

私がRustの特性(https://doc.rust-lang.org/1.8.0/book/traits.html)を理解する方法は、それらがHaskell型クラスのようなものであり、型に対していくつかのパラメーター化された関数を定義する必要があるということです。にトレイトを追加すると、トレイトを実装するすべてのタイプに対して定義されたいくつかの総称関数を使用できるようになります。 Rustの特性は、ここで提案されているものとは異なるものですか?

これは、ここで広範な議論が行われているため、おそらく大規模に移行されます。

これは、ここで広範な議論が行われているため、おそらく大規模に移行されます。

_提案の「動き」は無意味だと思います。人々が興味を示した場合は、提案を閉じて、godot-提案で再開するように依頼し、必要に応じて他の提案を実際に実装します。 いずれかの方法..._

私は1年前にこの問題に遭遇しましたが、形質システムの潜在的な有用性を理解し始めたのは今だけです。

私の現在のワークフローを共有することで、誰かが問題をよりよく理解するように刺激することを望んでいます(そしておそらく特性システムを実装する以外のより良い代替案を提案します)。

1.プロジェクトで使用されるノードタイプごとにコンポーネントテンプレートを生成するツールを作成します。

@willnationsdev https://github.com/godotengine/godot/issues/23101#issuecomment -431468744

さて、これを簡単にするのは、スニペットシステムまたはマクロシステムを使用して、開発者がこれらの再利用された宣言型コードセクションを簡単に作成できるようにすることです。

あなたの足跡をたどって...😅

tool
extends EditorScript

const TYPES = [
    'Node',
    'Node2D',
]
const TYPES_PATH = 'types'
const TYPE_BASENAME_TEMPLATE = 'component_%s.gd'

const TEMPLATE = \
"""class_name Component{TYPE} extends {TYPE}

signal host_assigned(node)

export(bool) var enabled = true

export(NodePath) var host_path
var host

func _ready():
    ComponentCommon.init(self, host_path)"""

func _run():
    _update_scripts()


func _update_scripts():

    var base_dir = get_script().resource_path.get_base_dir()
    var dest = base_dir.plus_file(TYPES_PATH)

    for type in TYPES:
        var filename = TYPE_BASENAME_TEMPLATE % [type.to_lower()]
        var code = TEMPLATE.format({"TYPE" : type})
        var path = dest.plus_file(filename)

        print_debug("Writing component code for: " + path)

        var file = File.new()
        file.open(path, File.WRITE)
        file.store_line(code)
        file.close()

2.コンポーネントをホスト(たとえばroot)に初期化するために再利用する静的メソッドを作成します。

class_name ComponentCommon

static func init(p_component, p_host_path = NodePath()):

    assert(p_component is Node)

    # Try to assign
    if not p_host_path.is_empty():
        p_component.host = p_component.get_node(p_host_path)

    elif is_instance_valid(p_component.owner):
        p_component.host = p_component.owner

    elif is_instance_valid(p_component.get_parent()):
        p_component.host = p_component.get_parent()

    # Check
    if not is_instance_valid(p_component.host):
        push_warning(p_component.name.capitalize() + ": couldn't find a host, disabling.")
        p_component.enabled = false
    else:
        p_component.emit_signal('host_assigned')

これは、最初のスクリプトで生成されたコンポーネント(トレイト)がどのように見えるかを示しています。

class_name ComponentNode2D extends Node2D

signal host_assigned(node)

export(bool) var enabled = true

export(NodePath) var host_path
var host

func _ready():
    ComponentCommon.init(self, host_path)

(オプション)3。コンポーネント(トレイト)を拡張します

@vnen https://github.com/godotengine/godot/issues/23101#issuecomment -471816901

それが私の考えです。 それは十分に単純で、コードの再利用のほとんどすべてのユースケースをカバーしていると思います。 トレイトを使用するクラスがトレイトメソッドをオーバーライドすることも許可します(コンパイル時に実行できる場合)。 特性は他の特性を拡張することもできます。

あなたの足跡をたどって...😅

class_name ComponentMotion2D extends ComponentNode2D

const MAX_SPEED = 100.0

var linear_velocity = Vector2()
var collision

export(Script) var impl
...

実際、エクスポートされたScriptはこれらのコンポーネントで使用され、コンポーネントごとに特定のホスト/ルートノードタイプの動作を駆動します。 ここで、 ComponentMotion2Dには主に2つのスクリプトがあります。

  • motion_kinematic_body_2d.gd
  • motion_rigid_body_2d.gd

したがって、子供たちはまだここでhost / rootの動作を駆動します。 hostの用語は、ステートマシンを使用している私から来ています。ここでは、ステートがノードimoとしてより適切に編成されているため、特性が完全に適合しない可能性があります。

コンポーネント自体は、 onreadyメンバーにすることでルートに「ハードワイヤード」され、ボイラープレートコードを効果的に削減します(実際にコンポーネントをobject.motionとして参照する必要があります)。

extends KinematicBody2D

onready var motion = $motion # ComponentMotion2D

これが問題の解決に役立つかどうかはわかりませんが、C#には、クラス型の機能を拡張する拡張メソッドと呼ばれるものがあります。

基本的に、関数は静的である必要があり、最初のパラメーターは必須であり、 selfである必要があります。 定義としては次のようになります。

extension.gd

# any script that uses this method must be an instance of `Node2D`
static func distance(self source: Node2D, target: Node2D):
    return source.global_position.distance_to(target.global_position)

# any script that uses this method must be an instance of `Rigidbody2D`
# a `Sprite` instance cannot use this method
static func distance(self source: Rigidbody2D, target: Node2D):
    return source.global_position.distance_to(target.global_position)

次に、 distanceメソッドを使用する場合は、次のようにします。

player.gd

func _ready() -> void:
    print(self.distance($Enemy))
    print($BulletPoint.distance($Enemy))

私はそれをよく知っていますが、それは問題の解決に役立ちません。 ヘヘ、でもありがとう。

@TheColorRed拡張メソッドはすでに提案されていますが、動的言語では実現可能ではないと思います。 また、彼らが最初にこの議論を始めた根本的な問題を解決するとは思わない。


別の注意点として、GDScriptの提案の多くをGIPとして開く予定です( @willnationsdevが気にしない場合は、これも含まれます)。

特性は、オブジェクト指向言語でコードを水平方向に共有するのが最も理にかなっていると今でも信じています(多重継承なしで、しかし私はそのように行きたくありません)。

動的言語では実現可能ではないと思います

GDSは動的ですか? 拡張メソッドは、型指定されたインスタンスのみに制限でき、他の言語とまったく同じように機能します。コンパイル中に、メソッド呼び出しを静的メソッド(関数)呼び出しに置き換える構文糖衣構文だけです。 正直なところ、JSのプロトタイプや、クラスやインスタンスにメソッドをアタッチする他の動的な方法の前に、ポン引き(拡張メソッド)を使用したいと思います。

私たちが何をしようと決心したとしても、それを「ポン引き」と名付けないことを願っています。

GDSは動的ですか?

この文脈では、「動的」の定義はたくさんあります。 明確にするために:変数の型はコンパイル時にわからない可能性があるため、メソッド拡張チェックは実行時に実行する必要があります(これは何らかの形でパフォーマンスを低下させます)。

拡張メソッドは、型指定されたインスタンスのみに制限できます

これを開始する場合は、GDScriptを型指定のみにすることもできます。 しかし、それは私がここで得ない全く別の議論です。

重要なのは、ユーザーがスクリプトにタイプを追加したために、動作を開始または停止してはならないということです。 それが起こるとき、それはほとんどいつも混乱します。

繰り返しますが、とにかく問題が解決するとは思いません。 同じコードを複数のタイプにアタッチしようとしていますが、拡張メソッドは1つのタイプにのみ追加します。

正直なところ、JSのプロトタイプや、クラスやインスタンスにメソッドをアタッチする他の動的な方法の前に、ポン引き(拡張メソッド)を使用したいと思います。

誰も(まだ)動的に(実行時に)メソッドをアタッチすることを提案していませんし、私もそれを望んでいません。 トレイトはコンパイル時に静的に適用されます。

私はもともとHaxeとそのミックスインマクロライブラリについてコメントしましたが、その後、ほとんどのユーザーがサードパーティの言語を使用しないことに気付きました。

私は最近これの必要性に遭遇しました。

ユーザーが操作できるオブジェクトがいくつかありますが、同じ親を共有することはできませんが、APIの同様のグループが必要です

たとえば、同じ親から継承できないクラスがいくつかありますが、同様のAPIのセットを使用しています。
倉庫:財務、削除、MouseInteractionなど
車両:財務、削除、MouseInteraction +その他
VehicleTerminal:財務、削除、MouseInteractionなど

私がコンポジションを使用した財務では、get_finances_component()はゲームオブジェクトをまったく気にしないので十分なAPIであるため、ボイラープレートコードが最小限で済みます。

他人:
MouseInteractionとDelectionゲームオブジェクトについて知る必要があるため、コピーして貼り付ける必要がありました。奇妙な委任を行わない限り、一部の構成はここでは機能しません。

Warehouse:
  func delete():
      get_delete_component().delete(self);

しかし、それでは、削除がどのように機能するかをオーバーライドすることはできません。継承されたクラスの場合、必要に応じて削除コードの一部を書き直すことができます。

MouseInteractionとDelectionゲームオブジェクトについて知る必要があるため、コピーして貼り付ける必要がありました。奇妙な委任を行わない限り、一部の構成はここでは機能しません。

現在、 onreadyノードを介してコンポーネントにアクセスしています。 私は似たようなことをしています:

# character.gd

var input = $input # input component

func _set(property, value):
    if property == "focused": # override
        input.enabled = value
    return true

したがって、この:

character.input.enabled = true

これになります:

character.focused = true

@Calinouが私の問題を親切に指摘したように、 https://github.com/godotengine/godot-proposals/issues/758はこれと密接に関連しています。 グループに特性を追加できるようにするという提案についてどう思いますか? これにより、スクリプトやその他のオーバーヘッドが大幅に必要になる可能性があります。

共有可能なコードをクラスに挿入する方法があれば素晴らしいと思います。値がエクスポートされている場合は、これらをインスペクターに表示し、メソッドとプロパティを使用可能にして、コードの完了によって検出します。

Godot Engineの機能と改善の提案は、専用のGodot Improvement Proposals(GIP)godotengine / godot-proposals )課題追跡システムで議論およびレビューされています。 GIPトラッカーには、生産的なディスカッションを開始し、コミュニティがエンジンの提案の有効性を評価するのに役立つすべての関連情報が提案に含まれるように設計された詳細な問題テンプレートがあります。

メイン( godotengine / godot )トラッカーは、バグレポートとプルリクエスト専用になり、寄稿者はバグ修正作業により集中できるようになりました。 そのため、メインの課題追跡システムで古い機能の提案をすべて終了します。

この機能の提案に興味がある場合は、指定された問題テンプレートに従ってGIPトラッカーで新しい提案を開いてください(まだ存在していないことを確認した後)。 関連する議論が含まれている場合は、このクローズされた問題を必ず参照してください(新しい提案に要約することもお勧めします)。 前もって感謝します!

注:これは人気のある提案です。誰かがそれをGodot Proposalsに移した場合は、ディスカッションも要約してみてください。

このページは役に立ちましたか?
0 / 5 - 0 評価