~~~ TraveL --- Forum jeux HTML ~~~
Vous souhaitez réagir à ce message ? Créez un compte en quelques clics ou connectez-vous pour continuer.


Passe une bonne journée Invité !
 
AccueilAccueil  PortailPortail  PublicationsPublications  Dernières imagesDernières images  RechercherRechercher  S'enregistrerS'enregistrer  ConnexionConnexion  
Le deal à ne pas rater :
Réassort du coffret Pokémon 151 Électhor-ex : où l’acheter ?
Voir le deal

 

 Tuto 2 : Pong SDL 2.0 dans Code::blocks

Aller en bas 
AuteurMessage
marcellus_wallace
Admin
Admin
marcellus_wallace


Masculin
Nombre de messages : 920
Age : 43
Parrain : Qui veut l'être ? Points Quêtes 100

Tuto 2 : Pong SDL 2.0 dans Code::blocks Empty
MessageSujet: Tuto 2 : Pong SDL 2.0 dans Code::blocks   Tuto 2 : Pong SDL 2.0 dans Code::blocks Icon_minitimeMer 16 Oct - 18:37

[edit: histoire de vivre avec son temps j'ai recompilé le projet en c++. ça ne change strictement rien au code source, y'a juste l'extension des fichiers qui a été modifiée]

Pour faire ce second tuto vous devez d'abord avoir fait le tuto 1 qui explique comment télécharger Code::Blocks et tester un hello world.

La librairie SDL

Simple Directmedia Layer est un projet open-source collaboratif qui permet de faciliter une partie problématique de la programmation multimédia, à savoir l'intégration OS. SDL gère pour vous l'intégration dans windows, mac, linux, android, etc... de façon beaucoup plus accessible que glut & dxut. (de façon moins subtile et moins optimisée, mais on verra comment améliorer ça lors d'un prochain tutoriel sur le polishing spécifique à windows)

Cette librairie est utilisée aussi bien dans les jeux amateur que dans les jeux professionnels.

Elle possède un moteur graphique facile d'utilisation mais pour les tutoriels qui vont suivre, notre but n'étant pas de devenir des spécialistes de SDL, on va utiliser la méthode bien connue dans les jeux amateur, que j'appellerai "ms-dos-like".

La méthode DOS-like

Cette méthode consiste à recréer les conditions de programmation des jeux sur ms-dos: on change simplement la couleur des pixels de l'ecran et on écrit directement des ondes dans le buffer audio.

Si on emploie beaucoup la technique dos-like dans les jeux amateur, c'est qu'à l'époque des jeux ms-dos, leur développement ne coûtait quasiment rien, c'est donc adapté aux faibles délais des dev amateur. Cette méthode est beaucoup plus efficace que d'utiliser une librairie qui dessine et joue la musique à votre place: il est plus rapide d'improviser des techniques de tracé maison et de mixage maison, plutôt que d'apprendre toute la doc d'un moteur graphique et d'un moteur de son. C'est à dire que vous ne faites que du calcul pur (et tant que vous ne faites que des jeux 2d, ça reste des maths niveau 6ème). Vous pouvez donc préparer vos algorithmes sur papier quand vous êtes dans le train, au bistro, etc... pas besoin d'être devant le pc à apprendre de la doc.

Le calcul pur a aussi l'avantage d'être très facile à porter, il est indépendant du hardware, de l'os, etc, on peut donc le trimballer facilement d'un environnement de développement à l'autre, y compris celui qui nous intéresse, c'est à dire flash (on verra ça dans les prochains tutoriels).

Partir d'un template codeblocks

Ce tuto s'adresse à des débutants donc je n'expliquerai pas ici comment configurer le projet. (Ca n'est pas très compliqué à faire si on sait se servir de google, si vous voulez un tutoriel sur la configuration d'un projet sdl sur code::blocks demandez moi.)

On va donc démarrer d'un petit template simple qui se trouve ici:

[Vous devez être inscrit et connecté pour voir ce lien]

Vérifiez qu'il se compile bien avant de poursuivre. (Si jamais ça plante reportez-moi le bug que j'étudie ça.)

Le template contient trois pages de programme: "main.cpp" qui s'occupe de SDL, "hart.c" qui dimensionne les buffers video/audio/clavier et gère la communication entre sdl et le jeu, et "game.cpp" où l'on programme le jeu proprement dit.

La seule page sur laquelle vous allez travailler est "game.cpp", vous n'avez besoin d'ouvrir que celle-là.
Code:
// hart data ponters

long game_scrW = 320, game_scrH = 240;
unsigned long * game_screen;
float * game_buf4096;
char * game_keys;

// game data

long game_scrollx=0, game_scrolly=0, game_speedx=0, game_speedy=0;

void game_init( char * keys, unsigned long * screen, long scrW, long scrH, float * buf_4096_64 ) {

    // alloc pointers

    game_scrW = scrW;
    game_scrH = scrH;
    game_screen = screen;
    game_buf4096 = buf_4096_64;
    game_keys = keys;

}

void game_close() {

    // dealloc pointers

    game_screen = 0;
    game_buf4096 = 0;
    game_keys = 0;

}

void game_logicRoutine( ) {

    char * keys = game_keys;

    game_speedx = keys[1]-keys[0];
    game_speedy = keys[3]-keys[2];

    game_scrollx += 4*game_speedx;
    game_scrolly += 4*game_speedy;

}

void game_graphicRoutine()
{
    unsigned long * screen = game_screen;
    long scrW=game_scrW,scrH=game_scrH,i,j,p=0,scrollx=game_scrollx,scrolly=game_scrolly,vy; // caching and local

    for ( j=0; j<scrH; j++ ) {

        vy = j+scrolly;

        for ( i=0; i<scrW; i++, p++ ) { // scanline loop should be optimized with assembler

            screen[p] = (i+scrollx)&vy;

        }
    }

}

long audiopos=0;

void game_audioRoutine()
{
    float * buf = game_buf4096;
    double val = 0, volLeft = (game_speedx<0), volRight = (game_speedx>0);
    long s=0, ap = audiopos, perShift = 8+game_speedy;

    for (s=0; s<4096; s+=2, ap++ ) { // audio buffer loop, should be optimized with assembler

        val = 0.01 * ( ( ( ( ap >> perShift ) & 1 ) << 1 ) - 1 )  ;

        buf[s+0] = val * volLeft;
        buf[s+1] = val * volRight;

    }

    audiopos = ap;
}
Je vais expliquer brièvement le code.

Tout d'abord on a 5 fonctions:

- game_init()
- game_close()
- game_logicroutine()
- game_graphicroutine()
- game_audioroutine()

Les fonctions game_init() et game_close() sont l'équivalent des constructeurs / destructeurs dans les langages objet. On référence et déréférence les pointeurs des zones mémoire écran/son/clavier.

Les pointeurs

Si vous faites de l'actionscript / javascript, vous savez sans le savoir ce que c'est qu'un pointeur. En actionscript ou javascript, toute variable est un pointeur, c'est à dire qu'elle n'enregistre pas directement les données, mais elle enregistre une adresse mémoire qui permet d'accéder un objet qui contient les données.

Dans la fonction init() vous aurez remarqué qu'il y'a des variables avec une syntaxe bizzare:

Code:
char * keys, unsigned long * screen, .... double * buf_4096_64
Lorsqu'en c/c+ on met une astérix devant la déclaration de variable, c'est qu'il s'agit d'un pointeur.

Grâce à ces pointeurs, on connait l'adresse mémoire de tableaux qui ont été déclarés sur une autre page du code (dans le fichier "hart.c"). Pour être exact, le pointeur contient l'adresse de la première case du tableau.

Les pointeurs C/C++ se comportent exactement comme des tableaux, on accède aux données en écrivant pointeur[i], à quelques petites différences près:

- un pointeur ne permet pas de connaitre la taille du tableau avec la fonction sizeof()

- un pointeur permet aussi de stocker l'adresse mémoire d'une simple variable, il fonctionne alors comme un tableau à une seule case. Pour ce faire on utilise l'éperulette: pointeur = &variable.

- un pointeur permet d'optimiser certains parcours de tableaux en faisant directement des calculs sur les adresses mémoire. Pour ça on remplace la syntaxe tableau par la syntaxe pointeur. Au lieu d'écrire pointeur[i]=10, on va écrire *(pointeur+i)=10

La différence des pointeurs en langages natif C/C+ avec les langages managés java/c#/javascript/actionscript, c'est que les pointeurs des langages managés ne permettent pas d'accéder directement à l'adresse mémoire. Ces pointeurs qui masquent l'adresse ram sont appelés des "références".

Les fonctions routines

Ce sont des fonctions appelées sur un intervalle de temps régulier. En flash vous connaissez la routine "enterFrame" appelée à chaque rafraichissement d'image, ou la routine "sampleDataEvent" appelée à chaque mise à jour de buffer audio.

Sur cet exemple j'en ai mis trois.

- game_logicroutine()
- game_graphicroutine()

Elles sont toutes les deux appelées sur le rafraichissement d'image, mais je les ai volontairement séparées car en programmation de jeux on sépare toujours la phase logique de la phase graphique.

La première lit le tableau clavier (qui dans mes exemples ne gère que les 4 touches haut/bas/gauche/droite), elle en déduit un déplacement (scrolling) d'image.

La seconde dessine un bête motif x&y. Elle sert juste à tester que l'affichage des pixels fonctionne.

- game_audioroutine()

Appelée sur l'update du buffer audio.

Elle fait un bruit bête en encodant une onde dite "square-wave" (carrée) qui devrait vous rappeler les vieux buzzers des premiers ordinateurs et consoles. C'est la forme d'onde la plus facile à programmer.

J'ai dimensionné le buffer audio en 4096 nombres décimaux de type "double" (64 bits) afin qu'il soit plus facile à porter sur flash (qui utilise un buffer de taille minimale 4096 décimales de 64 bit). Il y'a donc 2048 "samples", on divise 4096 par deux car pour chaque sample d'onde il faut deux nombres: un pour le haut-parleur gauche, un pour le haut-parleur droite.


Et maintenant on passe au pong.


Bien, pour le moment vous n'avez fait que lire mon bavardage, maintenant il faut commencer à bosser.

Vous allez d'abord faire un copier-coller du dossier du template et le renommer "pong".

Ensuite, modifiez le nom du fichier "sdl_template.cbp" en "pong.cbp"

Dans la colonne gauche de codeblocks, faites un clic-droit sur la racine du projet (qui n'a pas encore été renommée en "pong") et cliquez dans le menu tout en bas sur "properties"

Dans le premier onglet "project settings", changez le champ "Title" et écrivez "pong".

On va également renommer les fichiers exe. Cliquez sur l'onglet "build target". Dans la colonne de gauche vous pouvez selectionner la version debug et la version release du programme. Pour les deux versions, modifiez le champ "output filename", et mettez "bin\Debug\pong.exe" et "bin\Release\pong.exe"


Le programme du pong.


Maintenant ouvrez le fichier "game.cpp", vous allez remplacer le code du sample par celui-ci:

Code:
// hart data ponters
long game_scrW, game_scrH;
unsigned long * game_screen;
float * game_buf4096;
char * game_keys;

// game data
long pad_x, pad_y, ball_x, ball_y, ball_speed_x, ball_speed_y;
long audiopos=0; // audio stuff
double vol=0;

// game internal functions -------------------------------------------------------

void round_start() {
    pad_x = game_scrW / 2; pad_y = game_scrH - 16;
    ball_x = game_scrW / 2; ball_y = game_scrH / 2;
    ball_speed_x = 4; ball_speed_y = 4;
}
void game_clearScreen()
{
    long nPix = game_scrW * game_scrH;  // caching
    unsigned long * screen = game_screen;
    long p;

    for ( p=0; p<nPix; p++ ) screen[p] = 0x0; // optimize this shit with assembler

}
void game_whiteRect(long left, long top, long right, long bottom) {

    long i,j,scrW=game_scrW,scrH=game_scrH,rowFirstPix;
    unsigned long * screen = game_screen;

    if (left<0) left=0;
    if (top<0) top=0;
    if (right>scrW) right=scrW;
    if (bottom>scrH) bottom=scrH;

    for (j=top;j<bottom;j++) {
        rowFirstPix = j * scrW;
        for (i=left;i<right;i++) screen[rowFirstPix+i] = 0xffffff;  // optimize this shit with assembler
    }

}
void noise()
{
    vol = 1;
}


// init and close -----------------------------------------------------------------------------

void game_init( char * keys, unsigned long * screen, long scrW, long scrH, float * buf_4096_64 ) {
    // alloc pointers
    game_scrW = scrW; game_scrH = scrH; game_screen = screen; game_buf4096 = buf_4096_64; game_keys = keys;
    // first round
    round_start();
}

void game_close() {
    // dealloc pointers
    game_screen = 0; game_buf4096 = 0; game_keys = 0;
}

// routines ----------------------------------------------------------------------------

void game_logicRoutine( ) {

    // pad behavior

    pad_x += 8*(game_keys[1]-game_keys[0]);
    if (pad_x<16) pad_x=16; else if (pad_x>game_scrW-16) pad_x=game_scrW-16;

    // ball behavior

    ball_x += ball_speed_x;
    ball_y += ball_speed_y;

    if ( ball_x < 4 ) {
        ball_x = 4;
        ball_speed_x = 4;
        noise();
    }
    if ( ball_x > game_scrW-4 ) {
        ball_x = game_scrW-4;
        ball_speed_x = -4;
        noise();
    }

    if ( ball_y < 4 ) {
        ball_y = 4;
        ball_speed_y = 4;
        noise();
    }
    if ( ball_y > game_scrH ) ball_y -= game_scrH;

    // ball hit pad

    if ( ball_y > pad_y-4 && ball_y < pad_y+12 && ball_x > pad_x-20 && ball_x < pad_x+20  ) {
        ball_y = pad_y -4;
        ball_speed_y = -4;
        noise();
    }

}

void game_graphicRoutine()
{
    game_clearScreen();
    game_whiteRect(pad_x-16,pad_y,pad_x+16,pad_y+8); // draw pad
    game_whiteRect(ball_x-4,ball_y-4,ball_x+4,ball_y+4); // draw ball
}

void game_audioRoutine()
{
    float * buf = game_buf4096;
    double val = 0, volLeft = (double)(-ball_x+game_scrW)/game_scrW, volRight = (double)(ball_x)/game_scrW;
    long s = 0, ap = audiopos, perShift = 8;

    for (s=0; s<4096; s+=2, ap++ ) { // audio buffer loop, should be optimized with assembler

        vol *= 0.999;    //fade

        val = 0.02 * ( ( ( ( ap >> perShift ) & 1 ) << 1 ) - 1 )  ;

        buf[s+0] = val * volLeft  * vol;
        buf[s+1] = val * volRight * vol;

    }

    audiopos = ap;
}
A priori je n'ai rien à vous expliquer, c'est un exercice suffisemment simple pour que vous compreniez le code de vous-même. Mais si vous avez des questions à poser, n'hésitez pas.

Je vais quand même fournir quelques explications sur le code.

Vous avez du voir que j'ai ajouté 4 fonctions. Je les ai mises au début car en C il faut créer une fonction avant de s'en servir (c'est un langage simple donc bête).

- round_start()

Cette fonction ne sert à rien ici mais elle prévoit qu'on puisse rejouer une partie. L'initialisation de la partie se fait dedans.

- game_clearScreen()

Première fonction de mon moteur graphique. Celle-ci repeint tout l'écran en noir pour pouvoir retracer les sprites par dessus.

- game_whiteRect()

Deuxième fonction du moteur graphique. Elle dessine un rectangle blanc.

- noise()

Unique fonction du moteur audio. Elle déclenche un bruit. Le reste est géré dans la routine audio.


P.S. Vous avez du remarquer aussi un truc bizzare..
Dans les fonctions de dessin et de bruit, y'a des variables globales qui sont copiées dans des variables locales. Cette technique s'appelle le caching, elle permet d'accélérer les grosses boucles de calculs car l'accès aux variables locales est plus rapide. En as/js ça accélère la totalité des calculs. En c/c+ ça marche plus ou moins, le compileur va essayer de stocker un maximum de variables locales dans les registres (la mémoire processeur) plutôt que dans la ram, mais on abordera ces questions dans les tutoriels sur l'assembleur.


Voilà... vous avez un point de départ pour faire des jeux dos-like en C.
N'hésitez pas à bidouiller le code et à poser des questions.


Dans le prochain tutoriel on verra comment faire marcher ça dans le logiciel flash.

[edit: ce tuto a été mis à jour. j'ai remplacé les double (64bit) du audiobuffer de la zone calcul par des float (32bit), pour être en accord avec le format de l'audiobuffer de flash]
Revenir en haut Aller en bas
 
Tuto 2 : Pong SDL 2.0 dans Code::blocks
Revenir en haut 
Page 1 sur 1
 Sujets similaires
-
» Tuto 1 : Hello World en C dans Code::Blocks
» Tuto 4: pong dans Flash avec crossbridge
» tuto zero: hello world dans visual studio 2008
» Tuto 3 : mettre en route le compileur crossbridge
» Deuxième tutoriel C : pong

Permission de ce forum:Vous ne pouvez pas répondre aux sujets dans ce forum
~~~ TraveL --- Forum jeux HTML ~~~ :: Zone Apprentissage :: Tutoriels Programmation :: Pour les professionnels :: C / C++ /ASSEMBLEUR-
Sauter vers: