Snake - Komplette Schlange bei Richtungsänderung korrekt zeichnen

Hi,

Ich arbeite gerade an einen Snake Klon und stehe vor dem Problem, dass ich den typischen Bewegungsablauf der Schlange nicht richtig hinbekomme.
Derzeit bin ich soweit, dass das ganze Spiel zwar funktioniert, allerdings wenn meine Schlange schon mehr als aus nur 1 “Teilchen” besteht und ich dann bei Bewegung die Richtung ändere, dann ändert sich sofort die komplette Schlange. Die Schlange ist also komplett starr wie ein Lineal.

Die Theorie ist mir eigentlich klar:

  • Schlangenkopf zeichnen
  • Im nächsten Schritt dann die Koordinaten des vordersten Teils an den 2. Teil weitergeben usw.
  • Letzten Teil löschen bzw. nicht mehr zeichnen

Aber ich weiß einfach nicht wie ich es genau umsetzten soll…

Hier mal mein bisheriger Schlangen-Heichnungsalgorithmus:

// Schlangenkopf zeichnen
g2.fillRect(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10);
				
				//Schlangenrumpf zeichnen
				for(int i=1; i<SnakeSpiel.liste.size(); i++) {
					
					
					if(SnakeSpiel.schlangeLinks == true) {
						g2.fillRect(SnakeSpiel.schlangeX+13*i, SnakeSpiel.schlangeY, 10, 10);
					}else if(SnakeSpiel.schlangeRechts == true) {
						g2.fillRect(SnakeSpiel.schlangeX-13*i, SnakeSpiel.schlangeY, 10, 10);
					}else if(SnakeSpiel.schlangeOben == true) {
						g2.fillRect(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY+13*i, 10, 10);
					}else if(SnakeSpiel.schlangeUnten == true) {
						g2.fillRect(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY-13*i, 10, 10);
					}
					
					
				}

Erklärung zum Code:

  • Sobald der Kopf ein Essen berührt, wird in SnakeSpiel.liste (ist eine ArrayList welche Booleans enthält) ein Boolean “true” hinzugefügt. (Typ der Liste ist ja egal, es geht nur um die Anzahl der Schlangenkörperteile)
  • Die Variablen “schlangeLinks”… werden auf true gesetzt, wenn die Schlange nach links… läuft
  • schlangeX, schlangeY sind die Koordinaten des Kopfteils, welche in der Game-Loop ständig geändert werden (je nachdem welche Richtung gerade aktiv ist)

Ich würde vorschlagen, du nimmst in deiner liste liber Instanzen von Point oder Rectangle auf und zeichnest diese einfach stumpf. Dann musst du beim Zeichnen auch nix prüfen oder sowas. Bei jedem Tick wird einfach als Kopf ein neues Objekt mit den entsprechend neuen Koordinaten hinzugefügt und das letzte Objekt aus der Liste verbannt. Dadurch haben alle Körperteile automatisch die richtige Position, da der alte Kopf in der Liste quasi nach hinten rutscht und ein neuer Kopf hinzugefügt wird. Damit hättest du dann deine “Übergabe” des alten Kopfes an das zweite Körperteil.

Ja, das ist einer der Fälle wo sich eine http://docs.oracle.com/javase/6/docs/api/java/util/Deque.html anbietet, mit den Methoden addFirst und removeLast.

Ja, das wäre einer normalen Liste sogar zu bevorzugen :wink:

Obwohl … Wenn man eine normale Liste rückwärts betrachtet, dann wäre das Element 0 das Ende, das könnte man rausschmeißen und das neue Element wird ja an das Ende angefügt, also der neue Kopf.

Aber wie auch immer, jedenfalls spart eine solche Vorgehensweise merkwürdige Rechnereien, Prüfungen, etc. beim Zeichnen. Nur beim Hinzufügen des neuen Kopfes muss für diesen eine neue Koordinate bestimmt werden, somit ist der Code verständlicher, kompakter und das Zeichen ist nur das Durchiterieren über eine Collection, Welche auch immer das sein mag.

Danke für die bisherigen Antworten.

Ich habe es nun erstmal mit einer ArrayList vom Typ Rectangle probiert. Aber ich kriege es einfach nicht hin…Sie bewegt sich weiterhin komplett starr.

Hier ein Auszug aus meiner Gameloop:

//Hat Schlange das Essen berührt? Falls ja, Schlange vergrößern
			if(Math.abs(SnakeSpiel.essenX-SnakeSpiel.schlangeX)<=10  &&  Math.abs(SnakeSpiel.essenY-SnakeSpiel.schlangeY)<=10) {
				System.out.println("Kollsion");
				SnakeSpiel.essenGesetzt = false;
				
				if(SnakeSpiel.schlangeLinks == true) {
					SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX+13, SnakeSpiel.schlangeY, 10, 10));
				}else if(SnakeSpiel.schlangeRechts == true) {
					SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX-13, SnakeSpiel.schlangeY, 10, 10));
				}else if(SnakeSpiel.schlangeOben == true) {
					SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY+13, 10, 10));
				}else if(SnakeSpiel.schlangeUnten == true) {
					SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY-13, 10, 10));
				}
			}
			
			
			//Schlangenteile korrekt "weiterreichen" bzw. Koordinaten weiterreichen, damit sich die Schlange korrekt bewegt
			for(int i=0; i<SnakeSpiel.liste.size(); i++) {
				if(i+1<=SnakeSpiel.liste.size()-1 && SnakeSpiel.liste.size()>1) {
					
					if(i==0) {
						SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
					}
					
					SnakeSpiel.liste.get(i+1).x = SnakeSpiel.liste.get(i).x;
					SnakeSpiel.liste.get(i+1).y = SnakeSpiel.liste.get(i).y;
				}
				
				if(i==SnakeSpiel.liste.size()-1) {
					SnakeSpiel.liste.remove(i);
				}
			}

Auszug aus der paintComponent():

//Schlangenkopf zeichnen
				g2.fillRect(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10);
				
				//Schlangenrumpf zeichnen
				for(int i=1; i<SnakeSpiel.liste.size(); i++) {
					
					if(i==1){
						g2.setColor(Color.BLACK);
					}else {
						g2.setColor(Color.BLUE);
					}
					
					if(SnakeSpiel.schlangeLinks == true) {
						g2.fillRect(SnakeSpiel.schlangeX+13*i, SnakeSpiel.schlangeY, 10, 10);
					}else if(SnakeSpiel.schlangeRechts == true) {
						g2.fillRect(SnakeSpiel.schlangeX-13*i, SnakeSpiel.schlangeY, 10, 10);
					}else if(SnakeSpiel.schlangeOben == true) {
						g2.fillRect(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY+13*i, 10, 10);
					}else if(SnakeSpiel.schlangeUnten == true) {
						g2.fillRect(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY-13*i, 10, 10);
					}
					
					
				}

Vielleicht mal nebenbei, bevor das ausartet (falls es das nicht schon getan hat) : Die ganzen Variablen, die in “SnakeSpiel” liegen, scheinen “public static” zu sein. Das sollte man nicht machen. Erstelle dir stattdessen z.B. eine Klasse “Schlange”, die die nötigen fields (private!) enthält, und NUR über public-Methoden verändert werden kann.

Abgesehen davon dürfte das Problem irgendwie hier liegen

SnakeSpiel.liste.get(i+1).x = SnakeSpiel.liste.get(i).x;
SnakeSpiel.liste.get(i+1).y = SnakeSpiel.liste.get(i).y;

Überleg’ mal, was passiert, wenn die Schleife da durchläuft:
Element 2 wid auf die Position von Element 1 gesetzt.
Element 3 wid auf die Position von Element 2 gesetzt.
Element 4 wid auf die Position von Element 3 gesetzt.

Das ist wohl nicht richtig. Abgesehen davon: KEIN Element der Schlange, das schon in der Liste ist, muss überhaupt noch verändert werden. Es wird immer nur ein “neuer Kopf” vorne dran gesetzt, und das letzte Stück vom Schwanz abgeschnitten.

[QUOTE=Marco13]Vielleicht mal nebenbei, bevor das ausartet (falls es das nicht schon getan hat) : Die ganzen Variablen, die in „SnakeSpiel“ liegen, scheinen „public static“ zu sein. Das sollte man nicht machen. Erstelle dir stattdessen z.B. eine Klasse „Schlange“, die die nötigen fields (private!) enthält, und NUR über public-Methoden verändert werden kann.
[/QUOTE]

Das werde ich als nächstes erstmal ausbessern, bevor es weiter geht.

Das Problem ist nun gelöst (Danke nochmals! :wink: ).
Nachdem ich nun meine Gameloop verbessert habe (Nach dem Prinzip „Schlangenende löschen, Schlangenkopf hinzufügen“) und es erstmal immer noch nicht ging, ist mir dann aufgefallen, dass ich in meiner paintComponent() garnicht richtig die Rectangles aus meiner ArrayList zeichne. Ich hatte da zwar meine ArrayList als Abfrage in der for-Schleife drinnen, aber das war es dann auch. Gezeichnet hatte ich dann seltsamerweise neue Rectangles und nicht die aus der ArrayList.

Falls hier noch jemand an der Lösung intressiert sein sollte, hier die funktionierende Lösung, allerdings noch mit der unsauberen „public static Variablen statt Klassen und getter“ Variante:

Gameloop:

			//Hat Schlange das Essen berührt? Falls ja, Schlange vergrößern
			if(Math.abs(SnakeSpiel.essenX-SnakeSpiel.schlangeX)<=8  &&  Math.abs(SnakeSpiel.essenY-SnakeSpiel.schlangeY)<=8) {
				System.out.println("Kollsion");
				SnakeSpiel.essenGesetzt = false;
				
				if(SnakeSpiel.schlangeLinks == true) {
					SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
				}else if(SnakeSpiel.schlangeRechts == true) {
					SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
				}else if(SnakeSpiel.schlangeOben == true) {
					SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
				}else if(SnakeSpiel.schlangeUnten == true) {
					SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
				}
			}


			//Schlangenteile korrekt "weiterreichen" bzw. Koordinaten weiterreichen, damit sich die Schlange korrekt bewegt ->
			// Dazu das Schlangenende entfernen und einen neuen Kopf hinzufügen. Dadurch bewegt sich die Schlange um eins weiter
			SnakeSpiel.liste.add(new Rectangle(SnakeSpiel.schlangeX, SnakeSpiel.schlangeY, 10, 10));
			SnakeSpiel.liste.remove(0);

paintComponent():

				//Schlange zeichnen
				for(int i=1; i<=SnakeSpiel.liste.size(); i++) { 
					g2.fillRect(SnakeSpiel.liste.get(i-1).x,SnakeSpiel.liste.get(i-1).y, 10, 10);
				}