Figuren Bewegen



  • 1 Pixel pro Frame hat das Problem, dass es von der Framerate abhängt. Und diese wird massiv schwanken und du kannst nie die Geschwindigkeit ordentlich einstellen.

    Ich wäre für sowas (ungetestet):

    struct RectAnimation{
        int startx, endx, starty, endy, starttime, endtime, rectwidth, rectheight;
    };
    
    vector<RectAnimation> animations;
    
    //Animation hinzufügen
    int now = GetTickCount();
    animations.push_back({100, 100, 200, 300, now, now + 500 /*animation dauert 500 ms*/, 150, 200});
    
    //Animation animieren
    int now = GetTickCount();
    for (const auto &a : animations){
        RECT rect;
        if (now < a.starttime)
            rect = RECT{a.startx, a.starty, a.rectwidth, a.rectheight};
        else if (now >= a.endtime)
            rect = RECT{a.endx, a.endy, a.rectwidth, a.rectheight}-,
        else{
            //man könnte auch eine andere als lineare Interpolation nehmen
    #define F(START, END) START + (now - a.starttime) * (END - START) / (a.endtime - a.starttime)
            rect = RECT{F(a.startx, a.endx), F(a.starty, a.endy), a.rectwidth, a.rectheight)};
    #undef F
        }
        FillRect(hdc, &rect, brush);
    }
    
    //TODO: alle Animationen löschen wo now > endtime
    

    Jetzt bewegt sich das Rechteck immer gleich schnell.



  • Hallo, Danke für eure Hilfe!
    PeekMasseage kannte ich noch nicht, läuft einwandfrei mit der Funktion. 👍
    @nwp3 Deine Variante schaue ich mir nochmal an, wenn ich etwas mehr Ahnung habe;-)

    mgaeckler schrieb:

    Versuche aber vielleicht InvalidateRect ohne den dritten Parameter mit TRUE aufzurufen und die WM_PAINT-Behandlung so zu schreiben, daß sie NICHT davon ausgeht, daß das Fenster schon leer ist.

    Sorry, wie genau meinst du das? (Link reicht natürlich auch)

    Die Timer-Variante habe ich auch noch einmal geschrieben, läuft auch wie sie soll, trotzdem möchte ich die einfach zur Fehleranalyse noch einmal einstellen. Vielleicht hat ja jemand die Zeit/Geduld mir zu sagen was verbesserungswürdig ist. 🙂

    #include <windows.h>
    
    bool Sprung=FALSE;
    int top=50, bottom=200, left=50, right=200, Pos=0;
    
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){
      switch (message){
       	case WM_KEYDOWN:{
             switch (wParam)
             	{
             	case VK_RIGHT:
                	{
                   		SetTimer(hWnd, 1, 20, NULL);
                   		Sprung=TRUE;
                   		break;
                	}
             	break;
             }
          	break;
          }
       	case WM_TIMER:{
          		if(Pos>100){Pos=0; Sprung=FALSE; KillTimer(hWnd, 1);} 
    			  	else{Pos+=5; left+=5; right+=5;} 
             	InvalidateRect(hWnd, NULL, TRUE);
             	break;
          }
    
       	case WM_PAINT:{
            PAINTSTRUCT   ps;
            HDC           hDC;
    
            hDC = BeginPaint(hWnd, &ps);
    
    		HBRUSH hBrush;
    		hBrush = CreateSolidBrush (RGB(0,255,0));
    		SelectObject (hDC, hBrush);
    
    		Rectangle (hDC, left, top, right, bottom);
    
            DeleteObject(hBrush);	
            EndPaint(hWnd, &ps); 
            break;
          }
    
       	case WM_CLOSE:{
                DestroyWindow(hwnd);
                break;
          }
    
            case WM_DESTROY:{
            PostQuitMessage(0);
            break;
          }
        break;
       }
       	return DefWindowProc(hWnd, message, wParam, lParam);
    }
    
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){
       MSG         msg;
       HWND        hWnd;
       WNDCLASS    wc;
    
       wc.cbClsExtra          = 0;
       wc.cbWndExtra          = 0;
       wc.hbrBackground       = (HBRUSH) GetStockObject(WHITE_BRUSH);
       wc.hCursor             = LoadCursor(NULL, IDC_ARROW);
       wc.hIcon               = LoadIcon(NULL, IDI_APPLICATION);
       wc.hInstance           = hInstance;
       wc.lpfnWndProc         = WndProc;
       wc.lpszClassName       = "WindowClass";
       wc.lpszMenuName        = NULL;
       wc.style               = CS_HREDRAW | CS_VREDRAW;
    
       RegisterClass(&wc);
    
       hWnd = CreateWindow(  "WindowClass","Caption", WS_OVERLAPPED | WS_SYSMENU, 
       							20, 20, 640, 480, NULL, NULL, hInstance, NULL);
    
       ShowWindow(hWnd, iCmdShow);
       UpdateWindow(hWnd);
    
       while (GetMessage(&msg, NULL, 0, 0)>0){
          TranslateMessage(&msg);
          DispatchMessage(&msg);
       }
       return msg.wParam;
    }
    


  • John1 schrieb:

    Hallo, Danke für eure Hilfe!
    PeekMasseage kannte ich noch nicht, läuft einwandfrei mit der Funktion. 👍
    @nwp3 Deine Variante schaue ich mir nochmal an, wenn ich etwas mehr Ahnung habe;-)

    mgaeckler schrieb:

    Versuche aber vielleicht InvalidateRect ohne den dritten Parameter mit TRUE aufzurufen und die WM_PAINT-Behandlung so zu schreiben, daß sie NICHT davon ausgeht, daß das Fenster schon leer ist.

    Sorry, wie genau meinst du das? (Link reicht natürlich auch)

    z.B. so:

    http://stackoverflow.com/questions/7502291/drawing-without-flickering

    mal schaun, ob ioch ein Beispiel bauen kann.

    mfg Martin



  • mgaeckler schrieb:

    mal schaun, ob ioch ein Beispiel bauen kann.

    mfg Martin

    So:

    case WM_PAINT:{
    
                PAINTSTRUCT   ps;
                HDC           hDC;
                hDC = BeginPaint(hwnd, &ps);
    
    			HDC memDC = CreateCompatibleDC ( hDC );
    			HBITMAP memBM = CreateCompatibleBitmap ( hDC, 640, 480 );
    			SelectObject ( memDC, memBM );
    
    			HBRUSH hBrush;
    			hBrush = CreateSolidBrush (RGB(255,255,255));
    			SelectObject (memDC, hBrush);
    			Rectangle (memDC, 0, 0, 640, 480);
    
    			hBrush = CreateSolidBrush (RGB(0,255,0));
    			SelectObject (memDC, hBrush);
    			Rectangle (memDC, left, top, right, bottom);
    			BitBlt( hDC, 0, 0, 640, 480, memDC, 0, 0, SRCCOPY );
    
    			EndPaint(hwnd, &ps);
    			break;
            }
    

    Beim Registrieren der Klasse mußt Du allerdings noch den Hintergrund weglassen:

    memset(&wc,0,sizeof(wc));
        wc.cbSize        = sizeof(WNDCLASSEX);
        wc.lpfnWndProc   = WndProc;
        wc.hInstance     = hInstance;
        wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    
    //	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    	wc.lpszClassName = "WindowClass";
    	wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    	wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
    
        if(!RegisterClassEx(&wc)) {
            MessageBox(NULL, "Window Registration Failed!","Error!",MB_ICONEXCLAMATION|MB_OK);
            return 0;
        }
    

    Da ist natürlich noch genügend Platz für Optimierungen. Ist jetzt nur auf die schnelle hingerotzt, um das Prinzip zu demonstrieren.

    mfg Martin



  • mgaeckler schrieb:

    John1 schrieb:

    Auch wenn es nicht um Flüchtigkeitsfehler ging, ist Timerfunktion der entscheidende Denkansatz der mir fehlte.
    Von daher trotzdem Vielen Dank.
    MFG John.

    Abgesehen von der nicht initialiserten Variablen, waren Deine "Fehler" auch nicht wirklich ursächlich für Dein Problem und nicht besonders tragisch.

    Wenn ein Nullpointer (hwnd in move()) nicht tragisch ist, was dann ?

    @John1: Wieso trotzdem ?
    Ich habe alle Fehler, vollständig dokumentiert und einen wichtigen Hinweis für die vollständige Lösung gegeben 😕



  • Nee, Martin - setzen Sechs !!

    mgaeckler schrieb:

    case WM_PAINT:{
    	PAINTSTRUCT   ps;
    	HDC           hDC;
    	hDC = BeginPaint(hwnd, &ps);
    
    	HDC memDC = CreateCompatibleDC ( hDC );
    	HBITMAP memBM = CreateCompatibleBitmap ( hDC, 640, 480 );
    	SelectObject ( memDC, memBM );
    
    	HBRUSH hBrush;
    	hBrush = CreateSolidBrush (RGB(255,255,255));
    	SelectObject (memDC, hBrush);
    	Rectangle (memDC, 0, 0, 640, 480);
    
    	hBrush = CreateSolidBrush (RGB(0,255,0));
    	SelectObject (memDC, hBrush);
    	Rectangle (memDC, left, top, right, bottom);
    	BitBlt( hDC, 0, 0, 640, 480, memDC, 0, 0, SRCCOPY );
    
    	EndPaint(hwnd, &ps);
    	break;
            }
    

    Was soll das denn sein ?

    Bei jedem Aufruf von WM_PAINT neue HBRUSH-Objekte, Memory-DCs und Bitmaps erzeugen ist wohl
    weder intelligent noch performant.

    Dazu kommt noch das bei jedem Durchlauf groesser werdende memleak!
    http://de.wikipedia.org/wiki/Speicherleck

    When you no longer need the HBRUSH object, call the DeleteObject function to delete it.

    http://msdn.microsoft.com/en-us/library/windows/desktop/dd183518.aspx

    When you no longer need the memory DC, call the DeleteDC function.

    http://msdn.microsoft.com/en-us/library/windows/desktop/dd183489.aspx

    When you no longer need the bitmap, call the DeleteObject function to delete it.

    http://msdn.microsoft.com/en-us/library/windows/desktop/dd183488.aspx

    Edit: und was soll 640, 480 ??



  • John1 schrieb:

    Die Timer-Variante habe ich auch noch einmal geschrieben, läuft auch wie sie soll

    👍

    John1 schrieb:

    Vielleicht hat ja jemand die Zeit/Geduld mir zu sagen was verbesserungswürdig ist. 🙂

    int top=50, bottom=200, left=50, right=200, Pos=0;
    

    Einfacher wäre hier direkt ein RECT zu verwenden. Positions und Grössenänderungen wären
    dann auch einfacher.

    SetTimer(hWnd, 1, 20, NULL);
    

    Statt die 1 hart zu kodieren und danach zu ignorieren wäre es sinnvoll Namen zu vergeben und genau auf
    diesen Timer zu reagieren. Wenn es weitere geben sollte ..

    case WM_TIMER: 
        switch (wParam) { 
            case IDT_TIMER1: 
                // process the timer1
    

    Und irgendwo sollte auch

    KillTimer(hwnd, IDT_TIMER1);
    

    mal vorkommen.

    du bist ja schon drauf hingewiesen worden, das nicht immer der ganze Clientbereich gelöscht werden muss;

    InvalidateRect(hWnd, NULL, TRUE);
    

    macht aber genau das. Anschliessend muss alles neu gezeichnet werden.



  • N´abend.

    mgaeckler schrieb:

    Da ist natürlich noch genügend Platz für Optimierungen.

    Von daher werden in meinem Programm natürlich auch alle Objekte nach Benutzung gelöscht, von daher gibt es von mir eine Eins für Martins Beitrag 👍
    Funktioniert einwandfrei und das Thema Hintergrundbild was ich als nächstes angehen wollte, muss ich nicht mehr angehen, war ja gleich mit dabei...

    @merano: gekillt wird der Timer ja auch wieder direkt nachdem die Figur fertig mit der Bewegung ist (wäre ja auch ungünstig "Speicher-Leichen" zu hinterlassen):

    if(Pos>100){Pos=0; Sprung=FALSE; KillTimer(hWnd, 1);}

    Ist die Stelle falsch?

    Der neuzuzeichnende Bereich wird natürlich ausgerechnet, als RECT gespeichert und anstatt der NULL in InvalidateRect mitgegeben, war ich nur zu faul für im Beispielprogramm. 🙂

    Timer benennen und eine RECT fürs Rechteck zu verwenden sind gute Hinweise, Danke.
    Schönen Abend, John.

    Edit: Die 640, 480 entspricht meiner Fenstergröße 😉



  • merano schrieb:

    mgaeckler schrieb:

    John1 schrieb:

    Auch wenn es nicht um Flüchtigkeitsfehler ging, ist Timerfunktion der entscheidende Denkansatz der mir fehlte.
    Von daher trotzdem Vielen Dank.
    MFG John.

    Abgesehen von der nicht initialiserten Variablen, waren Deine "Fehler" auch nicht wirklich ursächlich für Dein Problem und nicht besonders tragisch.

    Wenn ein Nullpointer (hwnd in move()) nicht tragisch ist, was dann ?

    Guter Hinweis: Ich habe tatsächlich nicht gesehen, das hwnd auch noch lokal in WinMain definiert wurde. Ich dachte Du meinst die lokale Variable in WndProc. Das wäre aber kein Problem, da WM_KEYDOWN erst gesendet wird, wenn CreateWindow fertig ist. So wird InvalidateRect aber tatsächlich ein ungültiges Handle übergeben. Das Funktioniert aber trotzdem, wenn die gloabale hwnd NULL ist:

    A handle to the window whose update region has changed. If this parameter is NULL, the system invalidates and redraws all windows, not just the windows for this application, and sends the WM_ERASEBKGND and WM_NCPAINT messages before the function returns. Setting this parameter to NULL is not recommended.

    http://msdn.microsoft.com/en-us/library/windows/desktop/dd145002%28v=vs.85%29.aspx

    mfg Martin



  • merano schrieb:

    Nee, Martin - setzen Sechs !!
    ...

    Wer lesen kann, ist klar im Vorteil:

    mgaeckler schrieb:

    Da ist natürlich noch genügend Platz für Optimierungen. Ist jetzt nur auf die schnelle hingerotzt, um das Prinzip zu demonstrieren.

    mfg Martin



  • Noch ein Hinweis:

    Man sollte sich genau überlegen, wo es sich lohnt, Optimierungen zu implementieren. Wichtig beim UI Design ist, daß der Anwender hinreichend schnelles Feedback auf eine Aktion erhält. Was hinreichen schnell ist, hängt natürlich von der Anwendung ab.

    In einem Fall wie diesen, wo es darum geht, ein Damebrett zu zeichnen sollte jeder halbwegs aktuelle Rechner in der Lage sein, dies innerhalb von 0,1 Sekunden zu erledigen. Flakernde Fenster sind da viel störender, daher habe ich darauf hingewiesen.

    mfg Martin



  • mgaeckler schrieb:

    In einem Fall wie diesen, wo es darum geht, ein Damebrett zu zeichnen sollte jeder halbwegs aktuelle Rechner in der Lage sein, dies innerhalb von 0,1 Sekunden zu erledigen. Flakernde Fenster sind da viel störender, daher habe ich darauf hingewiesen.

    Wenn man das Spielfeld animiert mit 1 Pixel-Schritten ausprogrammiert kann da schon ne Menge auf
    der GUI los sein.
    Von einem konkreten Rechner auszugehen ist ungesund; letztlich weiß man nicht, welche Rechner der
    Kunde verwendet. Das Programm muß ja auch nicht unnötig langsam sein ...

    Ich würde auch nie von festen Fenstergrössen ausgehen, sondern diese immer abfragen (z.B. GetClientRect()).

    Der Nullpointer bei hwnd führt an dieser Stelle nicht zum Absturz, aber das alle Fenster neugezeichnet
    werden, war sich auch nicht gewollt. Da könnte schon mal was flackern, oder ?



  • John1 schrieb:

    @merano: gekillt wird der Timer ja auch wieder direkt nachdem die Figur fertig mit der Bewegung ist (wäre ja auch ungünstig "Speicher-Leichen" zu hinterlassen):

    if(Pos>100){Pos=0; Sprung=FALSE; KillTimer(hWnd, 1);}

    Ist die Stelle falsch?

    Die Stelle hatte ich übersehen; woran das wohl liegen könnte ? 😉



  • merano schrieb:

    mgaeckler schrieb:

    In einem Fall wie diesen, wo es darum geht, ein Damebrett zu zeichnen sollte jeder halbwegs aktuelle Rechner in der Lage sein, dies innerhalb von 0,1 Sekunden zu erledigen. Flakernde Fenster sind da viel störender, daher habe ich darauf hingewiesen.

    Wenn man das Spielfeld animiert mit 1 Pixel-Schritten ausprogrammiert kann da schon ne Menge auf
    der GUI los sein.
    Von einem konkreten Rechner auszugehen ist ungesund; letztlich weiß man nicht, welche Rechner der
    Kunde verwendet. Das Programm muß ja auch nicht unnötig langsam sein ...

    Bei der Animation magst Du durchaus recht haben. Ich würde das wahrscheinlich auch nicht über eine Redrawmessage realisieren, sondern die Animation beim move() zeichnen. Wie ich schon schrieb, das kommt halt auch einfach auf die Art der Zeichnung an.

    merano schrieb:

    Ich würde auch nie von festen Fenstergrössen ausgehen, sondern diese immer abfragen (z.B. GetClientRect()).

    Das war ja auch nur eine Demo. In einer realen Anwendung hast Du vollkommen recht.

    merano schrieb:

    Der Nullpointer bei hwnd führt an dieser Stelle nicht zum Absturz, aber das alle Fenster neugezeichnet
    werden, war sich auch nicht gewollt. Da könnte schon mal was flackern, oder ?

    Korrekt, wenn viele Fenster offen sind, dürfte das sogar heftigst flackern. Aber auch mit einem korrekten Handle war es sichtbar. Nocheinmal: das NULL-Handle haben wir ja alle bis auf Du 👍 übersehen, das muß natürlich korrigiert werden.

    mfg Martin



  • merano schrieb:

    John1 schrieb:

    @merano: gekillt wird der Timer ja auch wieder direkt nachdem die Figur fertig mit der Bewegung ist (wäre ja auch ungünstig "Speicher-Leichen" zu hinterlassen):

    if(Pos>100){Pos=0; Sprung=FALSE; KillTimer(hWnd, 1);}

    Ist die Stelle falsch?

    Die Stelle hatte ich übersehen; woran das wohl liegen könnte ? 😉

    Moin, genau das war mein Gedanke bei der Frage. 😉
    Bist du noch so freundlich zu verraten wo die richtige Stelle ist?

    Mein Gedankengang war den Timer direkt dort zu löschen wo die Figur ihre Endposition erreicht hat und der Timer somit wieder überflüssig wird<--Falsch gedacht scheinbar. 🙄
    LG John.



  • So:

    if(Pos>100)
    {
      Pos=0; 
      Sprung=FALSE; 
      KillTimer(hWnd, 1);
    }
    

    hätte das jeder gefunden, weil es so auch jeder erwartet.



  • Belli schrieb:

    So: ... hätte das jeder gefunden, weil es so auch jeder erwartet.

    👍

    @John1: Ich finde die Stelle nicht sooo verkehrt.



  • Belli schrieb:

    So [...] hätte das jeder gefunden, weil es so auch jeder erwartet.

    Hallo,
    das ist noch eine alte und vor allem schlechte Angewohnheit von mir...
    "Was Hänschen nicht lernt, lernt Hans nimmermehr" oder nur sehr schwer. Ich habe noch mit dem oder-Fall zu kämpfen;-)

    Auf jeden Fall an alle Helfer Vielen Dank! 👍 - Keine Fragen mehr...
    MFG John.


Anmelden zum Antworten