Problem mit 2D Kollisionen

Fragen zu Grafik- & Soundproblemen und zur Spieleprogrammierung haben hier ihren Platz.
Benutzeravatar
Mijikai
Beiträge: 754
Registriert: 25.09.2016 01:42

Problem mit 2D Kollisionen

Beitrag von Mijikai »

Hab ein Problem mit 2D Kollisionen. :freak:

Bei einer Kollision setzte ich momentan das Objekt das kollidiert an die ursprüngliche Position zurück.
Das geht zwar ist aber nicht sauber. :(
Ich hätte gerne das sich die Kollisionen im inneren der Welt genauso wie an den Rändern verhalten.

Perfekte Kollision am Punkt + nicht kollidierte Achse wird nicht blockiert.

Hier mal der Code zum rumspielen:

Bild

Code: Alles auswählen

Structure VECTOR_STRUCT
  X.f
  Y.f
EndStructure

Structure WORLD_POSITION_STRUCT
  X.f
  Y.f
  Id.b
EndStructure

Structure WORLD_STRUCT
  Array Offset.WORLD_POSITION_STRUCT(255,255)
EndStructure

Global Exit.b
Global Event.i
Global DeltaTime.f = 1;DUMMY!
Global WorldX.i
Global WorldY.i
Global Level.WORLD_STRUCT
Global Player.VECTOR_STRUCT
Global Move.VECTOR_STRUCT
Global *TileId.Ascii 

Procedure.b RectIntersect(*V1.VECTOR_STRUCT,*V2.VECTOR_STRUCT)
  If *V1\X < *V2\X + 16 And *V1\X + 16 > *V2\X
    If *V1\Y < *V2\Y + 16 And *V1\Y + 16 > *V2\Y
      ProcedureReturn #True
    EndIf
  EndIf
EndProcedure

Procedure.i WorldCollision()
  Protected X.i,Y.i
  If Move\X < 0:Move\X = 0:ElseIf Move\X > 304:Move\X = 304:EndIf;MAP BOUNDARIES
  If Move\Y < 0:Move\Y = 0:ElseIf Move\Y > 304:Move\Y = 304:EndIf
  For Y = 0 To 19
    For X = 0 To 19
      If Level\Offset(X,Y)\Id
        If RectIntersect(@Move,@Level\Offset(X,Y))          
          Move = Player;IF ANY COLLISION -> RESET PLAYER POS
        EndIf  
      EndIf
    Next
  Next
EndProcedure

If InitSprite() And InitKeyboard()
  If OpenWindow(0,#Null,#Null,600,400,#Null$,#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
    If OpenWindowedScreen(WindowID(0),#Null,#Null,800,600,#False,#Null,#Null);V-SYNC DOES NOT WORK ON MY PC!
      ;------------------------------------------------ GEN TILE LOOKUP TABLE
      For WorldY = 0 To 19
        For WorldX = 0 To 19
          *TileId = ?level + (WorldY * 20) + WorldX
          Level\Offset(WorldX,WorldY)\Id = *TileId\a
          Level\Offset(WorldX,WorldY)\X = WorldX * 16 
          Level\Offset(WorldX,WorldY)\Y = WorldY * 16 
        Next
      Next
      ;------------------------------------------------ INIT PLAYER POS
      Player\X = Level\Offset(12,10)\X
      Player\Y = Level\Offset(12,10)\Y 
      Repeat
        Repeat
          Event = WindowEvent()
          If Event = #PB_Event_CloseWindow:Exit = #True:EndIf 
        Until Event = #Null
        Move = Player
        ExamineKeyboard()
        If KeyboardPushed(#PB_Key_A):Move\X - 3 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_D):Move\X + 3 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_W):Move\Y - 3 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_S):Move\Y + 3 * DeltaTime:EndIf
        WorldCollision();CHECK FOR COLLISION
        Player = Move
        ClearScreen($0)
        If StartDrawing(ScreenOutput())
          For WorldY = 0 To 19
            For WorldX = 0 To 19
              If Level\Offset(WorldX,WorldY)\Id
                DrawingMode(#PB_2DDrawing_Default)
                Box(Level\Offset(WorldX,WorldY)\X + 140,Level\Offset(WorldX,WorldY)\Y + 40,16,16,$FFFFFF)
              Else
                DrawingMode(#PB_2DDrawing_Outlined)
                Box(Level\Offset(WorldX,WorldY)\X + 140,Level\Offset(WorldX,WorldY)\Y + 40,16,16,$3E071B)
              EndIf
            Next
          Next
          Box(Player\X + 140,Player\Y + 40,16,16,$43FF43)
          DrawingMode(#PB_2DDrawing_Outlined)
          Box(140,40,320,320,$FF5FB0);MAP OUTLINE
          StopDrawing()   
        EndIf
        FlipBuffers()
      Until Exit   
    EndIf
    CloseWindow(0)
  EndIf
EndIf

DataSection
  level:
  !db 1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,0,0
  !db 0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0
  !db 0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0
  !db 0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1
  !db 1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,1,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,1,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1
EndDataSection
Gibt man diesen Code in die Kollision:

Code: Alles auswählen

;-> einfügen anstelle von: Move = Player

          If Move\X < Level\Offset(X,Y)\X
            Move\X = Level\Offset(X,Y)\X - 16
          ElseIf Move\X > Level\Offset(X,Y)\X
            Move\X = Level\Offset(X,Y)\X + 16
          EndIf
          
          
;           If Move\Y < Level\Offset(X,Y)\Y
;             Move\Y = Level\Offset(X,Y)\Y - 16
;           ElseIf Move\Y > Level\Offset(X,Y)\Y
;             Move\Y = Level\Offset(X,Y)\Y + 16
;           EndIf
liegt das Objekt das kollidiert perfekt an - leider aber spinnt dann der Rest.

Wie kann ich das sauber lösen ?
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: Problem mit 2D Kollisionen

Beitrag von diceman »

Das Problem ist die Schrittweite von -3/+3 (deine tileSize = 16 ist durch 2 teilbar, aber nicht durch 3).
Wennst da z.B. eine 4 einsetzt, dann passt es. :)

Code: Alles auswählen

        If KeyboardPushed(#PB_Key_A):Move\X - 4 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_D):Move\X + 4 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_W):Move\Y - 4 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_S):Move\Y + 4 * DeltaTime:EndIf
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6996
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Problem mit 2D Kollisionen

Beitrag von STARGÅTE »

Das dein Spieler "springt" liegt daran, dass du auch Move\Y veränderst, obwohl es vllt garkeine Bewegung in Y richtung gab.

Mein Tip: Speicher das DeltaMove für \X und \Y und frage dann vorher ab:

Code: Alles auswählen

If DeltaMove\X <> 0
          If Move\X < Level\Offset(X,Y)\X
            Move\X = Level\Offset(X,Y)\X - 16
          ElseIf Move\X > Level\Offset(X,Y)\X
            Move\X = Level\Offset(X,Y)\X + 16
          EndIf  
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
Mijikai
Beiträge: 754
Registriert: 25.09.2016 01:42

Re: Problem mit 2D Kollisionen

Beitrag von Mijikai »

diceman hat geschrieben:Das Problem ist die -3/+3 (tileSize = 16 ist eine gerade Zahl).
Wennst das mit 4 ersetzt, dann passt es. :)

Code: Alles auswählen

        If KeyboardPushed(#PB_Key_A):Move\X - 4 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_D):Move\X + 4 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_W):Move\Y - 4 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_S):Move\Y + 4 * DeltaTime:EndIf
Das geht leider nicht wegen der DeltaTime, auch will ich den Wert später ändern können.
Bei einem statischen System wär das die Lösung :)
STARGÅTE hat geschrieben:Das dein Spieler "springt" liegt daran, dass du auch Move\Y veränderst, obwohl es vllt garkeine Bewegung in Y richtung gab.

Mein Tip: Speicher das DeltaMove für \X und \Y und frage dann vorher ab:

Code: Alles auswählen

If DeltaMove\X <> 0
          If Move\X < Level\Offset(X,Y)\X
            Move\X = Level\Offset(X,Y)\X - 16
          ElseIf Move\X > Level\Offset(X,Y)\X
            Move\X = Level\Offset(X,Y)\X + 16
          EndIf  
Werd ich gleich mal testen :)

Danke für die schnellen Antworten.
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: Problem mit 2D Kollisionen

Beitrag von diceman »

Eigentlich müßtest du ja bei einer Kollision nicht den Spieler zurücksetzen, sondern stattdessen auf den mindestmöglichen Abstand setzen, bevor eine Kollision stattfindet.
Ansonsten wirst immer einen Abstand zum Hindernis haben. Oder? :?
Zuletzt geändert von diceman am 04.06.2018 21:24, insgesamt 1-mal geändert.
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.
Benutzeravatar
Mijikai
Beiträge: 754
Registriert: 25.09.2016 01:42

Re: Problem mit 2D Kollisionen

Beitrag von Mijikai »

@STARGÅTE

Will leider nicht, hier der Code:

Code: Alles auswählen

         If Not Player\X = Move\X;<- CHECK FOR MOVEMENT FIRST
            If Move\X < Level\Offset(X,Y)\X
              Move\X = Level\Offset(X,Y)\X - 16
            ElseIf Move\X > Level\Offset(X,Y)\X
              Move\X = Level\Offset(X,Y)\X + 16
            EndIf
          EndIf
          
          If Not Player\Y = Move\Y
            If Move\Y < Level\Offset(X,Y)\Y
              Move\Y = Level\Offset(X,Y)\Y - 16
            ElseIf Move\Y > Level\Offset(X,Y)\Y
              Move\Y = Level\Offset(X,Y)\Y + 16
            EndIf
          EndIf 
Benutzeravatar
Mijikai
Beiträge: 754
Registriert: 25.09.2016 01:42

Re: Problem mit 2D Kollisionen

Beitrag von Mijikai »

diceman hat geschrieben:Eigentlich müßtest du ja bei einer Kollision nicht den Spieler zurücksetzen, sondern stattdessen auf den mindestmöglichen Abstand setzen, bevor eine Kollision stattfindet.
Ansonsten wirst immer einen Abstand zum Hindernis haben. Oder? :?
Ja, das reicht auch schon oft (für einfache Top-Down Spiele oder für Sidescroller Spiele mit Gravity).
Leider brauche ich es jetzt etwas genauer.

Der 2te CodeSchnipsel im ersten Post zeigt meinen (kläglichen) Versuch das Problem zu lösen.
Wenn nur eine der Achsen darauf angewendet wird sieht es sehr vielversprechend aus.
Leider fängt es an zu spinnen wenn die andere Achse mit ins Spiel kommt.

Irgendwas muss also noch berücksichtigt werden :freak:
Benutzeravatar
#NULL
Beiträge: 2235
Registriert: 20.04.2006 09:50

Re: Problem mit 2D Kollisionen

Beitrag von #NULL »

Ich hab mal was zusammenfrickelt.
WorldCollision2() verändert nichts sondern gibt nur boolean zurück.
Dann wird von Player nach Move interpoliert und die Zwischenschritte auf Kollision überprüft. Sobald eine Achse eine Kollision hatte wird sie nicht weiter verfolgt und der letzte gültige Schritt behalten, die andere Achse kann dabei noch weiter machen. Ich habe diese Flags x/yCollision verwendet, die einmal abschalten sozusagen, damit es auch funktioniert wenn der Move/Schritt über Wände hinaus gehen soll, wobei dahinter ja wieder keine Kollision stattfinden würde.

Code: Alles auswählen

Structure VECTOR_STRUCT
  X.f
  Y.f
EndStructure

Structure WORLD_POSITION_STRUCT
  X.f
  Y.f
  Id.b
EndStructure

Structure WORLD_STRUCT
  Array Offset.WORLD_POSITION_STRUCT(255,255)
EndStructure

Global Exit.b
Global Event.i
Global DeltaTime.f = 1;DUMMY!
Global WorldX.i
Global WorldY.i
Global Level.WORLD_STRUCT
Global Player.VECTOR_STRUCT
Global Move.VECTOR_STRUCT
Global *TileId.Ascii

Procedure.b RectIntersect(*V1.VECTOR_STRUCT,*V2.VECTOR_STRUCT)
  If *V1\X < *V2\X + 16 And *V1\X + 16 > *V2\X
    If *V1\Y < *V2\Y + 16 And *V1\Y + 16 > *V2\Y
      ProcedureReturn #True
    EndIf
  EndIf
EndProcedure

Procedure.i WorldCollision()
  Protected X.i,Y.i
  If Move\X < 0:Move\X = 0:ElseIf Move\X > 304:Move\X = 304:EndIf;MAP BOUNDARIES
  If Move\Y < 0:Move\Y = 0:ElseIf Move\Y > 304:Move\Y = 304:EndIf
  For Y = 0 To 19
    For X = 0 To 19
      If Level\Offset(X,Y)\Id
        If RectIntersect(@Move,@Level\Offset(X,Y))         
          Move = Player;IF ANY COLLISION -> RESET PLAYER POS
        EndIf 
      EndIf
    Next
  Next
EndProcedure

Procedure.i WorldCollision2(*Pos.VECTOR_STRUCT)
  Protected X.i,Y.i
  If *Pos\X < 0 Or *Pos\X > 304 Or *Pos\Y < 0 Or *Pos\Y > 304
    ProcedureReturn #True
  Else
    For Y = 0 To 19
      For X = 0 To 19
        If Level\Offset(X,Y)\Id
          If RectIntersect(*Pos,@Level\Offset(X,Y))
            ProcedureReturn #True
          EndIf 
        EndIf
      Next
    Next
  EndIf
  ProcedureReturn #False
EndProcedure

If InitSprite() And InitKeyboard()
  If OpenWindow(0,#Null,#Null,600,400,#Null$,#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
    If OpenWindowedScreen(WindowID(0),#Null,#Null,800,600,#False,#Null,#Null);V-SYNC DOES NOT WORK ON MY PC!
      ;------------------------------------------------ GEN TILE LOOKUP TABLE
      For WorldY = 0 To 19
        For WorldX = 0 To 19
          *TileId = ?level + (WorldY * 20) + WorldX
          Level\Offset(WorldX,WorldY)\Id = *TileId\a
          Level\Offset(WorldX,WorldY)\X = WorldX * 16
          Level\Offset(WorldX,WorldY)\Y = WorldY * 16
        Next
      Next
      ;------------------------------------------------ INIT PLAYER POS
      Player\X = Level\Offset(12,10)\X
      Player\Y = Level\Offset(12,10)\Y
      Repeat
        Repeat
          Event = WindowEvent()
          If Event = #PB_Event_CloseWindow:Exit = #True:EndIf
        Until Event = #Null
        Move = Player
        ExamineKeyboard()
        If KeyboardPushed(#PB_Key_A):Move\X - 3 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_D):Move\X + 3 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_W):Move\Y - 3 * DeltaTime:EndIf
        If KeyboardPushed(#PB_Key_S):Move\Y + 3 * DeltaTime:EndIf
        
        
        ;WorldCollision();CHECK FOR COLLISION
        
        delta.VECTOR_STRUCT
        pos.VECTOR_STRUCT
        
        delta\X = Move\X - Player\X
        delta\Y = Move\Y - Player\Y
        
        length.f = Sqr(delta\X * delta\X + delta\Y * delta\Y)
        
        Move = Player
        xCollision = #False
        yCollision = #False
        
        For i=1 To length
          f.f = 0.0 + i / length
          
          pos\X = Player\X + f * delta\X
          If WorldCollision2(@pos)
            xCollision = #True
            pos\X = Move\X
          EndIf
          If Not xCollision
            Move\X = pos\X
          EndIf
          
          pos\Y = Player\Y + f * delta\Y
          If WorldCollision2(@pos)
            yCollision = #True
            pos\Y = Move\Y
          EndIf
          If Not yCollision
            Move\Y = pos\Y
          EndIf
          
        Next
        Player = Move
          
          
          
        ClearScreen($0)
        If StartDrawing(ScreenOutput())
          For WorldY = 0 To 19
            For WorldX = 0 To 19
              If Level\Offset(WorldX,WorldY)\Id
                DrawingMode(#PB_2DDrawing_Default)
                Box(Level\Offset(WorldX,WorldY)\X + 140,Level\Offset(WorldX,WorldY)\Y + 40,16,16,$FFFFFF)
              Else
                DrawingMode(#PB_2DDrawing_Outlined)
                Box(Level\Offset(WorldX,WorldY)\X + 140,Level\Offset(WorldX,WorldY)\Y + 40,16,16,$3E071B)
              EndIf
            Next
          Next
          Box(Player\X + 140,Player\Y + 40,16,16,$43FF43)
          DrawingMode(#PB_2DDrawing_Outlined)
          Box(140,40,320,320,$FF5FB0);MAP OUTLINE
          StopDrawing()   
        EndIf
        FlipBuffers()
      Until Exit   
    EndIf
    CloseWindow(0)
  EndIf
EndIf

DataSection
  level:
  !db 1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,1,1,1,1,1,0,0,0,0,0,1,1,1,1,1,0,0
  !db 0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0
  !db 0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0
  !db 0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1
  !db 1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,1,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,1,1,1,1,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
  !db 1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,1
EndDataSection
my pb stuff..
Bild..jedenfalls war das mal so.
Benutzeravatar
diceman
Beiträge: 347
Registriert: 06.07.2017 12:24
Kontaktdaten:

Re: Problem mit 2D Kollisionen

Beitrag von diceman »

Hier wäre meine Lösung:
Bei einer Kollision wird der Wert bestimmt, um wieviel x bzw. y über das Hindernis hinausragt, und entsprechend korrigiert.

statt

Code: Alles auswählen

If RectIntersect(@Move,@Level\Offset(X,Y))         
     Move = Player;IF ANY COLLISION -> RESET PLAYER POS
EndIf 

Code: Alles auswählen

If RectIntersect(@Move,@Level\Offset(X,Y))
	Repeat
		If Move\x < player\x	:	move\x+1	:	EndIf
		If Move\x > player\x	:	move\x-1	:	EndIf
		If Move\y < player\y	:	move\y+1	:	EndIf
		If Move\y > player\y	:	move\y-1	:	EndIf
	Until Not RectIntersect(@Move,@Level\Offset(X,Y))
EndIf
Now these points of data make a beautiful line,
And we're out of Beta, we're releasing on time.
Benutzeravatar
#NULL
Beiträge: 2235
Registriert: 20.04.2006 09:50

Re: Problem mit 2D Kollisionen

Beitrag von #NULL »

Bei meinem Code gibt es manchmal noch Lücken zwischen Player und Wand, aufgrund der ganzen Float-Geschichten.
Man kann aber einfach die Genauigkeit erhöhen indem man length vervielfacht:

Code: Alles auswählen

length.f = 4 * Sqr...
f.f bleibt dabei trotzdem immer im Bereich 0..1.
my pb stuff..
Bild..jedenfalls war das mal so.
Antworten