Ich werde so direkt wie möglich in Bezug auf dieses Problem sein, denn es muss etwas fehlen, das ich völlig vermisse, wenn ich von einem strukturierten Programmierhintergrund komme.
Angenommen, ich habe eine Player-Klasse. Diese Player-Klasse ändert beispielsweise ihre Position in einer Spielwelt. Ich nenne diese Methode warp (), die eine Instanz der Positionsklasse als Parameter zur Änderung der internen Position des Players verwendet. Das macht für mich total Sinn, weil ich den Spieler auffordere, etwas zu tun.
Das Problem tritt auf, wenn ich neben der Änderung der Spielerposition noch andere Dinge tun muss. Angenommen, ich muss dieses Warp-Ereignis an andere Spieler in einem Online-Spiel senden. Sollte dieser Code auch innerhalb der Warp () - Methode von Player sein? Wenn nicht, dann würde ich mir vorstellen, irgendeine Art von sekundärer Methode innerhalb der Server-Klasse wie WarpPlayer (Spieler, Position) zu deklarieren. Das zu tun scheint alles zu reduzieren, was ein Spieler als eine Reihe von Gettern und Settors macht, oder liege ich hier einfach falsch? Ist das etwas völlig normales? Ich habe unzählige Male gelesen, dass eine Klasse, die alles als eine Reihe von Getter / Setter darstellt, eine ziemlich schlechte Abstraktion anzeigt (sie wird als Datenstruktur anstelle einer Klasse verwendet).
Das gleiche Problem tritt auf, wenn Sie Daten persistieren und in einer Datei speichern müssen. Da "Speichern" eines Players in einer Datei auf einer anderen Abstraktionsebene als die Player-Klasse ist, ist es sinnvoll, eine save () -Methode innerhalb der Player-Klasse zu haben? Wenn nicht, deklariert es extern wie savePlayer (player), dass die savePlayer-Methode eine Möglichkeit benötigt, alle benötigten Daten aus der Player-Klasse zu extrahieren, was letztendlich die gesamte private Implementierung der Klasse freilegt.
Weil OOP die am häufigsten verwendete Design-Methode ist (ich nehme an?), muss ich etwas verpassen, was diese Probleme angeht. Ich habe es mit meinen Kollegen besprochen, die auch Lichtentwicklung entwickeln, und sie hatten auch genau dieselben Probleme mit OOP. Vielleicht ist es nur dieser strukturierte Programmierhintergrund, der uns davon abhält, die vollen Vorteile von OOP als etwas mehr zu verstehen, als Methoden zum Festlegen und Abrufen von privaten Daten bereitzustellen, so dass sie von einem Ort aus geändert und abgerufen werden können.
Vielen Dank im Voraus und hoffentlich höre ich nicht zu sehr wie ein Idiot. Für diejenigen, die die mit diesem Design verbundenen Sprachen wirklich kennen müssen, ist es Java auf der Serverseite und ActionScript 3 auf der Clientseite.
Sorgen Sie sich nicht zu sehr darum, dass die Klasse Player
eine Menge Setter und Getter ist. Die Klasse Player
ist eine Modellklasse und Modellklassen neigen dazu, so zu sein. Es ist wichtig, dass Ihre Modellklassen klein und sauber sind, da sie im gesamten Programm wiederverwendet werden.
Ich denke, Sie sollten den von Ihnen vorgeschlagenen warpPlayer(player, position)
-Ansatz verwenden. Es hält die Klasse Player
sauber. Wenn Sie den Player nicht an eine Funktion übergeben möchten, könnten Sie eine PlayerController
-Klasse haben, die ein Player
-Objekt und eine warp(Position p)
-Methode enthält. Auf diese Weise können Sie dem Controller Ereignisse hinzufügen und sie aus dem Modell heraushalten.
Wie beim Speichern des Players würde ich Player
eine Art Serialisierungsschnittstelle implementieren. Die Player-Klasse ist für das Serialisieren und das Deserialisieren selbst zuständig, und einige andere Klassen sind für das Schreiben der serialisierten Daten in eine Datei verantwortlich.
Ich rate Ihnen, die Tatsache nicht zu fürchten, dass dieser Spieler eine Klasse von Getter und Setter sein wird. Was ist das Objekt überhaupt? Es ist eine Zusammenstellung von Attributen und Verhaltensweisen. In der Tat, je einfacher Ihre Klassen sind, desto mehr Vorteile einer OOP erhalten Sie im Entwicklungsprozess.
Ich würde Ihre Aufgaben / Features in solche Klassen aufteilen:
Spieler:
Fighter erweitert den Spieler (Sie sollten Player to Fighter spielen können, wenn er benötigt wird):
World verfolgt alle Spieler:
Battle behandelt Kämpfe zwischen zwei Kämpfern:
PlayerPersister erweitert AbstractPersister:
Natürlich wird die Aufschlüsselung Ihres Spiels sehr viel komplizierter sein, aber ich hoffe, dass dies Ihnen hilft, Probleme auf "mehr OOP" Weise zu denken:)
Ich würde wahrscheinlich ein Game-Objekt in Betracht ziehen, das das Player-Objekt verfolgt. So können Sie etwas wie game.WarpPlayerTo (WarpLocations.Forest); Wenn mehrere Spieler vorhanden sind, kann ein Spielerobjekt oder eine Guid übergeben werden. Ich denke, du kannst es immer noch behalten, und ein Spielobjekt würde die meisten deiner Probleme lösen, denke ich.
Die Probleme, die Sie beschreiben, gehören nicht nur zum Spieldesign, sondern zur Softwarearchitektur im Allgemeinen. Der übliche Ansatz besteht darin, einen Dependency Injection (DI) und Inversion of Control (IoC) Mechanismen zu haben. Kurz gesagt, versuchen Sie, aus Ihren Objekten auf einen lokalen Service zuzugreifen, um beispielsweise ein Ereignis (z. B. Warp), ein Protokoll usw. zu propagieren.
>Die Umkehrung der Kontrolle bedeutet, dass Sie, anstatt Ihre Objekte direkt zu erstellen, einem Dienst mitteilen, sie für Sie zu erstellen. Dieser Dienst wiederum verwendet die Abhängigkeitsinjektion, um die Objekte über die Dienste zu informieren, von denen sie abhängig sind.
Wenn Sie Daten für den Multiplayer zwischen verschiedenen PCs teilen, ist es eine Kernfunktion des Programms, diesen Status zwischen den PCs zu halten und zu synchronisieren. Wenn Sie diese Werte in vielen verschiedenen Klassen verstreut lassen, wird es schwierig zu synchronisieren.
In diesem Fall würde ich empfehlen, dass Sie die Daten entwerfen, die zwischen allen Clients synchronisiert werden müssen, und speichern Sie diese in einer einzelnen Klasse (z. B. GameState). Dieses Objekt übernimmt die gesamte Synchronisierung zwischen verschiedenen PCs und ermöglicht es Ihrem lokalen Code, Änderungen an den Daten anzufordern. Es wird dann die Spielobjekte (Player, EnemyTank, etc.) aus seinem eigenen Zustand "fahren". [edit: Der Grund dafür ist, dass es sehr wichtig ist, diesen Zustand so klein wie möglich zu halten und ihn effizient zwischen den Clients zu übertragen. Indem Sie alles an einem Ort aufbewahren, wird es viel einfacher, und Sie werden ermutigt, nur das absolut Wesentliche in diese Klasse zu legen, damit Ihre Kommunikation nicht mit unnötigen Daten aufgebläht wird.
Wenn du keinen Multiplayer spielst und du die Position des Spielers ändern musst, um mehrere Objekte zu aktualisieren (zB wenn die Kamera wissen soll, dass der Spieler sich bewegt hat, damit sie ihm folgen kann), ist ein guter Ansatz den Spieler für seine eigene Position verantwortlich machen, aber Ereignisse / Nachrichten auslösen, die andere Objekte abonnieren / hören können, um zu wissen, wann sich die Position des Spielers ändert. Sie bewegen den Player, und die Kamera erhält einen Rückruf, der besagt, dass die Position des Players aktualisiert wurde.
Ein anderer Ansatz dafür wäre, dass die Kamera einfach die Position des Spielers in jedem Frame liest, um sich selbst zu aktualisieren - aber das ist nicht so lose gekoppelt und flexibel wie die Verwendung von Events.
Manchmal ist der Trick bei OOP, zu verstehen, was ein Objekt ist und was die Funktionalität eines Objekts ist. Ich denke, es ist oft ziemlich einfach für uns, Objekte wie Player, Monster, Item usw. als "Objekte" im System einzufangen und dann Objekte wie Environment, Transporter usw. zu erstellen Verknüpfen Sie diese Objekte miteinander und es kann außer Kontrolle geraten, je nachdem wie die Konzepte zusammenarbeiten und was wir erreichen müssen.
Die wirklich guten Ingenieure, mit denen ich in der Vergangenheit gearbeitet habe, hatten eine Möglichkeit, Systeme als Objektansammlungen zu sehen. Manchmal wären sie in einem System Geschäftsobjekte (wie Artikel, Rechnung usw.), und manchmal wären sie Objekte, die Verarbeitungslogik (DyeInjectionProcessor, PersistanceManager) verkapseln, die mehrere Operationen und "Objekte" im System durchschneiden. In beiden Fällen funktionierten die Metaphern für das jeweilige System und erleichterten die Implementierung, Beschreibung und Pflege des Gesamtprozesses.
Die wahre Stärke von OOP besteht darin, dass es in großen komplexen Systemen einfacher ist, sich auszudrücken und zu verwalten. Dies sind die zu bearbeitenden OOP-Prinzipien, und Sie müssen sich nicht so sehr darum kümmern, ob sie zu einer starren Objekthierarchie passen.
Ich habe nicht am Spieldesign gearbeitet, also wird dieser Rat vielleicht nicht so gut funktionieren, in den Systemen, an denen ich arbeite und entwickle, war es eine sehr nützliche Veränderung, OOP im Sinne von Vereinfachung und Kapselung zu denken Weltobjekt zu 1 OOP-Klasse.
Ich möchte den letzten Absatz von GrayWizardx erweitern, um zu sagen, dass nicht alle Objekte die gleiche Komplexität haben müssen. Es kann sehr gut zu Ihrem Design passen, um Objekte zu haben, die einfache Sammlungen von get / set-Eigenschaften sind. Auf der anderen Seite ist es wichtig zu beachten, dass Objekte Tasks oder Collections von Tasks darstellen können, anstatt reale Entitäten.
Zum Beispiel ist ein Spielerobjekt möglicherweise nicht dafür verantwortlich, den Spieler zu bewegen, sondern stattdessen seine Position und seinen aktuellen Status darzustellen. Ein PlayerMovement-Objekt kann Logik zum Ändern der Position eines Spielers auf dem Bildschirm oder in der Spielwelt enthalten.
Bevor ich anfange zu wiederholen, was bereits gesagt wurde, werde ich auf die SOLID-Prinzipien hinweisen OOP-Design (Aviad P. erwähnte bereits zwei von ihnen). Sie können einige allgemeine Richtlinien zum Erstellen eines guten Objektmodells für ein Spiel bereitstellen.