Auf der Suche nach einem Einblick in das Programmierdesign für Angriffe und Angriffstypen in einem Spiel

14

Also beginne ich damit, das Angreifen in unser 2D-Weltraum-RTS einzuführen (Dies ist in Unity, es ist also komponentengetrieben). Anfänglich war es so einfach wie "Feind in Reichweite, verursachter Schaden". Es wird jedoch mehrere "Arten" von Waffen / Angriffen geben, die mit ihrem jeweiligen Schiff oder ihrer jeweiligen Struktur verbunden sind. Ebenso wie andere Faktoren, die nur den Rohschaden betreffen, wie z. B. die Schadensart und möglicherweise die Trägheit in der Zukunft.

Hättet ihr, dass jede Einheit und jeder Strukturtyp einen eigenen Angriffstyp hat? Das heißt, Sie erstellen ein Skript für jede Einheit / Struktur, das Angriffstyp, Schaden, Effekte, Reichweite, Partikel, Sprites usw. definiert. Und fügen Sie dies als Komponente hinzu?

Oder erstellen Sie ein Skript, das einen Angriffstyp definiert, ein Skript für den dazugehörigen Projektiltyp ... usw. Erweitern Sie diese und ändern Sie diese für jede Einheit, indem Sie jedes Skript an die Einheit / Struktur anhängen.


Ich hoffe, ich mache einen Sinn, ich habe so lange darüber nachgedacht, dass ich nicht sicher bin, ob ich ein Problem löse oder nur meine eigenen Probleme erfasse und mich in ein Loch grabe.

Wenn Sie ein Spiel haben, das eine Vielzahl von Angriffstypen haben kann, die möglicherweise auf eine bestimmte Einheit / Struktur beschränkt sind, wie entwerfen Sie das Framework, das diese mit den bestimmten Einheiten / Strukturen in einer komponentengesteuerten Entwurfsumgebung verknüpft ?

Wenn dies nicht klar genug ist, lassen Sie es mich wissen.

Edit: Tolle Antworten, danke.

Erweiterte Frage:

Die Antworten variieren anscheinend von "Jedes Objekt kann ein eigenes Angriffsskript haben" bis "Die Angriffstypen als eigene Skripte haben und diese für eine wiederverwendbarere Lösung jedem Objekt zuweisen". Nehmen wir an, ich habe einen "Blaster" -Angriff, er schießt mit einer bestimmten Geschwindigkeit auf ein rotes Projektil. Der Schaden, die Feuerrate und die Größe des Projektils hängen von der Einheit ab, die es abfeuert. Ist es besser, nur ein Angriffsskript für diese Einheit zu erstellen oder zu versuchen, einen "Blaster-Angriff" so zu modifizieren, dass er dem Zweck jeder Einheit entspricht, die ihn verwenden möchte?

Douglas Gaskell
quelle
1
Für allgemeine Spielprogrammierungsideen verweise ich gerne auf die vollständige Spezifikation des RPG FFV - gamefaqs.com/snes/588331-final-fantasy-v/faqs/30040
Code Whisperer

Antworten:

12

Nun, ich bin ehrlich gesagt kein Experte in diesem Bereich, aber ... ich denke, es hängt davon ab, wie komplex und abwechslungsreich Sie denken, dass die Angriffe werden. Da es sich um ein RTS handelt, werden Sie wahrscheinlich 10-50 verschiedene Einheiten oder Strukturen mit ihren eigenen Angriffsarten haben.

Option 1: Wenn es eine relativ geringe Anzahl von Einheiten gibt, die ähnliche Angriffe ausführen, würde ich einfach alles in einem großen Skript zusammenfassen und die im Inspektor verwendeten Parameter definieren.

Option 2: Wenn Sie sich andererseits eine große Anzahl von Angriffsarten mit unterschiedlichem Verhalten vorstellen, können Sie alles aufteilen, sodass jede Einheit und jedes Gebäude ihr eigenes einzigartiges Angriffsskript erhält. Ich denke, wenn Sie dies tun, möchten Sie vielleicht ein "Hilfsskript" erstellen, das häufig verwendete Codeblöcke definiert, aus denen viele der einzelnen Skripte greifen können. Auf diese Weise müssen Sie nicht alles neu schreiben und wissen, wo sich alles befindet.

Option 3: Was Sie wahrscheinlich nicht tun sollten, ist, dass bestimmte Gruppierungen von Einheiten Skripte gemeinsam nutzen. Dies wird Sie wahrscheinlich verwirren und zu einem Chaos werden, wenn sich der Code, den Sie für einen Angriff benötigen, in 10 verschiedenen Skripten befindet.

Hier habe ich dir ein Bild gezeichnet.

Bildbeschreibung hier eingeben

Mir
quelle
2
Vielen Dank für die Antwort. Aus irgendeinem Grund begann ich mich für Option 3 zu entscheiden, und es fiel mir schwer, einen Weg zu finden, dies zu rechtfertigen. Ich gehe wahrscheinlich den zweiten Weg. Jede Einheit erhält ein eigenes benutzerdefiniertes Angriffsskript, wobei gemeinsamer Code gemeinsam genutzt wird, indem der gemeinsame Code als Komponente jeder Einheit / jedes Gebäudes angegriffen wird. Ich bin mir nicht sicher, wohin ich mit dem Gedankengang gegangen bin, der mich zu Option 3 geführt hat, danke. Ich lasse dies offen, bis ich in der Früh aufstehe, falls es andere Plakate gibt, die sich einschalten wollen.
Douglas Gaskell
Kein Problem, es ist keine endgültige Antwort, aber ich hoffe, es hilft.
Mir
1
Sie können 1 und 2 hybridisieren, indem Sie ähnliche Angriffe in ein großes Skript schreiben und unterschiedliche Angriffe ausschließen
Ratchet Freak
4
Ich bin überrascht, # 3 wird dagegen empfohlen? Ist das nicht der Sinn von modularen / generischen Klassen, damit nicht jede Einheit ihren eigenen Typ definieren muss? Wenn es sich bei einem Spiel um ein RTS handelt und Belagerungsschaden (normalerweise "Langstreckenschaden") eine Schadensart ist, sollten Sie ihn einmal definieren und mehrere Einheiten im Artillerie-Stil darauf verweisen, wenn sie ihre Schadensberechnungen durchführen, damit Sie bei Belagerung Schaden erleiden Mussten Sie jemals neu ausbalanciert werden, um nur eine Klasse zu aktualisieren?
HC_
1
"Here, I drew you a picture."erinnerte mich daran
FreeAsInBeer
4

Ich weiß nicht viel über Unity und habe seit einiger Zeit keine Spieleentwicklung mehr durchgeführt. Lassen Sie mich Ihnen daher eine allgemeine Programmierantwort auf diese Frage geben. Ich habe meine Antwort auf das Wissen über Entitätskomponentensysteme im Allgemeinen gestützt, wobei eine Entität eine Zahl ist, die N vielen Komponenten zugeordnet ist, eine Komponente nur Daten enthält und ein System auf Mengen von Komponenten arbeitet, die zugeordnet sind die gleiche Einheit.

Ihr Problemraum ist dieser:

  • Es gibt mehrere Möglichkeiten, einen Gegner im gesamten Spiel anzugreifen.
  • Jedes Schiff, jede Struktur usw. kann mehrere Angriffsarten haben (jede wird auf eine bestimmte Weise bestimmt).
  • Jeder Angriff kann seine eigenen Partikeleffekte haben.
  • Der Angriff muss einige Faktoren berücksichtigen (z. B. Trägheit oder Panzerung), die auf dem Ziel und auf dem Benutzer vorhanden sind.

Ich würde die Lösung folgendermaßen strukturieren:

  • Ein Angriff hat eine Kennung - dies könnte eine Zeichenfolge sein.
  • Eine Entität "weiß", dass sie einen Angriff ausführen kann (basierend auf der Kennung des Angriffs).
  • Wenn der Angriff von der Entität verwendet wird, wird der Szene die entsprechende Anzeigekomponente hinzugefügt.
  • Sie haben eine Logik, die das Ziel des Angriffs, den Angreifer und den verwendeten Angriff kennt - diese Logik sollte entscheiden, wie viel Schaden Sie anrichten (und Zugriff auf die Trägheit oder was auch immer von beiden Entitäten haben).

Es ist wichtig, dass der Kontaktpunkt zwischen den Angriffen und den Entitäten so dünn wie möglich ist - dies hält Ihren Code wiederverwendbar und verhindert, dass Sie für jeden Entitätstyp, der denselben Angriffstyp verwendet, einen doppelten Code erstellen müssen . Mit anderen Worten, hier ist ein JavaScript-Pseudocode, der Ihnen eine Idee gibt.

// 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']));

Tut mir leid, diese Antwort ist ein bisschen "wässrig". Ich habe nur eine halbe Stunde Mittagspause und es ist schwierig, etwas zu finden, ohne sich vollständig mit Unity auskennen zu müssen :(

Dan Pantry
quelle
3

Wenn eine Einheit / Struktur / Waffe angreift, würde ich wahrscheinlich einen Angriff erstellen (untergeordnet mit all Ihren unterhaltsamen Details), der den Angreifer und den Verteidiger (oder die Verteidiger) aufnimmt. Der Angriff kann dann mit dem Ziel / Verteidiger interagieren (langsam, vergiften, beschädigen, Zustand ändern), sich selbst zeichnen (Strahl, Strahl, Kugel) und sich selbst entsorgen, wenn er fertig ist. Ich kann einige Probleme wie mehrere Giftangriffe vorhersehen, daher würden Ihre Ziele möglicherweise eine Damageable-Oberfläche implementieren, mit der der Angriff interagiert, aber ich denke, es ist ein praktikabler Ansatz, der modular und flexibel zu ändern ist.

Erweiterte Antwort
So würde ich den Blaster-Angriff mit diesem Ansatz angehen . Ich lasse die anderen für sich selbst antworten.

Ich würde meine Einheiten eine IAttacker-Schnittstelle oder -Klasse mit grundlegenden Angriffsstatistiken / -methoden implementieren lassen. Wenn ein IAttacker einen IDamageable angreift, erstellt er seinen spezifischen Angriff, der sich selbst und sein Ziel (der IAttacker und der IDamageable oder möglicherweise eine Sammlung von IDamageables) übergibt. Der Angriff erhält die vom IAttacker benötigten Statistiken (um Änderungen während Upgrades oder Ähnlichem zu vermeiden - wir möchten nicht, dass der Angriff seine Statistiken ändert, nachdem er bereits gestartet wurde) und wirft den IAttacker auf, wenn er spezielle Statistiken benötigt seinen benötigten Typ (z. B. IBlasterAttacker) und ruft die spezialisierten Statistiken auf diese Weise ab.

Bei diesem Ansatz muss ein BlasterAttacker lediglich einen BlasterAttack erstellen, und der Rest wird von BlasterAttack erledigt. Sie können BlasterAttack in Unterklassen unterteilen oder separate FastBlasterAttacker, MegaBlasterAttacker, SniperBlasterAttacker usw. erstellen. Der Angriffscode für jeden ist derselbe (und möglicherweise von BlasterAttack geerbt): Erstellen Sie den BlasterAttack und übergeben Sie mich und meine Ziele. BlasterAttack übernimmt die Details .

ricksmt
quelle
Im Wesentlichen erbt die Einheit von einer IAttacker-Schnittstelle (ich habe dies bereits), und es gibt eine IDamageable-Schnittstelle für den "Feind" (habe dies ebenfalls). Wenn der Angreifer angreift, wird ein BlasterAttack (oder eine abgeleitete Klasse) aufgerufen. Dieser "Angriff" ruft die benötigten Daten vom IAttacker ab und wendet sie auf den IDamageable an, wenn das Projektil trifft. Enthält das Projektil selbst die BlasterAttack-Klasse, sodass es nach dem Abfeuern nicht mehr von Änderungen am IAttacker betroffen ist und seine Schäden / Effekte nur dann auf das IDamageable anwenden kann, wenn das Projektil tatsächlich trifft.
Douglas Gaskell
Wenn Sie sagen, "ein BlasterAttack (oder eine abgeleitete Klasse) heißt", würde ich sagen, dass ein BlasterAttack erstellt wird. Diese neu geschaffene Instanz BlasterAttack stellt den Strahl (oder Kugel oder Strahl oder was auch immer), so dass es ist das Projektil. BlasterAttack kopiert alle benötigten Statistiken von den IAttacker- und IDamageable-Objekten: die Positionen, die Angreifer-Statistiken usw. Der BlasterAttack verfolgt dann seine eigene Position und fügt dem "Kontakt" gegebenenfalls Schaden zu. Sie müssen herausfinden, was zu tun ist, wenn es sein Ziel verfehlt oder erreicht (die alte Position des Ziels). Den Boden verbrennen? Verschwinden? Ihr Anruf.
Ricksmt
Für einen Nahkampfangriff möchten Sie möglicherweise Zugriff auf eine globale Sammlung von (feindlichen) Einheiten, da zwischen Feuer und Aufprall gewechselt werden kann, wer sich in Reichweite befindet und wer sich außerhalb der Reichweite befindet. Natürlich könnte ein ähnliches Argument für den BlasterAttack vorgebracht werden: Sie verpassen Ihr ursprüngliches Ziel, treffen aber den Kerl hinter ihm. Ich mache mir nur Sorgen, dass du eine Menge Angriffe haben könntest, die eine Menge Feinde durchlaufen, um herauszufinden, ob und was sie treffen. Das ist ein Leistungsproblem.
Ricksmt
Ah, das macht Sinn. Für einen verpassten Angriff hat das Projektil eine eigene voreingestellte Reichweite / Lebensdauer. Wenn es vor dem Ende dieses Lebens auf etwas anderes trifft, erhält es einen Verweis auf das Objekt, das den starren Körper besitzt, mit dem es kollidiert, und der Schaden wird auf diese Weise verursacht. Tatsächlich funktionieren alle Projektile so, sie wissen nicht, auf was sie zugehen, sondern nur, dass sie sich fortbewegen (mit Ausnahme von zielstrebigen Projektilen wie Raketen). AEO-Effekte können nur einen Kugelcollider am Ziel aktivieren und alle darin enthaltenen Objekte abrufen. Danke für die Hilfe.
Douglas Gaskell
Kein Problem. Ich bin froh, dass ich konnte. Ich habe vergessen, dass Unity all diese Kollisionssachen einfacher macht.
Ricksmt