2017-03-18 5 views
0

Um Java zu lernen, mache ich ein klassisches "Fly Around in Space" 2D-Spiel. Neben dem Spielerobjekt existieren zahlreiche Feinde/Hindernisse (Aasfresser, Jäger, Kometen, Asteroiden) mit jeweils einer eigenen Klasse, die eine GameObject-Klasse erweitert. Da es mehrere Aasfresser, Kometen usw. geben kann, werden diese in Arraylisten gespeichert. Da jedes Objekt jedoch miteinander interagieren kann, gibt es eine Menge von Schleifen und doppeltem Code, um beispielsweise jedes Alien entsprechend den Objekten in der Komet-Arraylist, der Asteroid-Arrayliste usw. interagieren zu lassen.Spieledesign: Organisieren Objekt Arraylists

In meiner Spiel-Update-Funktion habe ich:

public void update() { 

    ArrayList<Rock> rockList = rock.getRockList(); 
    ArrayList<Scavenger> scavengerList = scavenger.getScavengerList(); 
    ArrayList<Hunter> hunterList = hunter.getHunterList(); 
    .... 
    npc.update(player, scavengerList, hunterList, rockList); 
    ... 

}

und in meiner NPC-Klasse (die die Gameobject-Klasse erweitert)

public void update(Player player, ArrayList<Scavenger> scavengerList, ArrayList<Hunter> hunterList, ArrayList<Rock> rockList) { 

    for(int i = 0; i < scavengerList.size(); i++) {   
     scavengerList.get(i).update(player,scavengerList, ,hunterList rockList); 
    } 
    for(int i = 0; i < hunterList.size(); i++) {    
     hunterList.get(i).update(player,scavengerList, hunterList, rockList); 
    } 
... 
} 

Und schließlich habe ich eine Update-Funktion in meiner Scavenger-Klasse, meiner Jägerklasse usw. wie

public class Hunter extends NPC{ 
... 
public void update(Player player,ArrayList<Scavenger> scavengerList, ArrayList<Hunter> hunterList, ArrayList<Rock> rockList) { 
"update all hunter objects according to the player, scavengers, rocks etc" 
} 

Dieser Ansatz scheint ziemlich umständlich zu sein und wenn mehr Klassen erstellt werden, geraten die Anzahl oder die Arraylisten, die geparst und durchgeschleift werden müssen, außer Kontrolle. Kann jemand einen besseren Weg empfehlen, dies zu tun? Ich denke, der naheliegende Weg wäre, eine Liste zu haben, die alle NPC-Objekte enthält und dann ihren Klassentyp zu verfolgen und entsprechend zu aktualisieren. Ist das ein besserer Weg, oder kann mir jemand in die richtige Richtung zeigen?

+0

Wäre es nicht einfacher, eine "World" -Instanz zu übergeben? Sie könnten grundsätzlich 'for (updatableThing: updatableThings) {updatableThing.update (world); } ' – plalx

+0

Vielleicht verstehe ich nicht vollständig (noch lernen), aber diese World-Instanz würde immer noch eine Liste halten, die alle Objekte richtig enthält oder aktualisiert oder ich etwas falsch verstehe? – user2913053

Antwort

2

Ja, es gibt einen viel besseren Weg.

Für jede Art von Objekt in Ihrem Spiel, erarbeiten Sie die Reihe von Verhaltensweisen/Eigenschaften, die es zeigen muss. Diese Verhaltensweisen sollten als Schnittstellen definiert werden. Dann kann der Code, der sich mit den Verhaltensweisen/Merkmalen befasst, die Schnittstelle verwenden, ohne irgendetwas über die tatsächliche Klasse wissen zu müssen.

Zum Beispiel, wenn einige Objekte in jeder Runde entsprechend ihrer aktuellen Geschwindigkeit bewegen und können potentielle kollidieren mit anderen Objekten, dann könnte es eine Schnittstelle sein:

public interface Moving { 
    void move(); 
    boolean hasCollided(Shape shape); 
    void handleCollision(Collision collision); 
} 

Jede Klasse, die dann diese Schnittstelle implementieren würde bewegt. Das World Objekt könnte dann ein List<Moving> movingObjects und dann verwenden:

movingObjects.forEach(Moving::move); 

in seiner update Methode.

Kollisionen zu behandeln nach dem Bewegen Sie etwas haben könnte:

List<Collision> collisions = getAllCollisions(movingObjects); 
for (Collision collision: collisions) { 
    for (Moving element: collision.getCollidingObjects) { 
     element.handleCollision(collision); 
    } 
} 

Wenn mehrere Klassen, die die Schnittstelle einen ähnlichen Mechanismus selbst verwenden implementieren zu bewegen, dann sollten Sie diese Logik in eine eigene Klasse verschieben:

class Inertia implements Moving { 
    private Velocity velocity; 

    @Override 
    public void move(Shape shape) { 
     velocity.applyTo(shape); 
    } 

    @Override 
    public void applyForceFrom(Position position) { 
     velocity.accelerateAwayFrom(position); 
    } 
} 

Ihre Objekte Welt können dann ihre Bewegungsverhalten dieser Klasse delegieren:

class Asteroid implements Moving { 
    private final Inertia inertia; 
    private Shape shape = new Circle(radius); 

    @Override 
    public void move() { 
     inertia.move(shape); 
    } 

    @Override 
    public boolean hasCollided(Shape other) { 
     return this.shape.intersects(other); 
    } 

    @Override 
    public void handleCollision(Collision collision) { 
     intertia.applyForceFrom(collision.getCentreOfMass()); 
    } 
} 

Das mag eine unnötige Umleitung sein, aber die Erfahrung hat gezeigt, dass es sich auf lange Sicht lohnt. Weitere Informationen finden Sie unter Delegation Pattern.

Sie können mehrere Delegierte haben, wenn sich die Bewegungen pro Objekt unterscheiden (z könnte sein eigenes move implementieren, wenn sein Verhalten einzigartig ist. All dies kann passieren, ohne dass World über die Klasse des Objekts überhaupt etwas wissen muss, ruft move auf.

Als allgemeine Regel versuchen Sie zu vermeiden, extends für den Zweck zu verwenden, Verhalten von einer Oberklasse zu erben. Deine Struktur von Hunter erweitert NPC erweitern GameObject wird bequem bis zu dem Punkt, an dem Sie erkennen, dass Sie auch wollen, dass Jäger Enemy oder AIControlled oder etwas anderes verlängern. Harte Erfahrungen haben OO-Codern gezeigt, dass diese Art von Hierarchien zunächst vernünftig und elegant aussehen, aber nicht mehr zu verwalten sind, wenn Sie kompliziertere Funktionen hinzufügen.

Um noch weiter zu gehen und alle Details zu verbergen, welche Objekte welche Verhaltensinterfaces von World implementieren, möchten Sie vielleicht die Visitor Pattern betrachten. Dies würde es dem Weltobjekt ermöglichen, alle Spielobjekte als ein Beweger zu besuchen, dann als ein AIAgent, dann als ein benutzergesteuertes Objekt und so weiter, ohne jemals wissen zu müssen, was sie während des Besuchs tun (oder wenn sie überhaupt etwas tun). Es ist sehr leistungsfähig, wenn es gut angewendet wird, aber es erfordert etwas gewöhnungsbedürftig.

Schließlich gibt es ein sehr häufiges architektonisches Muster, das von Spielautoren genannt Entity Component System verwendet wird. Wenn Sie Java nur lernen, würde ich das im Moment ignorieren, aber wenn Sie ein ernsthafter Spieleentwickler werden, werden Sie wahrscheinlich feststellen, dass es eine Verbesserung gegenüber der Architektur ist, die ich oben beschrieben habe.

Ich habe im Beispiel vieler Details offensichtlich weggelassen (wie die Definitionen von Shape, Circle, Position, Velocity, Collision etc.), aber das ist die allgemeine Idee. Es gibt noch viel mehr und es lohnt sich, nach einem Buch oder Tutorial zu objektorientiertem Design zu suchen, um tiefer zu schauen.

+0

Vielen Dank für die sehr gründliche Antwort. Ich kann sehen, wie dies das Wiederholen von Code in ähnlichen Klassen vermeiden kann. Was ist jedoch, wenn die Bewegung der Objekte voneinander abhängt? Felsen können kollidieren (und somit die Richtung ändern), der Jäger kann einem Stein ausweichen oder den Spieler verfolgen. Das würde erfordern, dass die Bewegungsfunktion alle Objekte kennt und die Bewegung entsprechend berechnet. Würde die Move-Funktion wissen, welches Objekt sie aufgerufen hat, und dann die Moving-Liste durchlaufen, die, wie ich im Originalbeitrag erwähnt habe, im Grunde genommen alle meine aktuellen Listen zu einem zusammenfasst? – user2913053

+0

Ich werde ein gemeinsames Modell für Kollisionen in meiner Antwort geben, um Ihnen eine Idee zu geben, wie Sie damit umgehen. Ein ähnliches Modell sollte für Ihre anderen Beispiele funktionieren. – sprinter

+0

Ok, meine Antwort wird jetzt lächerlich lang. Wenn es hilfreich war, dann akzeptiere es bitte und stelle eine andere Frage, wenn es etwas Bestimmtes gibt, mit dem du Hilfe brauchst. – sprinter