Najlepszy sposób na zarządzanie wydarzeniami w grze?

13

Pracuję nad grą, w której od czasu do czasu muszą się zdarzyć niektóre wydarzenia w grze. Dobrym przykładem byłby samouczek. Rozpoczynasz grę, aw kilku momentach gry następuje zdarzenie:

  • Spotkasz pierwszego wroga, gra się zatrzyma i otrzymasz wyjaśnienie, jak go zabić.
  • Zabiłeś pierwszego wroga, otrzymasz wiadomość „dobrej roboty”.
  • Zyskujesz nowy przedmiot, menu z wyskakującymi statystykami przedmiotów.
  • itd itd.

Gra, nad którą pracuję, to łamigłówka, w której zasady są prawie takie same, więc wydaje się nieefektywne zakodowanie wszystkich tych zdarzeń na osobnych poziomach.

Czy powinienem jakoś zdefiniować te zdarzenia w zewnętrznym źródle, takim jak XML? Następnie napisać interpreter, który odczytuje XML i określa wymagania dotyczące zdarzeń na poziomie? Nie jestem pewien, jak mógłbym zdefiniować zdarzenie, które powinno nastąpić, gdy zabiłeś na przykład dwóch wrogów.

Żeby było jasne, nie szukam najlepszego języka programowania ani języka skryptowego do tego, ale więcej najlepszej metody radzenia sobie z tym.

Dzięki!


Edycja: Drugi przykład, ponieważ moje pytanie było dość trudne do zrozumienia:

Problem, który mam, polega na tym, by w grze wykonać kilka dodatkowych działań w taki sam sposób. Podobnie jak w bitwie RPG, każdy ma swoją kolej, wybiera umiejętność itp. - zawsze jest tak samo. Ale co, jeśli byłby przypadek, w którym chciałbym wyświetlić przerywnik gdzieś pomiędzy. Modyfikowanie całej struktury gry w zmienionej klasie bojowej z włączoną przerywnikiem wydaje się bardzo nieefektywne. Zastanawiam się, jak to się zwykle robi.

omgnoseat
źródło
8
Nie próbuj zbytnio uogólniać, na przykład tutoriale są bardzo specyficzne i zawierają wiele różnych wyzwalaczy / zdarzeń. Nie ma nic złego w kodowaniu / skryptowaniu.
Maik Semder
1
@ Maik Jeśli umieścisz to w odpowiedzi Id +1 to .. Proste i rozwiązane jest lepsze niż ładne każdego dnia.
James
Drugi przykład pokazuje, że abstrakcyjny system przesyłania wiadomości byłby wielką wygraną. W samouczku możesz po prostu zaprogramować rzeczy, ponieważ zdarzają się one tylko raz na początku, ale w przypadku wydarzeń trwających, które mogą się zdarzyć w dowolnym momencie przez cały czas gry, to coś innego.
jhocking
To wciąż trochę niejasne, proszę wymienić co najmniej 3 wyzwalacze dla 3 różnych przerywników. ogólnie trudno jest odpowiedzieć. Zasadniczo musisz znaleźć wspólny wzór, aby zrozumieć, jak najlepiej go wdrożyć.
Maik Semder
Co chcesz? Chcesz wstrzymać działania i zrobić dodatkowe, a następnie cofnąć działania?
user712092

Odpowiedzi:

7

Zależy to w dużej mierze od tego, jak zdarzenia są faktycznie komunikowane między obiektami w grze. Na przykład, jeśli używasz centralnego systemu przesyłania wiadomości, możesz mieć moduł samouczka, który nasłuchuje określonych wiadomości i tworzy wyskakujące okienka samouczka, gdy tylko usłyszy niektóre wiadomości. Następnie możesz ustawić, jakiej wiadomości należy słuchać, wraz z wyskakującym okienkiem do wyświetlenia, w pliku XML lub innym obiekcie analizowanym przez moduł samouczka. Mając osobny obiekt samouczka, który monitoruje stan gry i wyświetla wyskakujące okienka samouczka, gdy zauważy rzeczy w grze, możesz dowolnie zmieniać obiekt samouczka bez potrzeby zmiany czegokolwiek w grze. (Czy to wzór Observer? Nie znam wszystkich wzorów).

Ogólnie rzecz biorąc, zależy to od złożoności twojego samouczka, czy warto się o to martwić. Twarde kodowanie zdarzeń w twoim kodzie i / lub poziomach nie wydaje mi się wielką przeszkodą dla zaledwie kilku podręcznych okien podręcznych. Jestem ciekawy, co dokładnie masz na myśli, co sprawia, że ​​myślisz, że będzie to nieefektywne, ponieważ wszystko, co powinieneś robić przy każdym wyzwalaczu, to po prostu wysłanie wiadomości do modułu samouczka, coś takiego jak TutorialModule.show („1st_kill”);

jhocking
źródło
Myślę, że skoro jest to gra logiczna, jego logika znajduje się w jednym miejscu na wielu poziomach i dlatego sprawdzanie, czy powinniśmy zrobić samouczek, jest czymś, co trwa cały czas. Szczerze mówiąc, jeśli jest to gra logiczna, nie sądzę, że będzie to dla niej ogromny hit, nawet jeśli nie jest to najładniejszy kod, a na koniec kod działający w grze, która jest wysyłana, jest zawsze-zawsze- 100% lepszy niż ładny kod, który nigdy nie ujrzy światła dziennego;)
James
Nigdy nie myślałem o czymś takim jak wzorzec obserwatora, brzmi jak dobre rozwiązanie. Spróbuję, dzięki :)
omgnoseat
7

Oto ograniczenia projektowe, jakie rozumiem:

  1. Podstawowy kod rozgrywki nie dba o wymagania dotyczące poziomu i nie powinien być łączony z kodem, który je obsługuje.

  2. Jednocześnie jest to podstawowy kod rozgrywki, który wie, kiedy wystąpią określone zdarzenia spełniające te wymagania (zdobycie przedmiotu, zabicie wroga itp.)

  3. Różne poziomy mają różne zestawy wymagań, które należy gdzieś opisać.

Biorąc to pod uwagę, prawdopodobnie zrobiłbym coś takiego: Po pierwsze, stwórz klasę reprezentującą poziom gry. Będzie zawierał zbiór szczegółowych wymagań, jakie ma dany poziom. Ma metody, które można wywoływać w przypadku wystąpienia zdarzeń w grze.

Podaj podstawowy kod rozgrywki jako odniesienie do obiektu na bieżącym poziomie. W przypadku wystąpienia zdarzenia rozgrywki, powie to poziom poprzez wywołanie metody na niej: enemyKilled, itemPickedUp, itd.

Wewnętrznie Levelpotrzebuje kilku rzeczy:

  • Stan, aby śledzić, które zdarzenia już miały miejsce. W ten sposób rozróżnia pierwszego zabitego wroga od innych i wie, kiedy pierwszy raz podniosłeś dany przedmiot.
  • Lista LevelRequirementobiektów opisujących konkretny zestaw celów, których potrzebujesz na tym poziomie.

Kiedy wejdziesz na poziom, stworzysz Levelwłaściwy, LevelRequirements ustawi kod gry i nadasz mu ten poziom.

Za każdym razem, gdy pojawia się zdarzenie w grze, rozgrywka przechodzi do niego Level. To z kolei oblicza dane zagregowane (całkowita liczba zabitych wrogów, zabitych wrogów tego typu itp.) Następnie przechodzi przez obiekty wymagań, podając każdemu dane zbiorcze. Wymaganie sprawdza, czy jest spełniony, a jeśli tak, odradza się odpowiednie zachowanie wynikowe (wyświetlanie tekstu samouczka itp.)

LevelRequirement w zasadzie potrzebuje dwóch rzeczy:

  1. Opis testu mającego na celu stwierdzenie, czy wymóg został spełniony. Może to być tylko funkcja, jeśli Twój język to ułatwia, w przeciwnym razie możesz modelować ją w danych. (Tj. RequirementTypeMam wyliczenie z takimi rzeczami, FIRST_KILLa potem dużą rzecz, switchktóra wie, jak sprawdzić każdy rodzaj.)
  2. Akcja do wykonania po spełnieniu wymagania.

Wciąż pozostaje pytanie, gdzie te zestawy wymagań są opisane. Możesz zrobić coś takiego jak XML lub inny format pliku tekstowego. Jest to przydatne, jeśli:

  1. Nieprogramiści będą poziomami tworzenia.
  2. Chcesz móc zmieniać wymagania bez ponownej kompilacji i / lub ponownego uruchamiania.

Jeśli tak nie jest, prawdopodobnie po prostu zbuduję je bezpośrednio w kodzie. Prostsze jest zawsze lepsze.

hojny
źródło
Pierwsze 3 punkty to bardzo dokładny opis metody, której teraz używam, imponujący! Tak, z czym najbardziej zmagam się, gdzie opisać wymaganie i jak je przełożyć na grę (ponieważ najprawdopodobniej będzie to coś zewnętrznego). Dzięki za dokładne wyjaśnienie :)
omgnoseat
5

Pomyślałem, że musisz wiedzieć, jak tworzyć te zdarzenia, a reszta postu dotyczy tego. Jeśli chcesz po prostu przechowywać te zdarzenia, skorzystaj z relacyjnej bazy danych lub opisz je tekstem i użyj języka skryptowego (zrobi to parsowanie i ocenę Ty). :)

To, co chcesz, to rozpoznać zdarzenia, które się wydarzyły (1), a następnie wykonać pewne czynności wymagane przez te zdarzenia (wydrukuj wiadomość, sprawdź naciśnięcie klawisza ...) (2). Chcesz również, aby te zdarzenia zdarzały się tylko raz (3).

Zasadniczo chcesz sprawdzić warunki, a następnie zaplanować pewne zachowanie.

Jak rozpoznać zdarzenia (1)

  • Chcesz rozpoznać zdarzenia takie jak „pierwszy napotkany wróg”, „nowy przedmiot zdobyty”
  • jeśli zdarzy się część ogólna, „ napotkany wróg ”, „ przedmiot zdobyty ” Sprawdzasz konkretną część „ pierwszy ...”, „ nowy przedmiot zdobyty”

Z czego wykonane są wydarzenia

Mówiąc bardziej ogólnie, każde takie zdarzenie składa się z:

  • warunki wstępne , sprawdzasz je
  • działania, które zostaną wykonane, gdy zostaną spełnione warunki wstępne (powiedz „Zabiłeś pierwszego wroga!”, powiedz „” wykonaj kombinacje, naciskając przyciski A i B ”, powiedz„ naciśnij 'enter', aby kontynuować ”, klawisz wymagający„ enter ”)

Jak przechowywać te wydarzenia

W niektórych strukturach danych:

  • mieć listę warunków wstępnych (ciągi znaków lub kod, jeśli piszesz go w jakimś języku wysokiego poziomu)
  • mają listę akcji (mogą to być ciągi znaków, silnik Quake używa ciągów zdarzeń)

Możesz również przechowywać go w relacyjnej bazie danych, chociaż wygląda na to, że nie jest to konieczne, jeśli chcesz stworzyć tę grę w dużych rozmiarach, być może będziesz musiał ją stworzyć.

Następnie musisz przeanalizować te ciągi / rzeczy. Lub możesz użyć jakiegoś języka skryptowego, takiego jak Python lub LUA, lub języka takiego jak LISP, wszystkie one mogą przeanalizować i wykonać dla Ciebie. :)

Jak korzystać z tych zdarzeń w pętli gry (2)

Będziesz potrzebować tych dwóch struktur danych:

  • kolejka zdarzeń (tutaj umieszczane są zdarzenia, które mają zostać uruchomione)
  • kolejka akcji (zaplanowane akcje, zdarzenia wskazują, które akcje są wykonane)

Algorytm:

  • Jeśli uznają niektóre zdarzenia „s warunki te są spełnione, umieścić go w kolejce zdarzeń
  • (3) Następnie powinieneś upewnić się, że to zdarzenie miało miejsce tylko raz, jeśli chcesz :) (na przykład z tablicą boolowską has_this_event_happened [„napotkano pierwszego wroga”])
  • (jeśli kolejka akcji jest pusta, to) Jeśli w kolejce zdarzeń znajduje się zdarzenie Umieścisz jego akcje w kolejce akcji i usuniesz go z kolejki zdarzeń
  • Jeśli w kolejce akcji jest akcja Zaczynasz robić to, czego żąda
  • Jeśli takie działanie zostanie wykonane, usuwasz je z kolejki działań

Jak samodzielnie wykonać te działania (2)

Tworzysz listę obiektów, które mają funkcję „aktualizacja”. Czasami nazywane są bytami (w silniku Quake) lub aktorami (w silniku Unreal).

  1. Obiekty te uruchamia się, gdy mają być uruchamiane w kolejce działań.
  2. obiekty te mogą być używane do innych rzeczy, takich jak niektóre inne liczniki czasu. W Quake te byty są wykorzystywane do logiki całej gry, polecam przeczytać o tym trochę materiału .

Akcja „powiedz coś”

  1. Drukujesz coś na ekranie
  2. Chcesz, aby ten komunikat pojawiał się przez kilka sekund
  3. w „aktualizacji”:
    • utwórz zmienną remove_me_after i zmniejsz ją o upływ czasu
    • gdy zmienna ma wartość 0, usuwasz tę akcję z kolejki akcji
    • Usuwasz również ten obiekt (lub planujesz jego usunięcie ...)

Działanie „wymaga klucza”

  1. To zależy od tego, jak chcesz to zrobić, ale myślę, że przekazujesz wiadomość
  2. w „aktualizacji”:
    • Sprawdzasz tylko pożądane zdarzenie naciśnięcia klawisza
    • Prawdopodobnie potrzebujesz tablicy / kolejki do przechowywania zdarzeń naciśnięcia klawisza
    • następnie możesz usunąć go z kolejki akcji i usunąć obiekt

Jakie metody się uczyć

użytkownik712092
źródło
-1 prawda, albo po prostu wywołuje funkcję, poważnie, OP chce tylko okno komunikatu, gdy spełniony zostanie określony warunek
Maik Semder
To bardzo fajnie napisać, ale nie do końca to, czego szukałem. Trudno mi było wyjaśnić, czego chcę. Dodałem drugie wyjaśnienie: Problem, który mam, polega na umieszczeniu w grze dodatkowych działań w procedurze, która zawsze jest prawie taka sama. Podobnie jak w bitwie RPG, każdy ma swoją kolej, wybiera umiejętność itp. - zawsze jest tak samo. Ale co, jeśli byłby przypadek, w którym chciałbym wyświetlić przerywnik gdzieś pomiędzy. Modyfikowanie całej struktury gry, aby przejść w zmienionej klasie bitwy z włączonym cutsene, wydaje się bardzo nieefektywne. Zastanawiam się, jak to się zwykle robi?
omgnoseat