breakout in Winapi mit GDI und c++



  • Guten Abend,
    ich hab mir den Plan gefasst ein Spiel zu progen.
    Nun, da ich mich ja nicht überschätzen möchte, aber denoch gefordert werde soll, hab ich mir überlegt eine art breakout zu schreiben.
    erstmal ohne Steine, sprich: Schläger und ball.

    Da ich null ahnung von programm strukturierung und ähnlichem hab, wollt ich euch bitten mich etwas dabei(und bei programmiertechnischen) zu unterstützen.

    Nun zu Spiel:

    Ich dacht mir das so das ich eine klasse schläger und ein ball mache.

    mit methoden wie mBallDraw würde ich die ausgabe vornehmen.

    void ball::mBallDraw(par...) 
    {
      Ellipse(...);
    }
    

    aufrufen würde ich diese in der Paintnachricht.

    das selbe halt mit dem schläger.

    hm ja, so weit bin ich nun gekommen.

    Ist das brauchbar?? 😕 und wie weiter??

    Ich bin echt voll der N00b in Winapi und spieleprogrammierung sowieso.
    ich hoffe ihr könnt mir etwas unter die arme greifen





  • Also, ich hab vor etwa einem halben Jahr auch sowas wie Breakout geproggt, nur ein bissl mehr features noch drin. Grafik ist zwar erbärmlich, aber ich hatte aus Zeitgründen halt 8bit - Grafiken verwendet...naja, Spass machts trotzdem (finden viele), wenn du magst, kannst du ja mal reinschauen, gibts hier
    www.angelfire.com/falcon/ethereal/tools/qq.zip
    Zur Strukturierung:
    Ich habe eine Klasse, die DirectX initialisiert (du willst ja wohl mit GDI arbeiten - Viel Spass), aber is ja unwesentlich für dich. Dann habe ich eine Klasse Object{}, die sämtliche Funktionen beinhaltet, die auf ein Objekt ausgeführt werden müssen, also z.B. Erstellung, Darstellung, Bewegung, Kollisionsabfrage (!) , Animation,Löschen...
    Übrigens, mit der Kollisionsabfrage habe ich die meisten Probleme gehabt, es wäre also vielleicht ganz clever, wenn du dir halt erst mal überlegst, an welchen Stellen Probleme auftreten könnten...Zur Kollisionsabfrage gabs hier aber schon mehrere Posts, u.a.
    http://www.c-plusplus.net/forum/viewtopic.php?t=20693&highlight= oder auch
    http://www.c-plusplus.net/forum/viewtopic.php?t=46264&highlight=

    Und von dieser Objekt-Klasse erstelle ich dann mehrere Instanzen, eine für den Ball, eine für den Schläger, und eine für einen Hindernis-Block.
    Das ging ganz gut, ich würd also wahrscheinlich gar nicht separate Klassen für Ball und Schläger machen, da Grundfunktionen (Darstellung,Kollision) ja für beide gelten.
    Der Nachteil liegt IMHO allerding in der Tatsache, dass du GDI verwenden willst. Meiner Meinung nach kannst du da zwar auch mit backbuffering arbeiten (das ist sozusagen ein zweiter virtueller Grafikpuffer, in den du immer die nächste Szene zeichnest, die auf die jeweils angezeigte folgt), aber da kann ich dir leider nicht weiterhelfen. Ich weiss auch nicht, wie gut das so alles funzt. Meine Assoziationen mit dem Windows-GDI sind "sehr langsam", was natürlich bei nem Spiel nicht so schön ist. Aber wie gesagt, prinzipiell ist das schon möglich..
    So, hoffe, ich konnte dir ein bissl helfen.
    Gruß
    E-the-Real



  • Stimmt,GDI ist langsam, aber wenn man das Programm, wie bei DirectX auch oft gemacht wird, in der Hauptschleife laufen lässt, kann man noch ein bischen mehr Geschwindigkeit herausholen. Man muss dann halt mit PeekMessage arbeiten...

    @Ethereal
    Ist wirklich ein hübsches Spiel. Hast du die Musik eigentlich selbst gemacht? Und wieviele Levels hat das Spiel? Hast du einen Leveleditor benutzt?

    Mfg
    OP



  • Hast du die Musik eigentlich selbst gemacht?

    Ja. Alles ganz allein komponiert 🙂 Ich spiele auch noch Klavier und interessiere mich für Musik, deswegen macht's mir auch Spass, sowas zu komponieren.

    Und wieviele Levels hat das Spiel?

    Man, das ist schon etwas her....hmm, also ich glaub so zehn level, ja müsste eigentlich hinkommen.

    Hast du einen Leveleditor benutzt?

    Nein, sowas habe ich (leider) nicht. War auch ein bissl mühselig, aber im Prinzip ist es bei so einem Spiel noch nicht so schwer.
    Jede Blockart bekommt halt ne ID, also z.B. 1 für rot, 2 für blau usw.. 0 ist dann leerer Raum.
    Das ganze ist in nem Header als Level1/2/3...-Array deklariert, also wirklich nichts aufwendiges. Etwas nerviges war allerdings, zehn level à 160 potentiellen Blockpositionen als Array zu schreiben, is also ne Menge Schreibarbeit, die an sich keinen besonderen Spass macht, aber gelegentlich konnte ich auch ein paar Zeilen aus vorigen Levels per Copy&Paste übernehmen.
    So, das war eigentlich das ganze Geheimnis 🙂
    Gruß
    E-the-Real



  • danke für deine ausführliche hilfe,

    so, hab mich nun entschieden, dass ich erstmal nur nehn schläeger mach. (Ist schwer genung für mich 🤡 )

    Ich hab mal DAS geschrieben:

    #include <windows.h>
    
    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
    
    mousko(LPARAM lparam);
    
    class schlaeger
    {
    
    	public: 
    
    		static short xt, yt;  //Temp koordinaten
    
    		static short x,y; //Koordinaten
    
    	bool move;  //wird bewegt?
    
    	void mDraw(HDC hdc, LPARAM lParam);
    
    }stick;
    
    void schlaeger:: mDraw(HDC hdc, LPARAM lParam)
    {
    	y = 300;
    	x = mousko(lParam);
    	xt = x + 70;
    	yt = y + 20;
    	Rectangle(hdc, x, y, xt, yt);
    }
    
    int mousko(LPARAM lParam)
    {
    	return LOWORD(lParam);
    }
    
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        PSTR szCmdLine, int iCmdShow)
    {
         static char szAppName[] = "B" ;
    
    	 HWND        hwnd ;
    
    	 MSG         msg ;
    
         WNDCLASS  wc ;
         wc.style         = CS_HREDRAW | CS_VREDRAW;
    	 wc.lpfnWndProc   = WndProc;
    	 wc.cbClsExtra    = 0;
    	 wc.cbWndExtra    = 0;
    	 wc.hInstance     = hInstance;
    	 wc.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
    	 wc.hCursor       = LoadCursor (NULL, IDC_ARROW);
    	 wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    	 wc.lpszMenuName  = NULL;
    	 wc.lpszClassName = szAppName;
    	 RegisterClass (&wc);
    	 hwnd = CreateWindow (szAppName, "Fenstername", 
    						  WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 
    						  CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    						  NULL, NULL, hInstance, NULL) ;	
    
    	 ShowWindow (hwnd, iCmdShow) ;
    	 UpdateWindow (hwnd) ;
    
         while (GetMessage (&msg, NULL, 0, 0))
              {
    		    TranslateMessage (&msg) ;
    		    DispatchMessage (&msg) ;
              }
    	 return msg.wParam ;
    }
    
    LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (iMsg)
              {
    		  case WM_CREATE :
    
    			   return 0 ;
    
    		  case WM_PAINT :
    
    			  PAINTSTRUCT ps;
    
    			  HDC hdc;
    
    			  hdc = BeginPaint(hwnd, &ps);
    
    			  stick.mDraw(hdc, lParam);
    
    			  EndPaint(hwnd, &ps);
    
                   return 0 ;
    
    		 case WM_DESTROY :
    			   PostQuitMessage (0) ;
                   return 0 ;
              }
    
         return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
    }
    

    Fehlermeldung:

    WINMAIN.obj : error LNK2001: Nichtaufgeloestes externes Symbol "public: static short schlaeger::yt" (?yt@schlaeger@@2FA)
    WINMAIN.obj : error LNK2001: Nichtaufgeloestes externes Symbol "public: static short schlaeger::xt" (?xt@schlaeger@@2FA)
    WINMAIN.obj : error LNK2001: Nichtaufgeloestes externes Symbol "public: static short schlaeger::x" (?x@schlaeger@@2FA)
    WINMAIN.obj : error LNK2001: Nichtaufgeloestes externes Symbol "public: static short schlaeger::y" (?y@schlaeger@@2FA)

    was heisst den das?



  • Hi,
    warum machst du denn deine xt,yt,x,y - Koordinaten static ???
    Lass es mal weg, dann solltest du keine Link-Probleme haben, also

    class schlaeger 
    { 
    
        public:  
    
        short xt, yt;  //Temp koordinaten 
        short x,y; //Koordinaten 
    
        bool move;  //wird bewegt? 
    
        void mDraw(HDC hdc, LPARAM lParam); 
    
    }stick;
    

    Das müsste klappen
    Gruß
    E-the-Real



  • juhu, danke.
    daran hät ich jetzt echt nicht gedacht.

    nun, ich hab sie statisch gemacht weil ich dachte das ich den Variablenwert ja über den "ganzen" code brauche.

    So,

    #include <windows.h>
    
    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
    
    mousko(LPARAM lparam);
    
    class schlaeger
    {
    
    	public: 
    
    		short xt, yt;  //Temp koordinaten
    
    		short x,y; //Koordinaten
    
    	bool move;  //wird bewegt?
    
    	void mDraw(HDC hdc, LPARAM lParam);
    
    }stick;
    
    void schlaeger:: mDraw(HDC hdc, LPARAM lParam)
    {
    	y = 300;
    	x = mousko(lParam);
    	xt = x + 70;
    	yt = y + 20;
    	Rectangle(hdc, x, y, xt, yt);
    }
    
    int mousko(LPARAM lParam)
    {
    	return LOWORD(lParam);
    }
    
    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        PSTR szCmdLine, int iCmdShow)
    {
         static char szAppName[] = "B" ;
    
    	 HWND        hwnd ;
    
    	 MSG         msg ;
    
         WNDCLASS  wc ;
         wc.style         = CS_HREDRAW | CS_VREDRAW;
    	 wc.lpfnWndProc   = WndProc;
    	 wc.cbClsExtra    = 0;
    	 wc.cbWndExtra    = 0;
    	 wc.hInstance     = hInstance;
    	 wc.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
    	 wc.hCursor       = LoadCursor (NULL, IDC_ARROW);
    	 wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    	 wc.lpszMenuName  = NULL;
    	 wc.lpszClassName = szAppName;
    	 RegisterClass (&wc);
    	 hwnd = CreateWindow (szAppName, "Fenstername", 
    						  WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 
    						  CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    						  NULL, NULL, hInstance, NULL) ;	
    
    	 ShowWindow (hwnd, iCmdShow) ;
    	 UpdateWindow (hwnd) ;
    
         while (GetMessage (&msg, NULL, 0, 0))
              {
    		    TranslateMessage (&msg) ;
    		    DispatchMessage (&msg) ;
              }
    	 return msg.wParam ;
    }
    
    LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
    {
        switch (iMsg)
              {
    		  case WM_CREATE :
    
    			   return 0 ;
    
    		  case WM_MOUSEMOVE:
    			  InvalidateRect(hwnd, NULL, NULL);
    
    		  case WM_PAINT :
    
    			  PAINTSTRUCT ps;
    
    			  HDC hdc;
    
    			  hdc = BeginPaint(hwnd, &ps);
    
    			  stick.mDraw(hdc, lParam);
    
    			  EndPaint(hwnd, &ps);
    
                   return 0 ;
    
    		 case WM_DESTROY :
    			   PostQuitMessage (0) ;
                   return 0 ;
              }
    
         return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
    }
    

    Gleich noch neh kurze Frage.

    Ich hab jetzt meinen "Schläger".
    doch wird der Bildschirm durch InvalidateRect nicht korrekt gelöscht.
    durch diese Funktion wird ja das Fenster für ungültig erklärt.
    Und dann leer neugezeichnet.
    bei mir wird das alte Rechteck aber leider nicht gelöscht. 😕

    EDIT:
    letzten parameter von InvalidateRect auf True!! lang lebe die msdn!! 😃



  • hosti schrieb:

    nun, ich hab sie statisch gemacht weil ich dachte das ich den Variablenwert ja über den "ganzen" code brauche.

    Statisch heißt, daß die Variable für beliebig viele Instanzen einer Klasse trotzdem nur 1x existiert. Was Du meinst, ist "global"...



  • Wenn ich nun soweit bin, wie weiter.

    ich dachte erst mal die bewegung des Balles.. aber dazu brauch ich die kolison.. aber dafür brauch ich einen Ball der sich bewegt...

    hmmm... ok vieleicht einfach mal den ball wie wild vom rand abprallen lasse.

    Nun stellt sich die Frage, wie ich den Ball schräg über den Bildschirm bewegen könnte.
    Mit einer Schleife??

    So,
    if(i < 200)
    Ball zeichnen, x+1 y+1

    hmm. das wäre dann mit einem winkel von 45Grad, und ein murks.

    Vektor??
    mit Sinus winkel berechnen?? oh gott wie geht denndas in diesem Fall??

    naja, vieleicht kann mich ja jemand aufklären.

    EDIT:

    @Sgt. Nukem

    nunja, dann müsst ich sie ja ausserhalb eines jeden blockes definieren(dekla)

    aber eine statische variable hat doch auch die globale eingenschaft, nicht?
    also ich meine der wert wird nicht immer gelöscht, wenn man ausserhalb des blockes kommt



  • ethereal schrieb:

    also ich glaub so zehn level, ja müsste eigentlich hinkommen.

    10? Ist ja gar nix! Alles unter 100 ist Schrott. 😉



  • mach nun damit weiter, dass ich verhindere das der schläger aus dem Bild fährt.

    void schlaeger:: mDraw(HDC hdc, LPARAM lParam) 
    { 
    	y = 650; 
        x = mousko(lParam); 
    	xt = x + 90; 
        yt = 670; 
        if(xt < 1024)
    	{
    		Rectangle(hdc, x, y, xt, yt); 
    	}
    	else
    	{
    		x--;
    		xt = x + 90;
    		Rectangle(hdc, x, y, xt, yt);
    	}
    }
    

    klappt aber nicht, mir ist nur nicht klar wieso.

    wenn der rechte rand aus dem Fenster hängt wird ein pixelabgezogen und neu gezeichnet....

    Ich scheitere immer an so doofen kleinigkeiten 😞



  • Soll das ein Witz sein? Wenn x= 10000 ist, dann kann x - 1 nie auf dem Bildschirm sein. Denk mal darüber nach!

    Bye, TGGC



  • was??
    woher hast du denn die 10000 ??

    Mein Bildschirm hat eine Horizontale von 1024pixel.
    Also sind 1024 das maixmum meines oberen eckpunktes meines rechteckes.

    also sind alle werte unter 1024+90, oder nicht??

    wie würdest denn du das machen?

    EDIT:

    void schlaeger:: mDraw(HDC hdc, LPARAM lParam) 
    { 
    	y = 650; 
        x = mousko(lParam); 
    	xt = x + 90; 
        yt = 670; 
        if(xt < 1016)
    	{
    		Rectangle(hdc, x, y, xt, yt); 
    	}
    	else
    	{
    		x = 924;
    		xt = 1016;
    		Rectangle(hdc, x, y, xt, yt);
    	}
    }
    

    Es klappt, scheint aber etwas gemurckst.

    Bin ich auf dem richtigen Weg?



  • TGGC schrieb:

    ethereal schrieb:

    also ich glaub so zehn level, ja müsste eigentlich hinkommen.

    10? Ist ja gar nix! Alles unter 100 ist Schrott. 😉

    WIE GEMEIN !!! 😞 😞 😞



  • 10000 ist nur ein Beispiel. Nach dem Code könnte x ja alles sein.

    Wenn du weisst, wie du auf die passenden Zahlen kommst, bist du auf dem richtigen Weg. Sinnvoller ist es aber die Zahl einer Variablen oder Konstanten zuzuweisen und dort die entsprechende Formel hinzuschreiben. Wenn du die Breite des Pads änderst, sollte diese Zahl maximal einmal im Code vorkommen. Genauso die 1024 für die Bildschirmbreite.

    Bye, TGGC



  • hi,
    ich komm nich weiter (mal wieder 🙄)

    Wie mach ich die ballanimation oder die kollision.

    Ich hab Null ideen, hilfe.
    gebt mir bitte einen Gedanken Anstoss



  • Also,
    erst mal was zur Kollision:
    Die einfachste (und oft auch ausreichende) Methode einer Kollisionsabfrage besteht darin, deine Objekt mit einer Bounding-Box zu umgeben. Das bedeutet konkret: Du legst einen rechteckigen Bereich um das Objekt, der, wenn das Objekt Rechteckig ist, dieselben Masse haben sollte.

    bool KollisionsTest(void)
    {
        int Ballradius_x  = BallBreite/2;// Radius der BoundingBox vom Ball
        int Ballradius_y  = BallHoehe/2;
    
        int Schlaegerradius_x=SchlaegerBreite/2;/*Radius der BoundingBox vom Schlaeger*/
        int Schlaegerradius_y=SchlaegerHoehe/2;
    
       //Mittelpunkte beider BoundingBoxes 
    
        int BallCenter_x = Ball_x + Ballradius_x;  //Ball_x ist die x-Koord des B.
        int BallCenter_y = Ball_y + int Ballradius_y; // analog die y-Koord. 
    
        int SchlaegerCenter_x = Schlaeger_x + Schlaegerradius_x; 
        int SchlaegerCenter_y = Schlaeger_y + Schlaegerradius_y;
    
        // jetzt den Abstand der beiden Mittelpunkte
        int Abstand_x = abs(SchlaegerCenter_x - BallCenter_x);
        int Abstand_y = abs(SchlaegerCenter_y - BallCenter_y);
    
        // wenn abstand kleiner ist als beide Radien addiert...
       if((Abstand_x < (Ballradius_x + Schlaegerradius_x))&& (Abstand_y < (Ballradius_y + Schlaegerradius_y)))
        {     //...dann liegt eine Kollision vor, also true zurückgeben
                    return TRUE;
        }
    
        //...ansonsten keine Kollision -> false
        return FALSE;
    }
    

    So, hab's jetzt net getestet, aber ich meine, das müsste irgendwie so schon klappen...
    Zur Animation:
    das ganze ist etwas kompliziert und wird wahrscheinlich ordentlich ruckeln, wenn du immer noch versuchst, GDI zu verwenden, aber prinzipiel würd' das so machen:
    du hast eine Bitmap-Datei, in der jeweils in gleicher größe die einzelnen Bilder der Animation gespeichert sind (hintereinander).
    Dann würde ich mir (bei GDI min.)zwei (, wenn nicht sogar mehr, falls das geht und effektiv ist bei GDI - Frage an die experten !) Objekte erstellen, in die du dann immer das jeweils nächste Bild lädst. Ein Objekt (ich weiss nicht so genau wie das heisst, kann sein, dass das objekt ein HBITMAP sein muss, vielleicht auch nur BITMAP, frag mal rum !) wird dann immer angezeigt, während in das nächste jeweils der nächste Animationsschritt geladen wird. dann zeigst du dieses an und lädsts in das erste den nächsten Animationsschritt. Such mal nach Backbuffering oder double buffering etc... da solltest du eigentlich einiges finden, ich bin da auch kein experte.
    Ansonsten erstmal viel Erfolg
    Gruß
    E-the-Real



  • Auf www.zfx.info unter Tutorials gib's ein gutes Tutorial zu dem Thema, sehr ausführlich. Es wird beschrieben wie man ein einfaches Breakout programmiert.



  • @lustig:
    ich rate erstmal von dem tutorial da ab, denn ich weiss nicht, ob man als anfänger damit...

    Move(double elapsed, std::list<CRenderable> &bricks, CPaddle &paddle);
    

    ...was anfangen kann...
    Dazu kommt dynamische speicherallokierung und so'n ganzer kram. brauch man anfangs eigentlich gar nicht. sicher ist dein tip nicht schlecht, aber ich könnt mir vorstellen, dass er erst mal für verwirrung sorgen wird.
    Gruß
    E-the-Real


Anmelden zum Antworten