Wykonywanie ulepszeń w systemie opartym na komponentach

29

Zaczynam naprawdę skupiać się na projektowaniu opartym na komponentach. Nie wiem, jaki jest „właściwy” sposób, aby to zrobić.

Oto scenariusz. Gracz może założyć tarczę. Tarcza jest rysowana jako bąbel wokół gracza, ma osobny kształt kolizji i zmniejsza obrażenia, które gracz otrzymuje od efektów obszarowych.

Jak zbudowana jest taka tarcza w grze opartej na komponentach?

Mylę się, że tarcza ma oczywiście trzy elementy z nią związane.

  • Redukcja uszkodzeń / filtrowanie
  • Duszek
  • Zderzacz.

Co gorsza, różne odmiany tarczy mogą mieć jeszcze więcej zachowań, z których wszystkie mogą być składnikami:

  • zwiększyć maksymalne zdrowie gracza
  • regeneracja zdrowia
  • odchylenie pocisku
  • itp

  1. Czy zastanawiam się nad tym? Czy tarcza powinna być po prostu super elementem?
    Naprawdę uważam, że to zła odpowiedź. Więc jeśli uważasz, że to jest właściwa droga, proszę wyjaśnij.

  2. Czy tarcza powinna być własną jednostką, która śledzi lokalizację gracza?
    Może to utrudnić wdrożenie filtrowania uszkodzeń. Trochę też zaciera linie między dołączonymi komponentami i bytami.

  3. Czy tarcza powinna być komponentem, który mieści inne komponenty?
    Nigdy czegoś takiego nie widziałem ani nie słyszałem, ale może jest to powszechne i po prostu nie jestem jeszcze wystarczająco głęboki.

  4. Czy tarcza powinna być tylko zestawem elementów dodawanych do odtwarzacza?
    Prawdopodobnie z dodatkowym komponentem do zarządzania innymi, np. Aby można je wszystkie usunąć jako grupę. (niechcący zostawić komponent redukcji obrażeń, teraz byłoby fajnie).

  5. Coś innego, co jest oczywiste dla kogoś z większym doświadczeniem w komponentach?

deft_code
źródło
Pozwoliłem sobie na uszczegółowienie twojego tytułu.
Tetrad,

Odpowiedzi:

11

Czy tarcza powinna być własną jednostką, która śledzi lokalizację gracza? Może to utrudnić wdrożenie filtrowania uszkodzeń. Trochę też zaciera linie między dołączonymi komponentami i bytami.

Edycja: Myślę, że nie ma wystarczającej liczby „autonomicznych zachowań” dla wydzielonej istoty. W tym konkretnym przypadku tarcza podąża za celem, działa dla celu i nie przeżywa celu. Chociaż zwykle zgadzam się, że nie ma nic złego w koncepcji „obiektu tarczy”, w tym przypadku mamy do czynienia z zachowaniem, które dobrze pasuje do komponentu. Ale jestem także zwolennikiem czysto logicznych bytów (w przeciwieństwie do pełnowymiarowych systemów bytów, w których można znaleźć komponenty Transformacji i Renderingu).

Czy tarcza powinna być komponentem, który mieści inne komponenty? Nigdy czegoś takiego nie widziałem ani nie słyszałem, ale może jest to powszechne i po prostu nie jestem jeszcze wystarczająco głęboki.

Zobacz to z innej perspektywy; dodanie komponentu dodaje również inne komponenty, a po usunięciu również dodatkowe komponenty znikają.

Czy tarcza powinna być tylko zestawem elementów dodawanych do odtwarzacza? Prawdopodobnie z dodatkowym komponentem do zarządzania innymi, np. Aby można je wszystkie usunąć jako grupę. (niechcący zostawić komponent redukcji obrażeń, teraz byłoby fajnie).

Może to być rozwiązanie, promuje ponowne użycie, jednak jest również bardziej podatne na błędy (na przykład w przypadku wspomnianego problemu). To niekoniecznie jest złe. Możesz znaleźć nowe kombinacje zaklęć z próbą i błędem :)

Coś innego, co jest oczywiste dla kogoś z większym doświadczeniem w komponentach?

Zamierzam trochę rozwinąć.

Uważam, że zauważyłeś, że niektóre komponenty powinny mieć pierwszeństwo bez względu na to, kiedy zostały dodane do bytu (to również odpowiada na inne pytanie).

Zakładam również, że używamy komunikacji opartej na wiadomościach (dla celów dyskusji jest to w tej chwili tylko abstrakcja wywołania metody).

Za każdym razem, gdy komponent ekranowy jest „instalowany”, programy obsługi komunikatów komponentu ekranowego są łączone w określonej (wyższej) kolejności.

Handler Stage    Handler Level     Handler Priority
In               Pre               System High
Out              Invariant         High
                 Post              AboveNormal
                                   Normal
                                   BelowNormal
                                   Low
                                   System Low

In - incoming messages
Out - outgoing messages
Index = ((int)Level | (int)Priority)

Komponent „stats” instaluje moduł obsługi komunikatów „uszkodzenia” w indeksie In / Invariant / Normal. Za każdym razem, gdy odbierany jest komunikat „obrażenia”, zmniejsz HP o jego „wartość”.

Dość standardowe zachowanie (wprowadzające pewną naturalną odporność na obrażenia i / lub cechy rasowe, cokolwiek).

Komponent tarczy instaluje moduł obsługi komunikatów „uszkodzenia” o indeksie In / Pre / High.

Every time a "damage" message is received, deplete the shield energy and substract
the shield energy from the damage value, so that the damage down the message
handler pipeline is reduced.

damage -> stats
    stats
        stats.hp -= damage.value

damage -> shield -> stats
    shield
        if(shield.energy) {
            remove_me();
            return;
        }
        damage.value -= shield.energyquantum
        shield.energy -= shield.energyquantum;

     stats
        stats.hp -= damage.value

Widać, że jest to dość elastyczne, choć wymagałoby to starannego planowania podczas projektowania interakcji komponentów, ponieważ trzeba będzie określić, w której części obsługi zdarzeń komunikatów komponentu potoku obsługi komunikatów są zainstalowane.

Ma sens? Daj mi znać, czy mogę dodać więcej szczegółów.

Edycja: dotyczy instancji wielu komponentów (dwóch komponentów pancerza). Możesz albo śledzić całkowitą liczbę wystąpień tylko w jednym wystąpieniu encji (to jednak zabija stan poszczególnych składników) i po prostu dodawać procedury obsługi zdarzeń wiadomości lub upewnić się, że kontenery komponentów pozwalają na zduplikowanie typów komponentów z góry.

Raine
źródło
Odpowiedziałeś „Nie” na pierwsze pytanie bez podania przyczyny. Nauczanie innych polega na pomaganiu im w zrozumieniu uzasadnienia każdej decyzji. IMO, fakt, że w RL pole siłowe byłoby oddzielnym „bytem fizycznym” od własnego ciała, wystarcza, aby pozwolić mu być oddzielnym bytem w kodzie. Czy możesz zasugerować dobre powody, aby zasugerować, dlaczego wybranie tej trasy jest złe?
Inżynier
@Nick, w żadnym wypadku nie próbuję nikomu niczego uczyć, a raczej dzielę się tym, co wiem na ten temat. Zamierzam jednak dodać uzasadnienie tego „nie”, które, mam nadzieję, usunie ten nieprzyjemny komentarz :(
Raine
Twój punkt autonomii ma sens. Ale zauważasz: „w tym przypadku mamy do czynienia z zachowaniem”. Prawda - zachowanie obejmujące całkowicie oddzielny obiekt fizyczny (kształt zderzenia tarczy). Dla mnie jedna istota wiąże się z jednym ciałem fizycznym (lub złożonym zestawem ciał połączonych np. Przez stawy). Jak to pogodzisz? Z mojej strony czułbym się niekomfortowo dodając „atrapę” urządzenia fizycznego, które aktywowałoby się tylko wtedy, gdy gracz użyje tarczy. IMO nieelastyczne, trudne do utrzymania we wszystkich podmiotach. Rozważ też grę, w której pasy tarcz utrzymują tarcze nawet po śmierci (Diuna).
Inżynier
@Nick, większość systemów encji ma zarówno elementy logiczne, jak i graficzne, więc w tym przypadku posiadanie encji na osłonę jest absolutnie uzasadnione. W czysto logicznych systemach jednostek „autonomia” jest iloczynem złożoności obiektu, jego zależności i czasu życia. Ostatecznie wymóg jest najważniejszy - a biorąc pod uwagę, że nie ma prawdziwego konsensusu co do tego, czym jest system bytu, jest mnóstwo miejsca na rozwiązania dostosowane do projektu :)
Raine
@def_kod, daj mi znać, czy mogę poprawić swoją odpowiedź.
Raine
4

1) Czy zastanawiam się nad tym? Czy tarcza powinna być po prostu super elementem?

Może zależy od tego, jak chcesz, aby twój kod był wielokrotnego użytku i czy ma to sens.

2) Czy tarcza powinna być własną jednostką, która śledzi lokalizację gracza?

Nie, chyba że ta tarcza jest jakimś stworzeniem, które może na pewnym etapie chodzić niezależnie.

3) Czy osłona powinna być elementem składającym się z innych elementów?

To brzmi jak istota, więc odpowiedź brzmi „nie”.

4) Czy tarcza powinna być tylko zestawem elementów dodawanych do odtwarzacza?

Jest to prawdopodobne.

„Redukcja uszkodzeń / filtrowanie”

  • funkcjonalność elementu osłony rdzenia.

„A duszek”

  • czy istnieje powód, dla którego nie można dodać innego SpriteComponent do encji postaci (innymi słowy więcej niż jeden komponent określonego typu na encję)?

„Zderzacz”

  • czy na pewno potrzebujesz innego? To zależy od twojego silnika fizyki. Czy możesz wysłać wiadomość do ColliderComponent postaci i poprosić ją o zmianę kształtu?

„Zwiększ maksymalne zdrowie gracza, regenerację zdrowia, odchylenie pocisku itp.”

  • inne artefakty mogą to zrobić (miecze, buty, pierścienie, zaklęcia / mikstury / odwiedzanie świątyń itp.), więc powinny to być komponenty.
Legowisko
źródło
3

Tarcza, jako istota fizyczna , nie różni się niczym od żadnej innej istoty fizycznej , np. Drona, który krąży wokół ciebie (i który w rzeczywistości mógłby być rodzajem tarczy!). Zrób więc tarczę oddzielnym logicznym bytem (pozwalającym na przechowywanie własnych komponentów).

Daj swojej tarczy kilka komponentów: komponent fizyczny / przestrzenny reprezentujący jej formę kolizji oraz komponent DamageAffector, który zawiera odniesienie do jakiegoś bytu, któremu odniesie zwiększone lub zmniejszone obrażenia (np. Postać gracza) za każdym razem, gdy jednostka przytrzymanie ObrażeńAffektor otrzymuje obrażenia. Zatem twój gracz otrzymuje obrażenia „przez proxy”.

Ustaw pozycję elementu osłony na pozycję gracza przy każdym tyknięciu. (Napisz klasę komponentów wielokrotnego użytku, która to robi: pisz raz, używaj wiele razy).

Trzeba będzie utworzyć element osłony, np. po zebraniu bonusu. Używam ogólnej koncepcji o nazwie Emiter, która jest rodzajem komponentu encji, który odradza nowe encje (zwykle za pomocą EntityFactory, do którego się odwołuje). To, gdzie zdecydujesz się zlokalizować emiter, zależy od Ciebie - np. włóż go i włącz go, gdy zostanie on zebrany.


Czy tarcza powinna być własną jednostką, która śledzi lokalizację gracza? Może to utrudnić wdrożenie filtrowania uszkodzeń. Trochę też zaciera linie między dołączonymi komponentami i bytami.

Istnieje subtelna granica między podskładnikami logicznymi (przestrzennymi, AI, gniazdami broni, przetwarzaniem danych wejściowych itp.) A podskładnikami fizycznymi. Musisz zdecydować, po której stronie stoisz, ponieważ to silnie określa, jakiego rodzaju system jednostek posiadasz. Dla mnie podskładnik Fizyki mojego bytu obsługuje relacje fizyko-hierarchiczne (takie jak kończyny w ciele - pomyśl węzły scenegraph), podczas gdy wspomniane powyżej kontrolery logiczne są zwykle reprezentowane przez komponenty twojej istoty - a nie reprezentujące indywidualne fizyczne „urządzenia”.

Inżynier
źródło
3

Czy tarcza powinna być komponentem, który mieści inne komponenty?

Może nie mieści innych elementów, ale kontroluje żywotność podzespołów. Tak więc w pewnym szorstkim pseudo-kodzie kod klienta dodałby ten komponent „shield”.

class Shield : Component
{
    void Start() // happens when the component is added
    {
        sprite = entity.add_component<Sprite>( "shield" );
        collider = entity.add_component<Collider>( whatever );
        //etc
    }

    void OnDestroy() // when the component is removed
    {
        entity.remove_component( sprite );
        entity.remove_component( collider );
    }

    void Update() // every tick
    {
        if( ShouldRemoveSelf() ) // probably time based or something
            entity.remove_component( this );
    }
}
Tetrad
źródło
Nie jest jasne, co thisoznacza twoja odpowiedź. Czy thisodnosi się do komponentu Tarcza, czy miałeś na myśli byt, który używa tarczy, jego rodzic? Zamieszanie może być moją winą. „Oparte na komponentach” jest dość niejasne. W mojej wersji encji opartych na komponentach encja jest po prostu kontenerem komponentowym z minimalną własną funkcjonalnością (nazwa obiektu, znaczniki, przesyłanie komunikatów itp.).
deft_code
Byłoby mniej mylące, gdybym użył gameObjectczy coś. Jest to odniesienie do bieżącego obiektu gry / bytu / czegokolwiek, co posiada komponenty.
Tetrad,
0

Jeśli twój system komponentów pozwala na tworzenie skryptów, komponent shield może być prawie super komponentem, który po prostu wywołuje skrypt dla parametru „efektu”. W ten sposób zachowujesz prostotę pojedynczego komponentu dla osłon i odciążysz całą logikę tego, co faktycznie robi dla niestandardowych plików skryptów, które są dostarczane do tarcz przez definicje twojej jednostki.

Robię coś podobnego dla mojego ruchomego komponentu, zawiera on pole, które jest skryptem keyreaction (podklasa skryptu w moim silniku). Ten skrypt definiuje metodę, która podąża za moim komunikatem wejściowym. jako taki mogę po prostu zrobić coś takiego w moim pliku definicji tempalte

camera template
    moveable
    {
        keyreaction = "scriptforcameramoves"

    }  

player template
    moveable
    {
        keyreaction = "scriptfroplayerkeypress"

    }  

następnie w moim ruchomym komponencie podczas rejestracji wiadomości rejestruję metodę Do skryptów (kod w C #)

Owner.RegisterHandler<InputStateInformation>(MessageType.InputUpdate, kScript.Do);

Oczywiście opiera się to na mojej metodzie Do, zgodnie ze wzorem funkcji, które przyjmuje mój RegisterHandler. W tym przypadku jego (nadawca IComponent, argument typu ref)

więc mój „skrypt” (w moim przypadku również skompilowane środowisko wykonawcze w języku C #) definiuje

 public class CameraMoveScript : KeyReactionScript
{
 public override void Do(IComponent pSender, ref InputStateInformation inputState)
 {
    //code here
 }
}

i moja podstawowa klasa KeyReactionScript

public class KeyReactionScript : Script
{
      public virtual void Do(IComponent pSender, ref InputStateInformation inputState);
}

następnie później, gdy składnik wejściowy wysyła komunikat typu MessageTypes.InputUpdate z typem jako takim

 InputStateInformation myInputState = InputSystem.GetInputState();
 SendMessage<InputStateInformation>(MessageTypes.InputUpdate, ref myInputState);

Metoda w skrypcie powiązanym z tą wiadomością i typem danych zostanie uruchomiona i obsłuży całą logikę.

Kod jest dość specyficzny dla mojego silnika, ale logika i tak powinna działać. Robię to dla wielu typów, aby struktura komponentu była prosta i elastyczna.

exnihilo1031
źródło