Godot: Tambahkan sistem Sifat GDScript.

Dibuat pada 18 Okt 2018  ·  93Komentar  ·  Sumber: godotengine/godot

(Sunting:
Untuk meminimalkan masalah masalah XY lebih lanjut:
Masalah yang dibahas di sini adalah bahwa sistem Node-Scene / bahasa skrip Godot belum mendukung pembuatan implementasi yang dapat digunakan kembali dan dikelompokkan yang 1) khusus untuk fitur node root dan 2) yang dapat ditukar dan/atau digabungkan. Skrip dengan metode statis atau sub-simpul dengan skrip dapat digunakan untuk bit terakhir, dan untuk banyak kasus ini berfungsi. Namun, Godot umumnya lebih suka Anda untuk menyimpan logika untuk keseluruhan perilaku adegan Anda yang disimpan di node root saat menggunakan data yang dihitung oleh node anak atau mendelegasikan sub-tugas yang menyimpang secara signifikan kepada mereka, misalnya KinematicBody2D tidak mengelola animasi sehingga mendelegasikan itu ke AnimationPlayer.

Memiliki simpul akar yang lebih tipis yang menggunakan simpul anak "komponen" untuk mendorong perilakunya adalah sistem yang lemah dibandingkan. Memiliki node root yang sebagian besar kosong yang hanya mendelegasikan semua perilakunya ke node anak bertentangan dengan paradigma ini. Node anak menjadi ekstensi perilaku dari node root daripada objek mandiri yang menyelesaikan tugas dengan hak mereka sendiri. Ini sangat kikuk, dan desainnya dapat disederhanakan/ditingkatkan dengan memungkinkan kita untuk mengkonsolidasikan semua logika di simpul root, tetapi juga memungkinkan kita untuk memecah logika menjadi potongan yang berbeda dan dapat dikomposisi.

Saya kira topik Masalah ini lebih tentang bagaimana menangani masalah di atas daripada secara khusus tentang GDScript, namun saya percaya Sifat GDScript akan menjadi pendekatan paling sederhana dan paling mudah untuk memecahkan masalah.
)

Untuk yang tidak tahu, sifat pada dasarnya adalah cara memadukan dua kelas menjadi satu (cukup banyak mekanisme salin/tempel), hanya saja, daripada menyalin/menempel teks file secara harfiah, yang Anda lakukan hanyalah menggunakan pernyataan kata kunci untuk menghubungkan dua file. (Sunting: triknya adalah meskipun skrip hanya dapat mewarisi satu kelas, itu dapat mencakup banyak sifat)

Saya membayangkan sesuatu di mana file GDScript apa pun dapat digunakan sebagai sifat untuk file GDScript lain, selama tipe yang dicirikan memperluas kelas yang diwarisi oleh skrip yang digabungkan, yaitu GDScript yang memperluas Sprite tidak dapat menggunakan Resource GDScript sebagai sifat, tetapi dapat menggunakan GDScript Node2D. Saya akan membayangkan sintaks yang mirip dengan ini:

# 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")

Saya dapat melihat dua cara untuk melakukan ini:

  1. Pra-parsing skrip melalui RegEx untuk "^trait \proses isi ulang). Kami harus tidak mendukung penimbunan sifat atau terus-menerus memeriksa kembali kode sumber yang dihasilkan setelah setiap iterasi untuk melihat apakah lebih banyak penyisipan sifat telah dibuat.
  2. Parsing skrip secara normal, tetapi ajari pengurai untuk mengenali kata kunci, memuat skrip yang dirujuk, mengurai skrip ITU, dan kemudian menambahkan konten ClassNode ke ClassNode yang dihasilkan dari skrip saat ini (secara efektif mengambil hasil parsing dari satu skrip dan menambahkannya ke hasil parsing dari skrip lain). Ini akan secara otomatis mendukung persarangan tipe yang dicirikan.

Di sisi lain, orang mungkin ingin sifat GDScript memiliki nama tetapi mungkin TIDAK ingin class_name GDScript itu muncul di CreateDialog (karena itu tidak dimaksudkan untuk dibuat sendiri). Dalam hal ini, mungkin BUKAN ide yang baik untuk membuat skrip apa pun mendukungnya; hanya yang ditandai secara khusus (mungkin dengan menulis 'sifat' di bagian atas file?). Bagaimanapun, hal-hal untuk dipikirkan.

Pikiran?

Sunting: Setelah beberapa pertimbangan, saya percaya opsi 2 akan jauh lebih baik karena 1) kami akan tahu dari MANA skrip segmen skrip berasal (untuk pelaporan kesalahan yang lebih baik) dan 2) kami akan dapat mengidentifikasi kesalahan saat itu terjadi sejak skrip yang disertakan harus diuraikan secara berurutan, bukan hanya menguraikan semuanya di akhir. Ini akan mengurangi waktu pemrosesan yang ditambahkan ke proses parsing.

archived discussion feature proposal gdscript

Komentar yang paling membantu

@aaronfranke Traits , pada dasarnya sama dengan Mixins , memiliki kasus penggunaan yang sama sekali berbeda dari antarmuka justru karena mereka menyertakan implementasi metode. Jika sebuah antarmuka memberikan implementasi default, maka itu tidak akan benar-benar menjadi antarmuka lagi.

Sifat/Mixin ada di PHP, Ruby, D, Rust, Haxe, Scala, dan banyak bahasa lainnya (sebagaimana dirinci dalam Wiki tertaut), jadi mereka seharusnya sudah sangat akrab dengan orang-orang yang memiliki repertoar luas keakraban bahasa pemrograman.

Jika kita mengimplementasikan antarmuka (yang juga tidak saya lawan, terutama dengan pengetikan statis opsional yang akan datang), itu akan secara efektif hanya menjadi cara menentukan tanda tangan fungsi dan kemudian mengharuskan skrip GDScript yang relevan mengimplementasikan tanda tangan fungsi tersebut, dengan ciri-ciri disertakan (jika ada pada saat itu).

Semua 93 komentar

Apa untungnya daripada: extends "res://move_right_trait.gd"

@MrJustreborn Karena Anda dapat memiliki banyak sifat dalam satu kelas, tetapi Anda hanya dapat mewarisi satu skrip.

Jika saya mengerti dengan benar, ini pada dasarnya adalah apa yang disebut C# " antarmuka ", tetapi dengan metode non-abstrak? Mungkin lebih baik untuk memanggil antarmuka fitur daripada ciri-ciri yang akrab bagi programmer.

@aaronfranke Traits , pada dasarnya sama dengan Mixins , memiliki kasus penggunaan yang sama sekali berbeda dari antarmuka justru karena mereka menyertakan implementasi metode. Jika sebuah antarmuka memberikan implementasi default, maka itu tidak akan benar-benar menjadi antarmuka lagi.

Sifat/Mixin ada di PHP, Ruby, D, Rust, Haxe, Scala, dan banyak bahasa lainnya (sebagaimana dirinci dalam Wiki tertaut), jadi mereka seharusnya sudah sangat akrab dengan orang-orang yang memiliki repertoar luas keakraban bahasa pemrograman.

Jika kita mengimplementasikan antarmuka (yang juga tidak saya lawan, terutama dengan pengetikan statis opsional yang akan datang), itu akan secara efektif hanya menjadi cara menentukan tanda tangan fungsi dan kemudian mengharuskan skrip GDScript yang relevan mengimplementasikan tanda tangan fungsi tersebut, dengan ciri-ciri disertakan (jika ada pada saat itu).

Mungkin kata kunci seperti includes ?

extends Node2D
includes TraitClass

Meskipun nama lain seperti sifat, mixin, has, dll. pasti bagus juga.

Saya juga menyukai ide memiliki beberapa opsi untuk mengecualikan tipe class_name dari menu add. Itu bisa menjadi sangat berantakan dengan tipe kecil yang tidak berfungsi sendiri sebagai node.

Bahkan mungkin hanya menjadi topik fitur tersendiri.

(Tidak sengaja menghapus komentar saya, woops! Juga, wajib "mengapa Anda tidak mengizinkan beberapa skrip saja, kesatuan yang melakukannya" )

Bagaimana ini akan bekerja di VisualScript, jika ada?

Juga, dapatkah bermanfaat untuk menyertakan antarmuka inspektur untuk sifat, jika sifat diterapkan? Saya membayangkan beberapa kasus penggunaan untuk sifat dapat mencakup kasus penggunaan di mana hanya ada sifat dan tidak ada skrip (setidaknya, tidak ada skrip selain yang menyertakan file sifat). Meskipun, setelah memikirkannya lebih lanjut, saya bertanya-tanya apakah upaya yang dilakukan untuk membuat antarmuka seperti itu mungkin sepadan, dibandingkan dengan hanya membuat skrip yang menyertakan file sifat.

@LikeLakers2

Bagaimana ini akan bekerja di VisualScript, jika ada?

Jika dilakukan seperti yang saya sarankan, itu tidak akan terjadi untuk VisualScript sama sekali. GDScript saja. Sistem sifat apa pun yang diterapkan untuk VisualScript akan dirancang sepenuhnya berbeda karena VisualScript bukan bahasa yang diuraikan. Tidak menutup kemungkinan sama sekali (hanya perlu diterapkan secara berbeda). Plus, mungkin kita harus mempertimbangkan untuk mendapatkan dukungan pewarisan VisualScript terlebih dahulu? tertawa terbahak-bahak

Juga, dapatkah bermanfaat untuk menyertakan antarmuka inspektur untuk sifat, jika sifat diterapkan?

Tidak akan ada gunanya. Ciri-ciri hanya memberikan detail ke GDScript, menyerahkan properti, konstanta, sinyal, dan metode yang ditentukan oleh sifat.

Saya membayangkan beberapa kasus penggunaan untuk sifat dapat mencakup kasus penggunaan di mana hanya ada sifat dan tidak ada skrip (setidaknya, tidak ada skrip selain yang menyertakan file sifat).

Sifat, seperti yang direpresentasikan dalam bahasa lain, tidak pernah dapat digunakan secara terpisah, tetapi harus disertakan dalam skrip lain agar dapat digunakan.

Saya ingin tahu apakah upaya yang dilakukan untuk membuat antarmuka seperti itu mungkin sepadan

Membuat antarmuka Inspektur dalam beberapa hal tidak terlalu masuk akal untuk GDScript saja. Menambah atau menghapus suatu sifat akan melibatkan pengeditan langsung kode sumber dari properti source_code sumber daya Skrip, yaitu itu bukan properti pada Skrip itu sendiri. Oleh karena itu, baik...

  1. editor harus diajari bagaimana menangani secara khusus pengeditan kode sumber yang tepat untuk file GDScript untuk melakukan ini (rawan kesalahan), atau...
  2. semua Skrip harus mendukung sifat sehingga GDScriptLanguage dapat menyediakan proses internalnya sendiri untuk menambah dan menghapus sifat (tetapi tidak semua bahasa DO mendukung sifat, sehingga properti tidak akan berarti dalam semua kasus).

Apa perlunya fitur seperti itu? Apakah ada hal yang memungkinkan yang tidak dapat Anda lakukan sekarang? Atau apakah itu membuat beberapa tugas secara signifikan lebih cepat untuk ditangani?

Saya lebih suka membuat GDscript sebagai bahasa yang sederhana daripada menambahkan fitur kompleks yang hampir tidak pernah digunakan.

Ini memecahkan masalah Child-Nodes-As-Script-Dependencies yang bermasalah dengan orang ini , tetapi tidak datang dengan jenis bagasi yang sama dengan yang dimiliki MultiScript karena dibatasi pada satu bahasa. Modul GDScript dapat mengisolasi logika tentang bagaimana ciri-ciri berhubungan satu sama lain dan skrip utama sedangkan menyelesaikan perbedaan antara bahasa yang berbeda akan jauh lebih rumit.

Dengan tidak adanya banyak impor/warisan berganda, dependensi anak-simpul-sebagai-skrip adalah satu-satunya cara untuk menghindari pengulangan kode BANYAK, dan ini pasti akan menyelesaikan masalah dengan cara yang baik.

@groud @ Zireael07 Maksud saya, pendekatan lintas bahasa yang lebih radikal adalah 1) mendesain ulang sepenuhnya Objek untuk menggunakan ScriptStack untuk menggabungkan skrip yang ditumpuk menjadi representasi skrip tunggal, 2) memperkenalkan kembali MultiScript dan membuat dukungan editor yang secara otomatis mengonversi penambahan skrip ke dalam multiskrip (atau langsung membuat semua skrip menjadi multiskrip demi kesederhanaan, dalam hal ini implementasi MultiScript pada dasarnya adalah ScriptStack kami), atau 3) menerapkan semacam sistem sifat lintas bahasa untuk jenis Objek yang dapat digabungkan Skrip yang memperluas referensi sebagai ciri, menggabungkan kontennya seperti skrip pada umumnya. Semua opsi itu jauh lebih invasif ke mesin. Ini membuat semuanya lebih sederhana.

saya tidak berpikir sifat itu perlu. yang paling kita butuhkan adalah siklus pelepasan mesin yang cepat. maksud saya kita perlu membuat mesin lebih fleksibel untuk menambahkan fitur baru sesederhana hanya menambahkan dll baru atau lebih file dan mesin otomatis terintegrasi dengan itu sendiri, seperti gaya plugin di sebagian besar IDE. misalnya saya sangat membutuhkan websocket untuk bekerja, saya tidak perlu menunggu sampai 3.1 untuk rilis. 3.1 terlalu rusak sekarang dengan begitu banyak bug. akan sangat bagus jika kita memiliki fitur ini. kelas baru dapat secara otomatis menyuntikkan ke GDScript dari .dll atau .so acak di beberapa jalur. saya tidak tahu berapa banyak upaya yang harus dilakukan di c++ tetapi saya harap ini tidak terlalu sulit 😁

@fian46 Nah, jika seseorang telah mengimplementasikan soket web sebagai plugin GDNative yang dapat diunduh, maka ya, apa yang Anda gambarkan adalah alur kerjanya. Sebagai gantinya, mereka memilih untuk menjadikannya fitur terintegrasi yang tersedia di mesin vanilla. Tidak ada yang menghentikan orang untuk membuat fitur seperti itu, jadi poin Anda benar-benar tidak terkait dengan topik Masalah ini.

oops saya tidak tahu GDNative ada 😂😂😂. Trait memang luar biasa tetapi apakah lebih mudah untuk membuat kelas sifat palsu dan mencontohnya dan kemudian memanggil fungsi seperti skrip dasar?

Jika skrip Godot adalah kelas yang tidak disebutkan namanya, mengapa tidak membuat "move_right_trait.gd" di "my_sprite.gd" ?
Maaf atas ketidaktahuan saya jika saya tidak mengerti masalah ini.

Saya mengerti penggunaan ciri-ciri dalam bahasa yang diketik lebih kuat seperti Rust atau (antarmuka dalam) C++, tetapi dalam bahasa yang diketik merunduk bukankah itu sedikit tidak perlu? Cukup mengimplementasikan fungsi yang sama akan memungkinkan Anda untuk mencapai antarmuka yang seragam di antara tipe Anda. Saya kira saya agak tidak yakin apa masalah sebenarnya dengan cara GDScript menangani antarmuka atau bagaimana sistem sifat akan sangat membantu.

Tidak bisakah Anda juga menggunakan preload("Some-other-behavior.gd") dan menyimpan hasilnya dalam variabel untuk mencapai efek yang pada dasarnya sama?

@fian46 @DriNeo Ya, ya dan tidak. Memuat skrip dan menggunakan kelas skrip sudah mengatasinya, tetapi masalahnya melampaui itu.

@TheYokai

Menerapkan fungsi yang sama akan memungkinkan Anda untuk mencapai antarmuka yang seragam di antara tipe Anda

Masalahnya bukanlah mencapai antarmuka yang seragam yang Anda benar, mengetik bebek memecahkan dengan baik, melainkan mengatur (menggabungkan/menukar) kelompok implementasi terkait secara efisien.


Di 3.1, dengan kelas skrip, Anda dapat mendefinisikan fungsi statis pada skrip Referensi (atau jenis apa pun sebenarnya) dan kemudian menggunakan skrip itu sebagai namespace untuk mengakses fungsi tersebut secara global di 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!")

Namun, ketika menyangkut konten non-statis, terutama hal-hal yang menggunakan fungsi khusus node, sulit untuk membagi logika seseorang.

Misalnya, bagaimana jika saya memiliki KinematicBody2D dan saya ingin memiliki perilaku "Lompat" dan "Lari"? Setiap perilaku ini memerlukan akses ke penanganan input dan fitur move_and_slide dari KinematicBody2D. Idealnya, saya dapat menukar implementasi setiap perilaku secara independen dan menyimpan semua kode untuk setiap perilaku dalam skrip terpisah.

Saat ini, semua alur kerja yang saya kenal untuk ini tidak optimal.

  1. Jika Anda menyimpan semua implementasi dalam skrip yang sama dan hanya menukar fungsi mana yang sedang digunakan...

    1. mengubah "perilaku" mungkin melibatkan pertukaran beberapa fungsi sebagai satu set, sehingga Anda tidak dapat mengelompokkan perubahan implementasi secara efektif.

    2. Semua fungsi untuk setiap perilaku (X * Y) ada di skrip tunggal Anda, sehingga bisa membengkak dengan sangat cepat.

  2. Anda bisa mengganti seluruh skrip, tetapi itu berarti Anda harus membuat skrip baru untuk setiap kombinasi perilaku ditambah logika apa pun yang menggunakan perilaku tersebut.
  3. Jika Anda menggunakan node anak sebagai dependensi skrip, maka itu berarti Anda akan memiliki node Node2D "komponen" aneh ini yang mengambil induknya dan memanggil metode move_and_slide UNTUK itu yang agak tidak wajar, secara relatif.

    • Anda harus membuat asumsi bahwa orang tua Anda akan menerapkan metode itu atau melakukan logika untuk memeriksa apakah ia memiliki metode tersebut. Dan jika Anda melakukan pemeriksaan, maka Anda dapat gagal secara diam-diam dan berpotensi memiliki bug diam dalam gim Anda atau Anda dapat mengubahnya menjadi skrip alat yang tidak perlu hanya agar Anda dapat menyetel peringatan konfigurasi pada simpul untuk memberi tahu Anda secara visual di editor bahwa ada masalah.

    • Anda juga tidak mendapatkan penyelesaian kode yang tepat untuk operasi node yang dimaksudkan karena node tersebut berasal dari Node2D dan intinya adalah untuk mendorong perilaku induk KinematicBody2D.

Sekarang, saya akui bahwa opsi 3 adalah alur kerja paling efektif saat ini, dan bahwa masalahnya sebagian besar dapat diselesaikan dengan menggunakan pengetikan statis yang praktis untuk GDScript di 3.1. Namun, ada masalah yang lebih mendasar yang sedang dimainkan.

Sistem node-scene Godot umumnya memiliki bentuk pengguna yang membuat node atau adegan yang melakukan pekerjaan tertentu, dalam sistem tertutup mereka sendiri. Anda dapat membuat instance node/adegan tersebut dalam adegan lain dan meminta mereka menghitung data yang kemudian digunakan oleh adegan induk (seperti halnya dengan hubungan Area2D dan CollisionShape2D).

Namun, penggunaan mesin vanilla dan rekomendasi praktik terbaik umum adalah untuk menjaga perilaku adegan Anda terkunci ke simpul akar dan/atau skripnya. Hampir tidak pernah Anda memiliki node "komponen perilaku" yang benar-benar memberi tahu root apa yang harus dilakukan (dan ketika itu ada, rasanya sangat kikuk). Node AnimationPlayer/Tween adalah satu-satunya pengecualian yang dapat diperdebatkan yang dapat saya pikirkan, tetapi bahkan operasi mereka diarahkan oleh root (ini secara efektif mendelegasikan kontrol kepada mereka untuk sementara). (Sunting: Bahkan dalam kasus ini, menganimasikan dan tweening bukanlah pekerjaan KinematicBody2D, jadi masuk akal bahwa tugas-tugas itu akan didelegasikan. Namun, gerakan, seperti berlari dan melompat adalah tanggung jawabnya) Lebih sederhana dan lebih alami untuk memungkinkan implementasi sifat untuk mengatur kode karena menjaga hubungan antara node secara ketat data-up/behavior-down dan membuat kode lebih terisolasi ke dalam file skripnya sendiri.

Eh, menandai diri Anda sebagai 'menerapkan antarmuka/sifat' juga harus memenuhi tes * is * , yang nyaman untuk menguji fungsionalitas sesuatu.

@OvermindDL1 Maksud saya, saya memberi contoh melakukan tes seperti itu, tetapi saya menggunakan in sebagai gantinya karena saya ingin membedakan antara pewarisan dan penggunaan sifat.

Saya kira saya agak masuk ke masalah XY di sini, saya buruk. Saya baru saja datang dari 2 masalah lain (#23052, #15996) yang membahas topik ini dalam satu atau lain cara dan berpikir saya akan mengajukan proposal, tetapi saya tidak benar-benar memberikan semua konteksnya.

@groud solusi ini akan menyelesaikan salah satu masalah yang diajukan terhadap #19486.

@willnationsdev ide bagus, saya menantikannya!

Dari pemahaman saya yang terbatas, hal yang ingin dicapai oleh sistem Sifat ini adalah mengaktifkan sesuatu yang mirip dengan alur kerja yang ditunjukkan dalam video ini: https://www.youtube.com/watch?v=raQ3iHhE_Kk
(Perhatikan, saya sedang berbicara tentang _workflow_ yang ditampilkan, bukan fitur yang digunakan)

Dalam video tersebut, dibandingkan dengan jenis alur kerja lainnya, dengan kelebihan dan kekurangannya.

Setidaknya sepengetahuan saya, alur kerja semacam ini tidak mungkin di GDScript sekarang, karena cara kerja pewarisan.

@AfterRebelion Beberapa menit pertama dari video di mana ia mengisolasi modularitas, kemampuan edit, dan kemampuan debug dari basis kode (dan detail terkait dari atribut tersebut ) memang mengejar memiliki fitur ini.

Setidaknya sepengetahuan saya, alur kerja semacam ini tidak mungkin di GDScript sekarang, karena cara kerja pewarisan.

Sedikit ini tidak sepenuhnya benar, karena Godot sebenarnya melakukan ini dengan sangat baik dalam hal hierarki simpul dan merancang adegan. Adegan bersifat modular, properti dapat diekspor (dan bahkan dianimasikan) langsung dari editor tanpa pernah berurusan dengan kode, dan semuanya dapat diuji/di-debug secara terpisah karena adegan dapat dieksekusi secara independen.

Kesulitannya adalah ketika logika yang biasanya akan di-outsource ke node anak harus dieksekusi oleh node root karena logika bergantung pada fitur yang diwarisi dari node root. Dalam kasus ini, satu-satunya cara untuk menggunakan komposisi adalah membuat anak-anak mulai memberi tahu orang tua apa yang harus dilakukan daripada membuat mereka memikirkan urusan mereka sendiri sementara orang tua menggunakannya.

Ini bukan masalah di Unity karena GameObject tidak memiliki warisan nyata yang dapat dimanfaatkan pengguna. Di Unreal mungkin sedikit (?) masalah karena mereka memiliki hierarki internal berbasis simpul/komponen yang serupa untuk Aktor.

Oke, mari kita bermain Devil's Advocate di sini sebentar ( @MysteryGM , Anda mungkin mendapatkan tendangan dari ini). Menghabiskan waktu berpikir tentang bagaimana saya bisa menulis sistem seperti itu di Unreal dan itu memberi saya perspektif baru tentangnya. Maaf untuk orang-orang yang berpikir ini akan menjadi ide yang bagus / bersemangat tentang hal itu:

Memperkenalkan sistem sifat menambahkan lapisan kompleksitas ke GDScript sebagai bahasa yang mungkin membuatnya lebih sulit untuk dipelihara.

Selain itu, ciri sebagai fitur membuat lebih sulit untuk mengisolasi dari mana variabel, konstanta, sinyal, metode, dan bahkan subkelas sebenarnya berasal. Jika skrip Node Anda tiba-tiba memiliki 12 sifat yang berbeda, maka Anda belum tentu tahu dari mana semuanya berasal. Jika Anda melihat referensi ke sesuatu, maka Anda harus melihat melalui 12 file yang berbeda bahkan untuk mengetahui di mana sesuatu berada di basis kode.

Ini sebenarnya menghilangkan kemampuan debugg dari GDScript sebagai bahasa karena masalah tertentu mungkin mengharuskan Anda untuk memilih rata-rata 2 atau 3 lokasi berbeda di basis kode. Jika salah satu dari lokasi tersebut sulit ditemukan karena mereka mengatakan mereka berada di satu skrip tetapi sebenarnya terletak di tempat lain - dan jika keterbacaan kode tidak dengan jelas menyatakan hal mana yang bertanggung jawab atas data/logika - maka 2 atau 3 itu langkah-langkah dikalikan menjadi serangkaian langkah yang sangat besar dan penuh tekanan.

Peningkatan ukuran dan cakupan proyek memperbesar efek negatif ini lebih jauh dan membuat penggunaan sifat menjadi kualitas yang sangat tidak dapat dipertahankan.


Tapi apa yang bisa dilakukan untuk menyelesaikan masalah? Kami tidak ingin node "logika komponen" anak memberi tahu akar adegan apa yang harus dilakukan, tetapi kami juga tidak dapat mengandalkan pewarisan atau mengubah seluruh skrip untuk menyelesaikan masalah kami.

Nah, apa yang akan dilakukan mesin non-ECS dalam situasi ini? Komposisi masih menjadi jawabannya, tetapi dalam hal ini Node penuh tidak masuk akal jika diambil untuk menskalakan / memperumit dinamika hierarki kepemilikan. Sebagai gantinya, seseorang dapat mendefinisikan objek implementasi non-Node yang mengabstraksikan implementasi konkret dari suatu perilaku, tetapi semuanya masih dimiliki oleh node root. Ini dapat dilakukan dengan skrip 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(...)

Jika ekspor bekerja sedemikian rupa sehingga kami dapat mengeditnya di Inspector sebagai enum untuk kelas yang memperoleh tipe tertentu seperti yang kami dapat untuk instance Resource baru, maka itu akan keren. Satu-satunya cara untuk melakukannya sekarang adalah dengan memperbaiki ekspor skrip Sumber Daya dan kemudian membuat skrip implementasi Anda memperluas Sumber Daya (tanpa alasan lain). Meskipun, meminta mereka memperluas Resource akan menjadi ide yang bagus jika implementasi itu sendiri memerlukan parameter yang ingin Anda definisikan dari Inspector. :-)

Sekarang, apa yang akan membuat INI lebih mudah adalah memiliki sistem snippet atau sistem makro sehingga membuat bagian kode deklaratif yang digunakan kembali lebih mudah bagi pengembang.

Bagaimanapun, ya, saya pikir saya agak mengidentifikasi masalah mencolok dengan sistem sifat dan pendekatan yang lebih baik untuk memecahkan masalah. Hore untuk Masalah masalah XY! /S

Sunting:

Jadi, alur kerja contoh di atas akan melibatkan pengaturan skrip implementasi dan kemudian menggunakan instance skrip saat runtime untuk menentukan perilaku. Tetapi bagaimana jika implementasi itu sendiri memerlukan parameter yang ingin Anda atur secara statis dari Inspektur? Ini adalah versi yang didasarkan pada Sumber Daya sebagai gantinya.

# 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(...)

Terkait: #22660

@AfterRebelion

Mempertimbangkan, saya sedang berbicara tentang alur kerja yang ditampilkan, bukan fitur yang digunakan

Sungguh ironis bahwa, setelah Anda mengklarifikasi ini dan saya setuju dengan alur kerja yang optimal, dan kemudian tidak setuju dengan bagian komentar selanjutnya, saya kemudian menindaklanjutinya dengan mengatakan pada dasarnya bagaimana "fitur yang digunakan" dalam video itu sebenarnya adalah cara ideal untuk mengatasi masalah ini di Godot pula . Ha ha.

# 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
...

Wow, tidak tahu ekspor begitu kuat. Diharapkan hanya dapat berinteraksi dengan primitif, dan struktur data lainnya.

Ini membuat komentar saya sebelumnya tidak valid.
Seperti yang Anda katakan, jika beberapa jenis makro diimplementasikan untuk membuat implementasinya lebih mudah, itu akan menjadi cara terbaik untuk mengimplementasikan alur kerja itu tanpa perlu MultiScript. Mungkin tidak serbaguna seperti Unity, karena Anda masih perlu mendeklarasikan setiap skrip yang mungkin sebelumnya, tetapi masih merupakan opsi yang bagus.

@AfterRebelion

Seperti yang Anda katakan, jika beberapa jenis makro diimplementasikan untuk membuat implementasinya lebih mudah, itu akan menjadi cara terbaik untuk mengimplementasikan alur kerja itu tanpa perlu MultiScript.

Yah, pendekatan berbasis Resource - yang saya sebutkan di komentar yang sama, dikombinasikan dengan beberapa dukungan editor yang lebih baik dari #22660, akan membuat kualitasnya sebanding dengan apa yang dapat dilakukan Unity.

Mungkin tidak serbaguna seperti Unity, karena Anda masih perlu mendeklarasikan setiap skrip yang mungkin sebelumnya, tetapi masih merupakan opsi yang bagus.

Nah, jika mereka memperbaiki petunjuk tipe larik di 3.2, maka Anda dapat menentukan larik yang diekspor untuk jalur file yang harus memperluas skrip dan menambahkan skrip Anda sendiri secara efektif. Ini bahkan dapat dilakukan melalui plugin di 3.1 dengan menggunakan kelas EditorInspectorPlugin untuk menambahkan konten khusus ke Inspektur untuk sumber daya atau node tertentu.

Maksud saya, jika Anda menginginkan sistem seperti "Persatuan", maka Anda benar-benar AKAN memiliki sub-simpul yang memberi tahu root apa yang harus dilakukan dan Anda hanya perlu mengambilnya dengan merujuk pada namanya, tanpa mendeklarasikan atau menambahkannya secara manual dari skrip simpul akar. Metode Resource umumnya jauh lebih efektif dan mempertahankan basis kode yang lebih bersih.

Karena sistem sifat akan membebani kegunaan kode GDScript, berdasarkan alasan yang saya uraikan, saya akan melanjutkan dan menutup masalah ini. Perangkap penambahannya jauh lebih besar daripada manfaat yang relatif sedikit yang mungkin kita terima dari sistem semacam itu, dan manfaat yang sama dapat diimplementasikan dengan cara yang berbeda dengan kejelasan dan kegunaan yang lebih baik.

Yah, saya melewatkan diskusi ini saat saya keluar. Belum membaca semuanya, tetapi saya memiliki ide untuk menambahkan ciri ke GDScript untuk menyelesaikan masalah penggunaan kembali kode, yang menurut saya jauh lebih elegan dan jelas daripada pewarisan berganda palsu dalam melampirkan skrip ke tipe turunan. Meskipun apa yang akan saya lakukan adalah membuat file sifat tertentu, tidak mengizinkan file kelas apa pun menjadi sifat. Saya tidak berpikir itu akan terlalu buruk.

Tapi saya terbuka untuk saran untuk memecahkan masalah utama, yaitu menggunakan kembali kode dalam beberapa jenis simpul.

@vnen solusi yang saya buat yang merupakan item terakhir adalah mengalihdayakan bagian yang dapat digunakan kembali ke skrip Sumber Daya.

  • Mereka masih dapat diekspos dan propertinya diedit di Inspektur, sama seperti jika mereka adalah variabel anggota pada sebuah simpul.

  • Mereka mempertahankan jejak yang jelas dari mana data dan logika berasal, sesuatu yang dapat dengan mudah dikompromikan jika banyak ciri dimasukkan ke dalam satu skrip.

  • Mereka tidak menambahkan kompleksitas yang tidak semestinya ke GDScript sebagai bahasa. Misalnya, jika ada, kita harus menyelesaikan bagaimana kualitas bersama (properti, konstanta, sinyal) akan digabungkan ke dalam skrip utama (atau, jika tidak digabungkan, memaksa pengguna untuk menangani konflik ketergantungan).

  • Skrip sumber daya lebih baik karena dapat ditetapkan dan diubah dari Inspektur. Desainer, penulis, dll. akan dapat mengubah objek implementasi langsung dari editor.

@willnationsdev Saya mengerti (meskipun nama "skrip sumber daya" terdengar aneh, karena semua skrip adalah sumber daya). Masalah utama dengan solusi ini adalah bahwa itu tidak menyelesaikan sesuatu yang diharapkan orang dengan pendekatan pewarisan: menambahkan variabel dan sinyal yang diekspor ke simpul root dari adegan (khususnya ketika membuat adegan di tempat lain). Anda masih dapat mengedit variabel yang diekspor dari subsumber daya, tetapi itu menjadi kurang praktis (Anda tidak dapat mengetahui secara sekilas properti mana yang dapat Anda edit).

Masalah lainnya adalah kode boilerplate harus sering diulang. Anda juga harus memastikan skrip sumber daya benar-benar disetel sebelum memanggil fungsi.

Keuntungannya kita tidak perlu apa-apa, sudah tersedia. Saya kira ini harus didokumentasikan dan orang-orang yang menggunakan metode "lampirkan skrip ke simpul turunan" saat ini dapat mengomentari solusi untuk melihat seberapa layak itu.

@vnen

tetapi menjadi kurang praktis (Anda tidak dapat mengetahui secara sekilas properti mana yang dapat Anda edit).

Bisakah Anda menguraikan apa yang Anda maksud di sini? Saya tidak melihat bagaimana mungkin ada ketidakjelasan substansial mengenai properti mana yang dapat diakses, terutama jika sesuatu seperti #22660 digabungkan.

  1. Saya membuat contoh adegan, ingin tahu bagaimana saya bisa mengeditnya, dan jadi lihat skrip simpul akar adegan itu.
  2. Di dalam skrip, saya melihat ...

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

    • use FourWayMoveTrait

  3. Dengan asumsi kita memiliki sarana pelacakan klik pengenal (yang seharusnya benar-benar menjadi fitur 3.1.1!) untuk membuka skrip, kita akhirnya membuka skrip terkait dan dapat melihat propertinya.

Sepertinya jumlah langkah yang sama bagi saya, kecuali saya melewatkan sesuatu.

Lebih jauh lagi, properti mana yang dapat diedit sebenarnya bahkan lebih jelas dengan sumber daya yang saya katakan karena, jika suatu implementasi memiliki properti khusus untuknya, maka fakta bahwa data terkait dengan implementasi lebih jelas jika Anda harus mengawali mengaksesnya dengan contoh implementasi, yaitu move_impl.<property> .

Masalah lainnya adalah kode boilerplate harus sering diulang. Anda juga harus memastikan skrip sumber daya benar-benar disetel sebelum memanggil fungsi.

Ini benar, namun, menurut saya manfaat ekspor dengan inisialisasi lebih besar daripada biaya verbage tambahan:

("Rekan Tim Tingkat Tinggi", HLT, seperti desainer, penulis, seniman, dll.)

  • Seseorang dapat menetapkan nilai langsung dari Inspektur, daripada harus membuka skrip, menemukan baris yang benar untuk diubah, dan kemudian mengubahnya (sudah disebutkan, tetapi mengarah ke...).

  • Seseorang dapat menentukan bahwa konten yang diekspor memiliki persyaratan tipe dasar. Inspektur kemudian dapat memberikan daftar enumerasi dari implementasi yang diizinkan secara otomatis. HLT kemudian dapat dengan aman menetapkan hanya turunan dari jenis itu. Ini membantu melindungi mereka dari alternatif kebutuhan untuk mengetahui konsekuensi dari semua skrip sifat berbeda yang beredar. Kita juga harus memodifikasi pelengkapan otomatis di GDScript untuk mendukung pencarian file sifat bernama dan tidak bernama sebagai tanggapan untuk melihat kata kunci use .

  • Seseorang dapat membuat serial konfigurasi implementasi sebagai file *.tres. HLT kemudian dapat menarik dan melepaskannya dari dok FileSystem atau bahkan membuat haknya sendiri di Inspektur. Jika seseorang ingin melakukan hal yang sama dengan sifat, mereka harus membuat sifat turunan yang menyediakan konstruktor khusus untuk menggantikan yang default. Kemudian mereka akan menggunakan sifat itu sebagai "prakonfigurasi" melalui konstruktor yang dikodekan secara imperatif.

    1. Lebih lemah karena imperatif daripada deklaratif.
    2. Lebih lemah karena konstruktor harus didefinisikan secara eksplisit dalam skrip.
    3. Jika sifat tidak disebutkan namanya, maka pengguna perlu mengetahui di mana sifat itu berada agar dapat menggunakannya dengan benar alih-alih sifat dasar default. Jika sifat tersebut diberi nama, maka itu akan menyumbat namespace global yang tidak perlu.
    4. Jika mereka mengubah skrip menjadi use FourWayMoveTrait alih-alih use MoveTrait , tidak ada lagi indikasi yang bertahan lama bahwa skrip tersebut bahkan kompatibel dengan basis MoveTrait . Ini memungkinkan kebingungan HLT apakah FourWayMoveTrait bahkan dapat berubah ke MoveTrait yang berbeda tanpa merusak sesuatu.
    5. Jika HLT membuat implementasi sifat baru dengan cara ini, mereka tidak perlu mengetahui semua properti yang dapat/perlu ditetapkan dari sifat dasar. Ini bukan masalah dengan sumber daya yang dibuat di Inspektur.
  • Seseorang bahkan dapat memiliki beberapa Sumber Daya dengan jenis yang sama (jika ada alasan untuk itu). Suatu sifat tidak akan mendukung ini, tetapi sebaliknya akan memicu konflik penguraian.

  • Seseorang dapat mengubah konfigurasi dan/atau nilai individualnya tanpa harus meninggalkan Area Pandang 2D/3D. Ini jauh lebih nyaman untuk HLT (saya tahu banyak yang langsung merasa kesal ketika mereka harus melihat kode sama sekali ).

Dengan semua itu, saya setuju bahwa boilerplate itu menjengkelkan. Untuk mengatasinya, saya lebih suka menambahkan sistem makro atau cuplikan untuk menyederhanakannya. Sistem snippet akan menjadi ide yang bagus karena dapat mendukung bahasa apa pun yang dapat diedit di ScriptEditor, daripada hanya GDScript saja.

Tentang:

tetapi menjadi kurang praktis (Anda tidak dapat mengetahui secara sekilas properti mana yang dapat Anda edit).

Saya sedang berbicara tentang inspektur, jadi maksud saya "HLT" di sini. Orang yang tidak mau melihat kodenya. Dengan ciri-ciri, kami dapat menambahkan properti baru yang diekspor ke skrip. Dengan skrip sumber daya, Anda hanya dapat mengekspor variabel ke sumber daya itu sendiri, sehingga variabel tidak akan ditampilkan di inspektur kecuali Anda mengedit subsumber daya.

Saya mengerti argumen Anda tetapi itu melampaui masalah aslinya: hindari kode berulang (tanpa warisan). Sifat adalah untuk programmer. Dalam banyak kasus, hanya ada satu implementasi yang ingin Anda gunakan kembali, dan Anda tidak ingin mengeksposnya di inspektur. Tentu saja Anda masih dapat menggunakan skrip sumber daya tanpa mengekspor hanya dengan menetapkan langsung dalam kode, tetapi masih tidak menyelesaikan masalah penggunaan kembali variabel dan sinyal yang diekspor dari implementasi umum, yang merupakan salah satu alasan utama orang mencoba untuk menggunakan warisan.

Artinya, orang-orang saat ini mencoba menggunakan pewarisan dari skrip generik tidak hanya untuk fungsi, tetapi juga untuk properti yang diekspor (yang biasanya terkait dengan fungsi tersebut) dan sinyal (yang kemudian dapat dihubungkan dengan UI).

Ini adalah masalah yang sulit untuk dipecahkan. Ada beberapa cara untuk melakukannya dengan GDScript saja, tetapi sekali lagi mereka membutuhkan kode boilerplate untuk disalin.

Saya hanya bisa membayangkan bolak-balik yang harus dilakukan agar skrip eksternal berperilaku seolah-olah ditulis langsung di skrip utama itu sendiri.

Alangkah indahnya jika langit dan bumi tidak harus tergerak untuk mencapainya. X)

@vnen Saya mengerti apa yang Anda katakan sekarang. Yah, karena sepertinya masih ada kehidupan dalam Edisi ini, saya akan membukanya kembali ketika saya mendapatkan kesempatan berikutnya.

Ada berita tentang pembukaan kembali ini? Sekarang GodotCon 2019 diumumkan, dan Godot Sprint adalah sesuatu, mungkin ini layak untuk dibicarakan di sana.

@AfterRebelion saya baru saja lupa untuk kembali dan membukanya kembali. Terima kasih telah mengingatkan saya. XD

@willnationsdev Saya menyukai apa yang saya baca tentang EditorInspectorPlugin! pertanyaan singkat, ini berarti saya dapat membuat inspektur khusus untuk tipe data... misalnya... menambahkan tombol ke inspektur.
(sudah cukup lama sejak saya ingin melakukan ini, memiliki cara untuk memicu peristiwa untuk tujuan debugging dengan tombol di inspektur saya bisa membuat tombol tekan mengeksekusi beberapa skrip)

@xDGameStudios Yup, itu dan banyak lagi semuanya mungkin. Kontrol kustom apa pun dapat ditambahkan ke Inspector, baik di bagian atas, di bagian bawah, di atas properti, atau di bawah kategori.

@willnationsdev Saya tidak tahu apakah saya dapat menghubungi Anda melalui pesan pribadi!! Tapi saya ingin tahu lebih banyak tentang EditorInspectorPlugin (seperti beberapa kode contoh) .. sesuatu di baris jenis sumber daya khusus (misalnya) MyResource yang memiliki "nama" ekspor properti dan tombol inspektur yang mencetak "nama" variabel jika saya menekannya (di editor atau selama debugging)! Dokumentasi kurang banyak waktu dalam hal ini... Saya akan menulis dokumentasi sendiri jika saya tahu bagaimana menggunakan ini! :D terima kasih

Saya juga ingin tahu lebih banyak tentangnya. X)

Jadi apakah ini sama dengan skrip autoload dengan subclass yang berisi fungsi statis?

misalnya kasus Anda akan menjadi Traits.MoveRightTrait.move_right()

atau bahkan lebih sederhana, memiliki skrip autoload yang berbeda untuk kelas sifat yang berbeda:

Movement.move_right()

Tidak, ciri-ciri adalah fitur khusus bahasa yang setara dengan menyalin-menempelkan kode sumber dari satu skrip ke skrip lain (agak seperti penggabungan file). Jadi jika saya memiliki sifat dengan move_right() , dan saya kemudian menyatakan bahwa skrip kedua saya menggunakan sifat itu, maka itu juga dapat menggunakan move_right() , bahkan jika itu tidak statis dan bahkan jika itu mengakses properti di tempat lain di kelas. Itu hanya akan menghasilkan kesalahan penguraian jika properti tidak ada di skrip kedua.

Saya telah menemukan bahwa saya memiliki kode duplikat (fungsi yang lebih kecil, misalnya membuat busur) atau node yang berlebihan karena saya tidak dapat memiliki banyak pewarisan.

Ini akan luar biasa, saya mendapati diri saya harus membuat skrip dengan fungsi yang sama persis hanya digunakan pada jenis simpul yang berbeda menyebabkan extend s yang berbeda jadi pada dasarnya hanya untuk satu baris kode. Omong-omong, jika ada yang tahu cara membuatnya dengan sistem saat ini, beri tahu saya.

Jika saya memiliki fungsionalitas untuk skrip dengan extends Node , apakah ada cara untuk melampirkan perilaku yang sama ke tipe simpul lain tanpa harus menduplikasi file sumber dan mengganti dengan extend yang sesuai?

Ada kemajuan dalam hal itu? Saya tetap harus menggandakan kode atau harus menambahkan node, seperti yang saya katakan sebelumnya. Saya tahu itu tidak akan dilakukan di 3.1, tetapi mungkin bertujuan untuk 3.2?

Oh, saya belum mengerjakan ini sama sekali. Faktanya, saya lebih jauh dalam implementasi metode ekstensi saya daripada ini. Saya perlu berbicara dengan vnen tentang keduanya karena saya ingin mengerjakan detail sintaks/implementasi terbaik dengannya.

Sunting: jika orang lain ingin mencoba menerapkan sifat-sifat, mereka dipersilakan untuk terjun ke dalamnya. Hanya perlu berkoordinasi dengan para pengembang.

"implementasi metode ekstensi"?

@Zireael07 #15586
Ini memungkinkan orang untuk menulis skrip yang dapat menambahkan fungsi "bawaan" baru untuk kelas mesin. Interpretasi saya tentang sintaks akan menjadi seperti ini:

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

Kemudian, di tempat lain, saya hanya bisa melakukan:

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

Diam-diam akan membuat panggilan ke skrip ekstensi yang dimuat secara konstan dan memanggil fungsi statis dengan nama yang sama yang terikat ke kelas Array, meneruskan instance sebagai parameter pertama.

Saya sudah berbicara dengan @vnen dan @jahd2602 tentang ini. Satu hal yang terlintas dalam pikiran adalah solusi Jai untuk polimorfisme: mengimpor namespace properti.

Dengan cara ini, Anda dapat melakukan sesuatu seperti berikut:

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()"

Intinya adalah bahwa kata kunci using mengimpor ruang nama properti, sehingga b.foo() benar-benar hanya gula sintaksis untuk b.a.foo() .

Dan kemudian, memastikan bahwa b is A == true dan B dapat digunakan dalam situasi yang menerima A juga.

Ini juga memiliki manfaat bahwa hal-hal tidak harus dinyatakan sebagai sifat, ini akan bekerja untuk apa pun yang tidak memiliki nama kualitas umum.

Satu masalah adalah bahwa ini tidak cocok dengan sistem pewarisan saat ini. Jika A dan B keduanya adalah Node2D dan kita membuat sebuah fungsi di A: func baz(): print(self.position) , posisi manakah yang akan tercetak saat kita memanggil b.baz() ?
Salah satu solusinya adalah meminta penelepon menentukan self . Memanggil b.foo() akan memanggil foo() dengan b sebagai self dan bafoo() akan memanggil foo() dengan a sebagai self.

Jika kita memiliki metode yang berdiri sendiri seperti Python (di mana x.f(y) adalah gula untuk f(x,y) ), ini bisa sangat mudah diterapkan.

Ide lain yang tidak terkait:

Fokus hanya pada fungsi yang berdiri sendiri, gaya JavaScript.

Jika kita mengadopsi konvensi x.f(y) == f(x,y) untuk fungsi statis, kita dapat dengan mudah memiliki yang berikut:

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))

Ini akan berdampak minimal pada sistem kelas, karena itu benar-benar hanya memengaruhi metode. Namun, jika kita benar- benar ingin ini fleksibel, kita bisa menggunakan JavaScript penuh dan hanya mengizinkan definisi fungsi untuk ditetapkan, nilai yang dapat dipanggil.

@jabcross Kedengarannya bagus, saya suka konsep semacam namespace opsional, dan ide fungsinya menarik.

Mengenai namespace satu, saya bertanya-tanya mengapa tidak using A , hal-hal deklaratif lainnya tampak asing.

Penasaran juga bagaimana hal itu perlu diselesaikan dengan multiple inheritance. Saya kira opsi A memaksa kedua skrip untuk mewarisi tipe yang sama, jadi keduanya hanya diperluas di atas kelas yang sama, tanpa penggabungan khusus.

Opsi B, mungkin kata kunci GDScript tambahan untuk menentukan kelas sifat, dan dari kelas mana Anda ingin mendapatkan petunjuk. Itu akan menjadi ide yang sama, tetapi hanya langkah tambahan untuk tampil lebih eksplisit.

Di bagian atas A.gd:

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

Ohhh, saya sangat menyukai konsep import namespace. Ini tidak hanya memecahkan masalah sifat, tetapi juga berpotensi konsep "metode ekstensi" untuk menambahkan konten ke jenis mesin.

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 Jika kita kemudian juga menambahkan lambas dan/atau mengizinkan objek untuk mengimplementasikan operator panggilan (dan memiliki tipe callable untuk nilai yang kompatibel), maka kita dapat mulai menambahkan pendekatan berorientasi fungsional ke kode GDScript (yang Saya pikir akan menjadi ide bagus). Memang, berjalan lebih ke wilayah # 18698 @vnen pada saat itu, tapi...

Kami harus mempertimbangkan bahwa GDScript masih merupakan bahasa yang dinamis dan beberapa saran tersebut memerlukan pemeriksaan runtime untuk mengirimkan panggilan dengan benar, menambahkan penalti kinerja (bahkan untuk orang yang tidak menggunakan fitur, karena perlu memeriksa semua panggilan fungsi dan mungkin juga pencarian properti). Itu juga mengapa saya tidak yakin apakah menambahkan ekstensi adalah ide yang bagus (terutama karena pada dasarnya adalah gula sintaksis).

Saya lebih suka sistem sifat murni, di mana ciri-ciri bukan kelas tetapi sesuatu dari mereka sendiri. Dengan cara ini mereka dapat sepenuhnya diselesaikan dalam waktu kompilasi, dan memberikan pesan kesalahan dalam kasus konflik nama. Saya percaya ini akan menyelesaikan masalah tanpa tambahan runtime overhead.

@vnen Ahhh, tidak menyadari itu tentang biaya runtime. Dan jika itu berlaku untuk implementasi metode ekstensi apa pun, maka saya kira itu juga tidak ideal.

Jika kami melakukan sistem sifat murni, apakah Anda berpikir hanya membuat trait TraitName sebagai ganti extends dipasangkan dengan using TraitName di bawah ekstensi di skrip lain? Dan apakah Anda akan menerapkannya sendiri, atau akan didelegasikan?

Jika kami melakukan sistem sifat murni, apakah Anda berpikir hanya membuat trait TraitName sebagai ganti extends dipasangkan dengan using TraitName di bawah ekstensi di skrip lain?

Itu ide saya. Saya percaya ini cukup sederhana dan mencakup hampir semua kasus penggunaan untuk penggunaan kembali kode. Saya bahkan akan mengizinkan kelas menggunakan sifat untuk mengganti metode sifat (jika mungkin dilakukan pada waktu kompilasi). Sifat juga bisa memperluas sifat lainnya.

Dan apakah Anda akan menerapkannya sendiri, atau akan didelegasikan?

Saya tidak keberatan memberikan tugas itu kepada orang lain yang mau melakukannya. Lagipula aku kekurangan waktu. Tapi kita harus menyepakati desain terlebih dahulu. Saya cukup fleksibel dengan detailnya tetapi seharusnya 1) tidak dikenakan dalam pemeriksaan runtime (percaya bahwa GDScript cocok untuk hal-hal yang tidak mungkin diketahui saat kompilasi), 2) relatif sederhana, dan 3) tidak menambahkan terlalu banyak waktu kompilasi.

@vnen Menyukai ide-ide ini. Bertanya-tanya bagaimana Anda membayangkan suatu sifat akan dapat melakukan hal-hal seperti pelengkapan otomatis untuk kelas yang akan menyertakannya, atau apakah itu tidak mungkin?

Bertanya-tanya bagaimana Anda membayangkan suatu sifat akan dapat melakukan hal-hal seperti pelengkapan otomatis untuk kelas yang akan menyertakannya, atau apakah itu tidak mungkin?

Suatu sifat pada dasarnya akan menjadi "impor" dalam pandangan saya. Seharusnya sepele untuk menunjukkan penyelesaian, dengan asumsi bahwa penyelesaian untuk anggota bekerja.

@vnen Saya akan membayangkan bahwa Anda pada dasarnya akan mem-parsing ke ClassNode dengan flag trait di atasnya. Dan kemudian jika Anda melakukan pernyataan using , itu akan mencoba menggabungkan semua properti/metode/sinyal/konstanta/subkelas ke dalam skrip saat ini.

  1. Jika suatu metode bertabrakan, maka implementasi skrip saat ini akan menimpa metode dasar, seperti halnya menimpa metode yang diwarisi.

    • Tetapi apa yang harus dilakukan jika kelas dasar sudah memiliki metode "gabungan"?

  2. Jika properti, sinyal, dan konstanta tumpang tindih, periksa apakah itu jenis / tanda tangan sinyal yang sama. Jika tidak ada ketidakcocokan, maka anggap saja itu sebagai "penggabungan" properti/sinyal/konstanta. Jika tidak, beri tahu pengguna tentang konflik jenis/tanda tangan (kesalahan penguraian).

Ide buruk? Atau haruskah kita langsung melarang konflik non-metode? Bagaimana dengan subclass? Saya punya perasaan kita harus membuat itu menjadi konflik.

@willnationsdev Kedengarannya seperti "masalah berlian" (alias "berlian kematian yang mematikan"), ambiguitas yang terdokumentasi dengan baik dengan berbagai solusi yang sudah diterapkan dalam berbagai bahasa pemrograman populer.

Yang mengingatkan saya:
@vnen apakah ciri-ciri akan dapat memperluas sifat-sifat lain?

@jahd2602 Dia sudah menyarankan itu sebagai kemungkinan

Sifat juga bisa memperluas sifat lainnya.

@jahd2602 Berdasarkan solusi Perl/Python, sepertinya mereka pada dasarnya membentuk "tumpukan" lapisan yang berisi konten untuk setiap kelas sehingga konflik dari sifat yang terakhir digunakan berada di atas, dan menimpa, versi lainnya. Kedengarannya seperti solusi yang cukup bagus untuk skenario ini. Kecuali Anda atau @vnen memiliki pemikiran alternatif. Terima kasih atas ikhtisar terkait solusi jahd.

Beberapa pertanyaan.

Pertama: Dengan cara apa kita harus mendukung pernyataan using?

Saya berpikir bahwa pernyataan using harus memerlukan beberapa GDScript nilai konstan.

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

Saya memikirkan semua hal di atas.

Kedua: Apa yang harus kita katakan sintaks yang diizinkan untuk mendefinisikan suatu sifat dan/atau namanya seharusnya?

Seseorang mungkin tidak selalu ingin menggunakan kelas skrip untuk sifat mereka, jadi saya tidak berpikir kita harus menerapkan persyaratan trait TraitName . Saya berpikir bahwa trait harus dalam satu baris.

Jadi, jika itu adalah skrip alat, tentu saja harus memiliki alat di atas. Kemudian, jika itu adalah suatu sifat, itu harus menentukan apakah itu suatu sifat pada baris berikutnya. Secara opsional, izinkan seseorang untuk menyatakan nama kelas skrip mengikuti deklarasi sifat pada baris yang sama dan jika ya, jangan izinkan mereka juga menggunakan class_name . Jika mereka menghilangkan nama sifat, maka class_name <name> baik-baik saja. Kemudian, ketika memperluas tipe lain, kita dapat menyisipkan extends setelah deklarasi sifat dan/atau pada barisnya sendiri setelah deklarasi sifat. Jadi, saya akan menganggap masing-masing ini valid:

# 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

Ketiga: haruskah kita, untuk tujuan pelengkapan otomatis dan/atau deklarasi maksud/persyaratan, mengizinkan suatu sifat untuk menentukan tipe dasar yang harus diperluas?

Kita telah membahas bahwa sifat-sifat harus mendukung pewarisan sifat-sifat lain. Tetapi haruskah kita mengizinkan TraitA ke extend Node , mengizinkan skrip TraitA untuk mendapatkan pelengkapan otomatis Node, tetapi kemudian juga memicu kesalahan parse jika kita melakukan pernyataan using TraitA ketika kelas saat ini tidak memperluas Node atau salah satu dari jenis turunannya?

Keempat: Daripada memiliki sifat-sifat yang memperluas sifat-sifat lain, tidak bisakah kita menyimpan pernyataan extends yang disediakan untuk ekstensi kelas, mengizinkan suatu sifat untuk tidak memerlukan pernyataan ini sama sekali, tetapi daripada memperluas sifat dasar, izinkan ciri-ciri untuk hanya memiliki pernyataan using mereka sendiri yang meng-sub-impor ciri - ciri itu?

# 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".

Tentu saja, manfaatnya di sini adalah Anda dapat mengelompokkan beberapa sifat di bawah satu nama sifat dengan menggunakan beberapa pernyataan using , mirip dengan C++ termasuk file yang menyertakan beberapa kelas lain.

Kelima: jika kita memiliki suatu sifat, dan memiliki using atau extends untuk suatu metode, dan kemudian mengimplementasikannya sendiri, apa yang kita lakukan ketika memanggil, dalam fungsi itu .<method_name> untuk menjalankan implementasi dasar? Apakah kita berasumsi bahwa panggilan ini selalu dijalankan dalam konteks pewarisan kelas, dan hierarki sifat tidak memiliki pengaruh di sini?

cc @vnen

Pertama: Dengan cara apa kita harus mendukung pernyataan using?

Saya berpikir bahwa pernyataan using harus memerlukan beberapa GDScript nilai konstan.

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

Aku baik-baik saja dengan semua itu. Tetapi untuk jalur saya menggunakan string secara langsung: using "res://my_trait.gd"

Kedua: Apa yang harus kita katakan sintaks yang diizinkan untuk mendefinisikan suatu sifat dan/atau namanya seharusnya?

Seseorang mungkin tidak selalu ingin menggunakan kelas skrip untuk sifat mereka, jadi saya tidak berpikir kita harus menerapkan persyaratan trait TraitName . Saya berpikir bahwa trait pasti sedang dalam antrean.

Jadi, jika itu adalah skrip alat, tentu saja harus memiliki alat di atas. Kemudian, jika itu adalah suatu sifat, itu harus menentukan apakah itu suatu sifat pada baris berikutnya. Secara opsional, izinkan seseorang untuk menyatakan nama kelas skrip mengikuti deklarasi sifat pada baris yang sama dan jika ya, jangan izinkan mereka juga menggunakan class_name . Jika mereka menghilangkan nama sifat, maka class_name <name> baik-baik saja. Kemudian, ketika memperluas tipe lain, kita dapat menyisipkan extends setelah deklarasi sifat dan/atau pada barisnya sendiri setelah deklarasi sifat. Jadi, saya akan menganggap masing-masing ini valid:

# 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 pada suatu sifat seharusnya tidak membuat perbedaan, karena mereka tidak dieksekusi secara langsung.

Saya setuju bahwa suatu sifat tidak harus memiliki nama global. Saya akan menggunakan trait dengan cara yang mirip dengan tool . Itu harus menjadi hal pertama dalam file skrip (kecuali untuk komentar). Kata kunci opsional harus diikuti dengan nama sifat. Saya tidak akan menggunakan class_name untuk mereka, karena mereka bukan kelas.

Ketiga: haruskah kita, untuk tujuan pelengkapan otomatis dan/atau deklarasi maksud/persyaratan, mengizinkan suatu sifat untuk menentukan tipe dasar yang harus diperluas?

Sejujurnya saya tidak suka menambahkan fitur dalam bahasa demi editor. Di situlah anotasi akan berguna.

Nah jika kita ingin membuat ciri-ciri hanya diterapkan pada tipe tertentu (dan turunannya), maka tidak apa-apa. Faktanya, saya pikir ini lebih baik demi pemeriksaan statis: ini memungkinkan suatu sifat untuk menggunakan barang-barang dari kelas sementara kompilasi benar-benar dapat memeriksa apakah mereka digunakan dengan tipe yang benar dan yang lainnya.

Keempat: Daripada memiliki sifat-sifat yang memperpanjang sifat-sifat lain, tidak bisakah kita menyimpan pernyataan extends yang disediakan untuk ekstensi kelas, mengizinkan suatu sifat untuk tidak memerlukan pernyataan ini sama sekali, tetapi daripada memperluas sifat dasar, izinkan ciri-ciri untuk hanya memiliki pernyataan using mereka sendiri yang meng-sub-impor ciri-ciri _those_?

Yah, itu sebagian besar masalah semantik. Ketika saya menyebutkan bahwa sifat dapat memperluas yang lain, saya tidak benar-benar bermaksud menggunakan kata kunci extends . Perbedaan utama adalah bahwa dengan extends Anda hanya dapat memperluas satu, dengan using Anda dapat menanamkan banyak sifat lain menjadi satu. Saya baik-baik saja dengan using , selama tidak ada siklus, itu tidak masalah.

Kelima: jika kita memiliki suatu sifat, dan memiliki using atau extends untuk suatu metode, dan kemudian mengimplementasikannya sendiri, apa yang kita lakukan ketika memanggil, dalam fungsi itu .<method_name> untuk menjalankan implementasi dasar? Apakah kita berasumsi bahwa panggilan ini selalu dijalankan dalam konteks pewarisan kelas, dan hierarki sifat tidak memiliki pengaruh di sini?

Itu pertanyaan yang rumit. Saya akan berasumsi bahwa suatu sifat tidak memiliki urusan dengan pewarisan kelas. Jadi notasi titik harus memanggil metode pada sifat induk, jika ada, jika tidak maka akan terjadi kesalahan. Suatu sifat seharusnya tidak menyadari kelas tempat mereka berada.

OTOH, suatu sifat hampir seperti "termasuk", sehingga akan diterapkan kata demi kata ke kelas, oleh karena itu memanggil implementasi induk. Tapi sejujurnya saya hanya akan melarang notasi titik jika metode ini tidak ditemukan dalam sifat induk.

Bagaimana dengan sifat yang mengharuskan kelas memiliki satu atau lebih sifat lain? Misalnya, suatu sifat DoubleJumper yang membutuhkan kedua sifat Jumper , suatu sifat Upgradable dan kelas yang mewarisi KinematicBody2D .

Rust, misalnya, memungkinkan Anda menggunakan tanda tangan tipe seperti itu. Sesuatu seperti KinematicBody2D: Jumper, Upgradable . Tapi karena kita menggunakan : untuk membuat anotasi, kita bisa menggunakan KinematicBody2D & Jumper & Upgradable atau semacamnya.

Ada juga masalah polimorfisme. Bagaimana jika implementasi sifat berbeda untuk setiap kelas, tetapi memperlihatkan antarmuka yang sama?

Misalnya, kami menginginkan metode kill() dalam sifat Jumper , yang digunakan oleh Enemy dan Player . Kami menginginkan implementasi yang berbeda untuk setiap kasus, sambil tetap menjaga keduanya tetap kompatibel dengan tanda tangan tipe Jumper yang sama. Bagaimana cara melakukannya?

Untuk polimorfisme, Anda cukup membuat sifat terpisah yang menyertakan sifat dengan kill() dan kemudian mengimplementasikan versi spesifik dari metode tersebut. Menggunakan sifat-sifat yang menggantikan metode sifat-sifat yang disertakan sebelumnya adalah cara Anda menanganinya.

Juga, saya tidak berpikir ada rencana (belum) untuk membuat petunjuk tipe memiliki persyaratan sifat. Apakah itu sesuatu yang ingin kita lakukan?

buat sifat yang terpisah

Bukankah itu akan menghasilkan banyak file sifat sekali pakai? Jika kita bisa melakukan deklarasi sifat bersarang (mirip dengan kata kunci class ), itu bisa lebih nyaman. Kita juga bisa mengganti metode secara langsung di kelas yang menggunakan sifat tersebut.

Saya akan sangat menghargai sistem tanda tangan tipe yang kuat (mungkin dengan komposisi boolean dan opsional/non-nulls). Sifat akan cocok.

Saya tidak yakin apakah itu dibahas, tetapi saya pikir itu mungkin untuk memanggil versi fungsi khusus sifat. Sebagai contoh:

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()

yang mencetak: A , B , B .


Saya juga tidak sepenuhnya yakin tentang "tanpa biaya runtime". Bagaimana menangani skrip yang dimuat secara dinamis (tidak tersedia selama ekspor) dengan kelas yang menggunakan sifat yang ditentukan sebelum ekspor? Apakah saya salah memahami sesuatu? Atau kasus itu tidak dianggap "runtime"?

Saya tidak yakin apakah itu dibahas, tetapi saya pikir itu mungkin untuk memanggil versi fungsi khusus sifat.

Saya sudah mempertimbangkan ini, tetapi saya tidak yakin tentang mengizinkan kelas menggunakan sifat yang saling bertentangan (yaitu, sifat yang mendefinisikan metode dengan nama yang sama). Urutan pernyataan using seharusnya tidak ada bedanya.

Saya juga tidak sepenuhnya yakin tentang "tanpa biaya runtime". Bagaimana menangani skrip yang dimuat secara dinamis (tidak tersedia selama ekspor) dengan kelas yang menggunakan sifat yang ditentukan sebelum ekspor? Apakah saya salah paham tentang sesuatu? Atau kasus itu tidak dianggap "runtime"?

Ini bukan tentang mengekspor. Ini pasti akan memengaruhi waktu pemuatan, karena kompilasi terjadi saat pemuatan (walaupun menurut saya itu tidak akan terlalu signifikan), tetapi seharusnya tidak berdampak ketika skrip sedang dieksekusi. Idealnya skrip harus dikompilasi saat ekspor, tapi itu diskusi lain.

Halo semuanya.

Saya baru mengenal Godot dan sudah terbiasa selama beberapa hari terakhir. Saat saya mencoba mencari tahu praktik terbaik yang digunakan untuk membuat komponen yang dapat digunakan kembali, saya telah memutuskan sebuah pola. Saya akan selalu membuat Node root dari sub-adegan, yang dimaksudkan untuk dimasukkan ke dalam adegan lain, mengekspor semua properti yang ingin saya atur dari luar. Sebisa mungkin, saya ingin membuat pengetahuan tentang struktur internal dari cabang yang diinstance tidak diperlukan ke seluruh adegan.

Untuk membuatnya berfungsi, simpul akar harus "mengekspor" properti dan kemudian menyalin nilainya ke anak yang sesuai di _ready. Jadi, misalnya, bayangkan simpul Bom dengan Timer anak. Simpul Bom akar di sub-adegan akan mengekspor "detonation_time" dan kemudian akan melakukan $Timer.wait_time = detonation_time di _ready. Ini memungkinkan kita untuk mengaturnya dengan baik di UI Godot setiap kali kita membuat instance tanpa harus membuat anak-anak dapat diedit dan menelusuri Timer.

Namun
1) Ini adalah transformasi yang sangat mekanis sehingga sepertinya sesuatu yang serupa dapat didukung oleh sistem
2) Ini mungkin menambahkan sedikit inefisiensi atas pengaturan nilai yang sesuai secara langsung di simpul anak.

Sebelum saya melanjutkan, ini mungkin tampak bersinggungan dengan apa yang sedang dibahas karena tidak melibatkan mengizinkan semacam warisan "pribadi" (dalam bahasa C++). Namun, saya sebenarnya menyukai sistem Godot dalam membangun perilaku dengan menyusun elemen Scene daripada rekayasa yang lebih mirip pewarisan. Hubungan "tertulis" itu tidak berubah dan statis. OTOH, struktur adegannya dinamis, Anda bahkan dapat mengubahnya saat dijalankan. Logika permainan sangat dapat berubah selama pengembangan sehingga saya pikir desain Godot sangat cocok dengan kasus penggunaan.

Memang benar bahwa simpul anak digunakan sebagai ekstensi perilaku simpul akar tetapi itu tidak menyebabkan mereka kekurangan swasembada, IMO. Pengatur waktu sepenuhnya mandiri dan dapat diprediksi dalam perilaku terlepas dari apa yang digunakan untuk waktu. Apakah Anda menggunakan sendok untuk minum sup atau makan es krim, sendok itu menjalankan fungsinya dengan baik meskipun itu bertindak sebagai perpanjangan tangan Anda. Saya melihat simpul akar sebagai maestro yang mengoordinasikan perilaku simpul anak sehingga mereka tidak harus tahu secara langsung tentang satu sama lain dan KARENA ITU dapat tetap mandiri. Node induk/root adalah manajer terikat meja yang mendelegasikan tanggung jawab tetapi tidak melakukan banyak pekerjaan langsung. Karena tipis, mudah untuk membuat yang baru untuk perilaku yang sedikit berbeda.

Namun, saya pikir node root juga harus bertindak sebagai ANTARMUKA utama untuk fungsionalitas seluruh cabang yang dibuat. Semua properti yang dapat di-tweak dalam instance harus "dapat diatur" di simpul akar cabang bahkan jika pemilik utama properti adalah beberapa simpul anak. Kecuali saya melewatkan sesuatu, ini harus diatur secara manual di Godot versi saat ini. Akan lebih baik jika ini dapat diotomatisasi entah bagaimana untuk menggabungkan manfaat dari sistem dinamis dengan skrip yang lebih mudah.

Satu hal yang saya pikirkan adalah sistem "warisan dinamis", jika Anda mau, tersedia untuk subkelas Node.js. Akan ada dua sumber properti/metode dalam skrip semacam itu, skrip yang diperluas dan yang "digelembungkan" dari anak-anak dalam struktur adegan. Jadi contoh saya dengan Bom akan menjadi seperti export lifted var $Timer.wait_time [= value?] as detonation_time di dalam bagian variabel anggota skrip bomb.gd. Sistem pada dasarnya akan menghasilkan $Timer.wait_time = detonation_time dalam _ready callback dan menghasilkan pengambil/penyetel yang memungkinkan $Bomb.detonation_time = 5 dari induk simpul Bom menghasilkan $Timer.wait_time = 5 yang disetel.

Dalam contoh OP dengan MoveRightTrait kita akan memiliki simpul yang dilampirkan mysprite.gd memiliki MoveRightTrait sebagai simpul anak. Kemudian di mysprite.gd kita akan memiliki sesuatu seperti lifted func $MoveRightTrait.move_right [as move_right] (mungkin 'sebagai' bisa jadi opsional jika namanya sama). Sekarang memanggil move_right pada objek skrip yang dibuat dari mysprite.gd akan secara otomatis mendelegasikan ke simpul anak yang sesuai. Mungkin sinyal bisa digelembungkan sehingga bisa dilampirkan ke simpul anak dari root? Mungkin seluruh node dapat digelembungkan hanya dengan lifted $MoveRightTrait [as MvR] tanpa fungsi, sinyal, atau var. Dalam hal ini semua metode dan properti di MoveRightTrait dapat diakses dari mysprite secara langsung sebagai mysprite.move_right atau melalui mysprite.MvR.move_right jika 'sebagai MvR' digunakan.

Itulah salah satu ide tentang bagaimana menyederhanakan pembuatan INTERFACE ke struktur adegan di akar cabang yang diinstance, meningkatkan karakteristik "kotak hitam" mereka dan mendapatkan kenyamanan skrip bersama dengan kekuatan sistem adegan dinamis Godot. Tentu saja, akan ada banyak detail samping yang perlu dipertimbangkan. Misalnya, tidak seperti kelas dasar, node anak dapat dihapus saat run time. Bagaimana seharusnya fungsi dan properti yang digelembungkan/diangkat jika dipanggil/diakses dalam kasus kesalahan itu? Jika sebuah node dengan NodePath yang tepat ditambahkan kembali, apakah properti yang diangkat mulai bekerja kembali? [YA, IMO] Juga akan menjadi kesalahan untuk menggunakan 'diangkat' di kelas yang tidak diturunkan dari Node karena tidak akan pernah ada anak-anak untuk digelembungkan/diangkat dari dalam kasus itu. Selain itu, bentrokan nama dimungkinkan dengan duplikat "sebagai {name}" atau "mengangkat $Timer1 mengangkat $Timer2" di mana node memiliki properti/metode dengan nama yang sama. Penerjemah skrip idealnya akan mendeteksi masalah logis seperti itu.

Saya merasa ini akan memberi kita banyak hal yang kita inginkan meskipun sebenarnya hanya gula sintaksis yang menyelamatkan kita dari keharusan menulis fungsi penerusan dan inisialisasi. Juga, karena secara konseptual sederhana, seharusnya tidak terlalu sulit untuk diterapkan atau dijelaskan.

Bagaimanapun, jika Anda sampai sejauh ini, terima kasih telah membaca!

Saya menggunakan "diangkat" di mana-mana tapi itu hanya ilustrasi.
Sesuatu seperti using var $Timer.wait_time as detonation_time atau using $Timer jelas sama baiknya. Bagaimanapun, Anda dapat dengan mudah mewarisi pseudo dari node anak, menciptakan satu titik akses yang koheren ke fungsionalitas yang diinginkan di root cabang yang akan dibuat. Persyaratan pada bagian fungsionalitas yang dapat digunakan kembali adalah bahwa mereka memperluas Node atau subkelasnya sehingga mereka dapat ditambahkan sebagai anak-anak ke komponen yang lebih besar.

Cara lain untuk melihatnya adalah bahwa kata kunci "memperluas" pada skrip yang mewarisi dari Node memberi Anda hubungan "is-a" Anda saat menggunakan kata kunci "menggunakan" atau "mengangkat" pada skrip untuk "menggelembungkan" a anggota node keturunan memberi Anda sesuatu yang mirip dengan "menerapkan" [hei, kemungkinan kata kunci] yang ada dalam bahasa dengan pewarisan tunggal tetapi banyak "antarmuka" (mis. Java). Dalam pewarisan berganda tak terbatas (seperti c++) kelas dasar membentuk pohon [statis, tertulis]. Dengan analogi, saya agak mengusulkan pelapisan sintaksis yang nyaman dan penghapusan boilerplate di atas pohon Node Godot yang ada.

Jika pernah ditentukan bahwa ini adalah sesuatu yang perlu ditelusuri, ada aspek ruang desain yang perlu dipertimbangkan:
1) Haruskah kita mengizinkan hanya anak-anak langsung dalam "menggunakan". IOW using $Timer tetapi tidak 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 menggunakan "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. menggunakan var "Timer".wait_time sebagai 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 menggunakan MoveRightTrait sebagai 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 jika node.DirectionalMover:` akan menjadi pengujian fungsionalitas berdasarkan konvensi.
4) Pola Negara harus dapat diimplementasikan dengan mengganti simpul "menggunakan" dengan simpul lain yang memiliki perilaku varian tetapi antarmuka yang sama [mengetik bebek] dan NodePath yang sama seperti yang dirujuk dalam pernyataan "menggunakan". Dengan cara pohon adegan bekerja, ini akan berhasil hampir secara gratis. Namun, sistem harus melacak sinyal yang terhubung melalui induk dan memulihkan koneksi pada anak yang diganti.

Saya telah bekerja dengan GDScript untuk beberapa waktu sekarang dan saya harus setuju, beberapa jenis fitur sifat/mixin dan proxy/delegasi sangat dibutuhkan. Cukup menjengkelkan harus menyiapkan semua boilerplate ini hanya untuk menghubungkan properti atau mengekspos metode anak-anak di root adegan.

Atau menambahkan level pohon hanya untuk mensimulasikan komponen (itu menjadi agak rumit dengan cepat, karena Anda memutuskan semua jalur simpul dengan setiap komponen baru). Mungkin ada cara yang lebih baik, seperti meta/multi skrip yang memungkinkan banyak skrip pada satu simpul? Jika Anda memiliki solusi idiomatik, silakan bagikan ...

Melempar C++ (GDNative) ke dalam campuran membuat segalanya menjadi lebih buruk, karena _ready dan _init berperilaku berbeda di sana (baca: inisialisasi dengan nilai default setengah berfungsi atau tidak berfungsi sama sekali).

Ini adalah hal utama yang harus saya selesaikan di GDScript. Saya sering perlu berbagi fungsionalitas di seluruh node tanpa menyusun seluruh struktur warisan saya di sekitarnya -- misalnya, pemain dan pemilik toko saya memiliki inventaris, pemain+item+musuh saya memiliki statistik, pemain dan musuh saya memiliki item perlengkapan, dll.

Saat ini saya menerapkan 'komponen' bersama ini sebagai kelas atau node yang dimuat ke dalam 'entitas' yang membutuhkannya, tetapi berantakan (menambahkan banyak pencarian untuk node, membuat mengetik bebek hampir tidak mungkin, dll) dan pendekatan alternatif memiliki kelemahannya sendiri. Saya belum menemukan cara yang lebih baik. Sifat / campuran benar-benar akan menyelamatkan hidup saya.

Apa yang terjadi adalah kemampuan untuk berbagi kode antar objek tanpa menggunakan pewarisan, yang menurut saya perlu dan tidak mungkin dilakukan dengan bersih di Godot saat ini.

Cara saya memahami sifat-sifat Rust (https://doc.rust-lang.org/1.8.0/book/traits.html), adalah bahwa mereka seperti kelas tipe Haskell, di mana Anda memerlukan beberapa fungsi parameter untuk didefinisikan untuk tipe Anda menambahkan suatu sifat, dan kemudian Anda dapat menggunakan beberapa fungsi umum yang ditentukan di atas semua jenis yang menerapkan suatu sifat. Apakah sifat Rust berbeda dari yang diusulkan di sini?

Yang ini mungkin akan dimigrasikan secara grosir, karena telah dibahas secara luas di sini.

Yang ini mungkin akan dimigrasikan secara grosir, karena telah dibahas secara luas di sini.

_Saya menemukan "pemindahan" proposal tidak ada gunanya, mereka lebih baik ditutup dan diminta untuk dibuka kembali di godot-proposal jika orang menyatakan minat, dan biarkan proposal lain benar-benar dilaksanakan jika diperlukan. Omong-omong..._

Saya menemukan masalah ini setahun yang lalu, tetapi baru sekarang saya mulai memahami potensi kegunaan sistem sifat.

Berbagi alur kerja saya saat ini dengan harapan dapat menginspirasi seseorang untuk memahami masalah dengan lebih baik (dan mungkin menyarankan alternatif yang lebih baik selain menerapkan sistem sifat).

1. Buat alat untuk menghasilkan templat komponen untuk setiap jenis simpul yang digunakan dalam proyek:

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

Sekarang, apa yang akan membuat INI lebih mudah adalah memiliki sistem snippet atau sistem makro sehingga membuat bagian kode deklaratif yang digunakan kembali lebih mudah bagi pengembang.

Berjalan di jejakmu...

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. Buat metode statis untuk digunakan kembali untuk menginisialisasi komponen ke host (misalnya 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')

Beginilah tampilan komponen (sifat) setelah dibuat dengan skrip pertama:

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)

(Opsional) 3. Perluas komponen (sifat)

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

Itu ide saya. Saya percaya ini cukup sederhana dan mencakup hampir semua kasus penggunaan untuk penggunaan kembali kode. Saya bahkan akan mengizinkan kelas menggunakan sifat untuk mengganti metode sifat (jika mungkin dilakukan pada waktu kompilasi). Sifat juga bisa memperluas sifat lainnya.

Berjalan di jejakmu...

class_name ComponentMotion2D extends ComponentNode2D

const MAX_SPEED = 100.0

var linear_velocity = Vector2()
var collision

export(Script) var impl
...

Sebenarnya, Script yang diekspor digunakan dalam komponen ini untuk mendorong perilaku tipe node host/root tertentu per komponen. Di sini, ComponentMotion2D akan memiliki dua skrip:

  • motion_kinematic_body_2d.gd
  • motion_rigid_body_2d.gd

Jadi anak-anak masih mendorong perilaku host / root di sini. Terminologi host berasal dari saya menggunakan mesin status, dan di sinilah ciri-ciri mungkin tidak cocok, karena status lebih terorganisir sebagai node imo.

Komponen-komponen itu sendiri "terprogram" ke dalam root dengan menjadikannya onready anggota, secara efektif mengurangi kode boilerplate (dengan biaya untuk benar-benar merujuknya sebagai object.motion )

extends KinematicBody2D

onready var motion = $motion # ComponentMotion2D

Tidak yakin apakah ini akan membantu menyelesaikan masalah, tetapi C# memiliki sesuatu yang disebut metode ekstensi yang memperluas fungsionalitas tipe kelas.

Pada dasarnya fungsi harus statis, dan parameter pertama diperlukan dan harus self . Ini akan terlihat seperti ini sebagai definisi:

ekstensi.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)

Kemudian ketika Anda ingin menggunakan metode distance , Anda cukup melakukan ini:

pemain.gd

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

Saya mengenalnya, tetapi itu tidak membantu menyelesaikan masalah. Hehe, terima kasih meskipun.

@TheColorRed metode ekstensi sudah diusulkan, tapi saya rasa itu tidak layak dalam bahasa yang dinamis. Saya juga tidak berpikir mereka memecahkan akar masalah yang awalnya memulai diskusi ini.


Pada catatan lain, saya mungkin akan membuka banyak proposal untuk GDScript sebagai GIP (termasuk ini, jika @willnationsdev tidak keberatan).

Saya masih percaya bahwa ciri-ciri paling masuk akal untuk membagikan kode secara horizontal dalam bahasa OO (tanpa banyak pewarisan, tetapi saya tidak ingin seperti itu).

Saya tidak berpikir mereka layak dalam bahasa yang dinamis

Apakah GDS dinamis? Metode ekstensi dapat dibatasi hanya untuk instance yang diketik dan akan bekerja persis sama seperti dalam bahasa lain - hanya gula sintaksis selama kompilasi menggantikan panggilan metode dengan panggilan metode (fungsi) statis. Sejujurnya, saya lebih suka mucikari (metode ext.) sebelum prototipe ala JS atau cara dinamis lainnya untuk melampirkan metode ke kelas atau bahkan hanya contoh.

Apa pun yang kita putuskan untuk dilakukan, saya harap kita tidak memutuskan untuk menamakannya "mucikari".

Apakah GDS dinamis?

Ada banyak definisi "dinamis" dalam konteks ini. Untuk lebih jelasnya: jenis variabel mungkin tidak diketahui pada waktu kompilasi, jadi pemeriksaan ekstensi metode perlu dilakukan saat runtime (yang akan merusak kinerja dengan satu atau lain cara).

Metode ekstensi dapat dibatasi hanya untuk instance yang diketik

Jika kita mulai melakukan ini, sebaiknya kita membuat GDScript hanya diketik. Tapi itu diskusi lain yang tidak saya dapatkan di sini.

Intinya adalah: segala sesuatunya tidak boleh mulai atau berhenti berfungsi karena pengguna menambahkan tipe ke skrip. Hampir selalu membingungkan ketika itu terjadi.

Sekali lagi, saya tidak berpikir itu menyelesaikan masalah. Kami mencoba melampirkan kode yang sama ke beberapa jenis, sedangkan metode ekstensi hanya akan menambahkannya ke satu jenis.

Sejujurnya, saya lebih suka mucikari (metode ext.) sebelum prototipe ala JS atau cara dinamis lainnya untuk melampirkan metode ke kelas atau bahkan hanya contoh.

Tidak ada yang mengusulkan (belum) melampirkan metode secara dinamis (saat runtime) dan saya juga tidak menginginkannya. Sifat akan diterapkan secara statis pada waktu kompilasi.

Saya awalnya membuat komentar tentang Haxe dan perpustakaan makro mixin-nya, tetapi kemudian saya menyadari sebagian besar pengguna tidak akan menggunakan bahasa pihak ketiga.

Saya baru-baru ini mengalami kebutuhan untuk ini.

Saya memiliki beberapa objek yang dapat berinteraksi dengan pengguna tetapi tidak dapat berbagi induk yang sama, tetapi mereka membutuhkan grup API yang serupa

misalnya saya memiliki beberapa kelas yang tidak dapat mewarisi dari induk yang sama, tetapi menggunakan set API yang serupa:
Gudang: Keuangan, Penghapusan, MouseInteraction + lainnya
Kendaraan: Keuangan, Penghapusan, MouseInteraction + lainnya
VehicleTerminal: Keuangan, Penghapusan, MouseInteraction + lainnya

Untuk Keuangan saya telah menggunakan komposisi, karena ini memerlukan kode pelat boiler paling sedikit karena get_finances_component() adalah API yang cukup karena tidak terlalu peduli dengan objek game sama sekali.

Yang lain:
MouseInteraction dan Delection Saya baru saja menyalin dan menempel karena perlu mengetahui tentang objek game, beberapa komposisi tidak berfungsi di sini kecuali saya melakukan delegasi aneh:

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

tetapi itu tidak benar-benar memungkinkan saya untuk mengesampingkan cara kerja hapus di mana jika itu adalah kelas yang diwarisi, saya dapat memiliki kemampuan untuk menulis ulang beberapa kode penghapusan jika diperlukan.

MouseInteraction dan Delection Saya baru saja menyalin dan menempel karena perlu mengetahui tentang objek game, beberapa komposisi tidak berfungsi di sini kecuali saya melakukan delegasi aneh

Saya mengakses komponen melalui node onready saat ini. Saya melakukan sesuatu yang serupa:

# character.gd

var input = $input # input component

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

Jadi ini:

character.input.enabled = true

menjadi ini:

character.focused = true

Seperti yang ditunjukkan @Calinou dengan ramah, masalah saya https://github.com/godotengine/godot-proposals/issues/758 terkait erat dengan ini. Apa pendapat Anda tentang proposal untuk dapat menambahkan sifat ke grup? Ini bisa secara drastis kebutuhan skrip dan overhead lainnya.

Akan sangat bagus untuk memiliki cara untuk menyuntikkan kode yang dapat dibagikan ke dalam kelas, dan jika mereka memiliki nilai yang diekspor, ini akan muncul di inspektur, dan memiliki metode dan properti yang tersedia dan terdeteksi oleh penyelesaian kode.

Proposal fitur dan peningkatan untuk Godot Engine sekarang sedang dibahas dan ditinjau dalam pelacak masalah Godot Improvement Proposals (GIP) ( godotengine/godot-proposals ) khusus. Pelacak GIP memiliki templat masalah terperinci yang dirancang agar proposal mencakup semua informasi yang relevan untuk memulai diskusi yang produktif dan membantu komunitas menilai validitas proposal untuk mesin.

Pelacak utama ( godotengine/godot ) sekarang hanya didedikasikan untuk laporan bug dan Permintaan Tarik, memungkinkan kontributor untuk memiliki fokus yang lebih baik pada pekerjaan perbaikan bug. Oleh karena itu, kami sekarang menutup semua proposal fitur lama di pelacak masalah utama.

Jika Anda tertarik dengan proposal fitur ini, silakan buka proposal baru di pelacak GIP dengan mengikuti template masalah yang diberikan (setelah memeriksa bahwa itu belum ada). Pastikan untuk merujuk masalah tertutup ini jika mencakup diskusi yang relevan (yang juga disarankan untuk Anda rangkum dalam proposal baru). Terima kasih sebelumnya!

Catatan: ini adalah proposal yang populer, jika seseorang memindahkannya ke Proposal Godot, cobalah untuk meringkas diskusi juga.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat