2 Prozeduren für 2D-Spiele - Winkel

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
Ynnus
Beiträge: 855
Registriert: 29.08.2004 01:37
Kontaktdaten:

2 Prozeduren für 2D-Spiele - Winkel

Beitrag von Ynnus »

Hallöchen,

ich hab mich dazu entschlossen, meine 2 Prozeduren für 2D-Winkelsachen zu veröffentlichen.

Prozedur 1:

Dabei handelt es sich in der ersten Prozedur lediglich um die Errechnung eines Winkels im 360 Grad Bereich aus 2 Punkten. Wer also schnell und einfach den Winkel von Objekt 1 zu Objekt 2 sucht, der kann mit dieser Prozedur sich etwas Arbeit einsparen. (Gibt es schon mehrmals irgendwo aufgeschrieben, wollte es aber in Verbindung mit der 2ten Prozedur nochmals posten).
Der Rückgabewert ist der Winkel in Grad. Also von 0 - 360 Grad, wobei 360 Grad logischer Weise wieder 0 Grad ist. (keine Float sondern nur Ganzkomma-Werte).

Code: Alles auswählen

Procedure get_angle(start_x, start_y, end_x, end_y)
 Protected angle.w
 Protected delta_x.w
 Protected delta_y.w

  delta_x = start_x - end_x
  delta_y = start_y - end_y
  angle = (ATan(delta_y / delta_x) * 180 / 3.1415) * (-1)
 
  If angle < 0
    angle = 180 + angle
  EndIf
  If start_y < end_y
    angle = 180 + angle
  EndIf
  If angle = 0 And start_x > end_x
    angle = 180
  EndIf
  If angle = 180 And start_x < end_x
    angle = 0
  EndIf

  ProcedureReturn angle
EndProcedure

Prozedur 2:

Die zweite Prozedur dient dazu, aus 2 Winkeln den kürzesten Weg zu errechnen. Als Beispiel nehme man eine Figur und die Maus. Die Figur soll sich zur Maus hin drehen. Jetzt ist es wichtig zu wissen, in welche Richtung sich die Figur drehen muss, damit der kürzeste Weg gewählt wird. Also entweder nach links oder nach rechts rum.
Diese Prozedur also errechnet aus Ausgangswinkel und neuem Zielwinkel die Drehrichtung, Rückgabewert ist entweder 0 für links herum oder 1 für rechts herum drehen.

Code: Alles auswählen

Procedure get_rotate_direction(start_angle, end_angle)
  Protected temp.w
  Protected temp2.w
  
  If start_angle > end_angle
    temp = start_angle - end_angle
    temp2 = end_angle + (360 - start_angle)
    If temp < temp2
      ProcedureReturn 1       ;Rechtsrum ist kuerzester Weg
    Else
      ProcedureReturn 0       ;Linksrum ist kuerzester Weg
    EndIf
  Else
    temp = end_angle - start_angle
    temp2 = start_angle + (360 - end_angle)
    If temp < temp2
      ProcedureReturn 0       ;Linksrum ist kuerzester Weg
    Else
      ProcedureReturn 1       ;Rechtsrum ist kuerzester Weg
    EndIf
  EndIf
EndProcedure
Der Parameter Start_angle muss den derzeitigen Winkel der Figur enthalten. Der end_angle muss der neue Winkel sein, welchen die Figur einnehmen soll. Als Beispiel:

Die Maus steht zur Figur um 90 Grad Winkel, also über der Figur. Die Figur schaut nach links, also 180 Grad gedreht. Gibt man nun die Werte 180 und 90 ein, so ergibt sich der Rückgabewert der Prozedur = 1, also nach rechts rotieren. Denn rechtsrum muss nur ein 90 Grad Winkel überbrückt werden, linksherum sind es dann 270 Grad um zum Ziel zu gelangen.
Hat man die Drehrichtung, könnte man jetzt die Figur in diese Richtung drehen lassen. Also eigentlich ganz einfach und nützlich wenn man ein 2D-Spiel macht, indem man die Figur per Maus drehen kann, oder die Figur zu einem bestimmten Punkt drehen will, aber nicht weiß, ob rechts rum oder links herum der kürzere Weg ist.


Um nun ein praktisches Anwendungsbeispiel zu geben, habe ich diesen Code erstellt:

http://derfeind2k.de/daten/rotate_prozedure.rar (1,65 Kilobyte)

Das erste Objekt im Beispielprogramm dreht sich immer automatisch zur Maus hin. Das zweite Objekt dreht sich nur dann, wenn die linke Maustaste gedrückt wird. Dabei nutzt sie die Prozedur 2 und errechnet den kürzesten Weg und dreht sich dann in die entsprechende Richtung. Links herum oder rechts herum, kommt darauf an wo sich die Maus befindet. Wichtig dabei, die linke Maustaste gedrückt halten zum rotieren lassen.

So, ich hoffe es finden sich Leute die damit etwas anfangen können. Gedacht ist das Ganze für 2D-Spiele indem man Winkel abfragt oder kürzeste Drehrichtungen benötigt. ;)

mfg.

Sunny
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8675
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Beitrag von NicTheQuick »

Hier noch ein paar billige Procedures aus meiner momentanen Objekt-Engine.

Code: Alles auswählen

; Ermittelt die Entfernung zweier Punkte
Procedure.f Point_Distance(X1.f, Y1.f, X2.f, Y2.f)
  Protected a.f, b.f
  a = X2 - X1
  b = Y2 - Y1
  
  ProcedureReturn Sqr(a * a + b * b)
EndProcedure

; Ermittelt den Winkel zwischen zwei Punkten
Procedure.f Object_GetAngle_Points(X1.f, Y1.f, X2.f, Y2.f)
  Protected w.f
  w = ATan((Y2 - Y1) / (X2 - X1)) * 57.295776
  If X2 < X1
    w = 180 + w
  EndIf
  If w < 0 : w + 360 : EndIf
  If w > 360 : w - 360 : EndIf
  
  ProcedureReturn w
EndProcedure

; Ermittelt den Winkel zu einer Steigung
Procedure.f Object_GetAngle_Gradiant(X.f, Y.f)
  Protected w.f
  w = ATan(Y / X) * 57.295776
  If X < 0
    w = 180 + w
  EndIf
  If w < 0 : w + 360 : EndIf
  If w > 360 : w - 360 : EndIf
  
  ProcedureReturn w
EndProcedure

; Modulo für Fließkommazahlen
Procedure.f ModuloF(a.f, b.f)
  If b < 0 : b = -b : EndIf
  If a > 0
    ProcedureReturn a - Int(a / b) * b
  Else
    ProcedureReturn b + a - Int(a / b) * b
  EndIf
EndProcedure

; Gibt immer einen Winkel zwischen und 360 zurück
Procedure.f Mod_Angle(w.f)
  If w >= 360
    ProcedureReturn w - Int(w / 360) * 360
  ElseIf w < 0
    ProcedureReturn 360 + w - Int(w / 360) * b
  Else
    ProcedureReturn w
  EndIf
EndProcedure
;}
Bild
Benutzeravatar
Lars
Beiträge: 347
Registriert: 31.08.2004 23:53
Wohnort: Shanghai
Kontaktdaten:

Beitrag von Lars »

Kann mich mal jemand aufklären, was ein WInkel zwischen 2 Punkten ist?
ich kann mir da gerade nichts zu vorstellen und denke bisher eigentlich
immernoch, dass ein Winkel durch 3 Punkte definiert ist.
Lars
The only problem with troubleshooting is, that sometimes the trouble shoots back.
P4 2,6Ghz, 512MB RAM, GeForce 6200, WinXP Pro SP2, PB V3.94
Benutzeravatar
Deeem2031
Beiträge: 1232
Registriert: 29.08.2004 00:16
Wohnort: Vorm Computer
Kontaktdaten:

Beitrag von Deeem2031 »

Du musst dir das natürlich in einem Koordinatensystem denken. Der Winkel ist dann zwischen der Linie die durch die beiden Punkte geht und der X-Achse...
Bild
[url=irc://irc.freenode.org/##purebasic.de]irc://irc.freenode.org/##purebasic.de[/url]
Benutzeravatar
Lars
Beiträge: 347
Registriert: 31.08.2004 23:53
Wohnort: Shanghai
Kontaktdaten:

Beitrag von Lars »

Ahhh, immer diese abstrakten Dinge :D
Lars
The only problem with troubleshooting is, that sometimes the trouble shoots back.
P4 2,6Ghz, 512MB RAM, GeForce 6200, WinXP Pro SP2, PB V3.94
Benutzeravatar
Ynnus
Beiträge: 855
Registriert: 29.08.2004 01:37
Kontaktdaten:

Beitrag von Ynnus »

Ich hab noch eine Prozedur für Winkel-Arbeiten geschrieben.
Diesmal wird berechnet, ob 2 Winkel in einem vorher bestimmten Abstand zueinander stehen oder nicht. Klingt simpel, in der Praxis sieht es aber leider so aus, da man den Umsprung von 359° zu 1° als Abstand 2 ansehen muss, obwohl die Winkel um den Betrag 358 abweichen. Da muss man sich dann was einfallen lassen. DIe folgende Prozedur berechnet also, ob der derzeitige Winkel "is_angle" im Bereich von "get_angle" liegt. Der Bereich wird durch "range" angegeben. Beispiel:

- angle_is_close(100, 120, 19) würde 0 ergeben.

- angle_is_close(100, 120, 20) würde 1 ergeben.

- angle_is_close(5, 355, 9) würde 0 ergeben.

- angle_is_close(5, 355, 10) würde 1 ergeben.

- angle_is_close(355, 5, 10) würde 1 ergeben.

- angle_is_close(160, 190, 30) würde 1 ergeben.

Es wird also auch der Sprung von 360 auf 0 Grad berücksichtigt. Und da besteht auch der eigentliche Nutzen der Prozedur. Ansonsten könnte man es ja ganz simel ausrechnen. Allerdings ist dieser Unterschied doch leider der Knackepunkt, was die Prozedur dann doch recht groß macht.

Wichtig, diese Prozedur benötigt die get_rotate_direction() Prozedur welche ich ganz oben gepostet habe. Denn es ist hier unerlässlich, zu wissen, in welche Richtung gemessen wird. ALso einfach bei Benutzung dieser Prozedur noch die get_rotate_direction() Prozedur mit einbinden.

Code: Alles auswählen

Procedure angle_is_close(is_angle, get_angle, range)
  Protected grenze_oben.w
  Protected grenze_unten.w
  Protected temp.w

  temp = get_rotate_direction(is_angle, get_angle)
  
  If temp = 0
    If is_angle >= 180 And get_angle <= 180
      grenze_unten = is_angle - range
      If grenze_unten < 0
        grenze_unten = 360 - Abs(grenze_unten)
      EndIf
    
      grenze_oben = is_angle + range
      If grenze_oben > 360
        grenze_oben = grenze_oben - 360
      EndIf

      If grenze_oben >= 0 And grenze_oben <= 180 And get_angle <= grenze_oben
        ProcedureReturn #True
      Else
        ProcedureReturn #False
      EndIf
    
    ElseIf is_angle >= 180 And get_angle >= 180
      grenze_unten = is_angle - range
      If grenze_unten < 0
        grenze_unten = 360 - Abs(grenze_unten)
      EndIf
    
      grenze_oben = is_angle + range
      If grenze_oben > 360
        grenze_oben = grenze_oben - 360
      EndIf
      
      If grenze_oben < 180
        If get_angle <= 360 And get_angle >= grenze_unten
          ProcedureReturn #True
        Else
          ProcedureReturn #False
        EndIf
      Else
        If get_angle <= grenze_oben And get_angle >= grenze_unten
          ProcedureReturn #True
        Else
          ProcedureReturn #False
        EndIf
      EndIf
    
    ElseIf is_angle <= 180 And get_angle <= 180
      grenze_unten = is_angle - range
      If grenze_unten < 0
        grenze_unten = 360 - Abs(grenze_unten)
      EndIf
    
      grenze_oben = is_angle + range
      If grenze_oben > 360
        grenze_oben = grenze_oben - 360
      EndIf
      
      If grenze_unten > 180
        If get_angle <= grenze_oben And get_angle >= 0
          ProcedureReturn #True
        Else
          ProcedureReturn #False
        EndIf
      Else
        If get_angle <= grenze_oben And get_angle >= grenze_unten
          ProcedureReturn #True
        Else
          ProcedureReturn #False
        EndIf
      EndIf
        
    ElseIf is_angle <= 180 And get_angle >= 180
      grenze_unten = is_angle - range
      If grenze_unten < 0
        grenze_unten = 360 - Abs(grenze_unten)
      EndIf
    
      grenze_oben = is_angle + range
      If grenze_oben > 360
        grenze_oben = grenze_oben - 360
      EndIf
      
      If get_angle <= grenze_oben And get_angle >= 0
        ProcedureReturn #True
      Else
        ProcedureReturn #False
      EndIf
    EndIf

  Else        ;rechts herum ist kleinster Winkelabstand

    If is_angle >= 180 And get_angle <= 180
      grenze_unten = is_angle - range
      If grenze_unten < 0
        grenze_unten = 360 - Abs(grenze_unten)
      EndIf
    
      grenze_oben = is_angle + range
      If grenze_oben > 360
        grenze_oben = grenze_oben - 360
      EndIf

      If get_angle >= grenze_unten And get_angle <= grenze_oben
        ProcedureReturn #True
      Else
        ProcedureReturn #False
      EndIf
    
    ElseIf is_angle >= 180 And get_angle >= 180
      grenze_unten = is_angle - range
      If grenze_unten < 0
        grenze_unten = 360 - Abs(grenze_unten)
      EndIf
    
      grenze_oben = is_angle + range
      If grenze_oben > 360
        grenze_oben = grenze_oben - 360
      EndIf
      
      If grenze_oben < 180
        If get_angle <= 360 And get_angle >= grenze_unten
          ProcedureReturn #True
        Else
          ProcedureReturn #False
        EndIf
      Else
        If get_angle <= grenze_oben And get_angle >= grenze_unten
          ProcedureReturn #True
        Else
          ProcedureReturn #False
        EndIf
      EndIf
    
    ElseIf is_angle <= 180 And get_angle <= 180
      grenze_unten = is_angle - range
      If grenze_unten < 0
        grenze_unten = 360 - Abs(grenze_unten)
      EndIf
    
      grenze_oben = is_angle + range
      If grenze_oben > 360
        grenze_oben = grenze_oben - 360
      EndIf
      
      If grenze_unten > 180
        If get_angle <= grenze_oben And get_angle >= 0
          ProcedureReturn #True
        Else
          ProcedureReturn #False
        EndIf
      Else
        If get_angle <= grenze_oben And get_angle >= grenze_unten
          ProcedureReturn #True
        Else
          ProcedureReturn #False
        EndIf
      EndIf
        
    ElseIf is_angle <= 180 And get_angle >= 180
      grenze_unten = is_angle - range
      If grenze_unten < 0
        grenze_unten = 360 - Abs(grenze_unten)
      EndIf
    
      grenze_oben = is_angle + range
      If grenze_oben > 360
        grenze_oben = grenze_oben - 360
      EndIf
      
      If grenze_unten > 180 And get_angle >= grenze_unten
        ProcedureReturn #True
      Else
        ProcedureReturn #False
      EndIf
    EndIf
  EndIf
EndProcedure
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

hm... mords code... hat bestimmt ne menge schweiss gekostet...

... aber geht auch einfacher:

die proc gibt -1 zurück, wenn einer der beider winkel <0 oder >360 ist...

Code: Alles auswählen

;**********************************************************
Procedure angle_is_close ( angle1.l, angle2.l, range.l )

    Result.l

  If angle1 >= 0 And angle1 <= 360 And angle2 >= 0 And angle2 <= 360

      Diff.l

      Diff = angle1-angle2            ; die Winkel-differenz
    
      If Diff < 0 : Diff = -Diff : EndIf
      ; wir wollen den positiven wert
      ; ( war doch so, dass abs() mit long nich geht, oder ? ) 

      If Diff > 180 : Diff = 360-Diff : EndIf
      ; den längeren weg genommen ? dann jetzt den kürzeren...
    
      If Diff > range
          Result = 0
      Else
          Result = 1
      EndIf

  Else
      Result = -1
  EndIf
    
    ProcedureReturn Result

EndProcedure

;**********************************************************
;testwerte hier einsetzen

  a = 160
  b = 190
  c = 30

d = angle_is_close( a, b, c )

  out$ = "is_angle = " + Str(a) + Chr(10)
  out$ + "get_angle = " + Str(b) + Chr(10)
  out$ + "range = " + Str(c) + Chr(10)
  out$ + "Yes/No = " + Str(d)

MessageRequester("angle_is_close Testrun", out$ , 0)
... nichts für ungut...
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Robert Wünsche
Beiträge: 243
Registriert: 29.08.2004 12:46
Wohnort: Irgendwo im nirgendwo
Kontaktdaten:

Beitrag von Robert Wünsche »

NicTheQuick hat geschrieben:ein paar billige Procedures
... stimmt !

Dein code aus dem codearchiv ist langsammer als meiner !
:mrgreen: :mrgreen: :mrgreen:
Soviel dazu !
Tja, das machts :wink: .

PS:Warn zwar nur ein paar millisekunden / 10000 benchs, aber egal !
Benutzeravatar
bluejoke
Beiträge: 1244
Registriert: 08.09.2004 16:33
Kontaktdaten:

Beitrag von bluejoke »

Wenn deine Prozeduren schneller sind, dann poste sieh doch bitte,
damit auch die Allgemeinheit von deinem Porgrammierkönnen profitieren kann.
Benutzeravatar
Ynnus
Beiträge: 855
Registriert: 29.08.2004 01:37
Kontaktdaten:

Beitrag von Ynnus »

Naja, gibt sicher noch flottere Methoden oder einfachere Wege die Differenz der Winkel zu berechnen, mir hat meine Methode da ausgereicht. Und in wie fern da große Geschwindigkeitsunterschiede auftreten, bleibt abzuwarten. Und wenn man einmal das alles als Prozedur hat ist es ja ohnehin nur noch eine Zeile. Wie dem auch sei, man möge die Variante verwenden welche einem zusagt. ;)
Antworten