Autor Beitrag
IHops
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 26

Vista
Delphi 7, Delphi 2007
BeitragVerfasst: 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
ausblenden volle Höhe 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:
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 //Attribute
    FDX : integer;
    FDY : integer;
    FRadius : integer;
    FXM : integer;
    FYM : integer;
    FBall : TBall;
    FComponent : TComponent;
    FContainer : TImage;
    FSpeed : TTimer;

  private //Methoden
    procedure DrawBall; virtual;
    procedure TimerBall (Sender: TObject); virtual;

  public //Methoden
    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;

//-------- TimerBall (private) -----------------------------------------
procedure TBall.TimerBall(Sender: TObject);
begin
  ...
end;

//-------- Create (public) ---------------------------------------------
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 :P) 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2077
Erhaltene Danke: 2

Win XP
Delphi 5 Ent., Delphi 2007 Prof
BeitragVerfasst: Do 30.10.08 00:20 
ausblenden 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: 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]
user profile iconXentar hat folgendes geschrieben Zum zitierten Posting springen:
Ist ne kleine Macke von Delphi..
Nenn mir einen besseren Vorschlag ;) .
[/OT]

_________________
>λ=
Xentar
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 2077
Erhaltene Danke: 2

Win XP
Delphi 5 Ent., Delphi 2007 Prof
BeitragVerfasst: Do 30.10.08 00:25 
user profile iconKha hat folgendes geschrieben Zum zitierten Posting springen:
[OT]
user profile iconXentar hat folgendes geschrieben Zum zitierten Posting springen:
Ist ne kleine Macke von Delphi..
Nenn mir einen besseren Vorschlag ;) .
[/OT]


Hmm.. Delphi könnte vielleicht prüfen, ob davor ein := steht, und wenn ja, diese Klammern weglassen. Oder so ähnlich.

_________________
PROGRAMMER: A device for converting coffee into software.
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Do 30.10.08 00:34 
user profile iconXentar hat folgendes geschrieben Zum zitierten Posting springen:
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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 26

Vista
Delphi 7, Delphi 2007
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Do 30.10.08 00:57 
user profile iconIHops hat folgendes geschrieben Zum zitierten Posting springen:
ich weiß nicht, ob das wirklich besser (und einfacher) ist :?:
Beides, glaub mir ;) . Jede Physik-Simulation (und jedes Spiel) funktioniert so.

_________________
>λ=
IHops Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 26

Vista
Delphi 7, Delphi 2007
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Do 30.10.08 01:22 
user profile iconIHops hat folgendes geschrieben Zum zitierten Posting springen:
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:
ausblenden 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 26

Vista
Delphi 7, Delphi 2007
BeitragVerfasst: Do 30.10.08 20:14 
Also ich hab mal versucht, den Timer auszulagern und das dann wie folgt erledigt:
ausblenden volle Höhe 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:
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
    // Ball löschen
    Pen.Color := clWhite;
    Brush.Color := clWhite;
    Ellipse(FXM-FRadius,FYM-FRadius,FXM+FRadius,FYM+FRadius);
    // Koordinaten neu bestimmen
    FXM := FXM + FDX;
    FYM := FYM + FDY;
    if (FXM + FRadius >= FContainer.Width - 1)
      or (FXM - Fradius < 2then
    begin
      FDX := - FDX;
    end;
    FYM := FYM + FDY;
    if (FYM + FRadius >= FContainer.Height - 1)
      or (FYM - Fradius < 2then
    begin
      FDY := - FDY;
    end;
    // Ball neu zeichnen
    DrawBall;
    // Counter zurücksetzen
    FSpeedCounter := FSpeed;
  end;
end;

...

// Hauptprogramm
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 :D, 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 251

Vista Ultimate, Ubuntu
Turbo Delphi 2006
BeitragVerfasst: Do 30.10.08 20:31 
du könntest statt Timern die Application.Idle procedure bennutzen.

ausblenden 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;
...

// Im Create 
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

// Mach was
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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 26

Vista
Delphi 7, Delphi 2007
BeitragVerfasst: Do 30.10.08 23:53 
Hm, das mit dem Idle verstehe ich nicht ganz :oops:

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:

ausblenden volle Höhe 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:
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 = niland (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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 152

Win XP
Lazarus
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: So 02.11.08 14:22 
user profile iconGewuerzgurke hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 152

Win XP
Lazarus
BeitragVerfasst: So 02.11.08 14:36 
Und was, wenn sich zu viele Timer-Messages gestapelt haben?
Jerk
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 251

Vista Ultimate, Ubuntu
Turbo Delphi 2006
BeitragVerfasst: So 02.11.08 22:22 
Kann man ja einfach ausprobieren :roll:
IHops Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 26

Vista
Delphi 7, Delphi 2007
BeitragVerfasst: Fr 07.11.08 17:42 
Also ich habs nun mit einer Schleife probiert ... funktioniert:
ausblenden 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 = niland (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:
ausblenden volle Höhe 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:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
//-------- NewVector (public) ------------------------------------------
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
  // Bedingungen: keine Reibung, gleiche Masse und gleiche Geschwindigkeit
  // Zentralvektor (Radien)
  zx := FMX - FKugel.FMX;
  zy := FMY - FKugel.FMY;
  // Einheits-Zentralvektor
  l := sqrt(sqr(zx) + sqr(zy));
  ezx := zx/l;
  ezy := zy/l;
  // Einheits-Tangentialvektor
  etx := -ezy;
  ety := ezx;
  // Zerlegung des Geschwindigkeitsvektors in Zentral- und Tangentialteil
  wn := ezx*FVX + ezy*FVY;
  wt := etx*FVX + ety*FVY;
  wn1 := ezx*FKugel.FVX + ezy*FKugel.FVY;
  wt1 := etx*FKugel.FVX + ety*FKugel.FVY;
  // da die Massen gleich sind, gilt
  wn := ezx*FKugel.FVX + ezy*FKugel.FVY;  // Tausch der Tangentialteile
  wn1 := ezx*FVX + ezy*FVY;               // (Zentralteile bleiben gleich)
  // sofern sich die Kugeln nicht schon auseinander bewegen
  if wn - wn1 >= 0 then
  begin
    // zurück ins globale Koordinatensystem
    ez1x := wn*ezx;
    ez1y := wn*ezy;
    et1x := wt*etx;
    et1y := wt*ety;
    ez2x := wn1*ezx;
    ez2y := wn1*ezy;
    et2x := wt1*etx;
    et2y := wt1*ety;
    // resultierende Gesamtvektoren
    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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 26

Vista
Delphi 7, Delphi 2007
BeitragVerfasst: 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