Jakiej techniki należy użyć, aby ułatwić komunikację między XNA GameComponents (lub między komponentami dowolnego typu w grze)?

9

Zaczynam od pierwszego „właściwego” projektu gry i nieuchronnie uderzyłem w blok, próbując zdecydować, w jaki sposób komponenty gry w XNA powinny się komunikować.

Od poprzednich zdarzeń programowania GUI (Java), procedury obsługi i nasłuchiwania wydawały się być rozwiązaniem. Więc miałbym jakąś magistralę zdarzeń, która akceptuje rejestracje zdarzeń i klasy subskrybowane do tych zdarzeń, z programami obsługi zajmującymi się nimi. Na przykład (pseudokod):

class SpriteManager
    Update(){
        if(player.collidesWith(enemy)
             // create new 'PlayerCollisionEvent'
    }

class HUDManager
    onPlayerCollisionEvent(){
        // Update the HUD (reduce lives etc)
    }

Jednak nie jestem pewien konfiguracji kodu (w języku C #), który byłby wymagany do pełnego osiągnięcia tego celu. Co śledzi wydarzenia (jakiś autobus?) I jak jest zorganizowane?

Wydaje się również, że wiele mówi się o usługach gier, w których można zarejestrować GameComponent w głównej klasie Game.cs, a następnie pobrać go z dowolnego miejsca w kodzie, który ma odniesienie do głównego obiektu „Game”. Próbowałem tego z moim obiektem SpriteBatch i wydaje się to bardzo łatwe .. nie widzę jednak, aby był on tak elastyczny jak model zdarzeń.

Weźmy na przykład śmierć wroga. Chcemy zaktualizować wynik gry. Korzystając z usług, mogę uzyskać odniesienie do mojego obiektu StateManager utworzonego w Game1 i dodanego jako usługa, a następnie ustawić „score” na nową wartość. Myślałem, że zdarzenie „onEnemyDeath”, które może być obsługiwane w różny sposób przez wiele klas, ale zainicjowane przez 1 linię kodu w odpowiedniej sekcji „wykrywanie śmierci wroga”, byłoby lepsze niż indywidualne rzucanie każdego wymaganego GameComponent, a następnie wywoływanie metody są wymagane.

A może są to gorsze strategie niż coś innego?

Zdaję sobie sprawę, że jest to częściowo moja słaba znajomość języka C #, podobnie jak paradygmaty komunikacji w grach, ale naprawdę chciałbym, aby ta podstawowa rzecz była właściwa.

Aktualizacja

Patrząc na Usługi, jestem bardziej szczegółowy, jestem mniej przekonany - w zasadzie przekazuje zmienną globalną (z tego, co rozumiem).

Aktualizacja 2

Po zapoznaniu się z tym podstawowym samouczkiem na temat obsługi zdarzeń i testowania przykładowego kodu wydaje się, że zdarzenia byłyby logicznym wyborem dla tego, co omawiam. Ale nie mogę tego wiele użyć w próbkach, które widziałem. Czy jest jakiś oczywisty powód, dla którego nie należy?

kodowania
źródło
Polecam najpierw wypróbować FlatRedBall. Znajduje się na XNA i sprawia, że ​​życie jest naprawdę łatwe.
ashes999 16.10.11
3
Chciałbym naprawdę zrozumieć podstawy tego, co się dzieje przed skokiem do silnika.
codinghands
Myślę, że twoją porażką tutaj jest naprawdę brak informacji o C #. C # zwykle używa zdarzeń, aby ułatwić komunikację między klasami. To naprawdę zależy od ciebie, jak to zrobić - XNA zapewnia klasę SpriteBatch, a większość pracy zależy od ciebie. Silnik da Ci solidne wyobrażenie o tym, jak rzeczy działają na wyższym poziomie przed zanurzeniem się w szczegóły.
ashes999 16.10.11
1
Zgadzam się do pewnego stopnia, ale z tego, co przeczytałem, większość międzyklasowych połączeń między GameComponents można wykonać za pomocą Usług. Jestem przyzwyczajony do używania wydarzeń. Nie jestem pewien, która z nich jest najlepsza lub czy istnieją ważne alternatywy (singletony, klasy statyczne
udające globały

Odpowiedzi:

4

Od poprzednich zdarzeń programowania GUI (Java), procedury obsługi i nasłuchiwania wydawały się być rozwiązaniem. Więc miałbym jakąś magistralę zdarzeń, która akceptuje rejestracje zdarzeń i klasy subskrybowane do tych zdarzeń, z programami obsługi zajmującymi się nimi.

Powodem, dla którego nie znajdziesz wiele na temat magistrali zdarzeń w języku C #, jest to, że moim zdaniem nikt poza Javą nie nazywa czegoś takiego „autobusem”. Częstszymi terminami mogą być kolejka wiadomości, menedżer zdarzeń, sygnały / sloty, publikacja / subskrypcja itp.

Nie potrzebujesz żadnego z nich, aby stworzyć działającą grę, ale jeśli jest to system, z którym czujesz się komfortowo, to również ci dobrze pomoże. Niestety, nikt nie jest w stanie powiedzieć dokładnie, jak to zrobić, ponieważ wszyscy je rzucają inaczej, nawet jeśli w ogóle z nich korzystają. (Na przykład nie.) Możesz zacząć od prostego System.Collections.Generic.List<Event>dla kolejki, z różnymi zdarzeniami będącymi podklasami Zdarzenia i listą subskrybentów dla każdego typu zdarzenia. Dla każdego zdarzenia w kolejce pobierz listę subskrybentów, a dla każdego subskrybenta wywołaj handle(this_event)ją. Następnie usuń go z kolejki. Powtarzać.

Wydaje się również, że wiele mówi się o usługach gier, w których można zarejestrować GameComponent w głównej klasie Game.cs, a następnie pobrać go z dowolnego miejsca w kodzie, który ma odniesienie do głównego obiektu „Game”. Próbowałem tego z moim obiektem SpriteBatch i wydaje się to bardzo łatwe .. nie widzę jednak, aby był on tak elastyczny jak model zdarzeń.

Prawdopodobnie nie jest tak elastyczny, ale łatwiej go debugować. Zdarzenia i komunikaty są trudne, ponieważ oddzielają funkcję wywołującą od funkcji wywoływanej, co oznacza, że ​​stosy wywołań nie są strasznie pomocne podczas debugowania, akcje pośrednie mogą spowodować zerwanie czegoś, co normalnie działa, a rzeczy mogą zawieść cicho, gdy nie zostaną właściwie podłączone , i tak dalej. Wyraźna metoda „pobierz komponent i wywołaj to, czego potrzebujesz” działa dobrze, jeśli chodzi o wczesne wykrywanie błędów i posiadanie jasnego i wyraźnego kodu. To po prostu prowadzi do ściśle powiązanego kodu i wielu złożonych zależności.

Zdaję sobie sprawę, że jest to częściowo moja słaba znajomość języka C #, podobnie jak paradygmaty komunikacji w grach, ale naprawdę chciałbym, aby ta podstawowa rzecz była właściwa.

C # jest prawie identycznym językiem jak Java. Cokolwiek zrobiłeś w Javie, możesz zrobić w C #, tylko z inną składnią. Jeśli masz problem z przetłumaczeniem pojęcia, prawdopodobnie dzieje się tak dlatego, że znasz tylko jego nazwę.

Kylotan
źródło
Dziękuję @Kylotan. Z ciekawości, jakiego systemu używasz do komunikacji międzyklasowej - podejście Usług czy coś innego?
codinghands
AFAIK, szyny zdarzeń są podobne do kolejek komunikatów, ale dla zdarzeń.
ashes999 16.10.11
2
@codinghands, zwykle nie używam żadnej sformalizowanej metody komunikacji międzyklasowej. Po prostu zachowuję prostotę - jeśli jedna klasa musi wywoływać inną, zwykle albo ma już odniesienie do drugiej klasy jako członka, albo drugą klasę przekazano jako argument. Czasami jednak, gdy pracuję z silnikiem Unity, ma to podejście podobne do tego, co nazywacie podejściem „usługowym”, polegającym na tym, że szukałem obiektu według nazwy, który ma wymagany komponent, sprawdzam komponent na obiekcie, a następnie wywołaj metody dla tego komponentu.
Kylotan
@ ashes999 - Wiadomości i wydarzenia są w tym kontekście prawie takie same. Jeśli umieścisz wydarzenie w kolejce, autobusie lub czymkolwiek, to tak naprawdę przechowujesz wiadomość, która oznacza, że ​​wydarzenie miało miejsce. Podobnie umieszczenie komunikatu w kolejce oznacza, że ​​wystąpiło zdarzenie jego wygenerowania. Po prostu różne sposoby spojrzenia na asynchroniczne wywołanie funkcji.
Kylotan
Przepraszam, że nie oznaczyłem tego jeszcze jako „odebrano”, ale pomyślałem, że będzie więcej punktów widzenia, biorąc pod uwagę, że każda gra musi jakoś komunikować się między komponentami, niezależnie od języka ... Czy omówiliśmy główne możliwości?
codinghands
1

Czy próbowałeś po prostu użyć prostych zdarzeń statycznych? Na przykład, jeśli chcesz, aby twoja klasa wyników wiedziała, kiedy wróg zginął, zrobiłbyś coś takiego:

Score.cs

class Score
{
    public int PlayerScore { get; set; }

    public Score()
    {
        EnemySpaceship.Death += new EventHandler<SpaceshipDeathEventArgs>(Spaceship_Death);
    }

    private void Spaceship_Death(object sender, SpaceshipDeathEventArgs e)
    {
        PlayerScore += e.Points;
    }
}

EnemySpaceship.cs

class EnemySpaceship
{
    public static event EventHandler<SpaceshipDeathEventArgs> Death;

    public void Kill()
    {
        OnDeath();
    }

    protected virtual void OnDeath()
    {
        if (Death != null)
        {
            Death(this, new SpaceshipDeathEventArgs(50));
        }
    }
}

SpaceshipDeathEventArgs.cs

class SpaceshipDeathEventArgs : EventArgs
{
    public int Points { get; private set; }

    internal SpaceshipDeathEventArgs(int points)
    {
        Points = points;
    }
}
John Pollick
źródło
0

spróbuj użyć agregatora zdarzeń takiego jak ten

mindabyss
źródło
4
Ta odpowiedź byłaby lepsza, gdybyś bardziej szczegółowo rozwinął swoją sugestię, zamiast podawać tylko link.
MichaelHouse