# Tischtennis-Spiel



## bernii (6. Feb 2010)

Hi, ich will ein Tischtennisspiel programmieren, nur leider treten einige logische Fehler auf.
1.
Ich habe zunächst eine Frage bei der Richtungsänderung, wenn der Ball auf den schläger trifft.

Ich hab mir gedacht, dass, wenn der Ball auf den rechten Schläger trifft, x_speed = -1(bei  x_pos += x_speed) gesetzt wird und y_speed (vom Typ double) = -0.1 . Nur leider klappt das mit der y-richtung nicht, also der Ball soll ja sich ja nur minimal nach unten oder nach oben bewegen, da dies ja kein ping-pong-spiel ist, sondern eine tischtennisspiel, wo der ball schnell ins aus fliegen kann.

2.
Außerdem soll das Spiel in der "D-Sicht von oben gezeichnet werden. Hat jemand noch eine gute Idee wie ich es möglichst realistisch anstellen kann, dass es so aussieht, dass der Ball sich springen bewegt. Ich hab mir gedacht, dass der Ball, wenn er über dem "netz" ist größer wird, an einer bestimmten x_koordinate des feldes dann möglichst klein und dann wieder größer.
Oder aber ich lass den Ball immer gleich groß und mal dort nur Striche herum, wenn der Ball aufkommt. Das Problem ist wie ich bestimmen soll, wo der Ball aufkommen soll.

3.
Ein weiteres Problem ist, dass ich nicht weiß, wie ich es anstellen soll, dass der Spieler bestimmen kann, wie stark der Ball zurückgeschlagen werden soll und in welche Richtung genau. Ich kann ja schlecht messen, mit welcher Geaschwindigtkeit der Schläger auf den Ball trifft oder?

Wäre nett, wenn jemand Ideen hätte


----------



## Steev (6. Feb 2010)

Hi bernii,

ich hätte jetzt auf die Schnelle folgende Ideen:

Die Berechnung der Bälle müsste 3-dimensional erfolgen, damit der gewünschte Springeffekt zu stande kommt. Du brauchst ja schon bereits 2 richtungen um die "normale" Bewegung des Balls abbilden zu können.
Die dritte Dimension würde ich einfach nur in die Breite und Höhe des Balles mit einberechnen, damit der Spieler den Eindrück hat, dass der Ball auf oder herunter Bewegt wird.
Die Platte würde ich als eigene Klasse definieren, die eine eigene Z-Position hat. Wenn die Position sich innerhalb der Fläche der Platte befindet (Rectangle.contains), dann würde ich die Z-Position des Balles mit der Z-Position der Platte vergleichen. Ist die Z-Position des Balles kleiner oder gleich die der Platte, so muss die relative Z-Geschwindigkeit des Balles umgedreht werden. (ball.z = -ball.z; )

Die physikalische Korrektheit lasse ich jetzt der einfachheit halber mal ausehnvor. Normalerweise wird nämlich dann die Energie des Balles durch den Aufprall gemindert.

Die Z-Position des Balles wird auserdem permanent um den Schwerkraftfaktor verringert. Dabei wird überprüft, ob der Ball sich "auf" der Platte befindet, oder eben nicht.

Damit die Höhe des Balles besser ersichtlich ist, würde ich einen Schatten unter den Ball zeichnen. Der Schatten bekommt dieselbe X- und Y-Position wie der Ball. Die X- und Y-Position wird dann aber zusätzlich um die aktuelle Z-Position des Balles erhöht.

Soweit klar? Ok.

Du hast ja jetzt zwei Spieler und eine Platte. Am Anfang würde ich einfach einem der beiden Spieler den Ball geben und ihn eine Angabe machen lasse. Dabei wird der Ball direkt vor dem Schläger platziert. Jetzt würde ich "das Spiel anhalten" und dem Spieler die Möglichkeit geben über Buttons oder über die Tastatur oder über die Maus zu bestimmen, wie der Ball geschlagen werden soll (Richtung), wie feste der Ball geschlagen werden soll (Geschwindigkeitsfaktor) und wie der Ball angedreht werden soll (Kreisfaktor).

Dann würde ich den Spieler die Angabe machen lassen. Der Ball bekommt seine relative Geschwindigkeit und wird so lange bewegt, bis er entweder regelwiedrig auf den Platten aufgekommen ist, oder von dem zweiten Spieler nicht "gefangen" wurde. Hierbei würde ich prüfen, ob der Ball an einer bestimmten Position vor dem Schläger des zweiten Spielers ist (Rechteckiger Bereich). Wenn der Ball da drin ist, dann würde ich das Spiel wieder anhalten und dem zweiten Spieler die Möglichkeit geben, den Ball entsprechend zurückzuspielen. Dabei muss natürlich die Richtung und die Geschwindigkeit des Balles berücksichtigt werden.

Das Netz:
Das mit dem Netz ist im Grunde genommen auch ganz einfach: Ich würde einfach einen bereich auf der Platte definieren, wo der Ball eine Mindesthöhe haben muss. Ist dies nicht der Fall, so ist der Ball "im Netz" und der anderer Spieler bekommt eine Angabe.

Die Angabe:
Bei den Angabgen muss geprüft werden, dass der Ball nur einmal auf der Seite des Spielers und einmal auf der Seite des Gegenspielers aufkommt.
Das Aufkommen des Balles ist ja ganz einfach: Wenn die Z-Position des Balles kleiner oder gleich ist, als die Z-Position der Platte. So wird die Z-Gewschwindigkeit um einen Faktor verringert und umgedreht (z_speed = -z_speed).
Dabei kann ja eine Ereignismethode aufgerufen werden, wo geprüft wird, ob aktuell eine Angabe oder ein Normalspiel vorliegt.

So erstmal grob:

Wenn du noch Fragen hast, dann kannst du dich ja nochmal melden.

Gruß
Steev


----------



## bernii (7. Feb 2010)

Danke schonmal für die Antwort.
Ich will jetzt erstmal die grundlegenden Abläufe fertig machen, nur dabei habe ich das Problem mit dem Abprallen des Balls vom Schläger. Ich würd gerne ein flüssigen Spielablauf haben, also dass der Ball direkt abprallt, nachdem der Spieler oder der CPU bei der Angabe die Geschwindigkeit und die Richtung bestimmt hat.
Ich fänds am besten, wenn der x-speed des Balles beim Aufprall einfach umgedreht wird (x_speed = -x_speed) und der y_speed minimal nach oben oder nachunten verändert wird (y_speed = 0.1). Nur irgendwie ist es egal ob ich dort 0.1, 0.4, 0.9 usw hinschreibe, der Ball ändert sich in seiner y-pos gar nicht, genauso ist es egal ob ich y_speed zu 1.1 oder zu 1.9 definiere. Woran liegt das? An dem Typ double?
Der y-speed-Wert darf sich ja nur minimal ändern, weil der Ball sonst sofort in eine komplett andere Richtung schissen würde und nicht auf die Platte des Gegners.
Wenn ich das hingekriegt habe kümmer ich mich um den Springeffekt, dein Voerschlag Steev hört sich ja ganz gut an.
(Wobei ich noch nicht verstanden habe, wie bei deiner Idee bestimmt wird, wann und wo der Ball genau auftickt)

Danke


----------



## Steev (7. Feb 2010)

Hi bernii,



bernii hat gesagt.:


> Ich fänds am besten, wenn der x-speed des Balles beim Aufprall einfach umgedreht wird (x_speed = -x_speed) und der y_speed minimal nach oben oder nachunten verändert wird (y_speed = 0.1). Nur irgendwie ist es egal ob ich dort 0.1, 0.4, 0.9 usw hinschreibe, der Ball ändert sich in seiner y-pos gar nicht, genauso ist es egal ob ich y_speed zu 1.1 oder zu 1.9 definiere. Woran liegt das? An dem Typ double?
> Der y-speed-Wert darf sich ja nur minimal ändern, weil der Ball sonst sofort in eine komplett andere Richtung schissen würde und nicht auf die Platte des Gegners.
> Wenn ich das hingekriegt habe kümmer ich mich um den Springeffekt, dein Voerschlag Steev hört sich ja ganz gut an.
> (Wobei ich noch nicht verstanden habe, wie bei deiner Idee bestimmt wird, wann und wo der Ball genau auftickt)



Ich würde den Y-Speed neu setzten. Dabei würde ich so vorgehen wie beim herkömmlichen Ping Pong: Jenachdem, ob der Ball in der Mitte, oder mehr zu den Seiten des Schlägers hin aufprallt muss die Y-Geschwindigkeit entsprechend gesetzt werden. Trifft der Ball auf die genaue Mitte des Schlägers, so ist die Y-Geschwindigkeit exakt 0. Ich würde dann einfach eine maximale Y-Geschwindigkeit definieren und diese Prozentual zu der Größe des Schlägers und der Aufprallposition des Balles berechnen. Trifft der Ball auf die obere Hälfte des Schlägers, so ist die Y-Geschwindigkeit negativ, trifft der Ball auf die untere Hälfte des Schlägers, so ist die Y-Geschwindigkeit Positiv. Das kannst du einfach mit einer prozentualen Verteilung machen.

Bei meinem Vorschlag mit der Z-Geschwindigkeit ist einfach der Gedanke, dass eine bestimmte Mindest-Z-Position definiert wird, die der Ball nicht unterschreiten darf. Die Z-Geschwindigkeit wird einfach immer auf die Z-Position des Balles hinzugerechnet. Jetzt kommt aber noch ein Faktor hinzu, den wir bei der X- und Y-Position des Balles nicht benötigen: Die Grafitation. Die brauchen wir nämlich dazu, dass der Ball wieder herunter kommt, wenn er schräg nach oben fliegt.
Der Ball prallt genau dann auf, wenn die Z-Position des Balles die definierte Mindestposition unterschreitet. Dann wird einfach die Z-Geschwindigkeit umgedreht und der Ball "tickt auf" und fliegt wieder nach oben.

Ich persönlich würde halt die Unterbrechung des Spieles bevorzugen, weil man während dem Spiel kaum eine vernünftige Möglichkeit hat, all die wichtigen Parameter zu definieren, die man bei einer Spielunterbrechung definieren könnte. Ich würde zu dieser Unterbrechung halt einen Countdown setzen von sagen wir mal 2 Sekunden im Anfängermodus, nach dem der Ball einfach mit den bereits definierten Einstellungen weggeschlagen wird.

[edit:]
Dein Problem mit der Y-Geschwindigkeit kann an vielen Faktoren liegen.
Wie verwaltest du den Ball? Werden die Koordinaten in int gespeichert? Wenn ja, dann bitte in double wandeln, weil sonst alles was unter 0 ist und zu der Position zugerechnet wird keine Auswirkung hat.
Sonst kannst du ja auch mal den bewusten Abschnitt deines Quellcodes posten, dann kann man besser sagen, wo der Fehler liegt.

Gruß
Steev


----------



## bernii (8. Feb 2010)

Steev hat gesagt.:


> [edit:]
> Dein Problem mit der Y-Geschwindigkeit kann an vielen Faktoren liegen.
> Wie verwaltest du den Ball? Werden die Koordinaten in int gespeichert? Wenn ja, dann bitte in double wandeln, weil sonst alles was unter 0 ist und zu der Position zugerechnet wird keine Auswirkung hat.
> Sonst kannst du ja auch mal den bewusten Abschnitt deines Quellcodes posten, dann kann man besser sagen, wo der Fehler liegt.
> ...



Ok, das kann gut sein, da ich die koordinaten tatsächlich in int gespeichert hatte. Wenn ich sie jetzt aber auf double ändern funktioniert die g.fillOval(x_pos, y_pos, radius, radius) Methode nicht mehr, da sie nur int-Werte annimmt. Ich hab schon gegooglet aber nichts gefunden. Gibt es eine Methode, die auch mit double-Werten arbeitet?
Ich werd später mal die Codes posten, hab nochmehr fragen


----------



## Steev (8. Feb 2010)

Hi bernii,

an dieser Stelle kannst du ja dann wieder nach int casten. Es geht ja eigendlich nur daran, dass die Berechnung intern genauer ist. Solange kein Antialias verwendet wird, sieht man sowie so nicht die 0,23 Pixel Unterschied 

[Java]g.fillOval((int) x_pos, (int) y_pos, (int) radius, (int) radius)[/Java]

Gruß
Steev


----------



## bernii (8. Feb 2010)

cool thx, das klappt 
hm ich werd mich morgen wieder dransetzen, bisher hab ich jetzt eben den Abprall-Code so:


```
public void move ()
	{
	    if(x_pos > player.x_pos && x_pos < player.x_pos+player.breite)
        {
            if(y_pos > player.y_pos - player.hoehe && y_pos < player.y_pos)
            {
                  x_speed = x_speed*-1;
                  y_speed = 0.5;
            }
            else if(y_pos < player.y_pos - player.hoehe && y_pos > player.y_pos + player.hoehe)
            {
                  x_speed = x_speed*-1;
                  y_speed = -0.5;
            }
        }    
		x_pos += x_speed;
		y_pos += y_speed;
	
}
```

Jetzt fehlt noch, dass y_speed = 0 ist, wenn der Ball genau in die Mitte des Players trifft. Leider läuft die Kollisionsabfrage alles andere als flüssig im Moment, es gibt bestimmt eine Möglichkeit zu sagen, dass der x_ und y_speed sich dann ändert, wenn der Ball in ein bestimmtes Rechteck vor dem Spieler eintaucht, oder?
Hm, wie ich das mit der prozentualen Einteilung mache, muss ich auch noch sehen...

Ich hab deinen Ansatz mit der dritten Richtung (die Z-Richtung) jetzt auch verstanden und finds gut, ich werd es morgen direkt ausprobieren


----------



## Steev (9. Feb 2010)

Guten Morgen bernii,



bernii hat gesagt.:


> es gibt bestimmt eine Möglichkeit zu sagen, dass der x_ und y_speed sich dann ändert, wenn der Ball in ein bestimmtes Rechteck vor dem Spieler eintaucht, oder?



Normalerweise werden solche rechteckigen Kollisionsüberprüfungen (BB => Bounding Box) anhand der Klasse Rectangle vorgenommen.
Da gibt es dann eine Methode "contains" mit der geprüft werden kann, ob sich ein bestimmter Punkt innerhalb des Rechtecks befindet.

Vieleicht noch eine Frage zu deinem Code: Was ist x_pos und y_pos im Zusammenhang mit dem Ball? Die Linke obere Ecke des Balles oder der Mittelpunkt?

Hier vieleicht mal eine kleine Anregung:

[Java]public static final double max_y_speed = 2.5;
public synchronized void move () {
  // Gravitation
  z_speed -= z_speed / 200.; // Irgendwas, was noch gut aussieht k.A. 

  // Bewegung des Balles
  x_pos += x_speed;
  y_pos += y_speed;
  z_pos += z_speed;

  // Kollisionsberechnung und Spiellogik
  if (playerARect.contains(x_pos, y_pos, radius, radius) ||
      playerBRect.contains(x_pos, y_pos, radius, radius)) {
    x_speed = -x_speed;
    // Y-Position muss zwischen player.y_pos und player.y_pos + player.height liegen
    double yp = y_pos - player.y_pos + (player.height / 2.);
    double pw = 100 * yp / (player.height / 2.);
    y_speed = pw * max_y_speed;
  }

  // Aufspringen
  if (platte.contains(x_pos - radius, y_pos - radius) && z_pos <= platte.z_pos) {
    z_speed = -z_speed;
  }
}[/Java]

Gruß
Steev


----------



## bernii (9. Feb 2010)

//edit: ein problem erledigt 

Weiteres Problem jetzt ist, wie ich checken soll, ob der Ball beim Aufschlag zunächst auf der eigenen Hälfte aufkommt, dann auf die andere. Und danach muss der Computerschläger den Ball ja direkt auf die andere Platte schlagen (wie es beim tischtennis so üblich ist). 
Am besten nehm ich eine Methode (mit true und false) die checkt ob es sich gerade um einen Aufschlag handelt und dann den z_speed höher setzt (damit sich der radius schneller verkleinert und wieder vergrößert, sodass der Ball auf beiden aufzukommen scheint) und dann irgendwie in eine andere Methode springt, die den "normalen Ballwechsel" beinhaltet. Also wie Steev schon sagte mit einer Eregnismethode!? Ich weiß nich genau wie ich das maschen soll.


```
//Springen
        radius += z_speed;

            if(radius < 5)
            {
                z_speed *= -1;
            }
            if(radius > 11)
            {
                z_speed *= -1;
            }
```

Vlt gibts ja noch Vorschläge?

Danke


----------



## Steev (9. Feb 2010)

Hi bernii,



bernii hat gesagt.:


> ```
> //Springen
> radius += z_speed;
> 
> ...



Was ist das denn?
Die Z-Position sollte meiner Meinung nach erst bei der Renderung der Szene umgerechnet werden, also etwa so:
[Java]double zradius = radius + (z_pos / 2.); // Die Position auf der Z-Achse wird mit in den Radius eingerechnet (==> Tiefe)
g.fillOval((int) (x_pos - (zradius / 2.)), (int) (y_pos - (zradius / 2.)), (int) zradius, (int) zradius);[/Java]

Zu deinem Plattenproblem:
Ich würde eine Klasse Platte machen, die zwei Rectangle-Objekte behinhaltet. Für jede Platte eine.
Dann würde ich eine Methode tellPosition oder so machen, die du bei jeder Ballbewegung aufrufst.
In dieser Methode prüst du dann einfach ab, auf welcher Seite der Platte sich der Ball befindet (Rectangle.contains[...]) und prüst dann, ob die Z-Position des Balles kleiner oder gleich der Z-Position der Platte ist. Wenn dass der Fall ist, so wird eine Methode "tellHit" aufgerufen die dann den "Aufschlag" des Balles auf der entsprechenden Plattenhälfte handelt. Die Platte, auf der sich der Ball befindet kannst du ja als Parameter übergeben.
Den Ball würde ich ebenfalls als eigenes Objekt definieren.

PS: Netz nicht vergessen 

Gruß
Steev


----------

