Was sind die Nachteile der Verwendung von DrawableGameComponent für jede Instanz eines Spielobjekts?


25

Ich habe an vielen Stellen gelesen, dass DrawableGameComponents für Dinge wie "Level" oder irgendeine Art von Managern gespeichert werden sollten, anstatt sie beispielsweise für Charaktere oder Kacheln zu verwenden (wie dieser Typ hier sagt ). Aber ich verstehe nicht, warum das so ist. Ich habe diesen Beitrag gelesen und es hat mir sehr viel Sinn gemacht, aber das sind die Minderheiten.

Normalerweise würde ich solchen Dingen nicht allzu viel Aufmerksamkeit schenken, aber in diesem Fall würde ich gerne wissen, warum die offensichtliche Mehrheit glaubt, dass dies nicht der richtige Weg ist. Vielleicht fehlt mir etwas.


Ich bin gespannt, warum Sie zwei Jahre später die "akzeptierte" Marke aus meiner Antwort entfernt haben . Normalerweise würde es mich nicht wirklich interessieren - aber in diesem Fall verursacht es VeraShackles ärgerliche falsche Darstellung meiner Antwort, um nach oben zu blubbern :(
Andrew Russell

@AndrewRussell Ich habe diesen Thread vor nicht allzu langer Zeit gelesen (aufgrund einiger Antworten und Abstimmungen) und dachte, ich wäre nicht qualifiziert, eine Meinung darüber abzugeben, ob Ihre Antwort die richtige ist oder nicht. Wie Sie jedoch sagen, sollte VeraShackles Antwort nicht die mit den meisten positiven Stimmen sein, daher markiere ich Ihre Antwort erneut als richtig.
Kenji Kina

1
Ich kann meine Antwort hier wie folgt zusammenfassen: "Die Verwendung von DrawableGameComponentsperrt Sie in eine einzelne Gruppe von Methodensignaturen und ein bestimmtes beibehaltenes Datenmodell. Dies ist anfangs in der Regel die falsche Wahl, und die Sperrung behindert die zukünftige Entwicklung des Codes erheblich. "Aber lassen Sie mich Ihnen sagen, was hier passiert ...
Andrew Russell

1
DrawableGameComponentist Teil der Kern-XNA-API (wieder: meist aus historischen Gründen). Anfänger benutzen es, weil es "da" ist und "funktioniert" und sie es nicht besser wissen. Sie sehen, wie meine Antwort ihnen sagt, dass sie " falsch " sind und - aufgrund der Natur der menschlichen Psychologie - das ablehnen und die andere "Seite" auswählen. Das ist VeraShackles argumentative Nichtantwort; Das hat Momentum aufgenommen, weil ich es nicht sofort gerufen habe (siehe diese Kommentare). Ich habe das Gefühl, dass meine eventuelle Widerlegung dort ausreicht.
Andrew Russell

1
Wenn jemand daran interessiert ist, die Legitimität meiner Antwort auf der Grundlage von Gegenstimmen in Frage zu stellen: Zwar hat VeraShackles Antwort in den letzten Jahren ein paar Gegenstimmen mehr gebracht als meine, aber vergessen Sie nicht dass ich netzwerkweit tausende Upvotes habe, größtenteils auf XNA. Ich habe die XNA-API auf mehreren Plattformen implementiert. Und ich bin ein professioneller Spieleentwickler, der XNA verwendet. Der Stammbaum meiner Antwort ist einwandfrei.
Andrew Russell

Antworten:


22

"Game Components" waren ein sehr früher Bestandteil des XNA 1.0-Designs. Sie sollten wie Steuerelemente für WinForms funktionieren. Die Idee war, dass Sie sie per Drag-and-Drop in Ihr Spiel ziehen können, indem Sie eine grafische Benutzeroberfläche verwenden (ähnlich dem Bit zum Hinzufügen nicht-visueller Steuerelemente wie Timer usw.) und Eigenschaften für sie festlegen.

Sie könnten also Komponenten wie eine Kamera oder einen ... FPS-Zähler haben? ... oder ...?

Siehst du? Leider funktioniert diese Idee bei realen Spielen nicht wirklich. Die Komponenten, die Sie für ein Spiel benötigen, können nicht wirklich wiederverwendet werden. Möglicherweise haben Sie eine "Player" -Komponente, diese unterscheidet sich jedoch erheblich von der "Player" -Komponente jedes anderen Spiels.

Ich stelle mir aus diesem Grund und aus Zeitgründen vor, dass die grafische Benutzeroberfläche vor XNA 1.0 erstellt wurde.

Die API ist jedoch weiterhin verwendbar. Deshalb sollten Sie sie nicht verwenden:

Grundsätzlich kommt es darauf an, was als Spieleentwickler am einfachsten zu schreiben und zu lesen, zu debuggen und zu warten ist. Machen Sie so etwas in Ihrem Ladecode:

player.DrawOrder = 100;
enemy.DrawOrder = 200;

Ist viel weniger klar als dies einfach zu tun:

virtual void Draw(GameTime gameTime)
{
    player.Draw();
    enemy.Draw();
}

Vor allem, wenn Sie in einem realen Spiel am Ende etwas Ähnliches vorfinden:

GraphicsDevice.Viewport = cameraOne.Viewport;
player.Draw(cameraOne, spriteBatch);
enemy.Draw(cameraOne, spriteBatch);

GraphicsDevice.Viewport = cameraTwo.Viewport;
player.Draw(cameraTwo, spriteBatch);
enemy.Draw(cameraTwo, spriteBatch);

(Zugegeben, Sie könnten die Kamera und SpriteBatch mit der ähnlichen ServicesArchitektur gemeinsam nutzen. Aber das ist auch aus dem gleichen Grund eine schlechte Idee.)

Diese Architektur - oder deren Fehlen - ist viel leichter und flexibler!

Ich kann die Zeichenreihenfolge einfach ändern, mehrere Ansichtsfenster hinzufügen oder einen Rendereffekt hinzufügen, indem ich nur ein paar Zeilen ändere. Um es in dem anderen System zu tun, muss ich darüber nachdenken, was all diese Komponenten - verteilt auf mehrere Dateien - tun und in welcher Reihenfolge.

Es ist wie der Unterschied zwischen deklarativer und imperativer Programmierung. Es stellt sich heraus, dass für die Entwicklung von Spielen eine zwingende Programmierung weitaus vorzuziehen ist.


2
Ich hatte den Eindruck, es handele sich um komponentenbasiertes Programmieren , das anscheinend häufig für Spieleprogramme verwendet wird.
BlueRaja - Danny Pflughoeft

3
Die Klassen der XNA-Spielkomponenten sind für dieses Muster nicht direkt anwendbar. In diesem Muster geht es darum, Objekte aus mehreren Komponenten zusammenzusetzen. Die XNA-Klassen sind auf eine Komponente pro Objekt ausgerichtet. Wenn sie perfekt in Ihr Design passen, verwenden Sie sie auf jeden Fall. Aber Sie sollten nicht Ihr Design um sie herum bauen! Siehe auch meine Antwort hier - achten Sie darauf, wo ich erwähne DrawableGameComponent.
Andrew Russell

1
Ich bin damit einverstanden, dass die GameComponent / Service-Architektur nicht allzu nützlich ist und sich anhört, als würden Office-ähnliche oder Webanwendungskonzepte in ein Spiel-Framework gezwungen. Aber ich würde es vorziehen, die player.DrawOrder = 100;Methode der Zeichnungsreihenfolge vorzuziehen . Ich beende gerade ein Flixel-Spiel, bei dem Objekte in der Reihenfolge gezeichnet werden, in der Sie sie der Szene hinzufügen. Das FlxGroup-System verringert einige dieser Probleme, aber es wäre schön gewesen, eine Art Z-Index-Sortierung eingebaut zu haben.
michael.bartnett

@ michael.bartnett Beachten Sie, dass Flixel eine API im Retained-Mode-Modus ist, während XNA (mit Ausnahme des Systems der Spielkomponenten) eine API im unmittelbaren Modus ist. Wenn Sie eine Retained-Mode-API verwenden oder eine eigene implementieren , ist es auf jeden Fall wichtig, die Zeichenreihenfolge im Handumdrehen zu ändern. Tatsächlich ist die Notwendigkeit, die Zeichenreihenfolge im Handumdrehen zu ändern, ein guter Grund, um einen Beibehaltungsmodus zu erstellen (das Beispiel eines Fenstersystems kommt in den Sinn). Aber wenn Sie können etwas wie Immediate-Modus schreiben, wenn die Plattform - API (wie XNA) auf diese Weise aufgebaut ist, dann IMO Sie sollten.
Andrew Russell

Für weitere Informationen
Kenji Kina

26

Hmmm. Ich fürchte, ich habe diesen herausgefordert, um ein bisschen Gleichgewicht hier zu haben.

In Andrews Antwort scheinen sich 5 Anklagen zu befinden, also werde ich versuchen, sie nacheinander zu behandeln:

1) Komponenten sind veraltet und aufgegeben.

Die Idee des Entwurfsmusters von Komponenten existierte lange vor XNA - im Wesentlichen ist es einfach eine Entscheidung, Objekte anstelle des übergeordneten Spielobjekts zum Zuhause ihres eigenen Update- und Draw-Verhaltens zu machen. Für Objekte in seiner Komponentensammlung ruft das Spiel diese Methoden (und einige andere) zum entsprechenden Zeitpunkt automatisch auf - entscheidend ist, dass das Spielobjekt selbst keinen dieser Codes „kennen“ muss. Wenn Sie Ihre Spielklassen in verschiedenen Spielen (und damit verschiedenen Spielobjekten) wiederverwenden möchten, ist diese Entkopplung von Objekten grundsätzlich immer noch eine gute Idee. Ein kurzer Blick auf die neuesten (professionell produzierten) Demos von XNA4.0 aus dem AppHub bestätigt meinen Eindruck, dass Microsoft immer noch (zu Recht in meinen Augen) fest dahinter steckt.

2) Andrew kann sich nicht mehr als zwei wiederverwendbare Spielklassen vorstellen.

Ich kann. Tatsächlich kann ich an Lasten denken! Ich habe gerade meine aktuelle freigegebene Bibliothek überprüft und sie umfasst mehr als 80 wiederverwendbare Komponenten und Dienste. Um Ihnen einen Vorgeschmack zu geben, könnten Sie Folgendes haben:

  • Game State / Screen Manager
  • Partikelemitter
  • Zeichenbare Primitive
  • AI-Controller
  • Eingangssteuerungen
  • Animations-Controller
  • Werbetafeln
  • Physik & Kollisionsmanager
  • Kameras

Für eine bestimmte Spielidee sind mindestens 5-10 Dinge aus diesem Satz erforderlich - garantiert - und sie können in einem völlig neuen Spiel mit einer Codezeile voll funktionsfähig sein.

3) Die Steuerung der Zeichnungsreihenfolge durch die Reihenfolge der expliziten Zeichnungsaufrufe ist der Verwendung der DrawOrder-Eigenschaft der Komponente vorzuziehen.

Nun, ich stimme Andrew in diesem Punkt im Grunde zu. ABER, wenn Sie alle DrawOrder-Eigenschaften Ihrer Komponente als Standardeinstellungen belassen, können Sie ihre Zeichenreihenfolge weiterhin explizit durch die Reihenfolge steuern, in der Sie sie der Auflistung hinzufügen. Dies ist in Bezug auf die Sichtbarkeit wirklich identisch mit der expliziten Reihenfolge ihrer manuellen Ziehungsaufrufe.

4) Das Aufrufen einer Reihe von Draw-Methoden eines Objekts ist einfacher als das Aufrufen durch eine vorhandene Framework-Methode.

Warum? Außerdem überschatten Ihre Aktualisierungslogik und Ihr Draw-Code in allen Projekten, mit Ausnahme der trivialsten, Ihre Methodenaufrufe in Bezug auf Leistungseinbußen in jedem Fall völlig.

5) Das Platzieren Ihres Codes in einer Datei ist beim Debuggen vorzuziehen, anstatt ihn in der Datei des einzelnen Objekts zu haben.

Nun, erstens, wenn ich in Visual Studio debugge, ist der Speicherort eines Haltepunkts in Bezug auf Dateien auf der Festplatte heutzutage fast unsichtbar - die IDE behandelt den Speicherort ohne jegliches Drama. Bedenken Sie aber auch für einen Moment, dass Sie ein Problem mit Ihrer Skybox-Zeichnung haben. Ist das Wechseln zur Draw-Methode der Skybox wirklich aufwändiger als das Auffinden des Skybox-Zeichencodes in der (vermutlich sehr langen) Master- Draw-Methode im Game-Objekt? Nein, nicht wirklich - in der Tat würde ich behaupten, dass es das Gegenteil ist.

Also, zusammenfassend - schauen Sie sich bitte Andrews Argumente hier genau an. Ich glaube nicht, dass sie tatsächlich eine wesentliche Begründung für das Schreiben von verwickeltem Spielcode geben. Die positiven Seiten des entkoppelten Komponentenmusters (mit Bedacht verwendet) sind massiv.


2
Ich habe diesen Beitrag gerade erst bemerkt und muss einen starken Widerspruch aussprechen: Ich habe kein Problem mit wiederverwendbaren Klassen! (Möglicherweise gibt es Zeichnungs- und Aktualisierungsmethoden usw.) Ich habe ein spezielles Problem mit der Verwendung von ( Drawable) GameComponent, um Ihre Spielarchitektur bereitzustellen, da die explizite Kontrolle über die Zeichnungs- / Aktualisierungsreihenfolge (mit der Sie in # 3 einverstanden sind) in Bezug auf die Steifigkeit verloren gegangen ist ihrer Schnittstellen (die Sie nicht ansprechen).
Andrew Russell

1
In Punkt 4 verstehe ich unter dem Begriff "geringes Gewicht" die Leistung, wobei ich eigentlich die architektonische Flexibilität verstehe. Ihre verbleibenden Punkte # 1, # 2 und besonders # 5 scheinen den absurden Strohmann zu errichten, von dem ich denke, dass der meiste Code in Ihre Hauptspielklasse geschoben werden sollte! Lesen Sie hier meine Antwort für einige meiner detaillierteren Gedanken zur Architektur.
Andrew Russell

5
Zum Schluss: Wenn Sie eine Antwort auf meine Antwort schreiben und sagen, dass ich falsch liege, wäre es höflich gewesen, meiner Antwort auch eine Antwort hinzuzufügen, damit ich eine Benachrichtigung darüber sehen würde, anstatt darüber zu stolpern es zwei Monate später.
Andrew Russell

Ich habe vielleicht falsch verstanden, aber wie beantwortet das die Frage? Soll ich eine GameComponent für meine Sprites verwenden oder nicht?
Super


4

Ich bin ein bisschen anderer Meinung als Andrew, aber ich denke auch, dass es für alles eine Zeit und einen Ort gibt. Ich würde a nicht Drawable(GameComponent)für etwas verwenden, das so einfach ist wie ein Sprite oder ein Enemy. Ich würde es jedoch für ein SpriteManageroder verwenden EnemyManager.

Wenn ich noch einmal auf Andrews Ausführungen zur Reihenfolge des Zeichnens und Aktualisierens zurückkomme, Sprite.DrawOrderwäre das für viele Sprites sehr verwirrend. Darüber hinaus ist es relativ einfach, eine DrawOrderund UpdateOrderfür eine kleine (oder angemessene) Anzahl von Managern einzurichten (Drawable)GameComponents.

Ich denke, dass Microsoft sie beseitigt hätte, wenn sie wirklich so starr gewesen wären und niemand sie benutzt hätte. Aber sie haben es nicht, weil sie perfekt funktionieren, wenn die Rolle stimmt. Ich selbst benutze sie, um zu entwickeln Game.Services, die im Hintergrund aktualisiert werden.


2

Ich habe auch darüber nachgedacht, und mir ist der Gedanke gekommen: Um zum Beispiel das Beispiel in Ihrem Link zu stehlen, könnten Sie in einem RTS-Spiel jede Einheit zu einer Instanz einer Spielkomponente machen. Okay.

Sie können aber auch eine UnitManager-Komponente erstellen, in der eine Liste der Einheiten gespeichert, gezeichnet und bei Bedarf aktualisiert wird. Ich denke, der Grund, warum Sie Ihre Einheiten in erster Linie zu Spielkomponenten machen wollen, ist die Unterteilung des Codes. Würde diese Lösung dieses Ziel also angemessen erreichen?


2

In Kommentaren habe ich eine gekürzte Version meiner alten Antwort gepostet:

Die Verwendung DrawableGameComponentsperrt Sie in einen einzelnen Satz von Methodensignaturen und in ein bestimmtes beibehaltenes Datenmodell. Dies ist normalerweise anfangs die falsche Wahl, und das Lock-In behindert die zukünftige Entwicklung des Codes erheblich.

Hier versuche ich zu veranschaulichen, warum dies der Fall ist, indem ich Code aus meinem aktuellen Projekt veröffentliche.


Das Root-Objekt, das den Status der Spielwelt beibehält, hat eine UpdateMethode, die folgendermaßen aussieht:

void Update(UpdateContext updateContext, MultiInputState currentInput)

Es gibt eine Reihe von Einstiegspunkten Update, je nachdem, ob das Spiel im Netzwerk läuft oder nicht. Beispielsweise kann der Spielstatus auf einen vorherigen Zeitpunkt zurückgesetzt und dann erneut simuliert werden.

Es gibt auch einige zusätzliche update-ähnliche Methoden, wie zum Beispiel:

void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)

Innerhalb des Spielstatus gibt es eine Liste der zu aktualisierenden Akteure. Dies ist jedoch mehr als ein einfacher UpdateAnruf. Es gibt zusätzliche Pässe vor und nach der Physik. Es gibt auch einige ausgefallene Dinge für das zeitversetzte Laichen / Zerstören von Schauspielern sowie für Levelübergänge.

Außerhalb des Spielstatus gibt es ein UI-System, das unabhängig verwaltet werden muss. Einige Bildschirme in der Benutzeroberfläche müssen jedoch auch in einem Netzwerkkontext ausgeführt werden können.


Zum Zeichnen gibt es aufgrund der Art des Spiels ein benutzerdefiniertes Sortier- und Auslesesystem, das Eingaben von den folgenden Methoden übernimmt:

virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)

Und wenn es dann herausfindet, was zu zeichnen ist, ruft es automatisch auf:

virtual void Draw(DrawContext drawContext, int tag)

Das tag Parameter ist erforderlich, da einige Akteure sich an andere Akteure halten können und daher sowohl über als auch unter dem gehaltenen Akteur zeichnen können müssen. Das Sortiersystem sorgt dafür, dass dies in der richtigen Reihenfolge geschieht.

Es gibt auch das Menüsystem zum Zeichnen. Und ein hierarchisches System zum Zeichnen der Benutzeroberfläche rund um das Spiel.


Jetzt vergleicht diese Anforderungen mit dem, was es DrawableGameComponent:

public class DrawableGameComponent
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
    public int UpdateOrder { get; set; }
    public int DrawOrder { get; set; }
}

Es gibt keine vernünftige Möglichkeit, von einem System mit DrawableGameComponentzu dem oben beschriebenen System zu gelangen . (Und das sind keine ungewöhnlichen Anforderungen für ein Spiel von selbst bescheidener Komplexität).

Es ist auch sehr wichtig zu beachten, dass diese Anforderungen nicht einfach zu Beginn des Projekts entstanden sind. Sie haben sich über viele Monate der Entwicklungsarbeit entwickelt.


Normalerweise beginnen die Leute, wenn sie auf der DrawableGameComponentStrecke beginnen, auf Hacks aufzubauen:

  • Anstatt Methoden hinzuzufügen, machen sie ein hässliches Downcasting für ihren eigenen Basistyp (warum nicht einfach das direkt verwenden?).

  • Anstatt den Code zum Sortieren und Aufrufen von Draw/ zu ersetzen Update, werden einige sehr langsame Aktionen ausgeführt, um die Bestellnummern im laufenden Betrieb zu ändern.

  • Und das Schlimmste von allem: Anstatt den Methoden Parameter hinzuzufügen, "übergeben" sie Parameter an Speicherorte, die nicht der Stack sind (die Verwendung Serviceshierfür ist sehr beliebt). Dies ist kompliziert, fehleranfällig, langsam, zerbrechlich, selten threadsicher und wird wahrscheinlich zu einem Problem, wenn Sie Netzwerke hinzufügen, externe Tools erstellen usw.

    Es macht auch Wiederverwendung von Code schwieriger , denn jetzt Ihre Abhängigkeiten innerhalb der „Komponente“ versteckt sind, statt auf der API - Grenze veröffentlicht.

  • Oder sie sehen fast , wie verrückt das alles ist, und beginnen, "Manager" DrawableGameComponentzu tun , um diese Probleme zu umgehen. Außer sie haben noch diese Probleme an den "Manager" -Grenzen.

    Diese "Manager" könnten immer leicht durch eine Reihe von Methodenaufrufen in der gewünschten Reihenfolge ersetzt werden. Alles andere ist zu kompliziert.

Sobald Sie mit dem Einsatz dieser Hacks beginnen, ist es natürlich sehr aufwändig, diese Hacks rückgängig zu machen , sobald Sie feststellen, dass Sie mehr benötigen, als Sie DrawableGameComponentbereitstellen. Sie werden " eingesperrt ". Und die Versuchung besteht oft darin, sich weiterhin auf Hacks zu stapeln - was Sie in der Praxis stören und die Art der Spiele, die Sie machen können, auf relativ einfache Spiele beschränken wird.


Viele Menschen scheinen an diesem Punkt angelangt zu sein und eine viszerale Reaktion zu haben - möglicherweise, weil sie DrawableGameComponent"Unrecht" verwenden und nicht akzeptieren wollen. Sie klammern sich also an die Tatsache, dass es zumindest möglich ist , es "funktionieren" zu lassen.

Natürlich kann es gemacht werden, um "zu arbeiten"! Wir sind Programmierer - es ist praktisch unsere Aufgabe, Dinge unter ungewöhnlichen Bedingungen zum Laufen zu bringen. Aber diese Zurückhaltung ist nicht notwendig, nützlich oder rational ...


Besonders verblüffend ist, dass es erstaunlich trivial ist, diese ganze Angelegenheit zu umgehen . Hier gebe ich dir sogar den Code:

public class Actor
{
    public virtual void Update(GameTime gameTime);
    public virtual void Draw(GameTime gameTime);
}

List<Actor> actors = new List<Actor>();

foreach(var actor in actors)
    actor.Update(gameTime);

foreach(var actor in actors)
    actor.Draw(gameTime);

(Es ist einfach, eine Sortierung nach DrawOrderund hinzuzufügen UpdateOrder. Eine einfache Möglichkeit besteht darin, zwei Listen zu verwalten und zu verwenden List<T>.Sort(). Es ist auch trivial, mit Load/ umzugehen UnloadContent.)

Es ist nicht wichtig, was dieser Code tatsächlich ist . Wichtig ist, dass ich es einfach modifizieren kann .

Wenn ich feststelle, dass ich ein anderes Timing-System gameTimebenötige oder mehr Parameter (oder ein ganzes UpdateContextObjekt mit etwa 15 Dingen) oder eine völlig andere Reihenfolge für das Zeichnen oder einige zusätzliche Methoden benötige für verschiedene Update - Pässe kann, gehe ich einfach weiter und das tun .

Und ich kann es sofort tun. Ich muss mich nicht darum kümmern DrawableGameComponent, meinen Code herauszusuchen. Oder noch schlimmer: Hacken Sie es in eine Richtung, die es beim nächsten Mal noch schwieriger macht, wenn ein solches Bedürfnis besteht.


Es gibt wirklich nur ein Szenario, in dem dies eine gute Wahl DrawableGameComponentist: Wenn Sie buchstäblich Middleware im Drag-and-Drop-Komponentenstil für XNA erstellen und veröffentlichen. Welches war sein ursprünglicher Zweck. Welches - ich werde hinzufügen - niemand tut!

(Ebenso ist es nur sinnvollServices , benutzerdefinierte Inhaltstypen für zu erstellen ContentManager.)


Ich stimme Ihren Aussagen zwar zu, sehe aber auch keine besonderen Fehler darin, Dinge zu verwenden, die DrawableGameComponentfür bestimmte Komponenten erben , insbesondere für Dinge wie Menübildschirme (Menüs, viele Schaltflächen, Optionen und Dinge) oder die FPS / Debug-Überlagerungen du erwähntest. In diesen Fällen benötigen Sie normalerweise keine genaue Kontrolle über die Zeichenreihenfolge und Sie müssen weniger Code manuell schreiben (was eine gute Sache ist, es sei denn, Sie müssen diesen Code schreiben). Aber ich denke, das ist so ziemlich das Drag-and-Drop-Szenario, das Sie erwähnt haben.
Groo
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.