Breakout-Spiel und Kollisionsabfrage
PhilippH
- javascript
0 Christian S.0 PhilippH0 Christian S.0 PhilippH
0 Beat
Hallo liebe Selfhtml-Community,
ich bräuchte ein bisschen Unterstützung in Sachen Javascript.
Und zwar möchte ich ein Breakout-Spiel realisieren. Für jene, die nicht wissen, was Breakout ist, sei es kurz erklärt: Der Spieler kontrolliert im unteren Bildschirmberech einen Schläger, mit dem er versuchen muss, einen Ball hochzuhalten und mit diesem Steine im oberen Bildschirmbereich zu zerstören.
Jedenfalls bin ich bis jetzt ganz gut vorangekommen, allerdings habe ich bei ein paar Sachen Probleme. Da wäre erstens die Kollisionsabfrage für die Steine, die von allen Seiten des Steines erfolgen soll. Ich habe die Steine alle in eine Tabelle (15x6) geschrieben und ihnen eine ID zugewiesen:
// Tabelle schreiben
var steinID = 0;
document.write('<table border="0" cellspacing="0" cellpadding="0">');
for (var v = 0; v < 6; v++)
{
document.write('<tr>');
for (var i = 0; i < 15; i++)
{
document.write('<td style="visibility:visible;" id="'+steinID+'"></td>');
steinID++;
}
document.write('</tr>');
}
Die Steine werden so definiert:
td {width:50px;height: 26px; background-image: url(brick.png);}
Die Position des Balls ist mit Posx und Posy beschrieben. Nach dem Aufprall soll der Stein verschwinden (ich wollte da dann einfach visibility per getElementById auf hidden setzen) und der Ball die Richtung ändern.
Wie könnte ich jetzt eine Kollisionsabfrage von Ball und Steinen realisieren?
Zweites Problem. Das ganze funktioniert nur im Internet Explorer. Im Firefox
Browser lässt sich der Schläger nicht bewegen, vermutlich wegen der Ermittlung der Mausposition mit "x=window.event.x;", was ja laut Selfhtml im FF nicht möglich ist. Ich weiß zwar, dass das irgendwie mit "clientX" geht, habe allerdings keine Ahnung, wie das geht, auch nach dem Lesen des entsprechenden Eintrages. "x=clientX" funktioniert jedenfalls nicht.
Letztes Problem (bis jetzt ;-)) wäre dann noch, dass manchmal der Ball über den Schläger "schliddert", was ich mir überhaupt nicht erklären kann.
Leider ist es mir zur Zeit nicht möglich, die Dateien auf einen Webspace zu stellen, ich habe es allerdings bei einem Oneclickhoster hochgeladen. Ich hoffe das bereitet keine Umstände. Link ist im Anhang.
Danke schonmal für's Lesen und für baldige Hilfe,
Grüße, Philipp
Hi
Die Position des Balls ist mit Posx und Posy beschrieben. Nach dem Aufprall soll der Stein verschwinden (ich wollte da dann einfach visibility per getElementById auf hidden setzen) und der Ball die Richtung ändern.
Wie könnte ich jetzt eine Kollisionsabfrage von Ball und Steinen realisieren?
Du müsstest während einer Bewegung des Balls prüfen, ob sich eine seiner Kanten an einer Kante eines Steins befindet.
Dazu würde ich die Position (und Dimension) eines jeden Steines in einem Array als Objekte halten:
var steine = [];
var stein = {left: leftWert top: topWert};
steine.push(stein);
Dimension brauchst du eigentlich ja nicht, da sie immer gleich ist. Die werte berechnen sich aus den Laufvariablen deiner Schleifen.
Die Abfrage, ob die linke Kante (left) des Balls an die rechte Kante eines Steins (left + width) stößt, solltest du hinkriegen.
Und dass dann in einer for-Schleife, für alle noch übrigen Steine.
Wurde einer getroffen, würde ich den Stein aus dem Array entfernen (steine.splice(index, 1)).
Zweites Problem. Das ganze funktioniert nur im Internet Explorer. Im Firefox
Browser lässt sich der Schläger nicht bewegen, vermutlich wegen der Ermittlung der Mausposition mit "x=window.event.x;", was ja laut Selfhtml im FF nicht möglich ist. Ich weiß zwar, dass das irgendwie mit "clientX" geht, habe allerdings keine Ahnung, wie das geht, auch nach dem Lesen des entsprechenden Eintrages. "x=clientX" funktioniert jedenfalls nicht.
Ja, das Event Modell vom IE weicht vom Standard ab. Normalerweise bekommt man das event Objekt implizit als Parameter an die Funktion übergeben, beim IE steckts in window.event.
Jedoch kennt auch IE die Eigenschaft clientX. Diese solltest du auch dort verwenden.
function move(e)
{
e = e || window.event;
x= e.clientX;
if (x>50 && x<700)
[...]
}
Letztes Problem (bis jetzt ;-)) wäre dann noch, dass manchmal der Ball über den Schläger "schliddert", was ich mir überhaupt nicht erklären kann.
Ich vermute es liegt daran, dass du zuviel dy auf die top position des Balls addierst. Dann fragst du ab:
if (posy > 391)
Wenn die Position dann 392 ist, schliddert er durch. Irgendwie so jedenfalls... Ich blicke deinen Code auch nicht vollständig..
Gruß!
Erstmal danke an euch beide!
Du müsstest während einer Bewegung des Balls prüfen, ob sich eine seiner Kanten an einer Kante eines Steins befindet.
Dazu würde ich die Position (und Dimension) eines jeden Steines in einem Array als Objekte halten:
var steine = [];
var stein = {left: leftWert top: topWert};
steine.push(stein);
Danke, das hat mir den richtigen Denkansatz gegeben. Hab' mir erstmal angeguckt, was Arrays sind (bin noch nicht so lange dabei, hätte ich vielleicht mal vorher sagen sollen ;-) und das dann so umgesetzt:
// Array bauen
steineX = new Array()
steineY = new Array()
var steinX = 0;
var steinY = 0;
for (var v = 0; v < 6; v++)
{
for (var i = 0; i < 15; i++)
{
steineX.push(steinX);
steineY.push(steinY);
steinX = steinX + 50
}
steinY = steinY + 26
steinX = 0
}
Ist das so, wie du es meintest?
Die Abfrage, ob die linke Kante (left) des Balls an die rechte Kante eines Steins (left + width) stößt, solltest du hinkriegen.
Und dass dann in einer for-Schleife, für alle noch übrigen Steine.
Nein, bekomme ich leider nicht hin; aber für oben und unten klappt es schonmal, denke ich. Wie ich allerdings auch eine Abfrage für links/rechts machen soll, weiß ich nicht. Der Ball soll dann ja auch richtig abprallen... Ihr könnt ja mal den aktuellen Stand der Datei betrachten.
// Kollisionsabfrage
for (var i = 0; i < 90; i++)
{
if (posy < steineY[i]+26 && posy > steineY[i]-16 && posx > steineX[i]-8 && posx < steineX[i]+42 && steineX[i] != "kaputt")
{
dy=-dy;
document.getElementById("ball1").style.top = (posy+dy)+"px";
document.getElementById(i).style.visibility = "hidden";
steineX[i] = "kaputt";
}
}
Habe vorher garnicht daran gedacht, dass man eine for-Schleife mit if verbinden kann... gut zu wissen!
Wurde einer getroffen, würde ich den Stein aus dem Array entfernen (steine.splice(index, 1)).
Naja, aber würde sich das ganze dann nicht verschieben? Hab' das jetzt anders gemacht, so gehts auch (steineX[i] = "kaputt";).
Ja, das Event Modell vom IE weicht vom Standard ab. Normalerweise bekommt man das event Objekt implizit als Parameter an die Funktion übergeben, beim IE steckts in window.event.
Jedoch kennt auch IE die Eigenschaft clientX. Diese solltest du auch dort verwenden.
function move(e)
{
e = e || window.event;
x= e.clientX;
if (x>50 && x<700)[...]
}
Ist das eine if-Bedingung? Kann man das einfach weglassen? Hab' das jetzt mal direkt übernommen:
function move(e) //Bewegung des Schlägers
{
e = e || window.event
x= e.clientX;
if (x>50 && x<700)
{
sx=x-50;
document.getElementById("racket").style.left = (sx)+"px";
}
else if (x<50)
{
sx = 0;
document.getElementById("racket").style.left = (sx)+"px";
}
else if (x>700)
{
sx = 650;
document.getElementById("racket").style.left = (sx)+"px";
}
}
Der IE schluckts, Firefox meckert immer noch. Die Fehlerkonsole spuckt folgendes aus:
Warnung: Fehler beim Verarbeiten des Wertes für Eigenschaft 'left'. »» Deklaration ignoriert.
Zeile: 0
und
Fehler: e has no properties
Zeile: 129
Kann ich mir beides nicht erklären. Naja, dieses Problem hat erstmal eine geringere Priorität - bei der Vorlage meines Informatiklehrers hat es auch nicht geklappt und ich muss das am Montag fertig haben. Aber danach würde mich das doch noch interessieren.
Letztes Problem (bis jetzt ;-)) wäre dann noch, dass manchmal der Ball über den Schläger "schliddert", was ich mir überhaupt nicht erklären kann.
Ich vermute es liegt daran, dass du zuviel dy auf die top position des Balls addierst. Dann fragst du ab:
if (posy > 391)Wenn die Position dann 392 ist, schliddert er durch. Irgendwie so jedenfalls... Ich blicke deinen Code auch nicht vollständig..
Geht leider nicht anders, wenn ich immer nur 1 addieren würde, dann würde der Ball viel zu langsam fliegen. Und die Aktualisierungsrate kann ich nciht noch niedriger setzen. Aber wie gesagt, die Kollisionsabfrage ist erstmal wichtiger.
Habe die aktuelle Datei wieder in den Anhang gehängt.
Vielen lieben Dank nochmal und Gruß, Philipp
Hi,
Danke, das hat mir den richtigen Denkansatz gegeben. Hab' mir erstmal angeguckt, was Arrays sind (bin noch nicht so lange dabei, hätte ich vielleicht mal vorher sagen sollen ;-) und das dann so umgesetzt:
Eine der wohl wichtigsten und ältesten Konstrukte beim Programmieren...
// Array bauen
steineX = new Array()
steineY = new Array()
var steinX = 0;
var steinY = 0;
for (var v = 0; v < 6; v++)
{
for (var i = 0; i < 15; i++)
{
steineX.push(steinX);
steineY.push(steinY);
steinX = steinX + 50
}
steinY = steinY + 26
steinX = 0
}Ist das so, wie du es meintest?
Nicht ganz. Du benutzt hier 2 Arrays, was nicht nötig ist. Wolltest du einem Stein noch mehr Eigenschaften als nur x/y geben, würdest du jedesmal ein neues Array machen wollen!?
var steine = new Array(); // äquivalente Kurzschreibweise: var steine = [];
Darin hälst du alle Steine.
Ein Stein kann mehrere Eigenschaften haben, hier eben erstmal nur x und y.
var stein = new Object();
stein.x = 0;
stein.y = 0;
oder kurz:
var stein = {x:0, y:0};
und komplett dann in etwa so:
var steine = [];
for (var v = 0; v < 6; v++)
{
for (var i = 0; i < 15; i++)
{
var stein = {x: i * 50, y: v * 26};
steine.push(stein);
}
}
Die Abfrage, ob die linke Kante (left) des Balls an die rechte Kante eines Steins (left + width) stößt, solltest du hinkriegen.
Und dass dann in einer for-Schleife, für alle noch übrigen Steine.Nein, bekomme ich leider nicht hin; aber für oben und unten klappt es schonmal, denke ich. Wie ich allerdings auch eine Abfrage für links/rechts machen soll, weiß ich nicht. Der Ball soll dann ja auch richtig abprallen... Ihr könnt ja mal den aktuellen Stand der Datei betrachten.
Naja, links/rechts ist doch bloß analog zu oben/unten.
// Kollisionsabfrage
for (var i = 0; i < 90; i++)
oder:
for (var i = 0, length = steine.length; i < length; i++)
{
if (posy < steineY[i]+26 && posy > steineY[i]-16 && posx > steineX[i]-8 && posx < steineX[i]+42 && steineX[i] != "kaputt")
{
Das sieht doch schon ganz gut aus. Ich weiß jetzt nur nicht was posx ist; die links obere ecke des Balls, oder sein Mittelpunkt. Daher weiß ich auch nicht, wo die 16 und die 42 herkommt... Du scheinst abzufragen, ob der Ball 0-8px drin in einem Stein drin ist!?
Eigentlich müsstest du ja für jede Ecke des Balls abfragen, ob diese innerhalb des Rechtsecks des Steins liegt.
Abfragen kannst du die Steinposition dann mit:
steine[i].x oder steine[i].y
Habe vorher garnicht daran gedacht, dass man eine for-Schleife mit if verbinden kann... gut zu wissen!
Seit wann programmierst du schon? ;-)
Wurde einer getroffen, würde ich den Stein aus dem Array entfernen (steine.splice(index, 1)).
Naja, aber würde sich das ganze dann nicht verschieben? Hab' das jetzt anders gemacht, so gehts auch (steineX[i] = "kaputt";).
lieber vielleicht so: steine[i].kaputt = true;
Könntest du recht haben, je nachdem wie es umgesetzt ist. Also wenn der index im Array auch tatsächlich mit dem HTML Element zusammenhängt. Alternativ kannst du auch an ein Stein-Objekt noch die ID des Steins dran hängen. Das Entfernen aus dem Array hätte halt den kleinen Vorteil, dass du nicht immer alle Steine durchlaufen musst, was dann Performance Vorteile hätte.
Wenn du einen Stein getroffen hast, würde ich auch ein break setzen, um die Schleife zu beenden.
Wenn du allerdings während der Schleifendurchlaufens, ein Element entfernst, kann es krachen. Du läufst ja von 0-90. Dann sind nur noch 89 Elemente drin, und willst auf das 90te zugreifen... Lässt sich verhindern, wenn du einfach von 90-0 zählst.
Könntest du dir auch überlegen, da ja zu erst die hinteren Steine getroffen werden. D.h. die Wahrscheinlichkeit ist groß, dass ein Stein bereit bei den ersten paar durchläufen getroffen wurde.
Die ganze Kollisionsabfrage (also die Schleife) würde ich erst dann starten, wenn der Ball eine bestimmte y-pos unterschritten hat, also in die Nähe der Steine kommt.
Ist das eine if-Bedingung? Kann man das einfach weglassen? Hab' das jetzt mal direkt übernommen:
was ist eine If Bedingung? das hier?
e = e || window.event; ?
Das ist eine Oder Verknüpfung. Er versucht erst e zu nehmen, wenn er das nicht kennt, weist er window.event zu.
Der IE schluckts, Firefox meckert immer noch. Die Fehlerkonsole spuckt folgendes aus:
Stimmt, liegt daran, dass du onmousemove direkt im body tag angegeben hast. In dem Fall musst du schreiben:
onmousemove="move(event)";
Also das Event Objekt direkt übergeben. Geht auch in beiden Browsern, und du brauchst auch nicht mehr die Abfrage e = e || window.event, da e dann ja immer übergeben wird.
Ich vermute es liegt daran, dass du zuviel dy auf die top position des Balls addierst. Dann fragst du ab:
if (posy > 391)Wenn die Position dann 392 ist, schliddert er durch. Irgendwie so jedenfalls... Ich blicke deinen Code auch nicht vollständig..
Geht leider nicht anders, wenn ich immer nur 1 addieren würde, dann würde der Ball viel zu langsam fliegen. Und die Aktualisierungsrate kann ich nciht noch niedriger setzen. Aber wie gesagt, die Kollisionsabfrage ist erstmal wichtiger.
Dann musst du dir noch irgendwas einfallen lassen ;-)
Gruß!
Servus!
Hi,
Danke, das hat mir den richtigen Denkansatz gegeben. Hab' mir erstmal angeguckt, was Arrays sind (bin noch nicht so lange dabei, hätte ich vielleicht mal vorher sagen sollen ;-) und das dann so umgesetzt:
Eine der wohl wichtigsten und ältesten Konstrukte beim Programmieren...
Ja, mir war schon bewusst, was das ist, aber habe mich (bzw. wir im Unterricht) damit noch nie beschäftigt ;-)
Nicht ganz. Du benutzt hier 2 Arrays, was nicht nötig ist. Wolltest du einem Stein noch mehr Eigenschaften als nur x/y geben, würdest du jedesmal ein neues Array machen wollen!?
Hätte ich bis eben jetzt so gemacht, ja. Mir war nicht bewusst, dass ein Objekt im Array mehrere Eigenschaften haben kann. Hab' gedacht das sei nur ein "Gitter" von Variablen.
var steine = new Array(); // äquivalente Kurzschreibweise: var steine = [];
Darin hälst du alle Steine.
Ein Stein kann mehrere Eigenschaften haben, hier eben erstmal nur x und y.
var stein = new Object();
stein.x = 0;
stein.y = 0;oder kurz:
var stein = {x:0, y:0};
und komplett dann in etwa so:
var steine = [];
for (var v = 0; v < 6; v++)
{
for (var i = 0; i < 15; i++)
{
var stein = {x: i * 50, y: v * 26};
steine.push(stein);
}
}
Achso, das ist eine Kurzschreibweise, hatte mich schon gewundert. Habe das jetzt so übernommen und noch eine Kaputteigenschaft hinzugefügt, die anfangs auf false steht.
> Naja, links/rechts ist doch bloß analog zu oben/unten.
>
> > // Kollisionsabfrage
> > for (var i = 0; i < 90; i++)
>
> oder:
>
> for (var i = 0, length = steine.length; i < length; i++)
Ich muss ja irgendwie eine Fallunterscheidung machen, ob der Ball nun von der Seite oder von oben bzw. unten kommt, da er ja entsprechend abprallt. Das Problem ist, dass der Ball sich mit mehreren Pixeln pro Durchlauf bewegt, siehe weiter unten.
> > {
> > if (posy < steineY[i]+26 && posy > steineY[i]-16 && posx > steineX[i]-8 && posx < steineX[i]+42 && steineX[i] != "kaputt")
> > {
>
> Das sieht doch schon ganz gut aus. Ich weiß jetzt nur nicht was posx ist; die links obere ecke des Balls, oder sein Mittelpunkt. Daher weiß ich auch nicht, wo die 16 und die 42 herkommt... Du scheinst abzufragen, ob der Ball 0-8px drin in einem Stein drin ist!?
posx und posy beschreiben die links obere Ecke. Der Ball ist 16px breit/hoch. Daher subtrahiere ich bei der 2. Bedingung 16; ich will ja die untere Kante berechnen. in der 3. und 4. Bedingung ziehe ich dann die Hälfte der Breite, also 8, ab, damit der Ball den Stein nur zerstört, wenn er mindestens mit der Hälfte den Stein berührt. Andernfalls würde es ja so aussehen, als sei der Ball beim anliegenenden Stein, obwohl er den Stein, der bei posx liegt, zerstört. Ich hoffe, dass das einigermaßen verständlich ist ;-), ansonsten liefer' ich noch ein kleines Bild nach. Hab' im Code auch noch ein bisschen kommentiert.
Hier der Code:
~~~javascript
// Kollisionsabfrage
if (posy < 160) // Erst abfragen, wenn der Ball bei den Steinen ist
{
Abfrage: for (var i = 89; i >= 0; i--)
{
if (steine[i].kaputt != true && posy < steine[i].y+26 && posy > steine[i].y-16 /* Da posy die linke, obere Ecke beschreibt */ && posx > steine[i].x-8 && posx < steine[i].x+42 /* minus die Hälfte des Balles (16)*/)
{
dy=-dy;
document.getElementById("ball1").style.top = (posy+dy)+"px";
document.getElementById(i).style.visibility = "hidden";
steine[i].kaputt = true;
document.all.score.innerHTML = Vscore++;
break Abfrage;
}
if (steine[i].kaputt != true && posy < steine[i].y+23 && posy > steine[i].y-13 && ((posx+16 /*Rechte Seite des Balls*/ > steine[i].x && posx+16 < steine[i].x+3) /* 3px "Toleranz" */ || (posx < steine[i].x+50 && posx > steine[i].x+47)))
{
dx=-dx;
document.getElementById("ball1").style.left = (posx+dx)+"px";
document.getElementById(i).style.visibility = "hidden";
steine[i].kaputt = true;
document.all.score.innerHTML = Vscore++;
break Abfrage;
}
}
}
Das läuft soweit ganz gut; Ab und zu fliegt der Ball allerdings falsch. Ob man das ändern kann, weiß ich nicht. Ich habe da, wie man sieht, 3px Toleranz, da ja nur alle 3px abgefragt wird. Wenn der Ball aber an die Ecken des Steines kommt, wird es problematisch.
Habe vorher garnicht daran gedacht, dass man eine for-Schleife mit if verbinden kann... gut zu wissen!
Seit wann programmierst du schon? ;-)
Noch nicht lange, entschuldige die etwas unwissenden Äußerungen :-). Davor habe ich nur sowas wie Geldwechsler etc gemacht, also wesentlich einfacher. Dazu fielen leider auch einige IT-Stunden aus.
Ist das eine if-Bedingung? Kann man das einfach weglassen? Hab' das jetzt mal direkt übernommen:
was ist eine If Bedingung? das hier?
e = e || window.event; ?Das ist eine Oder Verknüpfung. Er versucht erst e zu nehmen, wenn er das nicht kennt, weist er window.event zu.
Okay, danke. Hab' da irgendwie ein if() vermisst.
Ich vermute es liegt daran, dass du zuviel dy auf die top position des Balls addierst. Dann fragst du ab:
if (posy > 391)Wenn die Position dann 392 ist, schliddert er durch. Irgendwie so jedenfalls... Ich blicke deinen Code auch nicht vollständig..
Geht leider nicht anders, wenn ich immer nur 1 addieren würde, dann würde der Ball viel zu langsam fliegen. Und die Aktualisierungsrate kann ich nciht noch niedriger setzen. Aber wie gesagt, die Kollisionsabfrage ist erstmal wichtiger.
Dann musst du dir noch irgendwas einfallen lassen ;-)
Müsste jetzt klappen. Ich habe einfach 2x dy addiert.
Gruß!
Gruß zurück, und nochmals vielen Dank - das ist echt sehr nett von dir, dass du mir hier so unter die Arme greifst :-)
Aktueller Stand wieder im Anhang.
Die Position des Balls ist mit Posx und Posy beschrieben. Nach dem Aufprall soll der Stein verschwinden (ich wollte da dann einfach visibility per getElementById auf hidden setzen) und der Ball die Richtung ändern.
Wie könnte ich jetzt eine Kollisionsabfrage von Ball und Steinen realisieren?
Du musst pro Zyklus alle Stein-Ball-Distanzen berechnen. Das kann ein Performance-Problem ergeben, was sich dadurch umgehen lässt, dass man die Neuberechnung für einen Stein erst dann macht, wenn entweder eine Zahl Zyklen vergangen ist, oder ein bestimmter Distanzwert unterschritten ist.
Die Abfrage lautet, ob der Stein-Mittelpunkt innerhalb eines Kreises ist, welcher der Ballposition und Radius entspricht.
Du fragst, ob die Distanz M(Stein)-M(Ball) <= Radius(Ball)
Es handelt sich um simple Trigonometrie der Form
WENN Radius(Ball)^2 >= d(x)^2 + d(y)^2
DANN Kollision
wobei d(x) = X(Stein) - X(Ball)
mfg Beat