![]() |
Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Hallo,
Ich schreibe derzeit ein Programm, indem man ein Raumschiff steuert, dass andere gegnerische Raumschiffe abschießen muss. Dabei erzeuge ich bei jedem Klick ein Image, dass meinen Laserschuss symbolisiert und dieser wird durch einen Timer im 30ms Takt (damit die Laserbewegung flüssig wird) nach oben verschoben. Wenn der Laser einen Gegner trifft, verliert dieser Leben und wenn das Leben des Gegners <= 0 ist, wird dieser zerstört. Die Gegner wiederum schießen ebenfalls zufällig Laser, denen ich dann ausweichen muss. Soweit so gut, das Grundkonzept habe ich programmiert und es klappt auch, bis auf einige Bugs, ganz gut. Allerdings verbraucht das Programm viel zu viel Prozessorleistung. Ich komme sehr schnell an den Punkt, wo das Programm einen Kern meines Prozessors komplett auslastet und die Laser dann nur noch extrem langsam fliegen, da die verschiedenen Timer die 30ms nicht mehr schaffen. Zudem kommt noch ein weiteres Problem. Sobald ich doublebuffered auf true setzt, um ein nerviges geflimmer zu vermeiden, verbraucht das Programm noch mehr Leistung und ist entgültig unspielbar. Gibt es dazu ressourcensparende Alternativen? -> In der Version im Anhang ist doublebuffered=false, trotzdem laggt es ab einem gewissem Punkt. Ansonsten weiß ich selber nicht, wie ich mein Programm "schneller" machen könnte. Ich schließe aber nicht aus, dass es dazu einige einfache Möglichkeiten gibt, die mir einfach nicht einfallen. Ich würde mich sehr freuen, wenn sich einige von euch das Programm mal ansehen könnten( -> Programm kann leider nicht in den Anhang, da die Zip zu groß ist (durch einige Bilder), deswegen gibt es das Programm ![]() Das Programm wurde mit Delphi 6 geschrieben. Gruß, Astobix |
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Anstatt viele Timer, kannst du das auch in einem Timer lösen. Da ein 30ms Timer eh nicht genau nach 30ms triggered, sondern zum nächstmöglichen Zeitpunkt nach 30ms, stimmt das Timing eh nicht exakt (das siehst du auch an dem Lag).
Nimm nur einen Timer mit einem entsprechend kleinen Intervall (z.B. 30ms). Für jedes bewegte Objekt am Bildschirm berechnest du dann in dem OnTimerEvent einfach die aktuelle Position anhand der verstrichenen Zeit seit dem letzten Event und platzierst es dementsprechend. Wenn dann die Berechnungen und die Darstellung innerhalb des Events immer noch länger brauchen als 30ms, dann ist dein System zu langsam oder deine Berechnungen zu kompliziert (oder nicht ausreichend optimiert). |
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Bei einem Timer kann noch passieren dass ein Event noch nicht abgearbeitet ist wenn der nächste kommt, was zu den Problemen mit der Last führen kann. Einfache Abhilfe ist folgendes:
Delphi-Quellcode:
if Timer1.Tag=1 then
exit; Timer1.Tag:=1; ..Tuwas; Timer1.Tag:=0; |
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Alternativ deaktiviert man den Timer in der Zeit, in der man ihn abarbeitet.
Sherlock |
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Zitat:
Delphi-Quellcode:
aufgerufen wird, was ich auf jeden Fall vermeiden würde! Andernfalls sind die Timer-Events durch die Message-Queue schon serialisiert und die gezeigte Blockung ist überflüssig. Natürlich kann nach dem Durchlaufen des Event-Handlers schon die nächste Timer-Message vorliegen, aber dann ist eben das System wirklich zu langsam. Dann würde das von Sherlock vorgeschlagene abschalten die Sache etwas entzerren. Andererseits würde ich dann wohl einen ganz anderen Ansatz wählen.
Application.ProcessMessages
|
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Du musst immer die Zeit berechnen, die zwischen dem letzten Aufruf und dem aktuellen Aufruf vergangen ist und dann berechnen, wie viele Schritte eigentlich hätten erfolgen sollen.
Die noch verbleibende Zeit speicherst du als Reserve und wird beim nächsten Schritt berücksichtigt. Diese Schritte werden dann ausgeführt und dann wird das Zeichnen einmalig veranlasst. Hier mal eine Klasse, die dieses berücksichtigt
Delphi-Quellcode:
Und ein kleines Testprogramm, was drei Shapes unterschiedlich schnell über die Form fliegen lässt
unit Animator;
interface uses Classes, SysUtils, ExtCtrls; type TAnimator = class( TComponent ) private FLocked : Boolean; FTimer : TTimer; FAccu : Integer; FLastCall : TDateTime; FOnStep : TNotifyEvent; FOnPaint : TNotifyEvent; FResolution : Cardinal; procedure TimerEvent( Sender : TObject ); procedure SetResolution( const Value : Cardinal ); function CalculateSteps : Integer; function GetEnabled : Boolean; procedure SetEnabled( const Value : Boolean ); procedure DoStep; procedure DoPaint; public constructor Create( AOwner : TComponent ); override; destructor Destroy; override; // Event für den Berechnungs-Schritt property OnStep : TNotifyEvent read FOnStep write FOnStep; // Event für die Zeichen-Schritt property OnPaint : TNotifyEvent read FOnPaint write FOnPaint; // Auflösung in Millisekunden property Resolution : Cardinal read FResolution write SetResolution default 30; property Enabled : Boolean read GetEnabled write SetEnabled default True; end; implementation uses DateUtils; { TAnimator } function TAnimator.CalculateSteps : Integer; var LNow : TDateTime; LSpan : Integer; begin LNow := Now; // Zeitspanne zwischen letzem Aufruf und Jetzt // plus dem noch nicht berücksichtigtem Zeitvorrat LSpan := MilliSecondsBetween( LNow, FLastCall ) + FAccu; // Anzahl der Schritt pro Zeitauflösung Result := LSpan div FResolution; // Restzeit in den Zeitvorrat FAccu := LSpan - Result * FResolution; FLastCall := LNow; end; constructor TAnimator.Create( AOwner : TComponent ); begin inherited; FLastCall := Now; FResolution := 30; FTimer := TTimer.Create( Self ); FTimer.OnTimer := TimerEvent; FTimer.Interval := 1; FTimer.Enabled := True; end; destructor TAnimator.Destroy; begin inherited; end; procedure TAnimator.DoPaint; begin if Assigned( OnPaint ) then OnPaint( Self ); end; procedure TAnimator.DoStep; begin if Assigned( OnStep ) then OnStep( Self ); end; function TAnimator.GetEnabled : Boolean; begin Result := FTimer.Enabled; end; procedure TAnimator.SetEnabled( const Value : Boolean ); begin if Value = Enabled then Exit; if Value then begin // Wird der Timer wieder eingeschaltet, dann // LastCall und Accu wieder zurücksetzen FLastCall := Now; FAccu := 0; end; FTimer.Enabled := Value; end; procedure TAnimator.SetResolution( const Value : Cardinal ); begin if ( Value = FResolution ) or ( Value = 0 ) then Exit; FResolution := Value; end; procedure TAnimator.TimerEvent( Sender : TObject ); var LSteps : Integer; begin if FLocked then Exit; FLocked := True; try LSteps := CalculateSteps; if LSteps = 0 then Exit; while ( LSteps > 0 ) do begin DoStep; Dec( LSteps ); end; DoPaint; finally FLocked := False; end; end; end.
Delphi-Quellcode:
unit ViewMain;
interface uses Animator, Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls; type TFloatPoint = record x, y : Extended; end; TMainView = class( TForm ) Shape1 : TShape; Shape2 : TShape; Shape3 : TShape; procedure FormCreate( Sender : TObject ); private FShape1Pos : TFloatPoint; FShape2Pos : TFloatPoint; FShape3Pos : TFloatPoint; FAnimator : TAnimator; procedure AnimatorStep( Sender : TObject ); procedure AnimatorPaint( Sender : TObject ); public end; var MainView : TMainView; implementation {$R *.dfm} procedure TMainView.AnimatorPaint( Sender : TObject ); begin // Hier wird das Zeichnen der Oberfläche veranlasst Shape1.Top := Round( FShape1Pos.y ); Shape1.Left := Round( FShape1Pos.x ); Shape2.Top := Round( FShape2Pos.y ); Shape2.Left := Round( FShape2Pos.x ); Shape3.Top := Round( FShape3Pos.y ); Shape3.Left := Round( FShape3Pos.x ); end; procedure TMainView.AnimatorStep( Sender : TObject ); begin // Hier erfolgen NUR die Berechnungen // Shape1 // Geschwindigkeit 500 Pixel/Sekunde FShape1Pos.y := FShape1Pos.y - ( 500 / 1000 * FAnimator.Resolution ); // Wenn es nach oben rausrutscht, dann von unten wieder komplett reinkommen lassen if FShape1Pos.y < 0 then FShape1Pos.y := FShape1Pos.y + Self.Height - Shape1.Height; // Shape2 // Geschwindigkeit 200 Pixel/Sekunde FShape2Pos.y := FShape2Pos.y - ( 200 / 1000 * FAnimator.Resolution ); // Wenn es nach oben rausrutscht, dann von unten wieder komplett reinkommen lassen if FShape2Pos.y < 0 then FShape2Pos.y := FShape2Pos.y + Self.Height - Shape2.Height; // Shape3 // Geschwindigkeit 800 Pixel/Sekunde FShape3Pos.y := FShape3Pos.y - ( 800 / 1000 * FAnimator.Resolution ); // Wenn es nach oben rausrutscht, dann von unten wieder komplett reinkommen lassen if FShape3Pos.y < 0 then FShape3Pos.y := FShape3Pos.y + Self.Height - Shape3.Height; end; procedure TMainView.FormCreate( Sender : TObject ); begin // Positionen der Objekte merken FShape1Pos.x := Shape1.Left; FShape1Pos.y := Shape1.Top; FShape2Pos.x := Shape2.Left; FShape2Pos.y := Shape2.Top; FShape3Pos.x := Shape3.Left; FShape3Pos.y := Shape3.Top; // Animator initialisieren FAnimator := TAnimator.Create( Self ); FAnimator.OnStep := AnimatorStep; FAnimator.OnPaint := AnimatorPaint; end; end. |
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Eine Alternative wäre - du richtest dich nicht nach Timer, sondern nach möglichen Fps.
Beispiel: du hast einen Ball der auf einem 1024 waagerechtem Bildschirm von links nach rechts bewegt werden soll. - Möglichkeit 1: du bewegst den Ball jeweils um einen Pixel nach rechts. Damit das alles innerhalb einer Sekunde angezeigt wird, müssten 1024 Bilder pro Sekunde aufgebaut werden. Wird wohl nicht klappen, wenn höchstens 100. also wird der Ball 10 Sekunden brauchen. Das Spiel ist flüssig, wirkt aber zu langsam. - Möglichkeit 2: du analysierst wie viel Bilder pro Sekunde gerade möglich sind, z. B. 30. Somit wird der Ball nicht um 1 Pixel pro Frame bewegt, sondern 1024 / 30 = 35 Pixel. Der Ball passiert den Bildschirm innerhalb einer Sekunde. Wenn FPS zu gering wird, hackt das Spiel, bleibt aber in der Zeit. |
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
@Popov
Genau so macht man das eben nicht. Man ermittelt, wieviele Zeiteinheits-Schritte man aktuell abarbeiten muss und arbeitet diese ab. Dann klatscht man das Ergebnis auf den Bildschirm (das dauert idR am längsten). Wie oft man das auf den Bildschirm bekommt, das ist dann die FPS (und die ist abhängig von der Komplexität der Animation sowie der aktuell verfügbaren Rechenleistung). Andersherum fällt man nur über die eigenen Füße (erst FPS berechnen und abhängig davon die Berechnung der Positionen). |
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Kleiner Tipp:
Wenn es "nur" auf LCD mit 60Hz Bildfrequenz laufen soll, dann macht es keinen Sinn, mehr als 60 Änderungen im Bild zu erzeugen. Es wird nicht angezeigt! Beispiel: Es soll sich etwas vom linken zum rechten Bildrand bewegen bei 1920 horizontale Pixel und einer Geschwindigkeit von 1920 Pixel/sec, dann muss man nur 1920/60=32 Positionen berechnen und nicht 1920! Bei 120Hz Bildfrequenz sind es entsprechend 64 Positionen. Der Rest ist Luxus. |
AW: Programm verbraucht zuviel Prozessorleistung - Wie kann ich Lag verhindern?
Liste der Anhänge anzeigen (Anzahl: 1)
Ich habe da mal den Animator noch etwas angepasst, und der ermittelt jetzt auch noch FPS (mit einer Glättung).
Auf der Form ist jetzt noch eine Checkbox zum Ein-/Ausschalten des Animators und ein SpinEdit zum Anpassen der Resolution (Zeiteinheit in Millisekunden). Durch das Erhöhen dieser Zeiteinheit sieht man, dass das Ergebnis mehr ruckelt und je kleiner, umso flüssiger wird es. Man sieht aber auch, dass das System nicht aus dem Tritt kommt und die Shapes immer passend ihre Bahnen ziehen und beim Zeichnen sind die da, wo man es auch zu dem Zeitpunkt vermuten würde. Anhang mit Sourcen und kompilierter EXE |
Alle Zeitangaben in WEZ +1. Es ist jetzt 16:04 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz