Ce tutoreil présente le développement d'un jeu de tir d'avions classique en utilisant le framework Qt. Il est destiné aux débutants et se concentre sur la fourniture du code source commenté pour faciliter la compréhension. La version utilisée est Qt 6.7.2 avec le modèle Qt Widget Application et le système de build qmake. L'approche privilégie la création d'éléments graphiques et la gestion des interactions directement dans le code C++ plutôt qu'à travers des fichiers UI.
Configuration de la scène de jeu
Le fichier main.cpp initialise l'application. Il configure l'icône de l'application, crée l'objet joueur (Player), initialise une scène graphique (QGraphicsScene), définit ses dimensions et son image de fond. Le joueur, l'interface de score et de santé sont ajoutés à la scène. Une vue graphique (QGraphicsView) est ensuite créée pour afficher la scène, avec une taille fixe et les barres de défilement désactivées. Enfin, une musique de fond est chargée et lancée.
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QIcon>
#include <QMediaPlayer>
#include <QAudioOutput>
#include <QUrl>
#include "GameSetting.h"
#include "Player.h"
#include "Score.h"
#include "Health.h"
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
app.setApplicationDisplayName("FighterGame v1.0");
app.setWindowIcon(QIcon(":/hero/imagesoundes/hero1.png"));
Player* player = new Player;
QGraphicsScene* scene = new QGraphicsScene;
scene->addItem(player);
scene->setSceneRect(0, 0, GameSetting::SceneWidth, GameSetting::SceneHeight);
scene->setBackgroundBrush(QImage(":/images/imagesoundes/mianimages.png"));
// Gestion du score et de la santé
scene->addItem(&Score::getInstance());
scene->addItem(&Health::getInstance());
scene->setStickyFocus(true);
QGraphicsView view(scene);
view.setFixedSize(GameSetting::SceneWidth, GameSetting::SceneHeight);
view.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
view.show();
// Musique de fond
QMediaPlayer bgMusic;
QAudioOutput audioOutput;
bgMusic.setAudioOutput(&audioOutput);
bgMusic.setSource(QUrl("qrc:/yinxia/SOUNDS/mian_sounds.wav"));
bgMusic.play();
return app.exec();
}
Les constantes globlaes comme la taille de la scène et la vitesse de déplacement sont définies dans une classe GameSetting.
Création du joueur
La classe Player hérite de QObject et QGraphicsPixmapItem pour gérer le joueur. Elle est initialisée avec une image, mise à l'échelle et positionnée en bas de l'écran. Le joueur peut obtenir le focus pour répondre aux événements clavier. Un timer est démarré pour la génération périodique d'ennemis et la vérification de l'état du jeu.
Player.h
#ifndef PLAYER_H
#define PLAYER_H
#include <QObject>
#include <QGraphicsPixmapItem>
#include <QKeyEvent>
#include <QSoundEffect>
class QGraphicsTextItem;
class Player : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
Player(QGraphicsItem* parent = nullptr);
protected:
void keyPressEvent(QKeyEvent* event) override;
void timerEvent(QTimerEvent* event) override;
private:
void spawnEnemy();
void handleGameOver();
bool m_isPlaying = true;
QSoundEffect m_bulletSound;
QGraphicsTextItem* m_messageItem = nullptr;
};
#endif // PLAYER_H
Player.cpp
#include "Player.h"
#include "GameSetting.h"
#include "Enemy.h"
#include "Bullet.h"
#include "Health.h"
#include "Score.h"
#include <QPixmap>
#include <QKeyEvent>
#include <QGraphicsScene>
#include <QFontMetrics>
#include <QGraphicsTextItem>
#include <QFont>
using namespace GameSetting;
Player::Player(QGraphicsItem* parent) : QGraphicsPixmapItem(parent) {
m_bulletSound.setSource(QUrl("qrc:/yinxia/SOUNDS/bomb.wav"));
setPixmap(QPixmap(":/hero/imagesoundes/hero1.png"));
setScale(PlayerScale);
// Positionnement initial du joueur
setPos(SceneWidth / 2 - boundingRect().width() * PlayerScale / 2, SceneHeight - boundingRect().height() * PlayerScale);
setFlag(QGraphicsItem::ItemIsFocusable);
setFocus();
startTimer(500); // Timer pour la génération d'ennemis
}
void Player::keyPressEvent(QKeyEvent* event) {
switch (event->key()) {
case Qt::Key_A: // Gauche
if (pos().x() > 0)
setPos(x() - PlayerMoveSpeed, y());
break;
case Qt::Key_D: // Droite
if (pos().x() < SceneWidth - boundingRect().width() * PlayerScale)
setPos(x() + PlayerMoveSpeed, y());
break;
case Qt::Key_S: // Bas
if (pos().y() < SceneHeight - boundingRect().height() * PlayerScale)
setPos(x(), y() + PlayerMoveSpeed);
break;
case Qt::Key_W: // Haut
if (pos().y() > 0)
setPos(x(), y() - PlayerMoveSpeed);
break;
case Qt::Key_Space: // Tirer
if (m_isPlaying) {
m_bulletSound.play();
Bullet* bullet = new Bullet;
// Calculer la position de tir au centre du joueur
int bulletX = x() + boundingRect().width() * PlayerScale / 2 - bullet->boundingRect().width() * BulletScale / 2;
bullet->setPos(bulletX, y());
scene()->addItem(bullet);
}
break;
case Qt::Key_H: // Redémarrer le jeu
if (!m_isPlaying) {
m_isPlaying = true;
Health::getInstance().reset();
Score::getInstance().reset();
if (m_messageItem) m_messageItem->hide();
}
break;
}
}
void Player::spawnEnemy() {
Enemy* enemy = new Enemy;
scene()->addItem(enemy);
}
void Player::handleGameOver() {
m_isPlaying = false;
// Supprimer tous les ennemis existants
for (auto item : scene()->items()) {
if (typeid(*item) == typeid(Enemy)) {
scene()->removeItem(item);
delete item;
}
}
// Afficher le message de fin de partie
if (!m_messageItem) {
m_messageItem = new QGraphicsTextItem;
scene()->addItem(m_messageItem);
QString message("Game Over! Appuyez sur H pour rejouer!");
m_messageItem->setPlainText(message);
m_messageItem->setDefaultTextColor(Qt::black);
QFont font("Courier New", FontSize * 1.5, QFont::Bold);
m_messageItem->setFont(font);
QFontMetrics fm(font);
int msgWidth = fm.horizontalAdvance(message);
m_messageItem->setPos(SceneWidth / 2 - msgWidth / 2, SceneHeight / 2);
} else {
m_messageItem->show();
}
}
void Player::timerEvent(QTimerEvent*) {
if (m_isPlaying) {
spawnEnemy(); // Générer un ennemi périodiquement
}
if (Health::getInstance().getHealth() <= 0) {
handleGameOver();
}
}
Ajout des ennemis
La classe Enemy, similaire à Player, hérite de QObject et QGraphicsPixmapItem. Elle utilise QRandomGenerator pour apparaître à une position horizontale aléatoire. Un timer gère son déplacement vers le bas et la détection des collisions avec le joueur ou les projectiles.
Enemy.h
#ifndef ENEMY_H
#define ENEMY_H
#include <QObject>
#include <QGraphicsPixmapItem>
class Enemy : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
Enemy(QGraphicsItem* parent = nullptr);
protected:
void timerEvent(QTimerEvent* event) override;
};
#endif // ENEMY_H
Enemy.cpp
#include "Enemy.h"
#include "GameSetting.h"
#include "Player.h"
#include "Bullet.h"
#include "Score.h"
#include "Health.h"
#include <QPixmap>
#include <QRandomGenerator>
#include <QGraphicsScene>
#include <QList>
#include <typeinfo> // Pour typeid
Enemy::Enemy(QGraphicsItem* parent) : QGraphicsPixmapItem(parent) {
setPixmap(QPixmap(":/images/imagesoundes/img-plane_2.png"));
setScale(GameSetting::EnemyScale);
// Positionnement horizontal aléatoire
int maxPosX = GameSetting::SceneWidth - boundingRect().width() * GameSetting::EnemyScale;
int randomPosX = QRandomGenerator::global()->bounded(maxPosX);
setPos(randomPosX, 0);
startTimer(GameSetting::EnemyTimer); // Timer pour le mouvement et la gestion
}
void Enemy::timerEvent(QTimerEvent*) {
QList<QGraphicsItem*> collidingItemsList = collidingItems();
for (auto item : collidingItemsList) {
// Collision avec le joueur
if (typeid(*item) == typeid(Player)) {
Health::getInstance().decrease();
scene()->removeItem(this);
delete this;
return;
}
// Collision avec un projectile
if (typeid(*item) == typeid(Bullet)) {
Score::getInstance().increase();
scene()->removeItem(item);
delete item;
scene()->removeItem(this);
delete this;
return;
}
}
// Mouvement de l'ennemi vers le bas
setPos(x(), y() + GameSetting::EnemySpeed);
// Si l'ennemi sort de l'écran, le supprimer
if (y() > GameSetting::SceneHeight) {
scene()->removeItem(this);
delete this;
}
}
Ajout des projectiles
La classe Bullet gère les projectiles tirés par le joueur. Elle hérite de QObject et QGraphicsPixmapItem. Les projectiles se déplacent vers le haut et sont supprimés lorsqu'ils sortent de l'écran.
Bullet.h
#ifndef BULLET_H
#define BULLET_H
#include <QObject>
#include <QGraphicsPixmapItem>
class Bullet : public QObject, public QGraphicsPixmapItem {
Q_OBJECT
public:
Bullet(QGraphicsItem* parent = nullptr);
protected:
void timerEvent(QTimerEvent* event) override;
};
#endif // BULLET_H
Bullet.cpp
#include "Bullet.h"
#include "GameSetting.h"
#include <QPixmap>
#include <QGraphicsScene>
Bullet::Bullet(QGraphicsItem* parent) : QGraphicsPixmapItem(parent) {
setPixmap(QPixmap(":/BulletScene/BulletScene/bomb46.png"));
setScale(GameSetting::BulletScale);
startTimer(GameSetting::BulletTimer); // Timer pour le mouvement
}
void Bullet::timerEvent(QTimerEvent*) {
// Mouvement vers le haut
setPos(x(), y() - GameSetting::BulletSpeed);
// Supprimer le projectile s'il sort de l'écran par le haut
if (y() + boundingRect().height() < 0) {
scene()->removeItem(this);
delete this;
}
}
Interface utilisateur : Score et Santé
Les classes Score et Health sont implémentées comme des singleton pour garantir une instance unique. Elles héritent de QGraphicsTextItem et affichent respectivement le score du joueur et ses points de vie sous forme de texte. Les méthodes increase(), decrease() et reset() permettent de mettre à jour ces indicateurs.
Score.h
#ifndef SCORE_H
#define SCORE_H
#include <QGraphicsTextItem>
#include <QObject>
class Score : public QGraphicsTextItem {
Q_OBJECT
public:
static Score& getInstance() {
static Score instance;
return instance;
}
void increase();
void reset();
private:
Score(QGraphicsItem* parent = nullptr);
int m_score = 0;
};
#endif // SCORE_H
Score.cpp
#include "Score.h"
#include "GameSetting.h"
#include <QFont>
#include <QString>
Score::Score(QGraphicsItem* parent) : QGraphicsTextItem(parent) {
reset();
}
void Score::reset() {
m_score = 0;
setPlainText("Score: " + QString::number(m_score));
setDefaultTextColor(Qt::green);
setFont(QFont("Courier New", GameSetting::FontSize, QFont::Bold));
}
void Score::increase() {
++m_score;
setPlainText("Score: " + QString::number(m_score));
}
Health.h
#ifndef HEALTH_H
#define HEALTH_H
#include <QGraphicsTextItem>
#include <QObject>
#include "GameSetting.h"
class Health : public QGraphicsTextItem {
Q_OBJECT
public:
static Health& getInstance() {
static Health instance;
return instance;
}
int getHealth() const { return m_health; }
void decrease();
void reset();
private:
Health(QGraphicsItem* parent = nullptr);
int m_health = GameSetting::HealthStart;
};
#endif // HEALTH_H
Health.cpp
#include "Health.h"
#include "GameSetting.h"
#include <QFont>
#include <QString>
Health::Health(QGraphicsItem* parent) : QGraphicsTextItem(parent) {
reset();
}
void Health::decrease() {
--m_health;
setPlainText("Health: " + QString::number(m_health));
}
void Health::reset() {
m_health = GameSetting::HealthStart;
setPlainText("Health: " + QString::number(m_health));
setDefaultTextColor(Qt::blue);
setFont(QFont("Courier", GameSetting::FontSize, QFont::Bold));
// Positionner la santé légèrement en dessous du score
setPos(x(), GameSetting::FontSize * 2);
}
Fin de partie et redémarrage
La logique de fin de partie et de redémarrrage est gérée dans la classe Player. Lorsque la santé atteint zéro, le jeu est marqué comme terminé, tous les ennemis sont supprimés, et un message de fin de partie s'affiche. Appuyer sur la touche 'H' réinitialise le jeu.
Ajout d'effets sonores
Les effets sonores pour les tirs et les explosions sont gérés par la classe QSoundEffect, intégrée dans les classes Player et Enemy pour les tirs et les collisions respectivement. La musique de fond est gérée dans main.cpp avec QMediaPlayer.