Menangani komponen yang ditulis dengan skrip dan "asli" dalam sistem entitas berbasis komponen

11

Saat ini saya sedang mencoba untuk mengimplementasikan sistem entitas berbasis komponen, di mana suatu entitas pada dasarnya hanya ID dan beberapa metode pembantu yang mengikat sekelompok komponen bersama-sama untuk membentuk objek permainan. Beberapa tujuan itu antara lain:

  1. Komponen hanya berisi keadaan (mis. Posisi, kesehatan, jumlah amunisi) => logika masuk ke "sistem", yang memproses komponen-komponen ini dan keadaannya (mis. PhysicsSystem, RenderSystem, dll.)
  2. Saya ingin mengimplementasikan komponen dan sistem baik dalam C # murni dan melalui scripting (Lua). Pada dasarnya saya ingin dapat mendefinisikan komponen dan sistem yang sepenuhnya baru secara langsung di Lua tanpa harus mengkompilasi ulang sumber C # saya.

Sekarang saya sedang mencari ide tentang bagaimana menangani hal ini secara efisien dan konsisten, jadi saya tidak perlu menggunakan sintaks yang berbeda untuk mengakses komponen C # atau komponen Lua, misalnya.

Pendekatan saya saat ini adalah mengimplementasikan komponen C # menggunakan properti publik biasa, mungkin dihiasi dengan beberapa atribut yang memberi tahu editor tentang nilai dan hal-hal standar. Lalu saya akan memiliki kelas C # "ScriptComponent", yang hanya membungkus tabel Lua secara internal dengan tabel ini dibuat oleh sebuah skrip dan menahan semua status jenis komponen tertentu. Saya tidak benar-benar ingin mengakses keadaan itu banyak dari sisi C #, karena saya tidak akan tahu pada waktu kompilasi, apa ScriptComponents dengan properti apa yang akan tersedia untuk saya. Namun, editor perlu mengaksesnya, tetapi antarmuka sederhana seperti berikut ini sudah cukup:

public ScriptComponent : IComponent
{
    public T GetProperty<T>(string propertyName) {..}
    public void SetProperty<T>(string propertyName, T value) {..}
}

Ini hanya akan mengakses tabel Lua dan mengatur dan mengambil properti-properti itu dari Lua dan itu dapat dengan mudah dimasukkan dalam komponen C # murni juga (tetapi gunakan properti C # biasa kemudian, melalui refleksi atau sesuatu). Ini hanya akan digunakan dalam editor, bukan oleh kode game biasa, jadi kinerja tidak sepenting di sini. Ini akan membuatnya perlu untuk menghasilkan atau menulis beberapa deskripsi komponen yang mendokumentasikan properti apa yang sebenarnya ditawarkan oleh tipe komponen tertentu, tetapi itu tidak akan menjadi masalah besar dan bisa cukup otomatis.

Namun, bagaimana cara mengakses komponen dari sisi Lua? Jika saya melakukan panggilan ke sesuatu seperti getComponentsFromEntity(ENTITY_ID)saya mungkin hanya akan mendapatkan banyak komponen C # asli, termasuk "ScriptComponent", sebagai data pengguna. Mengakses nilai dari tabel Lua yang dibungkus akan menghasilkan saya memanggil GetProperty<T>(..)metode daripada mengakses properti secara langsung, seperti dengan komponen C # lainnya.

Mungkin menulis getComponentsFromEntity()metode khusus hanya untuk dipanggil dari Lua, yang mengembalikan semua komponen C # asli sebagai userdata, kecuali untuk "ScriptComponent", di mana ia akan mengembalikan tabel yang dibungkus. Tetapi akan ada metode lain yang berhubungan dengan komponen dan saya tidak benar-benar ingin menduplikasi semua metode ini baik untuk dipanggil dari kode C # atau dari skrip Lua.

Tujuan utamanya adalah menangani semua jenis komponen dengan cara yang sama, tanpa sintaksis kasus khusus yang membedakan antara komponen asli dan komponen Lua - terutama dari sisi Lua. Misalnya saya ingin dapat menulis skrip Lua seperti itu:

entity = getEntity(1);
nativeComponent = getComponent(entity, "SomeNativeComponent")
scriptComponent = getComponent(entity, "SomeScriptComponent")

nativeComponent.NativeProperty = 5
scriptComponent.ScriptedProperty = 3

Script seharusnya tidak peduli komponen apa yang sebenarnya didapat dan saya ingin menggunakan metode yang sama yang saya gunakan dari sisi C # untuk mengambil, menambah atau menghapus komponen.

Mungkin ada beberapa contoh implementasi mengintegrasikan scripting dengan sistem entitas seperti itu?

Mario
sumber
1
Apakah Anda terjebak menggunakan Lua? Saya telah melakukan hal-hal serupa dengan scripting aplikasi C # dengan bahasa CLR lain yang di-parsing saat runtime (Boo, dalam kasus saya). Karena Anda sudah menjalankan kode tingkat tinggi di lingkungan yang dikelola dan semuanya IL di bawah tenda, mudah untuk mensubkelas kelas yang ada dari sisi "scripting" dan meneruskan contoh kelas tersebut kembali ke aplikasi host. Dalam kasus saya, saya bahkan akan men-cache kumpulan skrip yang dikompilasi untuk meningkatkan kecepatan memuat, dan hanya membangunnya kembali ketika skrip berubah.
justinian
Tidak, saya tidak terjebak menggunakan Lua. Secara pribadi saya sangat ingin menggunakannya karena kesederhanaannya, ukurannya yang kecil, kecepatan dan popularitasnya (jadi kemungkinan orang sudah terbiasa dengan itu kelihatannya lebih tinggi), tetapi saya terbuka untuk alternatif.
Mario
Bisakah saya bertanya mengapa Anda ingin memiliki kemampuan definisi yang sama antara C # dan lingkungan skrip Anda? Desain normal adalah definisi komponen dalam C # dan kemudian 'objek perakitan' dalam bahasa scripting. Saya bertanya-tanya masalah apa yang muncul karena ini adalah solusinya?
James
1
Tujuannya adalah membuat sistem komponen dapat dikembangkan dari luar kode sumber C #. Tidak hanya melalui pengaturan nilai properti dalam file XML atau skrip, tetapi dengan mendefinisikan seluruh komponen baru dengan seluruh properti baru dan sistem baru yang memprosesnya. Ini sebagian besar terinspirasi oleh deskripsi Scott Bilas tentang sistem objek di Dungeon Siege, di mana mereka memiliki komponen "asli" C ++ serta "GoSkritComponent", yang dengan sendirinya hanya merupakan pembungkus untuk skrip: scottbilas.com/games/dungeon -siege
Mario
Berhati-hatilah agar tidak terjerumus dalam membangun antarmuka skrip atau plugin yang sangat kompleks sehingga mendekati penulisan ulang alat asli (C # atau Visual Studio dalam kasus ini).
3Dave

Jawaban:

6

Sebuah akibat wajar dari jawaban FxIII adalah bahwa Anda harus mendesain segalanya (yang akan dapat dimodifikasi) terlebih dahulu dalam bahasa scripting (atau setidaknya sebagian dari itu) untuk memastikan bahwa logika integrasi Anda benar-benar memenuhi ketentuan untuk modding. Ketika Anda yakin bahwa integrasi skrip Anda cukup fleksibel, tulis ulang bit yang diperlukan dalam C #.

Saya pribadi benci dynamicfitur di C # 4.0 (saya pecandu bahasa statis) - tapi ini tanpa keraguan skenario di mana ia harus digunakan dan di mana itu benar-benar bersinar. Komponen C # Anda akan menjadi objek perilaku yang kuat / diperluas .

public class Villian : ExpandoComponent
{
    public void Sound() { Console.WriteLine("Cackle!"); }
}

//...

dynamic villian = new Villian();
villian.Position = new Vector2(10, 10); // Add a property.
villian.Sound(); // Use a method that was defined in a strong context.

Komponen Lua Anda akan benar-benar dinamis (artikel yang sama di atas akan memberi Anda gambaran tentang bagaimana menerapkan ini). Sebagai contoh:

// LuaSharp is my weapon of choice.
public class LuaObject : DynamicObject
{
    private LuaTable _table;

    public LuaObject(LuaTable table)
    {
        _table = table;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        var methodName = binder.Name;
        var function = (LuaFunction)_table[methodName];
        if (function == null)
        {
            return base.TryInvokeMember(binder, args, out result);
        }
        else
        {
            var resultArray = function.Call(args);
            if (resultArray == null)
                result = null;
            else if (resultArray.Length == 1)
                result = resultArray[0];
            else
                result = resultArray;
            return true;
        }
    }

    // Other methods for properties etc.
}

Sekarang karena Anda menggunakan dynamicAnda dapat menggunakan objek Lua seolah-olah mereka adalah kelas nyata di C #.

dynamic cSharpVillian = new Villian();
dynamic luaVillian = new LuaObject(_script["teh", "villian"]);
cSharpVillian.Sound();
luaVillian.Sound(); // This will call into the Lua function.
Jonathan Dickinson
sumber
Saya sangat setuju dengan paragraf pertama, saya sering melakukan pekerjaan saya dengan cara ini. Menulis kode yang Anda tahu harus menerapkannya dalam bahasa tingkat rendah, seperti mengambil pinjaman TAHU bahwa Anda harus membayar bunga. Desainer TI sering mengambil pinjaman: ini bagus ketika mereka tahu bahwa mereka melakukannya dan mereka tetap mengendalikan hutang mereka.
FxIII
2

Saya lebih suka melakukan yang sebaliknya: menulis semua menggunakan bahasa yang dinamis dan kemudian melacak bottleneks (jika ada). Setelah Anda menemukannya, Anda dapat secara selektif mengimplementasikan kembali fungsionalitas yang terlibat menggunakan C # atau (lebih tepatnya) C / C ++.

Saya memberi tahu Anda hal ini terutama karena yang ingin Anda lakukan adalah membatasi fleksibilitas sistem Anda di bagian C #; ketika sistem menjadi kompleks, "mesin" C # Anda akan mulai terlihat seperti penerjemah dinamis, mungkin bukan yang terbaik. Pertanyaannya adalah: apakah Anda bersedia menginvestasikan sebagian besar waktu Anda untuk menulis mesin C # generik atau untuk memperpanjang permainan Anda?

Jika target Anda adalah yang kedua, seperti yang saya yakini, Anda mungkin akan menggunakan waktu Anda dengan berfokus pada mekanisme permainan Anda, membiarkan penerjemah berpengalaman untuk menjalankan kode dinamis.

FxIII
sumber
Ya, masih ada ketakutan samar-samar tentang kinerja buruk untuk beberapa sistem inti, jika saya akan melakukan semuanya melalui skrip. Dalam hal ini saya harus memindahkan fungsionalitas itu kembali ke C # (atau apa pun) ... jadi saya akan memerlukan cara yang baik untuk berinteraksi dengan sistem komponen dari sisi C #. Itulah masalah intinya di sini: membuat sistem komponen yang bisa saya gunakan sama baiknya dari C # dan skrip.
Mario
Saya pikir C # digunakan sebagai semacam lingkungan "akselerasi scripting" untuk hal-hal seperti C ++ atau C? Bagaimanapun, jika Anda tidak yakin dalam bahasa apa Anda akan berakhir, mulailah mencoba untuk menulis beberapa logika dalam Lua dan C #. Saya bertaruh pada akhirnya Anda akan menjadi lebih cepat di C # sebagian besar waktu, karena fitur editor tooling dan debugging yang canggih.
Imi
4
+1 Saya setuju dengan ini untuk alasan lain - jika Anda ingin game Anda bisa diperluas, Anda harus memakan makanan anjing Anda sendiri: Anda mungkin akan mengintegrasikan mesin skrip hanya untuk menemukan komunitas modding potensial Anda tidak dapat benar-benar membuat apa pun dengan Itu.
Jonathan Dickinson