Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Wenn zwei Images "aufeinandertreffen" -> Timer ausschalten


Waldkauz - Do 28.05.09 22:52
Titel: Wenn zwei Images "aufeinandertreffen" -> Timer ausschalten
Hallo

Wie bereits beschrieben, mach ich ein Spiel.
Auf einem Image bewegen sich zwei Images.
Wenn Image2 auf Image3 trifft, bzw. umgekehrt, soll der Timer angehalten werden.
Ich hab da zwar eine Ahnung, wie ich das machen könnte, und zwar, dass er ihn ausschaltet, sobald mindestens ein Pixel des jeweiligen Image den gleichen Abstand (Left, Top) hat, wie das andere - aber das wird ja 'ne ziemlich umständliche Schleife...

Gehts auch einfacher?

mfg

Markus


jaenicke - Fr 29.05.09 00:28

Prüfe einfach für alle vier Ecken des zweiten Images, ob der Punkt im Rechteck des ersten Images ist...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var
  FirstImgRect, SecondImgRect: TRect;
begin
  FirstImgRect := FirstImage.BoundsRect;
  SecondImgRect := SecondImage.BoundsRect;
  if PtInRect(SecondImgRect.TopLeft, FirstImgRect) // top left
    or PtInRect(Point(SecondImgRect.Right, SecondImgRect.Top), FirstImgRect) // top right
    or ... then
Wenn die Kollision schon vor dem Überlappen ausgelöst werden soll, füge vor der Abfrage noch ein OffsetRect(FirstImage, -1, -1); hinzu, damit das erste Rechteck ein Pixel in jeder Richtung größer ist.


Waldkauz - Sa 30.05.09 11:01

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Prüfe einfach für alle vier Ecken des zweiten Images, ob der Punkt im Rechteck des ersten Images ist...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var
  FirstImgRect, SecondImgRect: TRect;
begin
  FirstImgRect := FirstImage.BoundsRect;
  SecondImgRect := SecondImage.BoundsRect;
  if PtInRect(SecondImgRect.TopLeft, FirstImgRect) // top left
    or PtInRect(Point(SecondImgRect.Right, SecondImgRect.Top), FirstImgRect) // top right
    or ... then
Wenn die Kollision schon vor dem Überlappen ausgelöst werden soll, füge vor der Abfrage noch ein OffsetRect(FirstImage, -1, -1); hinzu, damit das erste Rechteck ein Pixel in jeder Richtung größer ist.


mein Gott, stell ich mich jetzt blöd an, aber was sind die anderen zwei Parameter? :oops: :oops:


Marc. - Sa 30.05.09 11:08

Alle vier Ecken, zwei bleiben also noch übrig: Bottom Left und Bottom Right. :)


Waldkauz - Sa 30.05.09 11:16

user profile iconMarc. hat folgendes geschrieben Zum zitierten Posting springen:
Alle vier Ecken, zwei bleiben also noch übrig: Bottom Left und Bottom Right. :)


Er sagt ständig "Inkompatible Typen - TRect und Tpoint"


Marc. - Sa 30.05.09 11:37

user profile iconWaldkauz hat folgendes geschrieben Zum zitierten Posting springen:
Er sagt ständig "Inkompatible Typen - TRect und Tpoint"

Schau dir doch einmal in der DOH die Parameterreihenfolge an. Die ist im derzeitigen Beispiel verkehrt. ;)

Delphi-Quelltext
1:
2:
3:
4:
5:
   if PtInRect(FirstImgRect, SecondImgRect.TopLeft) // top left
    or PtInRect(FirstImgRect, Point(SecondImgRect.Right, SecondImgRect.Top)) // top right
    or PtInRect(FirstImgRect, SecondImgRect.BottomRight) // bottom right
    or ... //  bottom left
  then

Das wird aber so nur funktionieren, wenn das zweite Image kleiner-gleich groß dem Ersten ist. ;)


ffgorcky - Sa 30.05.09 11:42

Also wenn ich das gerade richtig überblicke, dann möchtest Du sowas schreiben:

Delphi-Quelltext
1:
2:
3:
//Dieses müsste dann vom Timer aufgerufen werden:
if PruefeObKollision(Image2,Image3) or PruefeObKollision(Image3,Image2) then
   ShowMessage('Ja! Sie haben sich getroffen!');

Und die Abfrage wäre dann so:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
function PruefeObKollision(erstesImage,zweitesImage:TImage):boolean
begin
//Prüfung, ob das eine auf das andere oben getroffen hat:
if erstesImage.Left<zweitesImage.Left+zweitesImage.width and zweitesImage.Left<erstesImage.Left+erstesImage.width and zweitesImage.top<erstesImage.top+erstesImage.height then
   PruefeObKollision:=true;
//Prüfung, ob eine auf das andere links getroffen hat:
if erstesImage.Left<zweitesImage.Left+zweitesImage.width and zweitesImage.Top>erstesImage.Top+erstesImage.Height and erstesImage.top>zweitesImage.top+zweitesImage.height then
   PruefeObKollision:=true;
end;

- Oder?
Ich kann jetzt leider so spontan nicht sagen, ob ich da jetzt alles richtig geschrieben habe (da ich das hier momentan leider schlecht überprüfen kann) und:
Ich weiß leider im Moment auch keine einfachere, prozessorsparendere Lösung.


Marc. - Sa 30.05.09 12:00

Ich hab auch noch schnell etwas gebastelt. Es funktioniert jedenfalls.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
  if   (FirstImage.Left + FirstImage.Width <= SecondImage.Left) // right
   and (FirstImage.Left >= (SecondImage.Left + SecondImage.Width))
   or  (FirstImage.Left <= SecondImage.Left + SecondImage.Width) // left
   and (FirstImage.Left + FirstImage.Width >= SecondImage.Left)
   and ((FirstImage.Top <= SecondImage.Top + SecondImage.Height) // top
   and (FirstImage.Top + FirstImage.Height >= SecondImage.Top)
   or  (FirstImage.Top + FirstImage.Height >= SecondImage.Top)  // bottom
   and (FirstImage.Top <= SecondImage.Top + SecondImage.Height))
  then ...

Grüße,
Marc


ffgorcky - Sa 30.05.09 12:09

Ja Marc, ich denke nur, dass meine Lösung (so sie denn funktioniert - was ich ja hoffe) dadurch, dass ich die Abfragen auf jeweils nur von links und oben beschränkt habe und das ganze dann einfach nur noch einmal mit vertauschten Parametern (also Image2 und Image3 umgedreht) abfrage, das ganze doch übersichtlicher und schneller mal wieder zu ändern macht - oder?
Siehst Du das nicht so?


Marc. - Sa 30.05.09 12:16

Das mag sein. Ich muss zugeben den Source relativ flott und ohne Gedanken runtergetippt zu haben. Ich muss jetzt auch leider direkt los, werd mir später nochmal den Kopf darüber zerbrechen. :?

Edit: Bei meinem Beispiel ließen sich auch die Parameter austauschen, vorrausgesetzt du verwendest ein entsprechendes Funktionsgerüst, wie bei dir.
Zudem, performanter wird dein Beispiel, wenn du statt den IF-Abfragen direkt den Boolschen-Wert als Rückgabe verwendest. :P
Timer1.Enabled := (A and B) or ..

Grüße,
Marc


Waldkauz - Sa 30.05.09 12:42

hab jetzt mal alle durchprobiert...
Sind zwar frei von Fehlermeldungen, aber funktionieren tun sie nicht...die Images "fliegen" trotzdem weiter


SvenAbeln - Sa 30.05.09 14:10


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function CheckCollision(FirstImage, SecondImage: TImage): boolean;
var
  FirstImgRect, SecondImgRect: TRect;
begin
  FirstImgRect := FirstImage.BoundsRect;
  SecondImgRect := SecondImage.BoundsRect;

  Result := (FirstImgRect.Left <= SecondImageRect.Right) and (FirstImgRect.Right>= SecondImageRect.Left)
        and (FirstImgRect.Top) <= SecondImageRect.Bottom) and (FirstImgRect.Bottom >= SecondImageRect.Top);
end;



@user profile iconffgorcky
In deiner Funktion prüfst du einige Bedingungen unötigerweise doppelt, ausserdem mußt du die beiden Abfragen noch miteinander verküpfen. Im Moment können die beiden Images sich über bzw untereinander befinden und du meldest trotzdem eine Kollision, da deine erste Bedingung schon erfüllt ist. z.B. Image2.Top = 5 und Image2.Top = 500

Ausserdem hast du noch einen groben Fehler:
Sollte es keine Kollision geben, wird bei dir gar kein Ergebnis geliefert, Result ist dann also undefiniert.


Delphi-Quelltext
1:
if PruefeObKollision(Image2,Image3) or PruefeObKollision(Image3,Image2) then                    

So ein Aufruf ist auch unnötig und eher verwirrent, denn wenn zwei Images kollidieren ist es egal in welcher Reihenfolge ich dies prüfe.

@user profile iconMarc.

Delphi-Quelltext
1:
2:
  if   (FirstImage.Left + FirstImage.Width <= SecondImage.Left) //
   and (FirstImage.Left >= (SecondImage.Left + SecondImage.Width))


Das wird nur Wahr für folgende Bedingungen:

Delphi-Quelltext
1:
2:
FirstImage.Left = SecondImage.Left und 
FirstImage.Width = SecondImage.Width = 0
da ein Bild nicht gleichzeitig Links und Rechts von einem anderen sein kann.


jaenicke - Sa 30.05.09 15:19

Ja, die Parameter für PtInRect habe ich vertauscht, aber ansonsten klappt das so wie ich anfangs geschrieben habe...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
var
  FirstImgRect, SecondImgRect: TRect;
begin
  FirstImgRect := FirstImage.BoundsRect;
  SecondImgRect := SecondImage.BoundsRect;
  if PtInRect(FirstImgRect, SecondImgRect.TopLeft) // top left
    or PtInRect(FirstImgRect, Point(SecondImgRect.Right, SecondImgRect.Top)) // top right
    or PtInRect(FirstImgRect, Point(SecondImgRect.Left, SecondImgRect.Bottom)) // bottom left
    or PtInRect(FirstImgRect, SecondImgRect.BottomRight) // bottom right
    then
user profile iconMarc. hat folgendes geschrieben Zum zitierten Posting springen:
Das wird aber so nur funktionieren, wenn das zweite Image kleiner-gleich groß dem Ersten ist. ;)
Das verstehe ich nicht, warum?


Waldkauz - Sa 30.05.09 15:50

es geht immer noch nicht...

hier mal der quelltext:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TForm2.Button5Click(Sender: TObject);
var
  FirstImgRect, SecondImgRect: TRect;
begin
  FirstImgRect := Image2.BoundsRect;
  SecondImgRect := Image4.BoundsRect;
  if PtInRect(FirstImgRect, SecondImgRect.TopLeft) // top left
    or PtInRect(FirstImgRect, Point(SecondImgRect.Right, SecondImgRect.Top)) // top right
    or PtInRect(FirstImgRect, Point(SecondImgRect.Left, SecondImgRect.Bottom)) // bottom left
    or PtInRect(FirstImgRect, SecondImgRect.BottomRight) // bottom right
    then  Timer4.Enabled:=false;


jaenicke - Sa 30.05.09 15:53

Das gehört ja wohl eher in den Timer und nicht in den ButtonClick, oder?

Dass es prinzipiell funktioniert, siehst du ja an der Demo, ich weiß ja nicht was du anders machst. Ich vermute der ButtonClick passiert nur nicht im richtigen Moment...


Waldkauz - Sa 30.05.09 15:56

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Das gehört ja wohl eher in den Timer und nicht in den ButtonClick, oder?

Dass es prinzipiell funktioniert, siehst du ja an der Demo, ich weiß ja nicht was du anders machst. Ich vermute der ButtonClick passiert nur nicht im richtigen Moment...


upps :oops: , hast recht, das kommt in den Timer...
jetzt funktionierts!

Vielen Dank an alle!


Marc. - Sa 30.05.09 17:55

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconMarc. hat folgendes geschrieben Zum zitierten Posting springen:
Das wird aber so nur funktionieren, wenn das zweite Image kleiner-gleich groß dem Ersten ist. ;)
Das verstehe ich nicht, warum?
Angenommen, du willst wissen, ob eine größeres Rect ein kleineres schluckt und testest dabei nur die äußerden Punkte des größeren Rects... dann kann's durchrutschen. :P

user profile iconSvenAbeln hat folgendes geschrieben Zum zitierten Posting springen:

Das wird nur Wahr für folgende Bedingungen:

Delphi-Quelltext
1:
2:
FirstImage.Left = SecondImage.Left und 
FirstImage.Width = SecondImage.Width = 0
da ein Bild nicht gleichzeitig Links und Rechts von einem anderen sein kann.

Korrekt. :oops:


Waldkauz - Mi 03.06.09 19:52

user profile iconMarc. hat folgendes geschrieben Zum zitierten Posting springen:
Angenommen, du willst wissen, ob eine größeres Rect ein kleineres schluckt und testest dabei nur die äußerden Punkte des größeren Rects... dann kann's durchrutschen.

Und wie kann ich das verhindern, dass es "durchrutscht" ?
Denn genau das Problem habe ich jetzt.
Wenn das größere Image nun auf dem kleineren liegt (da das größere eine Zufallsposition hat) und es praktisch verdeckt, treten nicht die Paramter einer Kollision ein.

Moderiert von user profile iconNarses: Zitat gekürzt.


Waldkauz - Do 18.06.09 00:33

push (sorry, aber das Problem mit dem Durchrutschen hab ich nach wie vor).
Und wird das nicht ein wenig zu aufwendig für eine Art Arkanoid-Spiel?


SvenAbeln - Do 18.06.09 09:37

user profile iconSvenAbeln hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function CheckCollision(FirstImage, SecondImage: TImage): boolean;
var
  FirstImgRect, SecondImgRect: TRect;
begin
  FirstImgRect := FirstImage.BoundsRect;
  SecondImgRect := SecondImage.BoundsRect;

  Result := (FirstImgRect.Left <= SecondImageRect.Right) and (FirstImgRect.Right>= SecondImageRect.Left)
        and (FirstImgRect.Top) <= SecondImageRect.Bottom) and (FirstImgRect.Bottom >= SecondImageRect.Top);
end;


Dies erkennt auch, wenn ein Bild komplett in dem Anderen liegt.


Delete - Do 18.06.09 11:17

Ich werfe dann mal IntersectRect in den Raum.

Delphi-Quelltext
1:
2:
3:
4:
5:
function RectsTouching(const Rect1, Rect2: TRect): Boolean;
var dummy: TRect;
begin
  Result := IntersectRect(dummy,Rect1,Rect2);
end;


Waldkauz - Mo 22.06.09 21:29

user profile iconSvenAbeln hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function CheckCollision(FirstImage, SecondImage: TImage): boolean;
var
  FirstImgRect, SecondImgRect: TRect;
begin
  FirstImgRect := FirstImage.BoundsRect;
  SecondImgRect := SecondImage.BoundsRect;

  Result := (FirstImgRect.Left <= [b]SecondImageRect[/b].Right) and (FirstImgRect.Right>= SecondImageRect.Left)
        and (FirstImgRect.Top) <= [b]SecondImageRect[/b].Bottom) and (FirstImgRect.Bottom >= SecondImageRect.Top);
end;

Dies erkennt auch, wenn ein Bild komplett in dem Anderen liegt.

danke, aber irgendwo ist da ein Fehler. Mit dem End oder mit den Klammern stimmt was nicht, aber ich komm nicht drauf...(den Fehler, dass du "SecondImageRect" statt "SecondImgRect" geschrieben hast, hab ich schon verbessert)

---Moderiert von user profile iconNarses: Beiträge zusammengefasst---

War nur ein kleiner Klammerfehler.
Kann man diese Funktion auch für ein Image und ein Shape verwenden?


Delete - Mo 22.06.09 22:15

Wenn man die Parameter etwas ändert und gleich die Rects übergibt, wieso nicht?


Waldkauz - Mo 22.06.09 22:22

user profile iconDeddyH hat folgendes geschrieben Zum zitierten Posting springen:
Wenn man die Parameter etwas ändert und gleich die Rects übergibt, wieso nicht?


wie denn?


Delete - Mo 22.06.09 22:26

Ich nehm jetzt mal meine Funktion von oben, weil die kürzer ist:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
function RectsTouching(const Rect1, Rect2: TRect): Boolean;
var dummy: TRect;
begin
  Result := IntersectRect(dummy,Rect1,Rect2);
end;

procedure TFormMain.Irgendwas;
begin
  if RectsTouching(MyShape.BoundsRect, MyImage.BoundsRect) then
    ShowMessage('Kollision');
end;

Analog würde das natürlich auch mit der anderen Funktion funktionieren, wenn man die entsprechend umstellt.


Waldkauz - Mo 22.06.09 22:34

Super, thx!