Szukam wglądu w projektowanie programów do atakowania i typów ataków w grze

14

Zaczynam więc wprowadzać atak na naszą przestrzeń 2D RTS (jest to Unity, więc jest sterowane komponentowo). Początkowo było tak proste, jak „wróg w zasięgu, zadane obrażenia”. Będzie jednak wiele „rodzajów” broni / ataków związanych z ich konkretnym statkiem lub strukturą. Jak również inne czynniki związane z przeszłymi obrażeniami, takie jak rodzaj obrażeń i ewentualnie bezwładność w przyszłości.

Czy mielibyście każdy typ jednostki i struktury, który ma swój własny typ ataku? Oznacza to, że tworzysz skrypt dla każdej jednostki / struktury, która określa jego typ ataku, obrażenia, efekty, zasięg, cząstki, duszki ... itd. I dołączasz jako element?

Lub stwórz skrypt, który definiuje typ ataku, skrypt dla typu pocisku powiązanego z tym ... itd. A następnie rozszerz je i zmodyfikuj dla każdej jednostki, dołączając każdy skrypt do jednostki / struktury.


Mam nadzieję, że mam jakiś sens, tak długo się nad tym zastanawiam. Nie jestem pewien, czy rozwiązuję problem, czy po prostu wymyślam własne problemy i wkopuję się w dziurę.

Jeśli masz grę, która może mieć wiele rodzajów ataków, które mogą, ale nie muszą ograniczać się do konkretnej jednostki / struktury, w jaki sposób projektujesz strukturę, która łączy ją z konkretnymi jednostkami / strukturami w środowisku projektowania opartym na komponentach ?

Jeśli nie jest to wystarczająco jasne, daj mi znać.

Edycja: Świetne odpowiedzi, dziękuję.

Rozszerzone pytanie:

Wydaje się, że odpowiedzi różnią się od „każdy obiekt może mieć własny skrypt atakujący” do „Mają typy ataków jako własne skrypty i przypisują je do każdego obiektu, aby uzyskać rozwiązanie wielokrotnego użytku”. Powiedzmy, że mam atak „blasterowy”, strzela on z pewnym czerwonym pociskiem z określoną prędkością. Obrażenia, szybkostrzelność i rozmiar pocisku zależą od tego, czy jednostka go wystrzeli. Czy lepiej jest po prostu wykonać skrypt ataku dla tej jednostki, czy spróbować zmodyfikować „atak blasterowy”, tak aby pasował do celu każdej jednostki, która chce z niej skorzystać?

Douglas Gaskell
źródło
1
Ogólne pomysły dotyczące programowania gier można znaleźć w pełnej specyfikacji RPG FFV - gamefaqs.com/snes/588331-final-fantasy-v/faqs/30040
Code Whisperer

Odpowiedzi:

12

Cóż, szczerze mówiąc, nie jestem ekspertem w tej dziedzinie, ale ... Myślę, że to zależy od tego, jak skomplikowane i różnorodne są ataki. Ponieważ jest to RTS, zgaduję, że będziesz mieć około 10-50 różnych jednostek lub struktur z własnymi typami ataku.

Opcja 1: Jeśli istnieje stosunkowo niewielka liczba jednostek, które będą miały nieco podobne ataki, po prostu umieściłbym wszystko w jednym dużym skrypcie i zdefiniował parametry używane w inspektorze.

Opcja 2: Jeśli z drugiej strony wyobrażasz sobie dużą liczbę typów ataków o odmiennym zachowaniu, możesz wszystko rozbić, aby każda jednostka i budynek otrzymały własny unikalny skrypt ataku. Myślę, że jeśli to zrobisz, możesz utworzyć skrypt pomocnika, który definiuje często używane fragmenty kodu, z których wiele indywidualnych skryptów może pobrać. W ten sposób nie będziesz musiał przepisywać wszystkiego i będziesz wiedział, gdzie to wszystko jest.

Opcja 3: To, czego prawdopodobnie nie powinieneś robić, to mieć pewne grupy skryptów współużytkujących jednostki, to prawdopodobnie wprowadzi cię w błąd i stanie się bałaganem, jeśli kod potrzebny do ataku jest w 10 różnych skryptach.

Tutaj narysowałem ci zdjęcie.

wprowadź opis zdjęcia tutaj

Mir
źródło
2
Dziękuję Ci bardzo za odpowiedź. Z jakiegoś powodu zacząłem skłaniać się ku opcji 3 i trudno mi było znaleźć sposób na jej uzasadnienie. Prawdopodobnie pójdę drugą drogą, każda jednostka otrzymuje własny niestandardowy skrypt atakujący ze wspólnym kodem udostępnianym przez atakowanie wspólnego kodu jako komponentu każdej jednostki / budynku. Dziękuję, nie jestem pewien, dokąd zmierzałem w myślach, które doprowadziły mnie do opcji 3. Zostawiam to otwarte, dopóki nie wstaję rano, na wypadek, gdyby pojawiły się inne plakaty, które chcą wejść.
Douglas Gaskell
Nie ma problemu, to nie jest ostateczna odpowiedź, ale mam nadzieję, że to pomoże.
Mir
1
Możesz hybrydyzować 1 i 2, umieszczając podobne ataki w jednym dużym skrypcie i oddzielić odmienne ataki
maniak ratchet
4
Jestem zaskoczony, że # 3 jest zalecane? Czy nie jest sens klas modułowych / ogólnych, aby każda jednostka nie musiała definiować własnego typu? Jeśli gra jest RTS, a obrażenia oblężnicze (zwykle „duży zasięg”) są rodzajem obrażeń, warto zdefiniować je raz i mieć do dyspozycji wiele jednostek w stylu artyleryjskim, wykonując zadawane obrażenia, tak aby w przypadku obrażeń oblężniczych kiedykolwiek musiałeś się denerwować (przywrócić równowagę), musiałbyś zaktualizować tylko jedną klasę?
HC_
1
"Here, I drew you a picture."przypomniał mi o tym
FreeAsInBeer
4

Nie wiem dużo o Unity i od jakiegoś czasu nie tworzyłem gier, więc dam ci ogólną odpowiedź programową na to pytanie. Swoją odpowiedź oparłem na wiedzy, którą posiadam na temat systemów encja-komponent, gdzie encja jest liczbą powiązaną z N wieloma komponentami, komponent zawiera tylko dane, a system działa na zestawach komponentów powiązanych z ten sam byt.

Twoja przestrzeń problemowa jest następująca:

  • Istnieje wiele sposobów atakowania wroga w grze jako całości.
  • Każdy statek, struktura itp. Może mieć wiele sposobów ataku (każdy określony w jakiś sposób)
  • Każdy atak może mieć własne efekty cząsteczkowe.
  • Atak musi uwzględniać niektóre czynniki (takie jak na przykład bezwładność lub zbroja), które są obecne na celu i na użytkowniku.

Skonstruowałbym rozwiązanie w następujący sposób:

  • Atak ma identyfikator - może to być ciąg znaków.
  • Jednostka „wie”, że może użyć ataku (na podstawie identyfikatora ataku).
  • Gdy jednostka używa ataku, odpowiedni element wyświetlania jest dodawany do sceny.
  • Masz pewną logikę, która wie o celu ataku, atakującym i używanym ataku - ta logika powinna decydować o tym, ile obrażeń wyrządzasz (i mieć dostęp do bezwładności lub cokolwiek z obu podmiotów).

Ważne jest, aby punkt kontaktu między atakami a bytami był jak najcieńszy - dzięki temu Twój kod będzie nadawał się do ponownego użycia i nie będziesz musiał wymyślać zduplikowanego kodu dla każdego innego rodzaju jednostki, która używa tego samego rodzaju ataku . Innymi słowy, oto pseudo-kod JavaScript, który da ci pomysł.

// components
var bulletStrength = { strength: 50 };
var inertia = { inertia: 100 };
var target = { entityId: 0 };
var bullets = {};
var entity = entityManager.create([bulletStrength, inertia, target, bullets]);

var bulletSystem = function() {
  this.update = function(deltaTime, entityId) {
    var bulletStrength = this.getComponentForEntity('bulletStrength', entityId);
    var targetComponent = this.getComponentForEntity('target', entityId);
    // you may instead elect to have the target object contain properties for the target, rather than expose the entity id
    var target = this.getComponentForEntity('inertia', targetComponent.entityId);

    // do some calculations based on the target and the bullet strength to determine what damage to deal
    target.health -= ....;
  }
};

register(bulletSystem).for(entities.with(['bullets']));

Niestety ta odpowiedź jest nieco „wodnista”. Mam tylko pół godziny przerwy na lunch i ciężko jest coś wymyślić, nie wiedząc w pełni o Unity :(

Dan Pantry
źródło
3

Kiedy jednostka / struktura / broń atakuje, prawdopodobnie stworzyłbym Atak (podzielony na wszystkie twoje zabawne szczegóły), który zabierze atakującego i obrońcę (lub obrońców). Atak może następnie wchodzić w interakcje z celem / obrońcą (spowolnienie, trucizna, obrażenia, zmiana stanu), sam się narysować (promień, promień, kula) i pozbyć się po zakończeniu. Mogę przewidzieć pewne problemy, takie jak wielokrotne ataki trucizną, więc być może twoje cele zaimplementują interfejs do uszkodzenia, z którym atak wchodzi w interakcję, ale myślę, że jest to praktyczne, modularne i elastyczne podejście do zmiany.

Rozszerzony Answer
To jak będę zbliżać się do ataku blaster z tym podejściem . Pozwolę innym odpowiedzieć za siebie.

Chciałbym, aby moje jednostki zaimplementowały interfejs lub klasę IAttacker z podstawowymi statystykami / metodami ataku. Kiedy IAttacker atakuje IDamageable, tworzy swój konkretny Atak, przekazując siebie i swój cel (IAttacker i IDamageable, lub może kolekcję IDamageable). Atak pobiera potrzebne statystyki od IAttackera (aby uniknąć zmian podczas aktualizacji lub czegoś podobnego - nie chcemy, aby atak zmieniał statystyki po tym, jak już został uruchomiony), a jeśli potrzebuje specjalistycznych statystyk, rzuca IAttacker na jego potrzebny typ (np. IBlasterAttacker) i w ten sposób uzyskuje specjalistyczne statystyki.

Stosując to podejście, BlasterAttacker musi tylko utworzyć BlasterAttack, a BlasterAttack zajmie się resztą. Możesz podklasować BlasterAttack lub utworzyć osobny FastBlasterAttacker, MegaBlasterAttacker, SniperBlasterAttacker itp., A kod ataku dla każdego z nich jest taki sam (i być może odziedziczony po BlasterAttack): Utwórz BlasterAttack i przekaż mnie i mój cel (-y). BlasterAttack obsługuje szczegóły .

ricksmt
źródło
Zasadniczo jednostka dziedziczy po interfejsie IAttackera (już to mam), a dla „wroga” istnieje interfejs możliwy do wykrycia przez ID (także to mieć). Gdy atakujący atakuje, wywoływany jest BlasterAttack (lub klasa pochodna). Ten „atak” pobierze potrzebne dane z IAttackera i zastosuje go na IDamageable, gdy pocisk trafi? Czy sam pocisk zawiera klasę BlasterAttack, więc po wystrzeleniu nie ma już wpływu na zmiany w IAttackerze i może zadawać obrażenia / efekty na IDamageable, tylko jeśli jego pocisk rzeczywiście trafi.
Douglas Gaskell
Gdy powiesz „Nazywa się BlasterAttack (lub klasa pochodna)”, powiedziałbym, że utworzono BlasterAttack. Ta nowo utworzona instancja BlasterAttack reprezentuje wiązkę (lub pocisk, promień lub cokolwiek innego), więc jest to pocisk. BlasterAttack kopiuje wszelkie potrzebne statystyki z IAttackera i obiektów możliwych do zidentyfikowania: pozycje, statystyki atakującego itp. BlasterAttack następnie śledzi własną pozycję i, jeśli ma to zastosowanie, zadaje obrażenia „kontaktowi”. Musisz dowiedzieć się, co zrobić, jeśli nie trafi lub osiągnie swój cel (stara pozycja celu). Spalić ziemię? Znikać? Twoja decyzja.
ricksmt
W przypadku Ataku obszarowego możesz chcieć uzyskać dostęp do globalnej kolekcji jednostek (wroga), ponieważ kto jest w zasięgu, a kto jest poza zasięgiem, może zmieniać pomiędzy ogniem i uderzeniem. Oczywiście podobny argument można wysunąć dla BlasterAttack: nie trafiasz w swój pierwotny cel, ale trafiasz w faceta za nim. Moją jedyną obawą jest to, że możesz mieć wiele Ataków powtarzających się przez wielu Wrogów próbujących dowiedzieć się, czy i co trafili. To problem z wydajnością.
ricksmt
Ach, to ma sens. W przypadku nieudanego ataku pocisk będzie miał swój wcześniej ustawiony zasięg / czas życia. Jeśli uderzy w coś innego przed końcem tego życia, otrzyma odniesienie do dowolnego obiektu, który posiada sztywne ciało, z którym zderzy się, i w ten sposób zostaną zadane obrażenia. W rzeczywistości tak działają wszystkie pociski, nie wiedzą one, „do czego” zmierzają, tylko to, że podróżują (z wyjątkiem pocisków naprowadzających podobnych do pocisków). Efekty AEO mogą po prostu włączyć zderzacz kul w miejscu docelowym i uzyskać wszystkie znajdujące się w nim obiekty. Dzięki za pomoc.
Douglas Gaskell
Nie ma problemu. Cieszę się, że mogłem. Zapomniałem, że Unity ułatwia wszystkie te kolizje.
ricksmt