Erstellen eines robusten Objektsystems


12

Mein Ziel ist es, ein modulares / möglichst allgemeines Item-System zu schaffen, das Dinge wie:

  • Aufrüstbare Gegenstände (+6 Katana)
  • Stat Modifikatoren (+15 Geschicklichkeit)
  • Gegenstandsmodifikatoren (% X Chance, Y Schaden zu verursachen, Chance zu frieren)
  • Wiederaufladbare Gegenstände (Magic-Mitarbeiter mit 30 Verwendungszwecken)
  • Set Items (Rüste 4 Teile des X-Sets aus, um die Y-Funktion zu aktivieren.)
  • Seltenheit (gewöhnlich, einzigartig, legendär)
  • Entzauberbar (bricht in einige Handwerksmaterialien ein)
  • Herstellbar (kann mit bestimmten Materialien hergestellt werden)
  • Verbrauchbar (5min% X Angriffskraft, Heilung +15 PS)

* Ich konnte Funktionen lösen, die im folgenden Setup fett gedruckt sind.

Jetzt habe ich versucht, uns viele Optionen hinzuzufügen, um zu reflektieren, was ich vorhabe. Ich habe nicht vor, alle diese Funktionen hinzuzufügen, aber ich möchte sie nach eigenem Ermessen implementieren können. Diese sollten auch mit dem Inventarsystem und der Serialisierung von Daten kompatibel sein.

Ich plane, überhaupt keine Vererbung zu verwenden, sondern einen auf Entitätskomponenten / Daten basierenden Ansatz. Anfangs dachte ich an ein System mit:

  • BaseStat: Eine generische Klasse, die Statistiken für unterwegs enthält (kann auch für Elemente und Charakterstatistiken verwendet werden).
  • Element: Eine Klasse, die Daten wie Liste, Name, Elementtyp und Dinge enthält, die mit der Benutzeroberfläche, dem Aktionsnamen, der Beschreibung usw. zusammenhängen.
  • IWeapon: Schnittstelle für Waffe. Jede Waffe hat eine eigene Klasse, in der IWeapon implementiert ist. Dies hat Attack und einen Verweis auf Charakterstatistiken. Wenn die Waffe ausgerüstet ist, werden ihre Daten (Status der Gegenstandsklasse) in die Charakterstatistik eingefügt (was auch immer BaseStat hat, sie wird der Charakterklasse als Stat-Bonus hinzugefügt). Wir möchten also zum Beispiel ein Schwert produzieren (denken Sie daran) Produziere Gegenstandsklassen mit json), damit das Schwert den Charakterstatistiken 5 Angriffe hinzufügt . Wir haben also einen BaseStat als ("Attack", 5) (wir können auch enum verwenden). Dieser Wert wird dem "Angriff" -Stat des Charakters als BonusStat (was eine andere Klasse wäre) hinzugefügt, wenn er ausgerüstet wird. Eine Klasse namens Sword implementiert IWeapon und wird erstellt, wenn sie 'Artikelklasse wird erstellt. So können wir diesem Schwert Charakterstatistiken hinzufügen und beim Angriff den gesamten Angriffsstatus aus dem Charakterstatus abrufen und bei der Angriffsmethode Schaden zufügen.
  • BonusStat: ist eine Möglichkeit, Statistiken als Boni hinzuzufügen, ohne den BaseStat zu berühren.
  • IConsumable: Gleiche Logik wie bei IWeapon. Das Hinzufügen eines direkten Status ist ziemlich einfach (+15 PS), aber ich bin mir nicht sicher, ob ich mit diesem Setup temporäre Waffen hinzufügen soll (% x, um 5 Minuten lang anzugreifen).
  • IUpgradeable: Dies kann mit diesem Setup implementiert werden. Ich denke, UpgradeLevel ist eine Basisstatistik, die beim Upgrade der Waffe erhöht wird. Nach dem Upgrade können wir den BaseStat der Waffe neu berechnen, um ihn an die Upgrade-Stufe anzupassen .

Bis zu diesem Punkt kann ich sehen, dass das System ziemlich gut ist. Aber für andere Funktionen denke ich, dass wir etwas anderes brauchen, weil ich zum Beispiel keine Craftable-Funktion in diese implementieren kann, da mein BaseStat diese Funktion nicht verarbeiten kann und ich hier stecken geblieben bin. Ich kann alle Zutaten als Statistik hinzufügen, aber das würde keinen Sinn ergeben.

Um es Ihnen zu erleichtern, dies beizutragen, sind hier einige Fragen, bei denen Sie helfen können:

  • Sollte ich mit diesem Setup fortfahren, um andere Funktionen zu implementieren? Wäre es ohne Vererbung möglich?
  • Gibt es eine Möglichkeit, all diese Funktionen ohne Vererbung zu implementieren?
  • Wie kann man das über Item Modifiers erreichen? Weil es von Natur aus sehr allgemein ist.
  • Was kann getan werden, um den Aufbau dieser Art von Architektur zu vereinfachen? Gibt es Empfehlungen?
  • Gibt es Quellen, die ich ausgraben kann und die mit diesem Problem zusammenhängen?
  • Ich versuche wirklich, Vererbung zu vermeiden, aber glauben Sie, dass diese mit Vererbung mit Leichtigkeit gelöst / erreicht werden können, während sie ziemlich wartbar bleibt?

Fühlen Sie sich frei, nur eine einzige Frage zu beantworten, da ich die Fragen sehr weit gefasst habe, damit ich Wissen aus verschiedenen Aspekten / Personen erhalten kann.


BEARBEITEN


Nach der Antwort von @ jjimenezg93 habe ich in C # ein sehr einfaches System zum Testen erstellt, es funktioniert! Überprüfen Sie, ob Sie etwas hinzufügen können:

public interface IItem
{
    List<IAttribute> Components { get; set; }

    void ReceiveMessage<T>(T message);
}

public interface IAttribute
{
    IItem source { get; set; }
    void ReceiveMessage<T>(T message);
}

Bisher sind IItem und IAttribute Basisschnittstellen. Es gab keine Notwendigkeit (die ich mir vorstellen kann), eine Basisschnittstelle / ein Basisattribut für die Nachricht zu haben, daher werden wir direkt eine Testnachrichtenklasse erstellen. Nun zu den Testklassen:


public class TestItem : IItem
{
    private List<IAttribute> _components = new List<IAttribute>();
    public List<IAttribute> Components
    {
        get
        {
            return _components;
        }

        set
        {
            _components = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        foreach (IAttribute attribute in Components)
        {
            attribute.ReceiveMessage(message);
        }
    }
}

public class TestAttribute : IAttribute
{
    string _infoRequiredFromMessage;

    public TestAttribute(IItem source)
    {
        _source = source;
    }

    private IItem _source;
    public IItem source
    {
        get
        {
            return _source;
        }

        set
        {
            _source = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        TestMessage convertedMessage = message as TestMessage;
        if (convertedMessage != null)
        {
            convertedMessage.Execute();
            _infoRequiredFromMessage = convertedMessage._particularInformationThatNeedsToBePassed;
            Debug.Log("Message passed : " + _infoRequiredFromMessage);

        }
    }
} 

public class TestMessage
{
    private string _messageString;
    private int _messageInt;
    public string _particularInformationThatNeedsToBePassed;
    public TestMessage(string messageString, int messageInt, string particularInformationThatNeedsToBePassed)
    {
        _messageString = messageString;
        _messageInt = messageInt;
        _particularInformationThatNeedsToBePassed = particularInformationThatNeedsToBePassed;
    }
    //messages should not have methods, so this is here for fun and testing.
    public void Execute()
    {
        Debug.Log("Desired Execution Method: \nThis is test message : " + _messageString + "\nThis is test int : " + _messageInt);
    }
} 

Dies sind die erforderlichen Einstellungen. Jetzt können wir das System verwenden (Folgendes gilt für Unity).

public class TestManager : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        TestItem testItem = new TestItem();
        TestAttribute testAttribute = new TestAttribute(testItem);
        testItem.Components.Add(testAttribute);
        TestMessage testMessage = new TestMessage("my test message", 1, "VERYIMPORTANTINFO");
        testItem.ReceiveMessage(testMessage);
    }

}

Hängen Sie dieses TestManager-Skript an eine Komponente in der Szene an, und Sie können beim Debuggen sehen, dass die Nachricht erfolgreich übergeben wurde.


Um die Dinge zu erklären: Jedes Element im Spiel implementiert die IItem-Oberfläche und jedes Attribut (Name sollte Sie nicht verwirren, es bedeutet Elementfunktion / -system. Wie Upgrade oder entzauberbar) implementiert IAttribute. Dann haben wir eine Methode, um die Nachricht zu verarbeiten (warum wir eine Nachricht benötigen, wird in einem weiteren Beispiel erläutert). Im Kontext können Sie also Attribute an ein Element anhängen und den Rest für Sie erledigen lassen. Das ist sehr flexibel, da Sie Attribute problemlos hinzufügen / entfernen können. Ein Pseudobeispiel wäre also entzaubernd. Wir werden eine Klasse namens Disenchantable (IAttribute) haben, und in der Disenchant-Methode wird gefragt:

  • Liste der Zutaten (wenn der Gegenstand entzaubert ist, welcher Gegenstand dem Spieler gegeben werden soll) Hinweis: IItem sollte um ItemType, ItemID usw. erweitert werden.
  • int resultModifier (Wenn Sie eine Art Boost der Entzauberungsfunktion implementieren, können Sie hier ein int übergeben, um die Zutaten zu erhöhen, die beim Entzaubern erhalten werden.)
  • int failChance (wenn der entzaubernde Prozess eine Fehlerchance hat)

usw.

Diese Informationen werden von einer Klasse namens DisenchantManager bereitgestellt. Sie erhalten den Gegenstand und bilden diese Nachricht entsprechend dem Gegenstand (Bestandteile des Gegenstands, wenn er enttäuscht ist) und dem Fortschritt des Spielers (resultModifier und failChance). Um diese Nachricht weiterzuleiten, erstellen wir eine DisenchantMessage-Klasse, die als Hauptteil für diese Nachricht fungiert. DisenchantManager füllt also eine DisenchantMessage und sendet sie an den Artikel. Das Element empfängt die Nachricht und leitet sie an alle angehängten Attribute weiter. Da die ReceiveMessage-Methode der Disenchantable-Klasse nach einer DisenchantMessage sucht, empfängt nur das Disenchantable-Attribut diese Nachricht und reagiert darauf. Hoffe das klärt die Dinge genauso wie für mich :).


1
Sie können einige nützliche Inspirationen in den Modifikatorsystemen finden, die in For Honor
DMGregory

@ DMGregory Hey! Danke für den Link. Obwohl es sehr einfallsreich erscheint, brauche ich leider das eigentliche Gespräch, um das Konzept zu verstehen. Und ich versuche, den tatsächlichen Vortrag herauszufinden, der leider nur für Mitglieder von GDCVault bestimmt ist (495 $ für ein Jahr sind verrückt!). (Sie können den Vortrag hier finden, wenn Sie GDCVault-Mitgliedschaft haben -, -
Vandarthul

Wie genau würde Ihr "BaseStat" -Konzept handwerkliche Waffen ausschließen?
Attackfarm

Es schließt nicht wirklich aus, passt aber nicht wirklich in den Kontext in meinem Kopf. Es ist möglich, "Wood", 2 und "Iron", 5 als BaseStat zu einem Handwerksrezept hinzuzufügen, das das Schwert ergeben würde. Ich denke, dass das Ändern des Namens von BaseStat in BaseAttribute in diesem Zusammenhang besser wäre. Trotzdem kann das System seinen Zweck nicht erfüllen. Denken Sie an Verbrauchsartikel mit 5min -% 50 Angriffskraft. Wie würde ich es als BaseStat übergeben? "Perc_AttackPower", 50 Dies muss gelöst werden als "Wenn es Perc ist, behandeln Sie die Ganzzahl als Prozentsatz" und es fehlen die Informationen von Minuten. O hoffe du verstehst was ich meine.
Vandarthul

@Attackfarm Beim zweiten Gedanken kann dieses "BaseStat" -Konzept mit einer Liste von Ints anstelle von nur einem Int erweitert werden. Also, für Verbrauchsmaterial-Buff kann ich "Attack", 50, 5, 1 und IConsumable nach 3 ganzen Zahlen suchen, 1. - Wert, 2. - Minuten, 3. - ob es ein Prozentsatz ist oder nicht. Aber es fühlt sich impulsiv an, wenn andere Systeme einsteigen und gezwungen sind, sich nur in int zu erklären.
Vandarthul

Antworten:


6

Ich denke, Sie können das erreichen, was Sie in Bezug auf Skalierbarkeit und Wartbarkeit wollen, indem Sie ein Entity-Component-System mit grundlegender Vererbung und ein Messaging-System verwenden. Denken Sie natürlich daran, dass dieses System das modularste / anpassbarste / skalierbarste ist, das ich mir vorstellen kann, aber es wird wahrscheinlich schlechter abschneiden als Ihre aktuelle Lösung.

Ich werde weiter erklären:

Zunächst erstellen Sie eine Schnittstelle IItemund eine Schnittstelle IComponent. Jedes Element, das Sie speichern möchten, muss erben IItem, und jede Komponente, die Sie auf Ihre Elemente auswirken möchten, muss erben IComponent.

IItemwird eine Reihe von Komponenten und eine Methode für die Handhabung haben IMessage. Diese Behandlungsmethode sendet einfach jede empfangene Nachricht an alle gespeicherten Komponenten. Dann verhalten sich die Komponenten, die an dieser bestimmten Nachricht interessiert sind, entsprechend, und die anderen ignorieren sie.

Eine Nachricht ist beispielsweise vom Typ Schaden und informiert sowohl den Angreifer als auch den Angreifer, sodass Sie wissen, wie viel Sie getroffen haben, und möglicherweise Ihren Wutbalken basierend auf diesem Schaden aufladen. Oder die KI des Feindes kann sich entscheiden zu rennen, wenn sie dich trifft und weniger als 2 PS Schaden verursacht. Dies sind dumme Beispiele, aber mit einem ähnlichen System wie dem, das ich erwähne, müssen Sie nichts weiter tun, als eine Nachricht und die entsprechenden Handhabungen zu erstellen, um die meisten dieser Mechaniken hinzuzufügen.

Ich habe eine Implementierung für ein ECS mit Messaging hier , aber dies für Unternehmen verwendet wird anstelle von Elementen und verwendet C ++. Wie auch immer, ich denke, es kann helfen, wenn Sie einen Blick darauf werfen component.h, entity.hund messages.h. Es gibt viele Dinge zu verbessern, aber es hat bei mir in dieser einfachen Universitätsarbeit funktioniert.

Ich hoffe es hilft.


Hey @ jjimenezg93, danke für deine Antwort. Um mit einem einfachen Beispiel zu erläutern, was Sie erklärt haben: Wir wollen ein Schwert, das: - Entzauberbar [Komponente] - Stat-Modifikator [Komponente] - Aufrüstbar [Komponente] Ich habe eine Liste von Aktionen, die alle Dinge wie enthält - DISENCHANT - MODIFY_STAT - UPGRADE Immer wenn ein Element diese Nachricht empfängt, alle seine Komponenten durchläuft und diese Nachricht sendet, weiß jede Komponente, was mit der angegebenen Nachricht zu tun ist. Theoretisch scheint das großartig! Ich habe Ihr Beispiel nicht überprüft, werde es aber tun, vielen Dank!
Vandarthul

@ Vandarthul Ja, das ist im Grunde die Idee. Auf diese Weise weiß das Objekt nichts über seine Komponenten, sodass überhaupt keine Kopplung erfolgt, und verfügt gleichzeitig über alle gewünschten Funktionen, die auch von verschiedenen Objekttypen gemeinsam genutzt werden können. Ich hoffe es passt zu deinen Bedürfnissen!
jjimenezg93
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.