En aquest tema, aprendrem a construir un joc simple utilitzant OpenGL. Aquest projecte ens permetrà aplicar molts dels conceptes apresos en els mòduls anteriors, com ara la renderització de formes, textures, il·luminació i més. El joc que crearem serà un senzill joc de "pong", on dos jugadors controlen pales per colpejar una pilota.
Objectius del Tema
- Aplicar conceptes bàsics i avançats d'OpenGL en un projecte pràctic.
- Aprendre a estructurar un projecte de joc.
- Implementar la lògica del joc i la interacció amb l'usuari.
- Gestionar la renderització i l'actualització de l'estat del joc.
Requisits Previs
- Coneixements bàsics d'OpenGL.
- Familiaritat amb la programació en C++ (o el llenguatge que estiguis utilitzant).
- Entorn de desenvolupament configurat per a OpenGL.
Passos per Construir el Joc
- Configuració del Projecte
Abans de començar a codificar, assegura't que el teu entorn de desenvolupament estigui configurat correctament per a OpenGL. Si no ho has fet, revisa el mòdul "Configurar el Teu Entorn de Desenvolupament".
- Estructura del Projecte
Organitzarem el nostre projecte en diversos fitxers per mantenir el codi net i manejable.
/pong ├── src │ ├── main.cpp │ ├── game.cpp │ ├── game.h │ ├── paddle.cpp │ ├── paddle.h │ ├── ball.cpp │ ├── ball.h ├── shaders │ ├── vertex_shader.glsl │ ├── fragment_shader.glsl ├── textures │ ├── paddle.png │ ├── ball.png ├── CMakeLists.txt
- Implementació del Joc
3.1. main.cpp
Aquest fitxer contindrà el punt d'entrada del nostre programa i la configuració inicial d'OpenGL.
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "game.h"
int main() {
    // Inicialitzar GLFW
    if (!glfwInit()) {
        return -1;
    }
    // Crear una finestra
    GLFWwindow* window = glfwCreateWindow(800, 600, "Pong", NULL, NULL);
    if (!window) {
        glfwTerminate();
        return -1;
    }
    // Establir el context d'OpenGL
    glfwMakeContextCurrent(window);
    // Inicialitzar GLEW
    if (glewInit() != GLEW_OK) {
        return -1;
    }
    // Configurar el joc
    Game game;
    game.init();
    // Bucle principal
    while (!glfwWindowShouldClose(window)) {
        // Actualitzar l'estat del joc
        game.update();
        // Renderitzar el joc
        game.render();
        // Intercanviar els buffers
        glfwSwapBuffers(window);
        // Processar els esdeveniments
        glfwPollEvents();
    }
    // Alliberar recursos
    glfwTerminate();
    return 0;
}3.2. game.h i game.cpp
Aquestes classes gestionaran la lògica principal del joc.
game.h
#ifndef GAME_H
#define GAME_H
class Game {
public:
    void init();
    void update();
    void render();
};
#endifgame.cpp
#include "game.h"
#include "paddle.h"
#include "ball.h"
// Objectes del joc
Paddle player1, player2;
Ball ball;
void Game::init() {
    // Inicialitzar objectes del joc
    player1.init(50, 300);
    player2.init(750, 300);
    ball.init(400, 300);
}
void Game::update() {
    // Actualitzar l'estat del joc
    player1.update();
    player2.update();
    ball.update();
}
void Game::render() {
    // Esborrar la pantalla
    glClear(GL_COLOR_BUFFER_BIT);
    // Renderitzar objectes del joc
    player1.render();
    player2.render();
    ball.render();
}3.3. paddle.h i paddle.cpp
Aquestes classes gestionaran les pales del joc.
paddle.h
#ifndef PADDLE_H
#define PADDLE_H
class Paddle {
public:
    void init(float x, float y);
    void update();
    void render();
private:
    float x, y;
};
#endifpaddle.cpp
#include "paddle.h"
#include <GL/glew.h>
void Paddle::init(float x, float y) {
    this->x = x;
    this->y = y;
}
void Paddle::update() {
    // Actualitzar la posició de la pala
    // (Afegeix la lògica de moviment aquí)
}
void Paddle::render() {
    // Renderitzar la pala
    glBegin(GL_QUADS);
    glVertex2f(x - 10, y - 50);
    glVertex2f(x + 10, y - 50);
    glVertex2f(x + 10, y + 50);
    glVertex2f(x - 10, y + 50);
    glEnd();
}3.4. ball.h i ball.cpp
Aquestes classes gestionaran la pilota del joc.
ball.h
#ifndef BALL_H
#define BALL_H
class Ball {
public:
    void init(float x, float y);
    void update();
    void render();
private:
    float x, y;
    float vx, vy;
};
#endifball.cpp
#include "ball.h"
#include <GL/glew.h>
void Ball::init(float x, float y) {
    this->x = x;
    this->y = y;
    this->vx = 0.1f;
    this->vy = 0.1f;
}
void Ball::update() {
    // Actualitzar la posició de la pilota
    x += vx;
    y += vy;
    // Rebotar en les parets
    if (y <= 0 || y >= 600) {
        vy = -vy;
    }
}
void Ball::render() {
    // Renderitzar la pilota
    glBegin(GL_QUADS);
    glVertex2f(x - 10, y - 10);
    glVertex2f(x + 10, y - 10);
    glVertex2f(x + 10, y + 10);
    glVertex2f(x - 10, y + 10);
    glEnd();
}
- Afegir Interacció amb l'Usuari
Per permetre als jugadors controlar les pales, afegirem la lògica de moviment en la funció update de la classe Paddle.
paddle.cpp
#include "paddle.h"
#include <GL/glew.h>
#include <GLFW/glfw3.h>
void Paddle::update() {
    // Controlar la pala amb les tecles
    if (glfwGetKey(glfwGetCurrentContext(), GLFW_KEY_W) == GLFW_PRESS) {
        y += 0.1f;
    }
    if (glfwGetKey(glfwGetCurrentContext(), GLFW_KEY_S) == GLFW_PRESS) {
        y -= 0.1f;
    }
}
- Afegir Col·lisions
Per fer el joc més interessant, afegirem col·lisions entre la pilota i les pales.
ball.cpp
#include "ball.h"
#include "paddle.h"
#include <GL/glew.h>
extern Paddle player1, player2;
void Ball::update() {
    // Actualitzar la posició de la pilota
    x += vx;
    y += vy;
    // Rebotar en les parets
    if (y <= 0 || y >= 600) {
        vy = -vy;
    }
    // Col·lisions amb les pales
    if ((x <= player1.x + 10 && y >= player1.y - 50 && y <= player1.y + 50) ||
        (x >= player2.x - 10 && y >= player2.y - 50 && y <= player2.y + 50)) {
        vx = -vx;
    }
}
- Afegir Textures i Shaders
Per millorar l'aparença del joc, podem afegir textures i shaders. Això es pot fer seguint els passos descrits en els mòduls anteriors sobre textures i shaders.
- Conclusió
En aquest tema, hem creat un joc simple utilitzant OpenGL. Hem après a estructurar un projecte de joc, implementar la lògica del joc, gestionar la renderització i afegir interacció amb l'usuari. Aquest projecte ens ha permès aplicar molts dels conceptes apresos en els mòduls anteriors i ens ha proporcionat una base sòlida per a projectes més complexos en el futur.
Exercicis Pràctics
- Afegir Puntuació: Implementa un sistema de puntuació que incrementi cada vegada que un jugador falla en colpejar la pilota.
- Millorar la Lògica de Moviment: Ajusta la velocitat de la pilota i les pales per fer el joc més equilibrat.
- Afegir Sons: Integra efectes de so per a col·lisions i punts utilitzant una llibreria d'àudio com OpenAL.
Solucions dels Exercicis
- Afegir Puntuació
// game.h
class Game {
public:
    void init();
    void update();
    void render();
private:
    int score1, score2;
};
// game.cpp
void Game::init() {
    score1 = 0;
    score2 = 0;
    // Inicialitzar objectes del joc
    player1.init(50, 300);
    player2.init(750, 300);
    ball.init(400, 300);
}
void Game::update() {
    // Actualitzar l'estat del joc
    player1.update();
    player2.update();
    ball.update();
    // Comprovar si la pilota surt de la pantalla
    if (ball.x <= 0) {
        score2++;
        ball.init(400, 300);
    } else if (ball.x >= 800) {
        score1++;
        ball.init(400, 300);
    }
}
void Game::render() {
    // Esborrar la pantalla
    glClear(GL_COLOR_BUFFER_BIT);
    // Renderitzar objectes del joc
    player1.render();
    player2.render();
    ball.render();
    // Renderitzar la puntuació
    // (Afegeix el codi per renderitzar el text aquí)
}
- Millorar la Lògica de Moviment
// paddle.cpp
void Paddle::update() {
    // Controlar la pala amb les tecles
    if (glfwGetKey(glfwGetCurrentContext(), GLFW_KEY_W) == GLFW_PRESS) {
        y += 0.2f;
    }
    if (glfwGetKey(glfwGetCurrentContext(), GLFW_KEY_S) == GLFW_PRESS) {
        y -= 0.2f;
    }
}
// ball.cpp
void Ball::init(float x, float y) {
    this->x = x;
    this->y = y;
    this->vx = 0.15f;
    this->vy = 0.15f;
}
- Afegir Sons
Per afegir sons, pots utilitzar una llibreria com OpenAL. Aquí tens un exemple bàsic de com integrar OpenAL per reproduir un so de col·lisió.
#include <AL/al.h>
#include <AL/alc.h>
// Inicialitzar OpenAL
ALCdevice* device = alcOpenDevice(NULL);
ALCcontext* context = alcCreateContext(device, NULL);
alcMakeContextCurrent(context);
// Carregar el so
ALuint buffer;
alGenBuffers(1, &buffer);
// (Carrega el fitxer de so en el buffer aquí)
// Crear una font de so
ALuint source;
alGenSources(1, &source);
alSourcei(source, AL_BUFFER, buffer);
// Reproduir el so en col·lisió
if ((x <= player1.x + 10 && y >= player1.y - 50 && y <= player1.y + 50) ||
    (x >= player2.x - 10 && y >= player2.y - 50 && y <= player2.y + 50)) {
    vx = -vx;
    alSourcePlay(source);
}Amb aquests exercicis i solucions, hauràs millorat el teu joc de pong i hauràs après a aplicar conceptes avançats d'OpenGL en un projecte pràctic.
Curs de Programació OpenGL
Mòdul 1: Introducció a OpenGL
- Què és OpenGL?
- Configurar el Teu Entorn de Desenvolupament
- Crear el Teu Primer Programa OpenGL
- Entendre el Pipeline d'OpenGL
Mòdul 2: Renderització Bàsica
- Dibuixar Formes Bàsiques
- Entendre les Coordenades i les Transformacions
- Coloració i Ombrejat
- Ús de Buffers
Mòdul 3: Tècniques de Renderització Intermèdies
- Textures i Mapeig de Textures
- Il·luminació i Materials
- Barreja i Transparència
- Prova de Profunditat i Prova de Plantilla
Mòdul 4: Tècniques de Renderització Avançades
Mòdul 5: Optimització del Rendiment
- Optimitzar el Codi OpenGL
- Ús d'Objectes de Matriu de Vèrtexs (VAOs)
- Gestió Eficient de la Memòria
- Perfilat i Depuració
