| Autor |
Beitrag |
IHops
      
Beiträge: 26
Vista
Delphi 7, Delphi 2007
|
Verfasst: Do 30.10.08 00:03
Hi, ich möchte auf einem "Tisch" (Image) einen Ball laufen lassen. Den Ball habe ich als eigenes Objekt definiert. Probleme habe ich mit dem zum Ball gehörenden Timer.
Hier der entsprechende Ausschnitt zum Objekt TBall
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48:
| type TBall = class
private FDX : integer; FDY : integer; FRadius : integer; FXM : integer; FYM : integer; FBall : TBall; FComponent : TComponent; FContainer : TImage; FSpeed : TTimer;
private procedure DrawBall; virtual; procedure TimerBall (Sender: TObject); virtual;
public constructor Create (X: integer; Y: integer; R: integer; DX: integer; DY: integer; Speed: integer; Container: TImage); virtual; procedure Fast (P: integer); virtual; procedure Slow (M: integer); virtual; destructor Destroy; override;
end;
procedure TBall.TimerBall(Sender: TObject); begin ... end;
constructor TBall.Create (X: integer; Y: integer; R: integer; DX: integer; DY: integer; Speed: integer; Container: TImage); begin FXM := X; FYM := Y; FRadius := R; FDX := DX; FDY := DY; FContainer := Container; FBall := nil; DrawBall; FSpeed := TTimer.Create(FComponent); FSpeed.Interval := Speed; FSpeed.OnTimer := TimerBall(???); FSpeed.Enabled := true; end; |
Ich würde gerne den Timer innerhalb des Objekts TBall definieren ... verschiedene Bälle könnten verschiedene Geschwindigkeiten haben. Probleme hatte ich schon mit FComponent, welches ich (einfach mal durch Probieren  ) zum Erstellen festgelegt hatte. nun müsste ich aber noch einen "Sender" für das OnTimer-Ereignis festlegen - hier weiß ich nicht weiter ... der Sender wäre ja eigentlich der Ball selbst, aber ich weiß nicht, wie ich das definiere.
Evtl. hat jemand eine Idee ...
Zuletzt bearbeitet von IHops am Do 30.10.08 19:40, insgesamt 2-mal bearbeitet
|
|
Xentar
      
Beiträge: 2077
Erhaltene Danke: 2
Win XP
Delphi 5 Ent., Delphi 2007 Prof
|
Verfasst: Do 30.10.08 00:20
Delphi-Quelltext 1:
| FSpeed.OnTimer := TimerBall; |
Ist ne kleine Macke von Delphi.. da du die Methode einem Ereignis zuweisen möchtest, brauchst du keinen Parameter angeben - dieser wird ja später vom Ereignis geliefert.
Da du aber den Methodennamen geschrieben hast, "glaubt" Delphi, dass du diese aufrufen möchtest, und schlägt deswegen die Klammern + Parameter vor.
_________________ PROGRAMMER: A device for converting coffee into software.
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: Do 30.10.08 00:23
Mehrere Timer machen keinen Sinn. Verwende einen Timer mit möglichst geringem Intervall (noch schneller geht es mit einer Schleife) und übergebe jedem Ball dann das Intervall (lieber mit GetTickCount selbst messsen) als delta t, woraus sie dann ihre neue Position und Geschwindigkeit berechnen können.
[OT]
Xentar hat folgendes geschrieben : | | Ist ne kleine Macke von Delphi.. |
Nenn mir einen besseren Vorschlag  .
[/OT]
_________________ >λ=
|
|
Xentar
      
Beiträge: 2077
Erhaltene Danke: 2
Win XP
Delphi 5 Ent., Delphi 2007 Prof
|
Verfasst: Do 30.10.08 00:25
_________________ PROGRAMMER: A device for converting coffee into software.
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: Do 30.10.08 00:34
Xentar hat folgendes geschrieben : | | Hmm.. Delphi könnte vielleicht prüfen, ob davor ein := steht, und wenn ja, diese Klammern weglassen. Oder so ähnlich. |
Achso, du beziehst dich auf das automatische Einfügen der Klammern. Stimmt, das lassen andere IDEs weg und spätestens ab Delphi 2009 mit seinen anonymen Methoden wäre das im BDS auch sinnvoll.
Aber theoretisch sind die Klammern nicht unbedingt falsch: Es könnte ja jedenfalls sein, dass du eine Funktion aufrufen willst, die ein TNotifyEvent zurückgibt. Auch wenn das in der Delphi-Welt eher rar sein dürfte  .
_________________ >λ=
|
|
IHops 
      
Beiträge: 26
Vista
Delphi 7, Delphi 2007
|
Verfasst: Do 30.10.08 00:36
Hm, das man die Klammern einfach weglassen kann ... funktioniert;
Ich wollte schon irgendwann mehrere Bälle mit unterschiedlichen Geschwindigkeiten auf dem Tisch haben (die sich noch gegenseitig kennen sollen ... erweitere also noch  ) Wenn ich nur einen Timer verwende, müsste der ja außerhalb des Objekts liegen - ich weiß nicht, ob das wirklich besser (und einfacher) ist
... schon mal vielen Dank für die schnelle Hilfe.
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: Do 30.10.08 00:57
IHops hat folgendes geschrieben : | ich weiß nicht, ob das wirklich besser (und einfacher) ist |
Beides, glaub mir  . Jede Physik-Simulation (und jedes Spiel) funktioniert so.
_________________ >λ=
|
|
IHops 
      
Beiträge: 26
Vista
Delphi 7, Delphi 2007
|
Verfasst: Do 30.10.08 01:09
Hm, d.h. ein Timer (im "Hauptprogramm") steuert die Bälle ... wie kann ich dann innerhalb der Bälle unterschiedliche Geschwindigkeiten "einstellen" - größere dx, bzw. dy dürften ungeeignet sein - es könnten Sprünge entstehen.
Ich versuche, ein Objekt Ball möglichst unabhängig von der Außenwelt zu definieren; ist das dann noch gewährleistet?
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: Do 30.10.08 01:22
IHops hat folgendes geschrieben : | | Hm, d.h. ein Timer (im "Hauptprogramm") steuert die Bälle ... wie kann ich dann innerhalb der Bälle unterschiedliche Geschwindigkeiten "einstellen" - größere dx, bzw. dy dürften ungeeignet sein - es könnten Sprünge entstehen. |
Die Geschwindigkeit übergibst du als Parameter (wie du es doch jetzt schon mit Speed machst). Am Beispiel im Eindimensionalen: Die Geschwindigkeit würde ich als Float in m/s übergeben, die von deinem Timer aufgerufene Prozedur könnte dann etwas so aussehen:
Delphi-Quelltext 1: 2: 3: 4: 5:
| procedure Advance(dT: Float) begin X := X + V * dt; V := V * Power(0.9, dt); end; | Das wäre jetzt eine primitive Reibung (v verringert sich pro Sekunde um 10%). In 2D werden Position und Geschwindigkeit zu Vektoren, die Rechnung bleibt aber exakt dieselbe.
| Zitat: | | Ich versuche, ein Objekt Ball möglichst unabhängig von der Außenwelt zu definieren; ist das dann noch gewährleistet? |
Kapselung ist immer eine gute Idee, aber dT ist eine globale Größe für das ganze System. Ansonsten könnte es dir z.B. passieren, dass ein Timer trotz gleichem Intervall ein wenig schneller tickt. Und die Simulation mal schnell zu pausieren ist mit einem einzigen Timer natürlich auch leichter als mit n Stück.
_________________ >λ=
|
|
IHops 
      
Beiträge: 26
Vista
Delphi 7, Delphi 2007
|
Verfasst: Do 30.10.08 20:14
Also ich hab mal versucht, den Timer auszulagern und das dann wie folgt erledigt:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59:
| constructor TBall.Create (X: integer; Y: integer; R: integer; DX: integer; DY: integer; Speed: integer; Container: TImage); begin FXM := X; FYM := Y; FRadius := R; FDX := DX; FDY := DY; FContainer := Container; FSpeed := Speed; FSpeedCounter := FSpeed; DrawBall; end;
procedure TBall.ChangeSpeed (DS: integer); begin FSpeed := FSpeed - DS; if FSpeed < 1 then FSpeed := 1; end;
procedure TBall.Move; begin Dec(FSpeedCounter); if FSpeedCounter = 0 then with FContainer.Canvas do begin Pen.Color := clWhite; Brush.Color := clWhite; Ellipse(FXM-FRadius,FYM-FRadius,FXM+FRadius,FYM+FRadius); FXM := FXM + FDX; FYM := FYM + FDY; if (FXM + FRadius >= FContainer.Width - 1) or (FXM - Fradius < 2) then begin FDX := - FDX; end; FYM := FYM + FDY; if (FYM + FRadius >= FContainer.Height - 1) or (FYM - Fradius < 2) then begin FDY := - FDY; end; DrawBall; FSpeedCounter := FSpeed; end; end;
...
procedure TFrmMain.BallTimerTimer(Sender: TObject); begin if Assigned(Ball1) then Ball1.Move; end; |
Unterschiedliche Geschwindigkeiten versuche ich über FSpeed und FSpeedCounter einzustellen. Der Counter zählt von einer Höhe (durch FSpeed) festgelegt auf 0 runter - dann wird gezeichnet. Durch Ändern von FSpeed läuft dann der Ball langsamer oder schneller.
Also ... funktionieren tut es  , das ist schön, aber so richtig auf Geschwindigkeit komme ich damit nicht ...  . Zumal ich plane, dass Programm (erstmal) auf 2 Bälle ... später auf beliebig viele zu erweitern. Daher zwei Fragen: Ist die Umsetzung so praktikabel? Wie bekomme ich höhere Geschwindigkeiten?
|
|
Jerk
      
Beiträge: 251
Vista Ultimate, Ubuntu
Turbo Delphi 2006
|
Verfasst: Do 30.10.08 20:31
du könntest statt Timern die Application.Idle procedure bennutzen.
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
| procedure StattTimer(Sender: Tobject; var done: boolean); ... var OldTick : Int64; NewTick : Int64; SpeedFactor : Extended; ScaleFactor : Integer; Frequency : Int64; ...
if not QueryPerformanceFrequency(Frequency) then raise Exception.create('Kein Hardware Timer vorhanden'); QueryPerformanceFrequency(Frequency); Application.Onidle := StattTimer; ... procedure StattTimer(Sender: Tobject; var done: boolean); begin
Bewege(Speedfacotr * Schritte);
OldTick := NewTick; QueryPerformanceCounter(NewTick); Speedfactor := ((NewTick - OldTick) / Frequency) * 100; end; |
Mit dem Speedfactor multiplizierst du dann alles um auf jedem Rechner im Prinzip eine gleichschnelle Bewegung zu haben.
|
|
IHops 
      
Beiträge: 26
Vista
Delphi 7, Delphi 2007
|
Verfasst: Do 30.10.08 23:53
Hm, das mit dem Idle verstehe ich nicht ganz
Ich hab's jetzt mal mit ner einfachen Endlosschleife probiert - geht viieeel schneller, hab die Faktoren um den Wert 200000 vergrößern müssen, damit ich was erkennen kann. Allerdings bin ich mir nicht sicher, ob das jetzt noch als "sauber programmiert" durchgeht:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32:
| procedure TFrmMain.BallMove; begin repeat if Assigned(Ball1) then Ball1.Move; if Assigned(Ball2) then Ball2.Move; Application.ProcessMessages; until (Ball1 = nil) and (Ball2 = nil); end;
procedure TFrmMain.BtnBall1Click(Sender: TObject); begin if Assigned(Ball1) then begin BtnBall1Fast.Enabled := false; BtnBall1Slow.Enabled := false; FreeAndNil(Ball1); end else begin BtnBall1Fast.Enabled := true; BtnBall1Slow.Enabled := true; Ball1 := TBall.Create(25,25,20,2,1,200000,UTable.FrmTable.ITable); if Assigned(Ball2) then begin Ball1.LearnToKnow(Ball2); Ball2.LearnToKnow(Ball1); end; BallMove; end; end; |
Es gibt hier zwar einige (für mich nebensächliche) Nachteile: unterschiedliche Geschwindigkeit auf verschiedenen Systemen, (im Moment) nur genau zwei Bälle möglich, ... aber ich möchte im Moment nur bidirektionale Assoziationen testen ...
... dazu mal schon eine Frage vorweg: eine beliebige Anzahl von Bällen müsste ich dann wohl am besten mit dynamischen Arrays oder Listen organisieren ... oder?
|
|
Gewuerzgurke
      
Beiträge: 152
Win XP
Lazarus
|
Verfasst: So 02.11.08 12:46
OnIdle wird immer dann ausgelöst, wenn das Programm gerade im Leerlauf ist. Die meisten komplexeren Animationen werden über eine solche Schleife programmiert. Das heist, dass erst verschiedene Prozeduren zum ermitteln aller nötigen Werte, wie etwa der Positionen der Bälle, durchlaufen und dann, wenn die alle fertig sind und alle Werte für das zu 'malende' Bild vorliegen, wird die Prozedur, die mit OnIdle eingestellt wurde, aufgerufen. Diese malt dann das Bild auf den Schirm und ruft anschließend die anderen Prozeduren wieder auf.
Ich weiss gar nicht, was passiert, wenn ein Timer 1000 Mal pro Sekunde eine Prozedur aufruft, für die der Prozessor mindestens 2 millisekunden benötigt. Stürzt das Programm dann ab? Aber mit OnIdle kann das auf jeden Fall nicht passieren.
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: So 02.11.08 14:22
Gewuerzgurke hat folgendes geschrieben : | | Das heist, dass erst verschiedene Prozeduren zum ermitteln aller nötigen Werte, wie etwa der Positionen der Bälle, durchlaufen und dann, wenn die alle fertig sind und alle Werte für das zu 'malende' Bild vorliegen, wird die Prozedur, die mit OnIdle eingestellt wurde, aufgerufen. Diese malt dann das Bild auf den Schirm und ruft anschließend die anderen Prozeduren wieder auf. |
Was ja eigentlich kein wirklicher Unterschied zu seiner Schleife+ProcessMessages-Lösung ist. Würde also sagen (bzw. sag ich's schon in meinem ersten Post  ), das kann man so stehen lassen.
| Zitat: | | Ich weiss gar nicht, was passiert, wenn ein Timer 1000 Mal pro Sekunde eine Prozedur aufruft, für die der Prozessor mindestens 2 millisekunden benötigt. Stürzt das Programm dann ab? |
Timer funktionieren über Windows-Messages. Solange eine Prozedur werkelt, steht die Nachrichtenschleife still und die Timer-Messages stapeln sich. Wobei Timer sowieso auf Intervalle von ca. 20ms beschränkt sind.
_________________ >λ=
|
|
Gewuerzgurke
      
Beiträge: 152
Win XP
Lazarus
|
Verfasst: So 02.11.08 14:36
Und was, wenn sich zu viele Timer-Messages gestapelt haben?
|
|
Jerk
      
Beiträge: 251
Vista Ultimate, Ubuntu
Turbo Delphi 2006
|
Verfasst: So 02.11.08 22:22
Kann man ja einfach ausprobieren 
|
|
IHops 
      
Beiträge: 26
Vista
Delphi 7, Delphi 2007
|
Verfasst: Fr 07.11.08 17:42
Also ich habs nun mit einer Schleife probiert ... funktioniert:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| procedure TFrmMain.KugelMove; begin repeat if Assigned(Kugel1) then Kugel1.Move; Application.ProcessMessages; if Assigned(Kugel2) then Kugel2.Move; Application.ProcessMessages; until (Kugel1 = nil) and (Kugel2 = nil); end; |
Diese Variante hat zwar einige Nachteile (z. B. kann ich das Programm nicht beenden, wenn die Kugeln noch aktiv sind ...) aber für dieses Programm hat es genügt.
Nun hab ich noch eine andere (hier anschließende Frage). Die beiden Kugeln kennen sich gegenseitig (bidirektionale Assoziation). Beim Berechnen der Kollisionsvektoren greift (bei mir) allerdings ein Ball auf die Parameter des anderen (direkt) zu:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43:
| procedure TKugel.NewVector; var zx, zy, ezx, ezy, l, etx, ety, wn, wt, wn1, wt1, ez1x, ez1y, et1x, et1y, ez2x, ez2y, et2x, et2y : Extended; begin zx := FMX - FKugel.FMX; zy := FMY - FKugel.FMY; l := sqrt(sqr(zx) + sqr(zy)); ezx := zx/l; ezy := zy/l; etx := -ezy; ety := ezx; wn := ezx*FVX + ezy*FVY; wt := etx*FVX + ety*FVY; wn1 := ezx*FKugel.FVX + ezy*FKugel.FVY; wt1 := etx*FKugel.FVX + ety*FKugel.FVY; wn := ezx*FKugel.FVX + ezy*FKugel.FVY; wn1 := ezx*FVX + ezy*FVY; if wn - wn1 >= 0 then begin ez1x := wn*ezx; ez1y := wn*ezy; et1x := wt*etx; et1y := wt*ety; ez2x := wn1*ezx; ez2y := wn1*ezy; et2x := wt1*etx; et2y := wt1*ety; FVX := ez1x + et1x; FVY := ez1y + et1y; FKugel.FVX := ez2x + et2x; FKugel.FVY := ez2y + et2y; end; end; | Mir gehts hier nicht um den physikalischen Teil. Ist es korrekt, dass die Bälle aufeinander zugreifen? anders habe ich es nicht hinbekommen ... 
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: Fr 07.11.08 19:42
Ich bin mir nicht sicher, was jetzt das Problem ist, denn es müssen ja auf jeden Fall beide Bälle in der Methode manipuliert werden. Da sich die Kollision aber weder dem einen noch dem anderen Ball wirklich zuordnen lässt, würde ich eher eine class procedure Collide(b1, b2: TBall); benutzen.
_________________ >λ=
|
|
IHops 
      
Beiträge: 26
Vista
Delphi 7, Delphi 2007
|
Verfasst: Mo 24.11.08 09:50
Hm, diese Methode Collide könnte dann aber nicht Bestandteil eines Objekts Ball sein ...
Naja, ich habs so wie oben gelassen ... vielen Dank an alle für die Hilfe
|
|