Linie zeichnen/berechnen zwischen zwei punkten

Fragen zu Grafik- & Soundproblemen und zur Spieleprogrammierung haben hier ihren Platz.
Agent
Beiträge: 296
Registriert: 13.09.2004 11:28
Kontaktdaten:

Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von Agent »

Hi PBler.

Trotz ausgiebiger Suche hier im Board sitz ich schon seit Stunden dran... oder ich stehe auf dem Schlauch oder bin schlicht zu doof...
Für ein Mini-Game möchte ich ein Sprite von Punkt A (x,y) nach Punkt B (x,y) bewegen. Ich hab sogar den Winkel dazu.

Dennoch war es mir bisher nicht möglich den Sprite von A nach B sauber zu bewegen.

Ich habe es über die Berchnung eines differenzierten Faktors für die Steigung X und Steigung Y versucht. Ich habe aus dem winkel über Cos() X berechnen lassen und über Sin() Y berchnen lassen für den Flug. Alles nix. Und jetzt seh ich vor läuter Bäume den Wald nicht mehr...

Hier ein paar snippets:

Code: Alles auswählen

Procedure.f Angle(x.f,y.f)
  Protected Angle.f
  Angle = ATan(y/x)
  If x < 0 : Angle + #PI : EndIf 
  If x = 0 And y = 0 : Angle = 0 : EndIf
  ProcedureReturn Angle*180/#PI
EndProcedure

Code: Alles auswählen

                  ; calculate angle
                  \Angle = Angle(\PosX  - \MoveToX, \PosY - \MoveToY)
                  
                  
                  ; Move the unit
                  If \MoveToX < \PosX
                    \PosX - Cos(\Angle) * \Speed / 100
                  Else
                    \PosX + Cos(\Angle) * \Speed / 100
                  EndIf
                  
                  If \MoveToY < \PosY
                    \PosY - Sin(\Angle) * \Speed / 100
                  Else
                    \PosY + Sin(\Angle) * \Speed / 100
                  EndIf

Alternative über den Faktor:

Code: Alles auswählen

...
        x = Abs(\MoveToX - \PosX)
        y = Abs(\MoveToY - \PosY)
        \MoveToXFactor    = Abs(x / y)
        \MoveToYFactor    = Abs(y / x)
...
              ; calculate angle
              \Angle = Angle(\PosX  - \MoveToX, \PosY - \MoveToY)                
              
              ; Move the unit
              If \MoveToX < \PosX
                ;\PosX - Abs(Sin(\angle)) * \Speed
                \PosX - \MoveToXFactor
              Else
                ;\PosX + Abs(Sin(\angle)) * \Speed
                \PosY + \MoveToXFactor
              EndIf
                              
              If \MoveToY < \PosY
                ; \PosY - Abs(Cos(\Angle)) * \Speed
                \PosY - \MoveToYFactor
              Else
                ; \PosY + Abs(Cos(\Angle)) * \Speed
                \PosY + \MoveToYFactor
              EndIf

HELP!

Ein Beispiel wäre das Bewegen eines Projektils vom Abschußpunkt zum Zielpunkt. (nur nochmal zur Erklärung)

Danke vorab.
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7031
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von STARGÅTE »

Also am einfachsten ist es (weil du ja eh den Winkel für das Sprite3D brauchst) mit dem Winkel zu arbeiten.

Dabei bekommst du mit diesem Code den Winkel (in Bogenmaß) heraus, um von Start nach Ziel zu "fliegen":

Code: Alles auswählen

Winkel = ATan2(ZielX-StartX, ZielY-StartY)
Für die änderung der Position deines Projektils kannst du diesen Code verwenden:

Code: Alles auswählen

ProjektilX + Cos(Winkel) * ProjektilGeschwindigkeit
ProjektilY + Sin(Winkel) * ProjektilGeschwindigkeit
Abfragen wie du sie drin hast, sind also nicht nötig.
Außerdem gibt deine Angle()-Prozedur einen Winkel in Grad zurück, aber Sin, Cos erwarten Bogenmaß

Edit:
Für RotateSprite3D() musst du denn beim Winkel Degree(Winkel) benutzen und ggf. noch 90 addieren, jenachdem wie dein Bild gedreht ist.
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
Agent
Beiträge: 296
Registriert: 13.09.2004 11:28
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von Agent »

HI Stargate.

Ich hätte wetten können das du antwortest ;-)
Vielen Dank!

Ich bin gerade über Atan2() gestolpert im englischen forum und habe mir fast sowas gedacht. Mathe ist einfach zu lange her *schäm*
Ja, Bogenmaß und winkelmaß...hab grad ein echt interessanten Code gefunden. Und dabei war der nicht mal von Dir! ;-)

Dann war ich ja schon fast richtig, hat nur noch ATan2() gefehlt so wie es ausschaut.

[EDIT]
Leider funktioniert Dein Vorschlag nicht wenn man sich gradlinig in eine der vier Himmelsrichtungen bewegt, könnte das sein?
[EDIT END]

[EDIT 2]
Kommando zurück. Fehler gefunden. Ich hab die ZielXY von Pixel auf ein Raster runtergerechnet. Daher. Jetzt sieht es besser aus. Aber die Einheit bleibt eben nicht mehr im Raster stehen sondern punktgenau. Aber das änder ich noch...
[EDIT 2 END]


Dann habe ich aber noch eine generelle Frage (falls du Lust hast):

Ich arbeite gerne mit LinkedLists. Dabei stolpere ich immer wieder über das Problem, wenn ich die Werte u.U. gleichzeitig ändere. Aus Performancegründen versuche ich immer Aufgaben des Programms (insbesondere meine Game-Versuche) in Threads auszulagern. Z.B. a) Zeichnen des Screens b) animationen und c) Abfragen von Maus/Tastatur.

So kommt es unweigerlich dazu, dass in verschiedenen Threads gleiche LL geändert werden müssen. Bisher löse ich das über LockMutex() bzw untergeordnete TryLockMutex().
Dennoch irgendwie unschön. Die Projektile habe ich jetzt auf Arrays geändert. Dort muss ich aber einen Maximalwert für den Array festlegen. Muss ich bei LL nicht. Wäre mir lieber. Außerdem kann ich bei LL nicht mehr benötigte Elemente einfach löschen. Ist irgendwie sauberer.

Nun die Frage:
Ist / Hälst du LL oder Arrays für die bessere Lösung?

Stell dir als Bsp das typische Szenario vor das es zu "organisieren" gibt:
a) Map (sprites)
b) Einheiten
c) Geschosse / Explosionen
d) Animationen auf der Map

Was wäre dein Lösungsansatz? :-)
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7031
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von STARGÅTE »

Agent hat geschrieben:Leider funktioniert Dein Vorschlag nicht wenn man sich gradlinig in eine der vier Himmelsrichtungen bewegt, könnte das sein?
Nein kann nicht sein, auch für exakt horizontale oder Verticale Richtungen gibt es gültige Winkel:

Code: Alles auswählen

Debug Degree(ATan2(0, 1))
Agent hat geschrieben:Ich arbeite gerne mit LinkedLists. Dabei stolpere ich immer wieder über das Problem, wenn ich die Werte u.U. gleichzeitig ändere. Aus Performancegründen versuche ich immer Aufgaben des Programms (insbesondere meine Game-Versuche) in Threads auszulagern. Z.B. a) Zeichnen des Screens b) animationen und c) Abfragen von Maus/Tastatur.
Das Auslagern von Berechung, Anzeige usw. in Threads ist (bitte nicht falsch verstehen) für dein Vorhaben völlig überflüssig und unnötig.
Bau dir eine vernünftige Hauptschleife, in der du nacheinander Maus, Tastatur usw. abfragst, und danach Berechungen und Anzeigen hinterlegst.
Die Arbeit mit Mutex damit du auf die selbe Liste zugreifen kannst, verballert viel mehr "Performance" als du dadurch gewinnen würdest, glaub mir. Denn im Endeffekt arbeiten die Threads eh wieder nacheinander, weil jeder auf den anderen warten muss.
Agent hat geschrieben:Die Projektile habe ich jetzt auf Arrays geändert.
Mach das rückgängig! Damit hast du nur Probleme und unzählige "löcher" wenn ein Projektil löschst und anderen noch da sind.
Agent hat geschrieben: a) Map (sprites)
b) Einheiten
c) Geschosse / Explosionen
d) Animationen auf der Map
a) Ein 2D-Array, da die Karte statisch ist.
b) Eine Liste
c) für beistes Listen
d) da weiß ich nicht was du damit meinst, aber die animation an sich kann auch ein Array sein, wo sie anzuzeigen ist, steht ja dann in der echten Map.
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
Agent
Beiträge: 296
Registriert: 13.09.2004 11:28
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von Agent »

Hi.

Also ich habe es hinbekommen mit den Bewegungen (auch im Raster).

Nur zur Beruhigung: Ich habe aktuelle nur 1 extra Thread für die Animationen (Wasser) in der Map. Diese ist in einem Array (x,y) organisiert. Der Thread tauscht lediglich die SpriteIDs durchlaufend aus ;-)

Zu den Listen (LinkedLists meinst Du damit vermute ich mal ;-))

Wie organisiere ich es wenn ich IN einer Liste die gerade die List Units() durchläuft ein Element für die Liste Units() hinzufügen muss ohne ein größeres durcheinander zu produzieren.

Beispiel:

Code: Alles auswählen

ForEach Units()
...
   
   -> Sprung zu einer Procedure die Werte eines anderen Elements der Liste ändern muss oder ein element hinzufügt.

...
Next

sowas passiert/benötige ich öfters. Unter anderem da ich ja mit Mausover arbeiten muss. Und um die abzufragen muss ich die Liste ja durchlaufen um die X,Y-Werte jedes Elements zu haben um mit MouseX() + MouseY() zu vergleichen.

Teils behelfe ich mir mit

Code: Alles auswählen

hndAltesElement = ListIndex(Units())

SelectElement(Units(), selectedUnit)
...

SelectElement(Units(), hndAltesElement)
Das kommt mir aber nicht sauber vor :-)
(Ich bin sogar der Meinung man kann hier mit Adressen arbeiten über ChangeCurrentElement() was sicher schneller ist...)
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7031
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von STARGÅTE »

Für diesen Fall kannst du auch PushListElement() zusammen mit PopListElement() nutzen.
Push sichert das aktuelle Element (also die Position) auf einem Stapel und Pop holt das Element wieder zurück (also setzt die Liste wieder an die alte Position). Du kannst auch verschachtelt Push benutzen.

Du kannst also gleich in Prozeduren, die eine Liste verändern wie folgt aufbauen:

Code: Alles auswählen

Procedure Test()
	PushListPosition(Beispiel())
	ForEach Beispiel()
	
	Next
	PopListPosition(Beispiel())
EndProcedure
So stellst du sicher, dass egal wo sie aufgerufen wird, die zuvor gewähle Position erhalten bleibt, oder die Liste eben am Anfang steht.

Wenn du zwei Elemente der gleichen Liste vergleichen willst (z.B. Spieler-Spieler-Kollision) solltest du ChangeCurrentElement() nutzen:

Code: Alles auswählen

Procedure Test()
	Protected *Player.Player
	ForEach Player()
		*Player = @Player()
		While NextElement(Player())
			;Vergleich zwischen *Player\Strukturfeld und Player()\Strukturfeld
		Wend
		ChangeCurrentElement(Player(), *Player)
	Next
EndProcedure
Hier kannst du auch Push und Pop nutzen, aber da du eh das eine Element wie hier in einem Pointer sichern musst, um es mit dem anderen zu vergleichen, spart ChangeCurrentElement() einen Prozeduraufruf.
Der Grund warum ich hier innen das While NextElement() verwende ist der, dass ich nicht alle Spieler zwei mal vergleiche:
Wenn 1 mit 2 verglichen wurde, ist ja danach 2 mit 1 "überflüssig"
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
Agent
Beiträge: 296
Registriert: 13.09.2004 11:28
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von Agent »

Ah cool!

PushList.. hab ich noch gar nicht gesehen.... Und das geht mehrfach? Und dann sozusagen rückwärts? Interessant...

Ich wollte gerade die Liste für die Explosion machen.

Code: Alles auswählen

Structure XY
  x.l
  y.l
EndStructure

Structure BOOM
    
  PosX.l
  PosY.l
  Radius.l
  SpriteID.l
  Dot.xy[100]
  
EndStructure

Global NewList Explosion.Boom()
Das "Dot.xy[100]" soll die umherfliegenden Pixel werden als 100er Array.

Wie greife ich denn auf den Array zu?

Ich habs mit

Code: Alles auswählen

Explosion()\Dot\x[c] + 1
als Beispiel versucht. Fehler...
(c ist eine For-Schleife von 1 bis 100)

[EDIT]
Kennst du einen einfachen Code der eine Art Feuerwerk berechnet für meine "wegfliegenden" Pixel? ... Nur so am Rande falls Du was rumliegen hast. In den Foren (DE+EN) siehts schlecht aus...

PS: hab mal auf Dein DANKE geklickt :-)
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 7031
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von STARGÅTE »

Zu dem Problem:
Dot.xy[100] erzeugt ein statisches Array mit 100 Felder welche von 0 bis 99 gehen (also nicht 1 bis 100)
Alternativ dazu wäre
Array Dot.xy(99) möglicht, was dann auch 100 Felder hat, 0 bis 99.
Es gibt also andere Definitionen der Felderanzahl.

Auch für die kleinen Explosionspixel würde ich dir eine Liste vorschlagen wo alle diese Pixel drin landen.
Denn schließlich folgen alle Pixel den gleichen Berechungen egal von welcher Explosion die stammen.
Das ganze lässt sich dann aufweiten zu einer kleinen ParticleEngine.

Hier ein vereinfachter Code, mit der linken Maus kannst du Explosionen erzeugen.

Code: Alles auswählen

; Gibt eine zufällige Fließkommazahl im Intervall [0.0, Maximum] zurück.
Procedure.f RandomFloat(Maximum.f=1.0)
	ProcedureReturn Maximum * 4.6566128752457969241e-10 * Random(2147483647)
EndProcedure

; Gibt einen zufälligen Winkel im Bogenmaß im Intervall [0, 2*Pi[ zurück.
Procedure.f RandomAngle()
	ProcedureReturn 2.9258361585343193621e-9 * Random(2147483647)
EndProcedure


; Partikelstruktur
Structure Particle
	X.f
	Y.f
	Color.i
	Radius.f
	DirectionX.f
	DirectionY.f
EndStructure

Global NewList Particle.Particle()

; Erzeugt eine Explosion
Procedure CreateExplosion(X.f, Y.f, Particles.i=100)
	Protected Index.i, Speed.f, Angle.f
	For Index = 1 To Particles
		AddElement(Particle())
		Particle()\X = X
		Particle()\Y = Y
		Angle = RandomAngle()
		Speed = RandomFloat(4)
		Particle()\DirectionX = Cos(Angle)*Speed ; Hier speicher ich die Veränderung für X und Y direkt im Element,
		Particle()\DirectionY = Sin(Angle)*Speed ;  um nicht jedes mal später Cos() und Sin() (langsammer) aufzurufen.
		Particle()\Color = RGB(128+Random(127), 128+Random(127), 128+Random(127))
		Particle()\Radius = RandomFloat(4)+2
	Next
EndProcedure



Enumeration
	#Window
EndEnumeration

InitSprite()
InitMouse()
InitKeyboard()

OpenWindow(#Window, 0, 0, 800, 600, "ScreenTitle", #PB_Window_SystemMenu|#PB_Window_ScreenCentered)
OpenWindowedScreen(WindowID(#Window), 0, 0, WindowWidth(#Window), WindowHeight(#Window), 0, 0, 0)



Define MousePushed.i, MouseX.i, MouseY.i

Repeat
	
	Repeat
		
		Select WindowEvent()
			Case #PB_Event_CloseWindow
				End
			Case #Null
				Break
		EndSelect
		
	ForEver
	
	If ExamineKeyboard()
		If KeyboardPushed(#PB_Key_Escape)
			End
		EndIf
	EndIf
	
	If ExamineMouse()
		MouseX = MouseX()
		MouseY = MouseY()
		If MouseButton(#PB_MouseButton_Left)
			MousePushed = #True
		ElseIf MousePushed
			CreateExplosion(MouseX, MouseY)
			MousePushed = #False
		EndIf
	EndIf
	
	; Partikelberechungen
	ForEach Particle()
		Particle()\X + Particle()\DirectionX
		Particle()\Y + Particle()\DirectionY
		Particle()\Radius - 0.1      ; Macht das Partikel kleiner und
		If Particle()\Radius <= 0.0  ;  löscht es dann wenn es zu klein wird
			DeleteElement(Particle())
		EndIf
	Next
	
	ClearScreen(0)
	
	If StartDrawing(ScreenOutput())
		; Partikelanzeige
		ForEach Particle()
			Circle(Particle()\X, Particle()\Y, Particle()\Radius, Particle()\Color)
		Next
		Circle(MouseX, MouseY, 4, $FFFFFF)
		StopDrawing()
	EndIf
	
	FlipBuffers()
	
ForEver
Alternativ kannst du statt des Drawings dann auch ein Sprite3D anzeigen mit ZoomSprite3D für den Radius.

PS: Das Thema wird hier echt zu einem kleinen Tutorial ^^
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
Agent
Beiträge: 296
Registriert: 13.09.2004 11:28
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von Agent »

WOW! :allright:

Einfach und effektiv. Alles verständlich (ist ja auch nicht sonderlich schwer), aber was völlig unklar ist - ich aber nicht dringend wissen muss -
sind die beiden ersten Procedures

Code: Alles auswählen

; Gibt eine zufällige Fließkommazahl im Intervall [0.0, Maximum] zurück.
Procedure.f RandomFloat(Maximum.f=1.0)
   ProcedureReturn Maximum * 4.6566128752457969241e-10 * Random(2147483647)
EndProcedure

; Gibt einen zufälligen Winkel im Bogenmaß im Intervall [0, 2*Pi[ zurück.
Procedure.f RandomAngle()
   ProcedureReturn 2.9258361585343193621e-9 * Random(2147483647)
EndProcedure
Warum sind die nötig? Erzeugt nicht ein einfaches Random() eine "brauchbare" Zufallszahl? Ok, Du willst eine Fließkomma haben, könnte man die nicht einfach teilen?
Und was hat es mit "4.6566128752457969241e-10" auf sich?

Ebenso für die 2. procedure. Wie kommt es zu einem Winkel wenn ich die maximale Float-Zahl mit "2.9258361585343193621e-9" multipliziere?
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
Agent
Beiträge: 296
Registriert: 13.09.2004 11:28
Kontaktdaten:

Re: Linie zeichnen/berechnen zwischen zwei punkten

Beitrag von Agent »

Nur mal so...

Wenn Du die Partikel auf 1000 setzt (eine Null mehr) und etwas mehr klickst hast du aber schon performance-probleme. Da dies ja nur eine von mehreren Listen ist wäre ich wieder für ein Thread ,-)
aber hier kommt die Problematik wieder die Liste wird 2x benutzt. 1x zur berechnung 1x zur Darstellung.

Stell Dir in meinem Beispiel mal vor da sind ein paar duzend Schüsse, Einheiten und animationen und dann noch die ein oder andere Explosion. Dann steht das ganze. Und ich habe aktuelle Hardware ;-)

Ich wüsste auf kurz oder lang keine andere Lösung als Threads, aber da hier der Zugriff auf die jeweilige Linkedlist ja "unvorhersehbar" geschiet, da die jeweilige Schleife ja durchläuft wüsste ich nicht das "sicher" zu erledigen (außer über Mutex - was in der Tat langsam ist). Wobei man das aus meiner Sicht auch über eine Variable zu lösen wäre ;-) Dennoch müsste die Zweite Schleife die die gleiche Liste bearbeiten will entweder warten oder besser noch, diese Bearbeitung aussetzen.

Wie lösen das andere?
Agent_Sasori
It's not a bug - it's a feature!
http://www.StephenKalisch.de | http://www.ria-tec.com | http://www.dirsync.de
Antworten