Welche Technik sollte ich verwenden, um die Kommunikation zwischen XNA GameComponents (oder zwischen Komponenten jeglicher Art in einem Spiel) zu erleichtern?

9

Ich beginne mit meinem ersten "richtigen" Spielprojekt und bin unweigerlich auf einen Block gestoßen, um zu entscheiden, wie Spielkomponenten in XNA kommunizieren sollen.

Von früheren (Java) GUI-Programmierereignissen schienen Handler und Listener der richtige Weg zu sein. Ich hätte also eine Art Ereignisbus, der Ereignisregistrierungen und Klassen akzeptiert, die diese Ereignisse abonniert haben, mit Handlern, die sich darum kümmern. Zum Beispiel (Pseudocode):

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

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

Ich bin mir jedoch nicht sicher, welches Code-Setup (in C #) erforderlich ist, um dies vollständig zu erreichen. Was verfolgt Ereignisse (eine Art Bus?) Und wie ist sie aufgebaut?

Es scheint auch viel über Game Services erwähnt zu werden, wobei Sie eine GameComponent in Ihrer Hauptklasse Game.cs registrieren und sie dann von einer beliebigen Stelle in Ihrem Code abrufen können, die einen Verweis auf das Hauptobjekt 'Game' enthält. Ich habe dies mit meinem SpriteBatch-Objekt versucht und es scheint sehr einfach zu sein. Ich kann jedoch nicht sehen, dass dies so flexibel ist wie ein Ereignismodell.

Nehmen wir zum Beispiel, wenn ein Feind stirbt. Wir möchten die Spielergebnisse aktualisieren. Mithilfe von Diensten kann ich einen Verweis auf mein in Game1 erstelltes und als Dienst hinzugefügtes StateManager-Objekt abrufen und dann "Punktzahl" auf den neuen Wert setzen. Ich hätte gedacht, dass ein "onEnemyDeath" -Ereignis, das von einer Vielzahl von Klassen unterschiedlich behandelt werden kann, aber durch eine Codezeile im entsprechenden Abschnitt "Erkennung feindlicher Todesfälle" ausgelöst wird, besser wäre, als jede erforderliche GameComponent einzeln zu wirken und dann irgendetwas aufzurufen Methoden sind erforderlich.

Oder sind diese Strategien etwas anderem unterlegen?

Mir ist klar, dass dies teilweise mein schlechtes C # -Wissen ist, ebenso wie Spielkommunikationsparadigmen, aber ich würde wirklich gerne diese grundlegende Sache richtig machen.

Aktualisieren

Nachdem ich mir Services angesehen habe, bin ich weniger überzeugt - es wird im Grunde genommen eine globale Variable weitergegeben (soweit ich weiß).

Update 2

Nachdem Sie sich dieses grundlegende Tutorial zur Ereignisbehandlung und zum Testen des Beispielcodes angesehen haben, scheint es, dass Ereignisse eine logische Wahl für das sind, was ich diskutiere. Aber ich kann nicht viel davon in den Beispielen verwenden, die ich gesehen habe. Gibt es einen offensichtlichen Grund, warum man nicht sollte?

Codierungshände
quelle
Ich würde empfehlen, zuerst FlatRedBall auszuprobieren. Es sitzt auf XNA und macht das Leben wirklich einfach.
Asche999
3
Ich möchte die Grundlagen des Geschehens wirklich verstehen, bevor ich zu einem Motor springe.
Codinghands
Ich denke, Ihr Versagen hier ist wirklich ein Mangel an Informationen über C #. C # verwendet normalerweise Ereignisse, um die Kommunikation zwischen Klassen zu erleichtern. Es liegt wirklich an Ihnen, wie es geht - XNA bietet eine SpriteBatch-Klasse, und der größte Teil der Arbeit liegt bei Ihnen zu schreiben. Ein Motor gibt Ihnen eine solide Vorstellung davon, wie die Dinge auf einer höheren Ebene funktionieren, bevor Sie auf Details eingehen.
Asche999
1
Ich stimme bis zu einem gewissen Grad zu, aber nachdem ich gelesen habe, kann die Kommunikation zwischen GameComponents zwischen den Klassen über Services erfolgen. Ich bin es gewohnt, Ereignisse zu verwenden. Ich bin mir nicht sicher, welche die beste ist oder ob es gültige Alternativen gibt (Singletons, statische Klassen, die sich als Globale tarnen, wie zwei andere Methoden, die ich weggelassen habe)
Codierungshände

Antworten:

4

Von früheren (Java) GUI-Programmierereignissen schienen Handler und Listener der richtige Weg zu sein. Ich hätte also eine Art Ereignisbus, der Ereignisregistrierungen und Klassen akzeptiert, die diese Ereignisse abonniert haben, mit Handlern, die sich darum kümmern.

Der Grund, warum Sie in C # nicht viel über Eventbusse finden, ist meiner Erfahrung nach, dass niemand außerhalb von Java so etwas als "Bus" bezeichnet. Häufigere Begriffe sind Nachrichtenwarteschlange, Ereignismanager, Signale / Slots, Veröffentlichen / Abonnieren usw.

Sie brauchen keines davon, um ein funktionierendes Spiel zu erstellen, aber wenn Sie mit diesem System vertraut sind, wird es Ihnen auch hier gute Dienste leisten. Leider kann dir niemand genau sagen, wie man einen macht, weil jeder sie ein bisschen anders rollt, wenn er sie überhaupt benutzt. (Ich zum Beispiel nicht.) Sie können mit einem einfachen System.Collections.Generic.List<Event>für die Warteschlange beginnen, wobei Ihre verschiedenen Ereignisse Unterklassen von Ereignis und eine Liste von Abonnenten für jeden Ereignistyp sind. Rufen handle(this_event)Sie für jedes Ereignis in der Warteschlange die Liste der Abonnenten ab und rufen Sie sie für jeden Abonnenten auf. Dann entfernen Sie es aus der Warteschlange. Wiederholen.

Es scheint auch viel über Game Services erwähnt zu werden, wobei Sie eine GameComponent in Ihrer Hauptklasse Game.cs registrieren und sie dann von einer beliebigen Stelle in Ihrem Code abrufen können, die einen Verweis auf das Hauptobjekt 'Game' enthält. Ich habe dies mit meinem SpriteBatch-Objekt versucht und es scheint sehr einfach zu sein. Ich kann jedoch nicht sehen, dass dies so flexibel ist wie ein Ereignismodell.

Es ist wahrscheinlich nicht so flexibel, aber es ist einfacher zu debuggen. Ereignisse und Nachrichten sind schwierig, da sie die aufrufende Funktion von der aufgerufenen Funktion entkoppeln. Dies bedeutet, dass Ihre Aufrufstapel beim Debuggen nicht besonders hilfreich sind. Zwischenaktionen können dazu führen, dass etwas kaputt geht, was normalerweise funktioniert. Wenn sie nicht richtig angeschlossen sind, können die Dinge stillschweigend fehlschlagen , und so weiter. Die explizite Methode "Nehmen Sie eine Komponente und rufen Sie auf, was Sie darauf benötigen" funktioniert gut, wenn es darum geht, Fehler frühzeitig zu erkennen und klaren und expliziten Code zu haben. Es führt nur zu eng gekoppeltem Code und vielen komplexen Abhängigkeiten.

Mir ist klar, dass dies teilweise mein schlechtes C # -Wissen ist, ebenso wie Spielkommunikationsparadigmen, aber ich würde wirklich gerne diese grundlegende Sache richtig machen.

C # ist so ziemlich die gleiche Sprache wie Java. Was auch immer Sie in Java getan haben, kann in C # ausgeführt werden, nur mit unterschiedlicher Syntax. Wenn Sie Probleme beim Übersetzen eines Konzepts haben, liegt dies wahrscheinlich nur daran, dass Sie nur einen Java-spezifischen Namen dafür kennen.

Kylotan
quelle
Vielen Dank, dass Sie @Kylotan. Welches System verwenden Sie aus Neugier selbst für die Kommunikation zwischen Klassen - den Service-Ansatz oder etwas anderes?
Codinghands
AFAIK, Ereignisbusse ähneln Nachrichtenwarteschlangen, jedoch für Ereignisse.
Asche999
2
@codinghands, ich verwende normalerweise keine formalisierte Kommunikationsmethode zwischen Klassen. Ich halte die Dinge einfach - wenn eine Klasse eine andere aufrufen muss, hat sie normalerweise entweder bereits einen Verweis auf die zweite Klasse als Mitglied oder hat die zweite Klasse als Argument übergeben. Wenn ich jedoch mit der Unity-Engine arbeite, hat dies einen ähnlichen Ansatz wie der von Ihnen als "Services" bezeichnete Ansatz, bei dem ich ein Objekt mit Namen nachschlagen würde, das die von mir benötigte Komponente enthält, und die Komponente nachzuschlagen Rufen Sie dann für das Objekt Methoden für diese Komponente auf.
Kylotan
@ ashes999 - Nachrichten und Ereignisse sind in diesem Zusammenhang fast dasselbe. Wenn Sie ein Ereignis in eine Warteschlange oder einen Bus oder was auch immer stellen, speichern Sie wirklich eine Nachricht, die angibt, dass ein Ereignis stattgefunden hat. Wenn Sie eine Nachricht in eine Warteschlange stellen, bedeutet dies, dass ein Ereignis aufgetreten ist, um diese Nachricht zu generieren. Nur verschiedene Sichtweisen auf einen asynchronen Funktionsaufruf.
Kylotan
Es tut mir leid, dass ich dies noch nicht als "beantwortet" markiert habe, aber ich dachte, es gäbe mehr Gesichtspunkte, da jedes Spiel irgendwie zwischen Komponenten kommunizieren muss, unabhängig von der Sprache ... Haben wir die Hauptmöglichkeiten abgedeckt?
Codinghands
1

Haben Sie versucht, nur einfache statische Ereignisse zu verwenden? Wenn Sie beispielsweise möchten, dass Ihre Punkteklasse weiß, wann ein Feind gestorben ist, würden Sie Folgendes tun:

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
quelle
0

Versuchen Sie, einen Ereignisaggregator wie diesen zu verwenden

mindabyss
quelle
4
Diese Antwort wäre besser, wenn Sie mehr über Ihren Vorschlag herausarbeiten würden, anstatt nur den Link bereitzustellen.
MichaelHouse