Créez votre premier jeu avec Love2D : Le Célèbre Pong ! (Guide pas à pas)
Salut les futurs développeurs et passionnés de jeux vidéo !
Aujourd'hui, sur Arcade Forge, nous allons faire nos premiers pas passionnants dans la création de jeux. Pas besoin d'être un expert en programmation ! Nous allons utiliser Love2D, un framework open-source fantastique qui rend le développement de jeux 2D accessible, simple et incroyablement amusant. Et pour commencer, quoi de mieux que l'indémodable Pong, le jeu qui a lancé l'industrie ?
Nous allons décortiquer un programme Pong complet, ajouter quelques fonctionnalités sympas, et vous donner les clés pour comprendre comment un jeu simple prend vie.
Un Petit Coup d'Histoire : La Naissance de Pong
Avant de plonger dans le code, prenons un instant pour saluer le grand-père des jeux vidéo !
Pong n'est pas juste un jeu, c'est une véritable icône culturelle. Créé par Allan Alcorn pour Atari en 1972, il est considéré comme l'un des tout premiers jeux vidéo à succès commercial. L'anecdote veut qu'Allan Alcorn, jeune ingénieur, ait été chargé par Nolan Bushnell (le co-fondateur d'Atari) de créer un "jeu simple" comme exercice. Bushnell s'attendait à ce que le projet prenne du temps et n'ait pas de suite immédiate.
Pourtant, quand le premier prototype de Pong a été installé dans un bar local, le jeu a connu un succès fou et inattendu. Les pièces de monnaie affluaient tellement que la machine a fini par se bloquer ! C'est ce succès fulgurant qui a convaincu Atari du potentiel énorme des jeux vidéo et a marqué le début d'une industrie que nous connaissons si bien aujourd'hui. Simple dans son concept (deux raquettes, une balle, un score), Pong a prouvé que la simplicité pouvait être la clé de la révolution.
Qu'est-ce que Love2D ?
Imaginez une boîte à outils magique pour créer des jeux. Love2D est exactement cela ! Il utilise le langage de programmation Lua, connu pour sa simplicité et sa légèreté, ce qui en fait un excellent choix pour les débutants. Avec Love2D, vous écrivez votre code, et il s'occupe de la partie complexe (affichage, son, gestion des entrées) pour vous.
Le Programme Pong : Anatomie d'un Jeu Simple
Votre fichier principal s'appelle toujours main.lua. Un jeu Love2D typique est structuré autour de quelques fonctions principales que Love2D appelle automatiquement. Plongeons dans le code !
1. love.load() : Le Setup de Votre Monde
C'est la première et la seule fonction que Love2D exécute au démarrage de votre jeu. C'est ici que vous préparez tout : la taille de votre fenêtre, les positions de départ de vos personnages, et le chargement de vos ressources (images, sons).
function love.load()
love.window.setTitle("Simple Pong Love2D") -- Définit le titre de la fenêtre
love.window.setMode(WINDOW_WIDTH, WINDOW_HEIGHT) -- Définit la taille de la fenêtre
-- Initialisation des raquettes et de la balle avec leurs positions, tailles et scores
player1_paddle = { /* ... */ }
player2_paddle = { /* ... */ }
ball = { /* ... */ }
-- Chargement des sons !
collision_sound = love.audio.newSource("sounds/paddle_hit.wav", "static")
score_sound = love.audio.newSource("sounds/score.wav", "static")
-- NOUVEAU : Variables pour gérer l'état du son
sound_enabled = true
sound_status_text = "Son: ON (appuyez sur 'S' pour couper)"
end Ce que vous apprenez ici :
- Variables globales :
WINDOW_WIDTH,PADDLE_WIDTH, etc. Elles définissent les caractéristiques de votre jeu. - Objets de jeu : Les raquettes (
player1_paddle,player2_paddle) et la balle (ball) sont créées comme des tables Lua (similaire à des objets) pour stocker leurs propriétés (positionx,y, taille, etc.). - Chargement des sons :
love.audio.newSource()charge un fichier audio. Remarquez que les chemins sont relatifs ("sounds/paddle_hit.wav") et non absolus (/sounds/...). C'est important ! - Contrôle du son : Nous ajoutons
sound_enabledpour savoir si le son est activé ou non.
2. love.update(dt) : Le Cœur Battant du Jeu
Cette fonction est appelée à chaque "frame" (chaque image) de votre jeu. C'est ici que toute la logique se déroule : le mouvement des objets, la détection des collisions, la mise à jour des scores, et la gestion des états du jeu (début, en jeu, fin de partie).
Le dt (delta time) est crucial ! Il représente le temps écoulé depuis la dernière mise à jour. En multipliant les vitesses par dt, on assure que le jeu tourne à la même vitesse, quelle que soit la puissance de l'ordinateur.
function love.update(dt)
if GAME_STATE == "play" then
-- Mouvement des raquettes (avec love.keyboard.isDown("touche") pour les pressions continues)
if love.keyboard.isDown("up") then
player1_paddle.y = player1_paddle.y - PADDLE_SPEED * dt
end
-- ... et pour le bas, l'IA de la raquette 2
-- Clamper les raquettes dans les limites de l'écran (elles ne sortent pas)
player1_paddle.y = math.max(0, math.min(WINDOW_HEIGHT - PADDLE_HEIGHT, player1_paddle.y))
-- Mouvement de la balle
ball.x = ball.x + ball.dx * dt
ball.y = ball.y + ball.dy * dt
-- Collisions avec les bords haut/bas et les raquettes
if ball.y <= 0 or ball.y + ball.size >= WINDOW_HEIGHT then
ball.dy = -ball.dy -- Inverse la direction
if sound_enabled then collision_sound:play() end -- NOUVEAU : Joue le son si activé
end
-- ... (logique de collision avec les raquettes, également mise à jour pour le son)
-- Gestion des points marqués
if ball.x < 0 then
player2_paddle.score = player2_paddle.score + 1
if sound_enabled then score_sound:play() end -- NOUVEAU : Joue le son si activé
resetBall()
-- ... (vérification de la victoire)
end
-- ... (idem pour le joueur 1)
end
end Ce que vous apprenez ici :
- Boucle de jeu :
love.updateest le moteur de votre jeu. - Mouvement indépendant du framerate : L'utilisation de
dtest une bonne pratique essentielle. - Gestion des entrées :
love.keyboard.isDown()vérifie si une touche est maintenue enfoncée. - Collisions simples : On utilise des comparaisons de positions (méthode de la "bounding box" ou boîte englobante) pour savoir si la balle touche les murs ou les raquettes.
- Contrôle du son en jeu : En ajoutant
if sound_enabled then, on s'assure que le son ne se déclenche que si l'utilisateur ne l'a pas coupé.
3. love.draw() : Ce que Vous Voyez à l'Écran
Cette fonction est responsable du rendu visuel de votre jeu. Elle est appelée juste après love.update. C'est là que vous dessinez la balle, les raquettes, les scores, et tout le texte.
function love.draw()
love.graphics.setColor(1, 1, 1) -- Définit la couleur de dessin (blanc)
love.graphics.rectangle("fill", ball.x, ball.y, ball.size, ball.size) -- Dessine la balle
-- ... (dessin des raquettes, de la ligne centrale)
-- Affichage du score
love.graphics.setFont(love.graphics.newFont(30)) -- Définit une police de taille 30
love.graphics.printf(player1_paddle.score, WINDOW_WIDTH / 2 - 100, 50, 0, "right")
-- ... (affichage du score du joueur 2)
-- Affichage des messages d'état du jeu (début, fin de partie)
if GAME_STATE == "start" then
love.graphics.setFont(love.graphics.newFont(40))
love.graphics.printf("Appuyez sur ESPACE pour commencer", 0, WINDOW_HEIGHT / 2 - 20, WINDOW_WIDTH, "center")
-- NOUVEAU : Affichage des commandes des raquettes
love.graphics.setFont(love.graphics.newFont(20))
love.graphics.printf("Raquettes: Flèches HAUT/BAS", 0, WINDOW_HEIGHT / 2 + 50, WINDOW_WIDTH, "center")
elseif GAME_STATE == "game_over" then
-- ... (messages de fin de partie)
end
-- NOUVEAU : Affichage de l'état du son
love.graphics.setFont(love.graphics.newFont(16))
love.graphics.printf(sound_status_text, 0, 10, WINDOW_WIDTH - 20, "right")
end Ce que vous apprenez ici :
- Dessin de formes :
love.graphics.rectangle("fill", ...)est votre ami pour dessiner des carrés ou rectangles. - Couleurs :
love.graphics.setColor(r, g, b)change la couleur de dessin (valeurs entre 0 et 1). - Texte :
love.graphics.setFont()etlove.graphics.printf()permettent d'afficher du texte stylisé. - Informations utilisateur : Il est crucial de donner des indications claires au joueur, comme ici pour les commandes et l'état du son.
4. love.keypressed(key) : Interagir avec Votre Jeu
Cette fonction est appelée chaque fois qu'une touche est pressée une seule fois. C'est idéal pour les actions qui ne doivent se produire qu'une fois (démarrer le jeu, changer d'état, ou... couper le son !).
function love.keypressed(key)
if GAME_STATE == "start" and key == "space" then
GAME_STATE = "play" -- Passe en mode jeu
resetBall()
elseif GAME_STATE == "game_over" and key == "r" then
-- ... (logique de redémarrage)
elseif key == "s" then -- NOUVEAU : Touche 'S' pour le son
sound_enabled = not sound_enabled -- Inverse l'état du son (true devient false, false devient true)
if sound_enabled then
love.audio.setVolume(1) -- Remet le volume global à fond
sound_status_text = "Son: ON (appuyez sur 'S' pour couper)"
else
love.audio.setVolume(0) -- Coupe le volume global
sound_status_text = "Son: OFF (appuyez sur 'S' pour activer)"
end
end
end Ce que vous apprenez ici :
- Actions à la pression d'une touche : Différent de
love.keyboard.isDownqui est pour le mouvement continu. - Bascule d'état :
sound_enabled = not sound_enabledest une manière très élégante de faire passer une variable detrueàfalseet vice-versa. - Contrôle du volume global :
love.audio.setVolume(0)coupe complètement tout le son du jeu, etlove.audio.setVolume(1)le remet au maximum.
5. resetBall() : Une Fonction Utilitaire
C'est une fonction que nous avons créée nous-mêmes (elle n'est pas appelée automatiquement par Love2D). Son but est de réinitialiser la balle au centre et de lui donner une nouvelle direction aléatoire après qu'un point ait été marqué.
function resetBall()
ball.x = WINDOW_WIDTH / 2
ball.y = WINDOW_HEIGHT / 2
ball.dx = (math.random(0, 1) == 0 and -1 or 1) * GAME_SPEED -- Direction X aléatoire
ball.dy = (math.random(-1, 1) * 0.5 + 0.5) * GAME_SPEED * (math.random(0,1) == 0 and -1 or 1) -- Direction Y aléatoire
-- ... (ajustements d'angle et de vitesse)
end Ce que vous apprenez ici :
- Fonctions personnalisées : Vous pouvez créer vos propres fonctions pour organiser votre code et éviter les répétitions.
- Aléatoire :
math.random()est parfait pour ajouter un peu d'imprévisibilité à votre jeu.
Le Code Complet et Final
-- main.lua - Un jeu Pong simple pour Love2D
-- Variables globales du jeu
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 600
-- Vitesse du jeu (pour ajuster la difficulté)
GAME_SPEED = 200 -- pixels par seconde
-- Définition des raquettes
PADDLE_WIDTH = 20
PADDLE_HEIGHT = 100
PADDLE_SPEED = 300 -- pixels par seconde
-- Définition de la balle
BALL_SIZE = 15
-- Score
player1_score = 0
player2_score = 0
-- États du jeu
GAME_STATE = "start" -- "start", "play", "game_over"
-- Son (ajoute des fichiers .wav ou .ogg dans un dossier 'sounds')
-- Assurez-vous d'avoir des fichiers audio pour ces noms
collision_sound = love.audio.newSource("sounds/paddle_hit.wav", "static")
score_sound = love.audio.newSource("sounds/score.wav", "static")
-- Nouvelle variable pour gérer l'état du son (true = activé, false = coupé)
sound_enabled = true
-- Nouvelle variable pour le texte de contrôle du son
sound_status_text = "Son: ON (appuyez sur 'S' pour couper)"
-- Fonction love.load() : Initialisation du jeu
function love.load()
love.window.setTitle("Simple Pong Love2D")
love.window.setMode(WINDOW_WIDTH, WINDOW_HEIGHT)
-- Centrer la fenêtre si possible (dépend du système d'exploitation)
-- love.window.setPosition(nil, nil, true)
-- Initialisation des raquettes
player1_paddle = {
x = 50,
y = WINDOW_HEIGHT / 2 - PADDLE_HEIGHT / 2,
width = PADDLE_WIDTH,
height = PADDLE_HEIGHT,
score = 0
}
player2_paddle = {
x = WINDOW_WIDTH - PADDLE_WIDTH - 50,
y = WINDOW_HEIGHT / 2 - PADDLE_HEIGHT / 2,
width = PADDLE_WIDTH,
height = PADDLE_HEIGHT,
score = 0
}
-- Initialisation de la balle
ball = {
x = WINDOW_WIDTH / 2,
y = WINDOW_HEIGHT / 2,
size = BALL_SIZE,
-- Vecteur de vitesse initial (vers la droite ou la gauche aléatoirement)
dx = (math.random(0, 1) == 0 and -1 or 1) * GAME_SPEED,
dy = (math.random(-1, 1) * 0.5 + 0.5) * GAME_SPEED * (math.random(0,1) == 0 and -1 or 1) -- Légèrement aléatoire
}
-- Ajustement de l'angle initial pour ne pas être trop horizontal
if math.abs(ball.dy) < GAME_SPEED * 0.2 then
ball.dy = GAME_SPEED * 0.2 * (math.random(0,1) == 0 and -1 or 1)
end
end
-- Fonction love.update(dt) : Logique de jeu, mise à jour des positions
function love.update(dt)
if GAME_STATE == "play" then
-- Mouvement des raquettes (contrôlé par le joueur 1)
if love.keyboard.isDown("up") then
player1_paddle.y = player1_paddle.y - PADDLE_SPEED * dt
end
if love.keyboard.isDown("down") then
player1_paddle.y = player1_paddle.y + PADDLE_SPEED * dt
end
-- Mouvement de la raquette 2 (IA simple ou joueur 2)
-- IA : suit la balle
if ball.dy < 0 then -- Balle monte
if player2_paddle.y > ball.y then
player2_paddle.y = player2_paddle.y - PADDLE_SPEED * dt
end
else -- Balle descend
if player2_paddle.y < ball.y then
player2_paddle.y = player2_paddle.y + PADDLE_SPEED * dt
end
end
-- Clamper les raquettes dans les limites de l'écran
player1_paddle.y = math.max(0, math.min(WINDOW_HEIGHT - PADDLE_HEIGHT, player1_paddle.y))
player2_paddle.y = math.max(0, math.min(WINDOW_HEIGHT - PADDLE_HEIGHT, player2_paddle.y))
-- Mouvement de la balle
ball.x = ball.x + ball.dx * dt
ball.y = ball.y + ball.dy * dt
-- Collisions avec les bords haut et bas de l'écran
if ball.y <= 0 or ball.y + ball.size >= WINDOW_HEIGHT then
ball.dy = -ball.dy -- Inverser la direction Y
if sound_enabled then collision_sound:play() end -- Joue le son si activé
end
-- Collisions avec les raquettes
-- Collision Player 1
if ball.x <= player1_paddle.x + PADDLE_WIDTH and
ball.x + ball.size >= player1_paddle.x and
ball.y + ball.size >= player1_paddle.y and
ball.y <= player1_paddle.y + PADDLE_HEIGHT then
ball.dx = -ball.dx -- Inverser la direction X
-- Augmenter légèrement la vitesse après chaque frappe
ball.dx = ball.dx * 1.1
ball.dy = ball.dy * 1.1
if sound_enabled then collision_sound:play() end -- Joue le son si activé
-- Ajuster l'angle en fonction de la position de frappe sur la raquette
local hit_position = (ball.y + ball.size / 2) - (player1_paddle.y + PADDLE_HEIGHT / 2)
ball.dy = hit_position * 5 -- Plus le coup est excentré, plus l'angle est grand
-- Collision Player 2
elseif ball.x + ball.size >= player2_paddle.x and
ball.x <= player2_paddle.x + PADDLE_WIDTH and
ball.y + ball.size >= player2_paddle.y and
ball.y <= player2_paddle.y + PADDLE_HEIGHT then
ball.dx = -ball.dx -- Inverser la direction X
-- Augmenter légèrement la vitesse après chaque frappe
ball.dx = ball.dx * 1.1
ball.dy = ball.dy * 1.1
if sound_enabled then collision_sound:play() end -- Joue le son si activé
-- Ajuster l'angle en fonction de la position de frappe sur la raquette
local hit_position = (ball.y + ball.size / 2) - (player2_paddle.y + PADDLE_HEIGHT / 2)
ball.dy = hit_position * 5
end
-- Point marqué
if ball.x < 0 then
player2_paddle.score = player2_paddle.score + 1
if sound_enabled then score_sound:play() end -- Joue le son si activé
resetBall()
if player2_paddle.score >= 5 then
GAME_STATE = "game_over"
end
elseif ball.x > WINDOW_WIDTH then
player1_paddle.score = player1_paddle.score + 1
if sound_enabled then score_sound:play() end -- Joue le son si activé
resetBall()
if player1_paddle.score >= 5 then
GAME_STATE = "game_over"
end
end
elseif GAME_STATE == "start" or GAME_STATE == "game_over" then
-- Attendre l'entrée du joueur pour commencer/recommencer
end
end
-- Fonction love.draw() : Dessin des éléments à l'écran
function love.draw()
-- Dessin de la balle
love.graphics.setColor(1, 1, 1) -- Blanc
love.graphics.rectangle("fill", ball.x, ball.y, ball.size, ball.size)
-- Dessin des raquettes
love.graphics.rectangle("fill", player1_paddle.x, player1_paddle.y, player1_paddle.width, player1_paddle.height)
love.graphics.rectangle("fill", player2_paddle.x, player2_paddle.y, player2_paddle.width, player2_paddle.height)
-- Dessin de la ligne centrale (optionnel)
for i = 0, WINDOW_HEIGHT, 20 do
love.graphics.rectangle("fill", WINDOW_WIDTH / 2 - 2.5, i, 5, 10)
end
-- Affichage du score
love.graphics.setFont(love.graphics.newFont(30)) -- Définit une police de taille 30
love.graphics.printf(player1_paddle.score, WINDOW_WIDTH / 2 - 100, 50, 0, "right")
love.graphics.printf(player2_paddle.score, WINDOW_WIDTH / 2 + 100, 50, 0, "left")
-- Affichage du message d'état du jeu
if GAME_STATE == "start" then
love.graphics.setFont(love.graphics.newFont(40))
love.graphics.printf("Appuyez sur ESPACE pour commencer", 0, WINDOW_HEIGHT / 2 - 20, WINDOW_WIDTH, "center")
-- Affichage de la mention pour les commandes
love.graphics.setFont(love.graphics.newFont(20))
love.graphics.printf("Raquettes: Flèches HAUT/BAS", 0, WINDOW_HEIGHT / 2 + 50, WINDOW_WIDTH, "center")
elseif GAME_STATE == "game_over" then
love.graphics.setFont(love.graphics.newFont(40))
local winner = ""
if player1_paddle.score > player2_paddle.score then
winner = "Joueur 1 gagne !"
else
winner = "Joueur 2 gagne !"
end
love.graphics.printf(winner, 0, WINDOW_HEIGHT / 2 - 40, WINDOW_WIDTH, "center")
love.graphics.printf("Appuyez sur R pour rejouer", 0, WINDOW_HEIGHT / 2 + 20, WINDOW_WIDTH, "center")
end
-- Affichage de l'état du son (en haut à droite, par exemple)
love.graphics.setFont(love.graphics.newFont(16))
love.graphics.printf(sound_status_text, 0, 10, WINDOW_WIDTH - 20, "right")
end
-- Fonction love.keypressed(key) : Gestion des événements clavier
function love.keypressed(key)
if GAME_STATE == "start" and key == "space" then
GAME_STATE = "play"
resetBall()
elseif GAME_STATE == "game_over" and key == "r" then
GAME_STATE = "play"
player1_paddle.score = 0
player2_paddle.score = 0
resetBall()
elseif key == "s" then
-- Bascule l'état du son
sound_enabled = not sound_enabled
if sound_enabled then
love.audio.setVolume(1) -- Rétablit le volume global à 1 (max)
sound_status_text = "Son: ON (appuyez sur 'S' pour couper)"
else
love.audio.setVolume(0) -- Coupe le volume global
sound_status_text = "Son: OFF (appuyez sur 'S' pour activer)"
end
end
end
-- Fonction utilitaire pour réinitialiser la balle après un point
function resetBall()
ball.x = WINDOW_WIDTH / 2
ball.y = WINDOW_HEIGHT / 2
ball.dx = (math.random(0, 1) == 0 and -1 or 1) * GAME_SPEED
ball.dy = (math.random(-1, 1) * 0.5 + 0.5) * GAME_SPEED * (math.random(0,1) == 0 and -1 or 1)
-- Ajustement de l'angle initial pour ne pas être trop horizontal
if math.abs(ball.dy) < GAME_SPEED * 0.2 then
ball.dy = GAME_SPEED * 0.2 * (math.random(0,1) == 0 and -1 or 1)
end
-- Réinitialiser la vitesse de base de la balle après chaque point
ball.dx = math.abs(ball.dx) / (math.abs(ball.dx) / GAME_SPEED) * (ball.dx > 0 and 1 or -1)
ball.dy = math.abs(ball.dy) / (math.abs(ball.dy) / GAME_SPEED) * (ball.dy > 0 and 1 or -1)
end N'hésitez pas à modifier les valeurs numériques, à changer les couleurs, à essayer de nouvelles idées. C'est en faisant qu'on apprend le mieux !
Rendez-vous sur le site officiel de Love2D pour télécharger le framework et consulter la documentation. Et partagez vos créations avec la communauté Arcade Forge !
Bon codage et amusez-vous !
Cliquez ici pour jouer à Pong !
Aucun commentaire:
Enregistrer un commentaire