Obsługa skryptu i „rodzimych” komponentów w systemie encji opartej na komponentach

11

Obecnie próbuję wdrożyć system encji oparty na komponentach, w którym encja jest w zasadzie tylko identyfikatorem, a niektóre metody pomocnicze wiążą kilka komponentów razem, tworząc obiekt gry. Niektóre cele tego obejmują:

  1. Komponenty zawierają tylko stan (np. Pozycja, zdrowie, ilość amunicji) => logika przechodzi do „systemów”, które przetwarzają te komponenty i ich stan (np. PhysicsSystem, RenderSystem itp.)
  2. Chcę implementować komponenty i systemy zarówno w czystym języku C #, jak i za pomocą skryptów (Lua). Zasadniczo chcę mieć możliwość definiowania całkowicie nowych komponentów i systemów bezpośrednio w Lua bez konieczności ponownej kompilacji mojego źródła C #.

Teraz szukam pomysłów, jak radzić sobie z tym wydajnie i konsekwentnie, więc nie muszę używać innej składni, aby na przykład uzyskać dostęp do komponentów C # lub Lua.

Moje obecne podejście polegałoby na implementacji komponentów C # przy użyciu zwykłych, publicznych właściwości, prawdopodobnie ozdobionych pewnymi atrybutami informującymi redaktora o domyślnych wartościach i innych rzeczach. Następnie miałbym klasę C # „ScriptComponent”, która po prostu wewnętrznie otacza tabelę Lua, tworząc tę ​​tabelę za pomocą skryptu i utrzymując cały stan tego konkretnego typu komponentu. Tak naprawdę nie chcę uzyskać dostępu do tego stanu od strony C #, ponieważ nie wiedziałbym w czasie kompilacji, jakie ScriptComponents z jakich właściwości byłyby dla mnie dostępne. Jednak edytor będzie musiał uzyskać do niego dostęp, ale wystarczy prosty interfejs podobny do następującego:

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

Spowodowałoby to po prostu dostęp do tabeli Lua oraz ustawienie i odzyskanie tych właściwości z Lua, i mogłoby to być łatwo zawarte w czystych komponentach C # (ale wtedy używaj zwykłych właściwości C #, poprzez odbicie lub coś innego). Byłoby to używane tylko w edytorze, a nie w zwykłym kodzie gry, więc wydajność nie jest tutaj tak ważna. Konieczne byłoby wygenerowanie lub ręczne napisanie niektórych opisów komponentów dokumentujących, jakie właściwości faktycznie oferuje dany typ komponentów, ale nie byłby to ogromny problem i mógłby być wystarczająco zautomatyzowany.

Jak jednak uzyskać dostęp do komponentów od strony Lua? Jeśli zadzwonię do czegoś takiego getComponentsFromEntity(ENTITY_ID), prawdopodobnie dostanę kilka rodzimych składników C #, w tym „ScriptComponent”, jako dane użytkownika. Dostęp do wartości z zawiniętej tabeli Lua spowodowałby, że wywołałem GetProperty<T>(..)metodę zamiast bezpośredniego dostępu do właściwości, tak jak w przypadku innych składników C #.

Może napisz specjalną getComponentsFromEntity()metodę, która ma być wywoływana z Lua, która zwraca wszystkie natywne komponenty C # jako dane użytkownika, z wyjątkiem „ScriptComponent”, gdzie zamiast tego zwróci zapakowaną tabelę. Ale będą inne metody związane z komponentami i tak naprawdę nie chcę powielać wszystkich tych metod zarówno w celu wywołania z kodu C # lub ze skryptu Lua.

Ostatecznym celem byłaby obsługa wszystkich typów komponentów w ten sam sposób, bez specjalnej składni przypadków rozróżniającej komponenty natywne i komponenty Lua - szczególnie od strony Lua. Np. Chciałbym móc napisać taki skrypt Lua:

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

nativeComponent.NativeProperty = 5
scriptComponent.ScriptedProperty = 3

Skrypt nie powinien dbać o to, jakiego rodzaju komponent faktycznie ma, i chciałbym użyć tych samych metod, których użyłem po stronie C # do pobierania, dodawania lub usuwania komponentów.

Może istnieją jakieś przykładowe implementacje integracji skryptów z takimi systemami?

Mario
źródło
1
Utknąłeś przy użyciu Lua? Zrobiłem podobne rzeczy w skryptach aplikacji C # z innymi językami CLR analizowanymi w czasie wykonywania (Boo, w moim przypadku). Ponieważ już uruchamiasz kod wysokiego poziomu w środowisku zarządzanym, a wszystko to jest IL pod maską, łatwo jest podklasować istniejące klasy od strony „skryptowej” i przekazywać instancje tych klas z powrotem do aplikacji hosta. W moim przypadku buforowałbym nawet skompilowany zestaw skryptów w celu zwiększenia prędkości ładowania i po prostu przebudowałem go po zmianie skryptów.
justinian
Nie, nie utknąłem przy użyciu Lua. Osobiście bardzo chciałbym go używać ze względu na jego prostotę, mały rozmiar, szybkość i popularność (więc szanse, że ludzie już go znają, wydają się wyższe), ale jestem otwarty na alternatywy.
Mario,
Czy mogę zapytać, dlaczego chcesz mieć możliwości równej definicji między C # a środowiskiem skryptowym? Normalnym projektem jest definicja komponentu w języku C #, a następnie „składanie obiektów” w języku skryptowym. Zastanawiam się, jaki problem się pojawił, że jest to rozwiązanie?
James
1
Celem jest umożliwienie rozszerzenia systemu komponentów poza kod źródłowy C #. Nie tylko poprzez ustawianie wartości właściwości w plikach XML lub skryptach, ale poprzez definiowanie całych nowych składników z zupełnie nowymi właściwościami i nowymi systemami przetwarzającymi je. Jest to w dużej mierze zainspirowane przez opisy systemu obiektowego Scotta Bilasa w Dungeon Siege, gdzie miały one „rodzime” komponenty C ++, a także „GoSkritComponent”, który sam w sobie jest jedynie opakowaniem skryptu: scottbilas.com/games/dungeon -siege
Mario,
Uważaj, aby nie wpaść w pułapkę budowy interfejsu skryptowego lub wtyczki tak złożonego, że zbliża się on do przepisania oryginalnego narzędzia (w tym przypadku C # lub Visual Studio).
3Dave

Odpowiedzi:

6

Następstwem odpowiedź FXIII za to, że należy zaprojektować wszystko (że byłoby modyfikowalne) w języku skryptowym pierwszy (lub przynajmniej bardzo przyzwoity część z nich), aby upewnić się, że logika integracji faktycznie rezerwy na modding. Gdy masz pewność, że twoja integracja skryptów jest wystarczająco wszechstronna, przepisz wymagane bity w języku C #.

Osobiście nienawidzę tej dynamicfunkcji w C # 4.0 (jestem ćpunem w języku statycznym) - ale bez wątpienia jest to scenariusz, w którym powinien być używany i tam, gdzie naprawdę świeci. Twoje komponenty C # byłyby po prostu silnymi / rozwijanymi obiektami behawioralnymi .

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.

Twoje komponenty Lua byłyby całkowicie dynamiczne (ten sam artykuł powyżej powinien dać ci wyobrażenie o tym, jak to zaimplementować). Na przykład:

// 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.
}

Teraz, ponieważ używasz dynamic, możesz używać obiektów Lua tak, jakby były prawdziwymi klasami w języku 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
źródło
Całkowicie zgadzam się z akapitem pierwszym, często wykonuję swoją pracę w ten sposób. Pisanie kodu, o którym wiesz, że może być konieczne jego ponowne zaimplementowanie w języku niższego poziomu, jest jak zaciągnięcie pożyczki WIEDZĄ, że będziesz musiał zapłacić za odsetki. Projektanci IT często biorą pożyczki: jest to dobre, gdy wiedzą, że to zrobili i utrzymują swoje długi pod kontrolą.
FxIII
2

Wolę zrobić coś przeciwnego: napisz wszystko za pomocą dynamicznego języka, a następnie wyśledzić wąskie gardła (jeśli istnieją). Po znalezieniu możesz selektywnie zaimplementować funkcjonalność za pomocą C # lub (raczej) C / C ++.

Mówię ci to głównie dlatego, że staramy się ograniczyć elastyczność twojego systemu w części C #; w miarę jak system się komplikuje, Twój „silnik” C # zacznie wyglądać jak dynamiczny interpreter, prawdopodobnie nie najlepszy. Pytanie brzmi: czy chcesz zainwestować większość czasu w pisanie ogólnego silnika C # czy przedłużyć swoją grę?

Jeśli twoim celem jest drugi, jak sądzę, prawdopodobnie wykorzystasz swój czas, skupiając się na mechanice gry, pozwalając doświadczonemu tłumaczowi na uruchomienie dynamicznego kodu.

FxIII
źródło
Tak, nadal istnieje niejasny strach przed niską wydajnością niektórych podstawowych systemów, jeśli zrobię wszystko za pomocą skryptów. W takim przypadku musiałbym przenieść tę funkcjonalność z powrotem do C # (lub cokolwiek innego) ... więc potrzebowałbym dobrego sposobu na komunikację z systemem komponentów po stronie C #. To jest podstawowy problem tutaj: tworzenie systemu komponentów, którego mogę używać równie dobrze zarówno z C #, jak i skryptu.
Mario
Myślałem, że C # jest używany jako rodzaj „przyspieszonego skryptowego” środowiska dla rzeczy takich jak C ++ lub C? W każdym razie, jeśli nie jesteś pewien, w jakim języku się skończysz, po prostu zacznij pisać logikę w Lua i C #. Założę się, że w końcu będziesz szybszy w C #, ze względu na zaawansowane narzędzia edytora i funkcje debugowania.
Imi
4
+1 Zgadzam się z tym z innego powodu - jeśli chcesz, aby twoja gra była rozszerzalna, powinieneś jeść własne psie pożywienie: możesz skończyć z integracją silnika skryptowego tylko po to, by przekonać się, że twoja potencjalna społeczność modująca nie jest w stanie nic zrobić to.
Jonathan Dickinson