# Aufbau für 2D Spiele



## sophismo (24. Aug 2012)

Ich habe mit Qualxis Tutorial angefangen mein erstes grafisches Spiel zu schreiben und hab schon während des "mitmachens" einiges verändert, vor allem was den Aufbau angeht.

Es geht mir um einen Sidescroller, so ähnlich wie das Spiel im Tutorial, nur dass es verschiedene Gegner / Level und so weiter geben soll. Wer das Tutorial kennt wird sich jetzt vielleicht eher auskennen, was ich meine, aber ich werd versuchen das möglichst neutral zu erklären.

Der Umfang ist erstmal nur ganz klein und soll halt irgendwie ein Level sein. Aber mit dem Hintergedanken, dass der Aufbau auch mehr erlauben soll (speichern, laden, > zumindest solang man das Spiel nicht als ganzes schließt, ein Menü, Lvl Auswahl stehen bisher auf meinem Plan). Umsetzung dann bis inkl. nächstes Leben, aber Stück für Stück mehr.

Meine Frage betrifft eben den Aufbau eines Spiels (recht generell):

Bisher gibts ein GamePanel mit Spielschleife, Tastaturabfrage, ein Started Boolean (der die Spielschleife am Laufen hält) , ein Vektor mit meinen Actors<Sprite> (der wird übergeben) und eine "Engine".

Die Klasse/Objekt, dass vom Panel erzeugt wird, Engine wiederum ruft jegliche Logik auf, bewegt die Acteure und lädt für sich wieder Bibliotheken für Sound und Bildmaterial. Und gibt den Vektor mit den Sprites zurück an das Panel, dass es zeichnet.


Am wichtigsten erstmal ist: wie mach ich am besten "Welten"? Wobei es eigentlich nur darum geht, dass diese Welt (wo im Code soll das sein?) verschiedene Gegner aufruft. Ich hab mir das so gedacht, dass immer bei Logik (in der SpielSchleife) oder so die Zeit an das Lvl Objekt übergeben wird, und dann nach x Sekunden der und der Gegner spawnt. Aber dann weiß ich nicht genau, wie ich eine unterscheidung treffe, welche lvl jetzt gespielt wird.. Also dass ich einen Wert mit übergeben kann ja, aber soll ich dann für jede Lvl eine Klasse schreiben?

Im Sinne von


```
//GamePanel->
while (spielschleife){
engine.doLogic();
..
}

//Engine
void doLogic(){
updateLvl();
..
}


Class Level{
Engine parent;
int Spielzeit

void updateLvl(){

switch(spielZeit){

case 1: this.parent.addGegner();
case 15: this.parent.add.Gegner2();

spielzeit = System.getMillis() / 10e3;
}
}
}
```

Zum Menü: wie macht Ihr das? Dass man von einem "Fenster" sag ich jetzt mal auf die SpielFläche mit dem eigentlichen Lvl darauf kommt? Sollte die Spielschleife wo anders hin?! Oder passt das schon so, dass das mein Spielfenster ist, auf dem jedes Lvl kommt, und ansonsten komme ich ohne aus?


Bin da sehr am Anfang - ist mein erstes grafisches Spiel und ich suche keine Bibel, sondern Vorschläge, oder Pseudo-Code, wies "schön" programmiert ist. Also wofür man eine Klasse aufmacht und was durchaus auch mit anderen Dingen in einen Topf geschmissen werden kann.

Danke


----------



## Marco13 (24. Aug 2012)

Ja, es ist schwierig irgendwelche konkreten Vorschläge zu machen. Deswegen alles unter Vorbehalt. Warte auch ab, was ggf. andere (vielleicht auch Quaxli) dazu sagen. 

Soweit ich das Tutorial überblicke, wären die meisten Klassen/Interfaces kaum von einer Erweiterung auf mehrere Level betroffen. Der erste Ansatzpunkt wäre die Klasse "GamePanel". Dort steckt ja im Moment als Tribut an Kompaktheit und Übersichtlichkeit für den ersten Einstieg, den das Tutorial bieten soll) ziemlich viel drin. Du hast aber schon eine Klasse "Engine" erwähnt, d.h. ich gehe davon aus, dass du schon die erste Trennung vollzogen hast, nämlich zwischen dem Spielablauf und der Darstellung (und ggf. der Eingabe). 

Man müßte sich überlegen, wie man die Übergänge zwischen den Leveln machen wollte. Soll das Spiel da angehalten werden und irgendein Info-Screen erscheinen, oder soll sich das nur dadurch äußern, dass neue Gegner-Arten kommen? Über den Übergang und die benötigten Abläufe (Start/Pause/Stop/Reset?, Levelübergänge) sollte man sich einigermaßen im Klaren sein. 

Für das Level an sich könnte man sich ein Interface und / oder eine abstrakte Basisklasse erstellen. Dann stellt sich sofort die Frage, wodurch sich die Level unterscheiden sollen. NUR durch die Art (und Häufigkeit) der Gegner? Dann würde es für diesen Punkt ja schon eine Methode tun wie

```
abstract Sprite createEnemy();
```
die dann, sinngemäß, dort aufgerufen wird, wo im Original-GamePanel "createRocket" aufgerufen wurde. Diese Methode kann man dann für die verschiedenen Level passend überschreiben. Die Häufigkeit würde dann einfach über einen anderen Delay-Parameter für den 'Timer' gesteuert. Ob das reicht, oder es elegantere Lösungen gibt, müßte man sich überlegen. Auch ob die Gegner noch mehr machen können sollen (Schießen, ausweichen, ... )

Nochmal: Alles unter Vorbehalt.


----------



## sophismo (24. Aug 2012)

Genau, also Spielverlauf/fluss läuft in der Engine ab, also dass sich die einzelnen Sprites bewegen und Tätigkeiten ausüben etc.!
Darstellung und Eingabe sind auf dem Panel.

Also genauer, wie ich mir das vorstelle (in vielen Jahren, noch kurz bevor dem letzten atomaren Krieg):

Man öffnet das Programm: Es kommt ein Menü.
Ohne Swing oder dergleichen, sondern Bilder und da soll man mit den Pfeil Tasten ein Lvl auswählen (im idealfall soll das freigeschalten werden müssen).

Dann startet das GamePanel (ich stell mir das so vor, dass dann einfach ein Parameter übergeben wird für die Auswahl - ist das grauenhaft, oder passt das schon?)

und dann soll im GamePanel das ablaufen, was das Lvl hergibt. Also diverse Gegner, "Schwärme" also Muster in denen die auftreten - kann man glaub ich gut in Methoden mit Schleifen abstrahieren. Dass die Gegner verschieden sind, und auch Schießen etc das kann ich glaub ich ganz gut allein mit neuen Classes, die sich halt im "Verhalten" unterscheiden.

Ich würde nur gerne wissen, wo/wie das sinnvoll ist die Gegner aufzurufen und wohin und welcher "Uhrmechanismus" - falls der Ansatz überhaupt taugt - und auch wohin mit dem? Also in die Engine?

Betreffend der abstrakten Klasse, die du gemeint hast:

Meine Engine kann schon Gegner erzeugen und ins Spiel bringen, nur soll das ja Haufenweise sein  -> wie gedenkst du das mit einer Methode?
Dank dir hab ich jetzt immerhin eine Möglichkeit: eine abstrakte Klasse mit mehreren Methoden (war das so gemeint?):


```
abstract Class Generator{

private void updateLevel1(int timeIndicator){
switch (timeIndicator){
case 1: engine.erzeuge1();
...
}
} 
private void updateLevel2(int timeInd){..}
}
```

Und je nach Übergabe an die Engine wird dann die Methode von der Engine im Zuge der Spielschleife aufgerufen.

Zum Reseten und dergleichen klingt das aber sehr umständlich.
Jemand mit Spielerfahrung (nicht so wie ich nur im spielen^^) einen Vorschlag?
Danke schon wieder, immerhin hab ich jetzt 1 Idee.


----------



## Marco13 (24. Aug 2012)

Hm... ohne dass es einen "zu" konkreten Anlass gibt, das zu erwähnen: Du solltest dir auch genauer überlegen, ob die Klassen aus den Tutorial für dein Ziel passen. Du musst sie ja nicht 1:1 übernehmen, das wichtige bei den Tutorial sind ja die Konzepte und Grundlagen. (Z.B. wenn du jetzt davon redest, dass die Gegner verschiedene "Verhalten" haben sollen, kann es sein, dass da eine aufgebohrte "Sprite"-Klasse nicht ausreicht  )

Die Frage mit der abstrakten Klasse und der Erzeugung der Gegner habe ich nicht ganz kapiert... Welche Kriterien soll es dafür geben, dass ein Gegner erscheint? Nur die Zeit? Wo soll der Gegner erscheinen? An einer zufälligen Position? Was ich mit der abstrakten Klasse meinte, dass eine "minimale" Erweiterung der Klassen aus dem Tutorial _im Pseudocode_ eben sein könnte

```
class Engine
{
    private Level currentLevel = new LevelWithRockets();

    public void run()
    {
        while (!gameOver())
        {  
            while (!currentLevel.isOver())
            {
                currentLevel.doOneStep();
            }
            // Now currentLevel is over, create new one, with Planes - smarter than Rockets!
            currentLevel = new LevelWithPlanes();
        }
    }
}


abstract class Level
{
    // All the logic stuff that is equal for all levels, including...
    public void doOneStep()
    {
        ...
        if (newEnemyShouldAppear())
        {
            Enemy enemy = createNewEnemy(); // Call the abstract method
            enemies.add(enemy);
        }
    }

    protected abstract Enemy createNewEnemy(); // The abstract method
}

// Two Levels that create different enemies:

class LevelWithRockets extends Level
{
    @Override
    protected abstract Enemy createNewEnemy()    
    {
        return new Rocket(atRandomPosition());
    }
}


class LevelWithPlanes extends Level
{
    @Override
    protected abstract Enemy createNewEnemy()    
    {
        return new Plane(nearThePlayer());
    }
}
```


----------



## c_sidi90 (27. Aug 2012)

Ich habe damals auch einen Sidescroller programmiert. Ich hatte da aber etwas unkonventionelle Lösung für das Einlesen der Entitäten und "spawnen" von Gegnern. Mein Levelobjekt hat bei Initialisierung direkt alle Werte aus einer XML gezogen. Diese XML-Datei beinhaltete Nodes von Monstern/Blöcken usw mit childElementen wie spawnAt x=wert y=wert typ:Monster Enum, collidable = true/false; dmg =value usw.

Das Levelobjekt hat die XML beim Start ausgelesen und alle Entitäten wie Blöcke und Monster wurden Erstellt und an eine EntityManager Klasse übergeben. Diese beinhaltete einen Thread der stets die aktuelle X-Position des Spielers abgefragt hat. Wenn Diese X-Koordinate >= eines Spawnpoints eines Monster - Bildschirmbreite war, wurde die Methode spawn() des Monster/Block - Objekts aufgerufen.

Ich hab es zwar nicht ausprobiert, kann mir aber vorstellen das es wesentlich performanter ist, alle Entities beim Laden des Levels gleich zu initialisieren und spawnen zu lassen.


----------



## sophismo (27. Aug 2012)

Das ist eine gute Idee! Danke!

Also ich habe jetzt schon dran rumgewerkt und ich hab eine sehr ähnliche Vision im Kopf (Einlesen etc.). Ja, alles Laden wäre Wahnsinn, natürlich mach ich das nicht. Aber bei mir wird jetzt getimed das Level durchschritten. Also wenn die Spielzeit gerade Division durch 3 erlaubt wird ein Kreis an Reketen gespawnt und dergleichen..

Das Multithreaded ding klingt aber sehr interessant.. Bei mir wird einfach jede Spielschleife eine updateLevel(long spielZeit) Methode ausgeführt. Falls möglich könntest du mir den Code, der die Lvls aus der XML ließt und den Multithreaded Teil des Codes raussuchen und zukommen lassen, oder vielleicht einfach etwas pseudocode schreiben, wie du das gemacht hast: ich hab vor allem Angst, dass das Updaten der Lvl dann zu Laufzeitfehlern und Doppelzugriffen führt.. (bin da noch sehr blau hinter den Ohren was Threading betrifft).

Wäre sehr dankbar, weil mein Gameloop sowieso 10ms pausiert jeden Durchlauf und das könnte genug Zeit sein genau dann die Lvl zu erweitern..

Danke schonmal für den Tipp!


----------



## c_sidi90 (27. Aug 2012)

Hab den Code nicht mehr, da das ganze bestimmt an die 5 Jahre her ist. Aufjedenfall findest du auch hier im Forum einige Threads und Tutorials wie du Daten aus einer XML-Datei parsen kannst.

Damals habe ich dafür JDOM verwendet. 

Du liest also alle Nodes (Einträge) der Datei ein und Filterst. (am besten Vor dem Spielstart alles einlesen)

Klasse Level:

Methode: loadLevel(String pfadZurXML)

```
if(aktuelleNode(Element) type = "Monster") 
         Monster m = new Monster();
         //lese die Attribute der aktuellen Node und weise sie dem Monster-Objekt zu
         monster.setX(aktuelle Node.getAttribute("X")
         monster.setY(aktuelle Node.getAttribute("Y")
         monster.setType(aktuelle Node.getAttribute("type");
         //Füge das Objekt der EntityManager Klasse hinzu (Verwaltungsklasse)
         EntityManager.addEntity(m);

//Das selbe tust du auch für Nodes vom typ ="Block" o.ä.
```

Klasse EntityManager implements Runnable

```
//Konstruktor
EntityManager(Game game)//Konstruktor bekommt die Instanz des Spiels übergeben, um an Werte wie z.B. die aktuelle Spielerposition zu gelangen.


player_xPos;
//ArrayList für alle Entities
ArrayList entities;

update(){
         player_xPos = game.getPlayerPositionX();

        for(Entity e : entities){
            if(e.getSpawnAt >= player_xPos - Game.getWidth)
                   e.spawn();

}
```

Ich habe die Objekte also erst spawnen lassen, wenn sich der Spieler sich der Spawnposition der Objekte nähert. Dies hatte den Grund, das jedes Objekt (Entity) sich auch bewegen konnte, und ich so erst die update Methoden der Objekte aufrufen musste, wenn sie überhaupt gerendert werden.

Hoffe das hilt dir und verwirrt dich nicht, so ungefähr hat das von der Logik damals ausgesehen, vlt nicht sehr schön, funktioniert hat mein Spiel jedoch.


----------



## sophismo (27. Aug 2012)

Also scheinbar gibts ja da keine halbwegs konventionelle Lösung, aber nachdem immerhin 2 relevante Vorschläge waren, erklär ich das hier mal für beantwortet.

Danke an euch beide!


----------

