C: Hur man gör själva spelet

De här instruktionerna är oberoende av vilket operativsystem man använder.

1. Utgångspunkten

Utgå från exempelprogrammet vi använde tidigare:
#define USE_CONSOLE
#include <allegro.h>

int main(void) {
        if (allegro_init() != 0)
                return 1;
        allegro_message("Hej!");
        return 0;
}
END_OF_MAIN()

2. Grafik

Det programmet gör inget grafiskt. Prova i stället med följande program. (Programmet avslutas genom att man trycker en tangent.)
#define USE_CONSOLE
#include <allegro.h>

int main(void) {
    if (allegro_init() != 0)
        return 1;

    if (set_gfx_mode(GFX_AUTODETECT, 1024, 768, 0, 0) != 0) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("Kunde inte byta till grafikläget.\n");
        return 1;
    }

    install_keyboard();

    while (!keypressed()) {
        acquire_screen();

        clear_to_color(screen, makecol(255, 255, 255));
        circlefill(screen, 100, 100, 50, makecol(255, 0, 0));

        release_screen();
    }

    return 0;
}
END_OF_MAIN()
Om allt fungerar ska programmet ta över hela skärmen, göra den vit, och sen rita en röd cirkel i övre vänstra hörnet.

Vi analyserar programkoden lite närmare:

#define USE_CONSOLE
#include <allegro.h>
allegro.h är förstås Allegro-bibliotekets include-fil. Raden med USE_CONSOLE behövs eftersom operativsystemet Windows startar C-program på olika sätt beroende på om det är ett konsolprogram eller ett fönsterprogram, och vi måste tala om för systemet att det här är ett konsolprogram. (Inte för att vi skriver ut något på den, utan det är bara en formsak.)

I början på main-funktionen anropar vi allegro_init, som sätter upp lite interna saker i Allegro. Om den misslyckas, returnerar den en felkod skild från 0, och i så fall avslutar vi programmet.

int main(void) {
    if (allegro_init() != 0)
        return 1;
Sen väljer vi grafikläge med funktionen set_gfx_mode. Det här säger att vi vill ha en rityta som är 1024 gånger 768 bildpunkter. (Om man vill kan man läsa mer i manualen om set_gfx_mode,) Parametern GFX_AUTODETECT som vi skickar med betyder att vi i första hand vill ta över hela skärmen ("fullscreen mode"), men om inte det går så nöjer vi oss med ett fönster.
    if (set_gfx_mode(GFX_AUTODETECT, 1024, 768, 0, 0) != 0) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("Kunde inte byta till grafikläget.\n");
        return 1;
    }
Programmet ska avslutas när man trycker en tangent. För att kunna läsa från tangentbordet måste vi anropa funktionen install_keyboard:
    install_keyboard();
Huvudloopen ska köras så länge man inte trycker på någon tangent:
    while (!keypressed()) {
Funktionen acquire_screen "låser" skärmen, vilket bara gör att programmet går lite jämnare:
        acquire_screen();
(Om du kör Linux i stället för Windows, så låt bli att använda acquire_screen och release_screen. Det fungerar inte alls bra.)

Nu kommer själva ritandet. Anropet till clear_to_color gör hela ritytan vit, och anropet till circlefill ritar en röd cirkel med centrum i punkten (x = 100, y = 100) och diametern 50.

        clear_to_color(screen, makecol(255, 255, 255));
        circlefill(screen, 100, 100, 50, makecol(255, 0, 0));
Observera att vi alltså ritar upp hela bilden på nytt i varje varv i loopen! Det kan kännas onödigt, men senare ska bollen röra sig, och då ser ju bilden olika ut i varje varv.

I slutet av while-loopen anropar vi funktionen release_screen, som "låser upp" den "låsta" skärmen.

        release_screen();
    }
Slutet på main-funktionen. END_OF_MAIN() är en specialgrej som Allegro använder sig av för att fönsterprogram under Windows ska startas på rätt sätt.
    return 0;
}
END_OF_MAIN()

På vissa system bli grafiken ful, med flimmer och ränder. Det beror på att vi ritar (med clear_to_color och circlefill) direkt i det minnesutrymme som samtidigt visas på skärmen. Skärmen uppdateras asynkront, dvs oberoende av vad vi håller på med i programmet. Det betyder att ibland när skärmen ska visas, så håller vi just då på och ritar om innehållet, och det blir en halvfärdig bild som visas. Det orsakar flimmer.

Lösningen är att använda så kallad dubbelbuffring, dvs att man ritar i en separat minnesarea, och lämnar över den färdigritade bilden för visning först när den är klar.

Mer om detta nedan.

3. Rörlig grafik

Följande program ritar en röd fyrkant, som rör sig över skärmen. Ändringarna, som är markerade med rött, diskuterar vi efter programmet.
#define USE_CONSOLE
#include <allegro.h>

int main(void) {
    double x_pos, y_pos, delta_x, delta_y;

    if (allegro_init() != 0)
        return 1;

    if (set_gfx_mode(GFX_AUTODETECT, 1024, 768, 0, 0) != 0) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("Kunde inte byta till grafikläget.\n");
        return 1;
    }

    install_keyboard();

    x_pos = 100;
    y_pos = 100;
    delta_x = 2.5;
    delta_y = 0.5;

    while (!keypressed()) {
        acquire_screen();

        clear_to_color(screen, makecol(255, 255, 255));
        rectfill(screen, x_pos, y_pos, x_pos + 100, y_pos + 100,
                 makecol(255, 0, 0));

        release_screen();

        x_pos += delta_x;
        y_pos += delta_y;
        if (x_pos < 0)
            x_pos += 1024;
        else if (x_pos >= 1024)
            x_pos -= 1024;
        if (y_pos < 0)
            y_pos += 768;
        else if (y_pos >= 768)
            y_pos -= 768;
    }

    return 0;
}
END_OF_MAIN()
I stället för att hela tiden rita fyrkanten på samma ställe, håller vi reda på dess aktuella position med variablerna x_pos och y_pos:
    double x_pos, y_pos, delta_x, delta_y;
delta_x och delta_y är fyrkantens hastighet i x- och y-led, dvs hur mycket vi ska ändra aktuell position i varje steg i loopen.

Man kan behöva justera hastigheten, beroende på hur snabb dator man har. Far fyrkanten iväg fortare än man hinner se, minska delta_x och delta_y. Står den tråkigt still, öka dem.

I ett riktigt spel skulle man hellre använda någon form av timers, dvs funktioner som väntar en viss, angiven tid. Då slipper man justera spelets hastighet beroende på hur snabb datorn är.

Vi sätter startpositionen och fyrkantens hastighet:

    x_pos = 100;
    y_pos = 100;
    delta_x = 2.5;
    delta_y = 0.5;
Anropet till rectfill ritar en röd fyrkant med motstående hörn i punkterna (x1 = x_pos, y2 = y_pos) och (x2 = x_pos + 100, y2 = y_pos + 100).
        rectfill(screen, x_pos, y_pos, x_pos + 100, y_pos + 100,
		 makecol(255, 0, 0));
I varje varv justerar vi fyrkantens position genom att lägga till delta_x och delta_y. Om fyrkanten hamnar utanför skärmen, flyttar vi den till motsatta kanten, så det ser ut som om fyrkanten kommer in igen på andra sidan.
        x_pos += delta_x;
        y_pos += delta_y;
        if (x_pos < 0)
            x_pos += 1024;
        else if (x_pos >= 1024)
            x_pos -= 1024;
        if (y_pos < 0)
            y_pos += 768;
        else if (y_pos >= 768)
            y_pos -= 768;

4. Musen

Nu ska vi lägga till mushantering. Prova vad följande program gör!

Ändringarna, som är markerade med rött, diskuterar vi efter programmet.

#define USE_CONSOLE
#include <allegro.h>

int main(void) {
    double x_pos, y_pos, delta_x, delta_y;

    if (allegro_init() != 0)
        return 1;

    if (set_gfx_mode(GFX_AUTODETECT, 1024, 768, 0, 0) != 0) {
        set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
        allegro_message("Kunde inte byta till grafikläget.\n");
        return 1;
    }

    install_keyboard();
    install_mouse();

    x_pos = 100;
    y_pos = 100;
    delta_x = 2.5;
    delta_y = 0.5;

    while (!keypressed()) {
        acquire_screen();

        clear_to_color(screen, makecol(255, 255, 255));
        rectfill(screen, x_pos, y_pos, x_pos + 100, y_pos + 100,
                 makecol(255, 0, 0));
        rectfill(screen, mouse_x, mouse_y, mouse_x + 10, mouse_y + 10,
                 makecol(0, 0, 0));

        release_screen();

        x_pos += delta_x;
        y_pos += delta_y;
        if (x_pos < 0)
            x_pos += 1024;
        else if (x_pos >= 1024)
            x_pos -= 1024;
        if (y_pos < 0)
            y_pos += 768;
        else if (y_pos >= 768)
            y_pos -= 768;
    }

    return 0;
}
END_OF_MAIN()
För att kunna ta reda på musens position måste vi först anropa funktionen install_mouse:
    install_mouse();
Anropet till rectfill ritar en liten, svart fyrkant som anger var musen pekar:
        rectfill(screen, mouse_x, mouse_y, mouse_x + 10, mouse_y + 10,
                 makecol(0, 0, 0));

5. Dubbelbuffring

Det här steget är frivilligt, men kan göra spelet mycket snyggare.

Om grafiken flimrar och är randig, bör vi byta till dubbelbuffring. Börja med att skapa en variabel för den extra minnesarean:

    BITMAP* buffer;
Skapa minnesarean:
    buffer = create_bitmap(1024, 768);
Gör detta före loopen. (Övning: Varför?)

När vi tidigare ritade direkt på screen:

        acquire_screen();

        clear_to_color(screen, makecol(255, 255, 255));
        rectfill(screen, x_pos, y_pos, x_pos + 100, y_pos + 100,
                 makecol(255, 0, 0));
        rectfill(screen, mouse_x, mouse_y, mouse_x + 10, mouse_y + 10,
                 makecol(0, 0, 0));

        release_screen();
...ska vi nu rita i buffer, och sen kopiera till screen med kopieringsfunktionen blit:
        clear_to_color(buffer, makecol(255, 255, 255));
        rectfill(buffer, x_pos, y_pos, x_pos + 100, y_pos + 100,
                 makecol(255, 0, 0));
        rectfill(buffer, mouse_x, mouse_y, mouse_x + 10, mouse_y + 10,
                 makecol(0, 0, 0));
	blit(buffer, screen, 0, 0, 0, 0, 1024, 768);

6. Spelet!

Gör så att fyrkanten stannar om man pekar på den med musen!

Det är inte så svårt. Allt man behöver göra är att se om musens x- och y-koordinater befinner sig inuti den stora röda rektangeln, och i så fall låta bli att ändra den rektangelns position.

Tips: Funktionen inuti från inlämningsuppgift 3.

7. Frivilliga utökningar av spelet

Det är bara obligatoriskt att göra ändringen ovan, men här är några förslag på ytterligare förbättringar av programmet som man kan göra om man vill:
  1. Dubbelbuffring.
  2. Låt fyrkanten påverkas av gravitationen. Det betyder att y-komponenten i hastigheten ökar av sig själv hela tiden.
  3. Låt den röda fyrkanten byta riktning och "studsa tillbaka" när den når kanten på skärmen.
  4. Alternativt: Gör så att "överlappningen" blir snyggare. Nu ser det ut som att den hoppar från ena kanten till den andra, men om man ritar den på två ställen kan man få det att se ut som att den glider över kanten och kommer in igen.
  5. Låt den röda fyrkanten byta riktning i stället för att bara stanna när den krockar med musen.
  6. Fyrkanten borde gå fortare ju hårdare man slår till den.
  7. Byt till en boll, dvs en röd cirkel i stället för en rektangel. Då blir "inuti"-kollen lite mer komplicerad.
  8. Ha mer än en boll, och gör så att bollarna kan studsa mot varandra.
  9. Inför någon sorts poängräkning, till exempel genom att man får en minuspoäng för varje gång en boll krockar med kanten eller en annan boll. Spelarens uppgift är alltså att hindra kollisioner.
  10. Gör det möjligt att på något sätt välja antalet bollar.

Thomas Padron-McCarthy (Thomas.Padron-McCarthy@oru.se), 16 januari 2009