personalisation du personnage

This commit is contained in:
2025-12-02 19:32:30 +01:00
parent 58ed4774ba
commit 5091fc3d6e
3 changed files with 516 additions and 6 deletions

248
game.js
View File

@ -13,6 +13,9 @@ const finalScoreElement = document.getElementById('finalScore');
const finalLevelElement = document.getElementById('finalLevel');
const overlayRestartBtn = document.getElementById('overlayRestartBtn');
const confirmUsernameBtn = document.getElementById('confirmUsernameBtn');
const gameOverUsernameInput = document.getElementById('gameOverUsername');
const saveScoreBtn = document.getElementById('saveScoreBtn');
const scoreSavedMsg = document.getElementById('scoreSavedMsg');
// === GESTION DU MENU ===
const mainMenu = document.getElementById('mainMenu');
@ -29,6 +32,12 @@ const menuLeaderboard = document.getElementById('menuLeaderboard');
const leaderboardContainer = document.getElementById('leaderboardContainer');
const closeLeaderboardBtn = document.getElementById('closeLeaderboardBtn');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const customizeBtn = document.getElementById('customizeBtn');
const customizeModal = document.getElementById('customizeModal');
const closeCustomizeBtn = document.getElementById('closeCustomizeBtn');
const saveCustomizeBtn = document.getElementById('saveCustomizeBtn');
const playerPreviewCanvas = document.getElementById('playerPreviewCanvas');
const colorOptions = document.querySelectorAll('.color-option');
// Fonction pour vérifier si on est en mode plein écran
function isFullscreen() {
@ -37,6 +46,10 @@ function isFullscreen() {
}
let usernameConfirmed = false;
let gameOverUsername = null;
// Personnalisation du joueur
let playerColor = localStorage.getItem('playerColor') || 'rainbow';
const CELL_SIZE = 25;
const COLS = 30;
@ -240,7 +253,7 @@ let traps = [];
let specialZones = [];
class Pacman {
constructor() {
constructor(color = null) {
this.x = 14;
this.y = 23;
this.direction = 0;
@ -252,6 +265,7 @@ class Pacman {
this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2;
this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2;
this.colorAnimation = 0;
this.color = color || playerColor;
}
update() {
@ -456,8 +470,22 @@ class Pacman {
const rotation = [Math.PI * 1.5, 0, Math.PI * 0.5, Math.PI];
ctx.rotate(rotation[this.direction]);
const hue = (this.colorAnimation * 180 / Math.PI) % 360;
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
// Utiliser la couleur personnalisée
if (this.color === 'rainbow') {
const hue = (this.colorAnimation * 180 / Math.PI) % 360;
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
} else {
const colorMap = {
'yellow': '#ffd700',
'red': '#ff4444',
'blue': '#4444ff',
'green': '#44ff44',
'purple': '#aa44ff',
'orange': '#ff8844',
'pink': '#ff44aa'
};
ctx.fillStyle = colorMap[this.color] || '#ffd700';
}
ctx.beginPath();
@ -1237,7 +1265,7 @@ function checkCollisions() {
gameRunning = false;
statusElement.textContent = 'Game Over !';
showGameOver();
saveScore();
// Le score sera sauvegardé quand l'utilisateur clique sur "Sauvegarder"
} else {
restartCurrentLevel();
}
@ -1954,9 +1982,15 @@ function getScores() {
return scoresJson ? JSON.parse(scoresJson) : [];
}
function saveScore() {
function saveScore(usernameForScore = null) {
let username;
if (!usernameConfirmed) {
// Priorité au nom passé en paramètre, puis au nom du Game Over, puis au nom confirmé, sinon "Anonyme"
if (usernameForScore) {
username = usernameForScore.trim() || 'Anonyme';
} else if (gameOverUsername) {
username = gameOverUsername.trim() || 'Anonyme';
} else if (!usernameConfirmed) {
username = 'Anonyme';
} else {
username = usernameInput.value.trim() || 'Anonyme';
@ -2127,6 +2161,21 @@ usernameInput.addEventListener('keypress', (e) => {
function showGameOver() {
finalScoreElement.textContent = score;
finalLevelElement.textContent = level;
// Réinitialiser le champ de nom et cacher le message
if (gameOverUsernameInput) {
gameOverUsernameInput.value = usernameInput.value.trim() || '';
gameOverUsernameInput.disabled = false;
gameOverUsernameInput.focus();
}
if (saveScoreBtn) {
saveScoreBtn.disabled = false;
}
if (scoreSavedMsg) {
scoreSavedMsg.style.display = 'none';
}
gameOverUsername = null; // Réinitialiser
gameOverlay.classList.add('active');
restartBtn.style.display = 'block';
}
@ -2135,7 +2184,39 @@ function hideGameOver() {
gameOverlay.classList.remove('active');
}
// Sauvegarder le score avec le nom du Game Over
if (saveScoreBtn && gameOverUsernameInput) {
saveScoreBtn.addEventListener('click', () => {
const username = gameOverUsernameInput.value.trim();
if (username.length === 0) {
alert('Veuillez entrer un nom d\'utilisateur !');
gameOverUsernameInput.focus();
return;
}
gameOverUsername = username;
saveScore(username);
// Afficher le message de confirmation
if (scoreSavedMsg) {
scoreSavedMsg.style.display = 'block';
}
// Désactiver le champ et le bouton
gameOverUsernameInput.disabled = true;
saveScoreBtn.disabled = true;
});
// Permettre de sauvegarder avec la touche Entrée
gameOverUsernameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !saveScoreBtn.disabled) {
saveScoreBtn.click();
}
});
}
overlayRestartBtn.addEventListener('click', () => {
hideGameOver();
initGame();
});
@ -2404,6 +2485,161 @@ if (rulesModal) {
});
}
// === GESTION DU MODAL DE PERSONNALISATION ===
let selectedColor = playerColor;
// Ouvrir le modal de personnalisation
if (customizeBtn) {
customizeBtn.addEventListener('click', () => {
if (customizeModal) {
customizeModal.style.display = 'flex';
selectedColor = playerColor;
updateColorSelection();
updatePlayerPreview();
startPreviewAnimation();
}
});
}
// Fermer le modal de personnalisation
if (closeCustomizeBtn) {
closeCustomizeBtn.addEventListener('click', () => {
if (customizeModal) {
customizeModal.style.display = 'none';
stopPreviewAnimation();
}
});
}
// Fermer en cliquant en dehors du modal
if (customizeModal) {
customizeModal.addEventListener('click', (e) => {
if (e.target === customizeModal) {
customizeModal.style.display = 'none';
stopPreviewAnimation();
}
});
}
// Gérer la sélection de couleur
if (colorOptions) {
colorOptions.forEach(option => {
option.addEventListener('click', () => {
selectedColor = option.getAttribute('data-color');
updateColorSelection();
updatePlayerPreview();
});
});
}
// Mettre à jour la sélection visuelle
function updateColorSelection() {
if (colorOptions) {
colorOptions.forEach(option => {
if (option.getAttribute('data-color') === selectedColor) {
option.classList.add('active');
} else {
option.classList.remove('active');
}
});
}
}
// Prévisualisation du joueur
let previewAnimationId = null;
function startPreviewAnimation() {
if (!playerPreviewCanvas) return;
let angle = 0;
let mouthOpen = true;
function animate() {
if (!playerPreviewCanvas) return;
const ctx = playerPreviewCanvas.getContext('2d');
ctx.clearRect(0, 0, playerPreviewCanvas.width, playerPreviewCanvas.height);
// Fond
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, playerPreviewCanvas.width, playerPreviewCanvas.height);
// Dessiner le joueur au centre
ctx.save();
ctx.translate(playerPreviewCanvas.width / 2, playerPreviewCanvas.height / 2);
// Couleur
if (selectedColor === 'rainbow') {
angle += 0.05;
const hue = (angle * 180 / Math.PI) % 360;
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
} else {
const colorMap = {
'yellow': '#ffd700',
'red': '#ff4444',
'blue': '#4444ff',
'green': '#44ff44',
'purple': '#aa44ff',
'orange': '#ff8844',
'pink': '#ff44aa'
};
ctx.fillStyle = colorMap[selectedColor] || '#ffd700';
}
// Animation de la bouche
if (Math.floor(angle * 5) % 2 === 0) {
mouthOpen = !mouthOpen;
}
ctx.beginPath();
const radius = 40;
if (mouthOpen) {
ctx.arc(0, 0, radius, 0.2, Math.PI * 2 - 0.2);
} else {
ctx.arc(0, 0, radius, 0, Math.PI * 2);
}
ctx.lineTo(0, 0);
ctx.fill();
ctx.restore();
previewAnimationId = requestAnimationFrame(animate);
}
animate();
}
function stopPreviewAnimation() {
if (previewAnimationId) {
cancelAnimationFrame(previewAnimationId);
previewAnimationId = null;
}
}
function updatePlayerPreview() {
// L'animation se mettra à jour automatiquement car elle utilise selectedColor
}
// Sauvegarder la personnalisation
if (saveCustomizeBtn) {
saveCustomizeBtn.addEventListener('click', () => {
playerColor = selectedColor;
localStorage.setItem('playerColor', playerColor);
// Mettre à jour le joueur actuel si le jeu est en cours
if (pacman) {
pacman.color = playerColor;
}
// Fermer le modal
if (customizeModal) {
customizeModal.style.display = 'none';
stopPreviewAnimation();
}
// Message de confirmation
alert('Personnalisation sauvegardée !');
});
}
// Fermer le classement dans le jeu
if (closeLeaderboardBtn) {
closeLeaderboardBtn.addEventListener('click', () => {

View File

@ -21,6 +21,9 @@
<button class="menu-btn" id="playBtn">
<span class="btn-icon"></span> JOUER
</button>
<button class="menu-btn" id="customizeBtn">
<span class="btn-icon">🎨</span> PERSONNALISER
</button>
<button class="menu-btn" id="scoresBtn">
<span class="btn-icon">🏆</span> CLASSEMENT
</button>
@ -87,6 +90,12 @@
<h2 class="game-over-title">GAME OVER</h2>
<p class="final-score">Score Final: <span id="finalScore">0</span></p>
<p class="final-level">Niveau Atteint: <span id="finalLevel">1</span></p>
<div class="game-over-username-section">
<label for="gameOverUsername">Nom pour le classement:</label>
<input type="text" id="gameOverUsername" placeholder="Entrez votre nom" maxlength="15">
<button id="saveScoreBtn" class="save-score-btn">💾 Sauvegarder</button>
</div>
<p class="score-saved-msg" id="scoreSavedMsg" style="display: none; color: #00ff00; font-size: 0.7em; margin-top: 10px;">Score sauvegardé !</p>
<button id="overlayRestartBtn">REJOUER</button>
</div>
</div>
@ -222,6 +231,60 @@
</div>
</div>
<!-- MODAL PERSONNALISATION -->
<div class="customize-modal" id="customizeModal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h2>🎨 PERSONNALISER VOTRE JOUEUR</h2>
<button class="close-modal-btn" id="closeCustomizeBtn">×</button>
</div>
<div class="customize-content">
<div class="customize-section">
<h3>Couleur du joueur</h3>
<div class="color-options">
<div class="color-option" data-color="yellow" data-name="Jaune">
<div class="color-preview" style="background: #ffd700;"></div>
<span>Jaune (Classique)</span>
</div>
<div class="color-option" data-color="red" data-name="Rouge">
<div class="color-preview" style="background: #ff4444;"></div>
<span>Rouge</span>
</div>
<div class="color-option" data-color="blue" data-name="Bleu">
<div class="color-preview" style="background: #4444ff;"></div>
<span>Bleu</span>
</div>
<div class="color-option" data-color="green" data-name="Vert">
<div class="color-preview" style="background: #44ff44;"></div>
<span>Vert</span>
</div>
<div class="color-option" data-color="purple" data-name="Violet">
<div class="color-preview" style="background: #aa44ff;"></div>
<span>Violet</span>
</div>
<div class="color-option" data-color="orange" data-name="Orange">
<div class="color-preview" style="background: #ff8844;"></div>
<span>Orange</span>
</div>
<div class="color-option active" data-color="rainbow" data-name="Arc-en-ciel">
<div class="color-preview rainbow-preview"></div>
<span>Arc-en-ciel (Animé)</span>
</div>
<div class="color-option" data-color="pink" data-name="Rose">
<div class="color-preview" style="background: #ff44aa;"></div>
<span>Rose</span>
</div>
</div>
</div>
<div class="customize-preview">
<h3>Aperçu</h3>
<canvas id="playerPreviewCanvas" width="200" height="200"></canvas>
</div>
<button class="save-customize-btn" id="saveCustomizeBtn">💾 Sauvegarder</button>
</div>
</div>
</div>
<script src="game.js"></script>
</body>
</html>

211
style.css
View File

@ -1123,6 +1123,71 @@ h1 {
transform: translateY(0) scale(0.98);
}
.game-over-username-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin: 20px 0;
}
.game-over-username-section label {
font-size: 0.7em;
color: #ffd700;
font-weight: bold;
}
#gameOverUsername {
padding: 10px 15px;
font-size: 0.8em;
font-family: 'Press Start 2P', cursive;
border: 2px solid #ffd700;
border-radius: 10px;
background: rgba(0, 0, 0, 0.7);
color: #fff;
outline: none;
max-width: 250px;
width: 100%;
text-align: center;
transition: all 0.3s;
}
#gameOverUsername:focus {
border-color: #ffed4e;
box-shadow: 0 0 15px rgba(255, 215, 0, 0.6);
background: rgba(0, 0, 0, 0.9);
}
.save-score-btn {
padding: 10px 20px;
font-size: 0.7em;
font-family: 'Press Start 2P', cursive;
background: linear-gradient(180deg, #00ff00 0%, #00cc00 100%);
color: #000;
border: 2px solid #00ff00;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 10px rgba(0, 255, 0, 0.3);
}
.save-score-btn:hover {
background: linear-gradient(180deg, #00ff88 0%, #00ff00 100%);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(0, 255, 0, 0.5);
}
.save-score-btn:active {
transform: translateY(0) scale(0.98);
}
.save-score-btn:disabled {
background: rgba(100, 100, 100, 0.5);
border-color: #666;
cursor: not-allowed;
opacity: 0.5;
}
/* === CONTROLES TACTILES MOBILE === */
.mobile-controls {
display: none;
@ -1268,3 +1333,149 @@ footer {
font-size: 0.9em;
}
}
/* === MODAL PERSONNALISATION === */
.customize-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
z-index: 1000;
justify-content: center;
align-items: center;
animation: fadeIn 0.3s ease-out;
}
.customize-modal.active {
display: flex;
}
.customize-content {
display: flex;
flex-direction: column;
gap: 30px;
max-width: 800px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.customize-section {
background: rgba(0, 0, 0, 0.6);
padding: 20px;
border-radius: 15px;
border: 2px solid #ffd700;
}
.customize-section h3 {
color: #ffd700;
font-size: 1em;
margin-bottom: 15px;
text-align: center;
}
.color-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin-top: 15px;
}
.color-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid rgba(255, 215, 0, 0.3);
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
}
.color-option:hover {
background: rgba(255, 215, 0, 0.1);
border-color: #ffd700;
transform: scale(1.05);
}
.color-option.active {
background: rgba(255, 215, 0, 0.2);
border-color: #ffd700;
box-shadow: 0 0 15px rgba(255, 215, 0, 0.5);
}
.color-preview {
width: 60px;
height: 60px;
border-radius: 50%;
border: 3px solid #fff;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.rainbow-preview {
background: linear-gradient(45deg, #ff0000, #ff8800, #ffff00, #00ff00, #0088ff, #0000ff, #8800ff);
animation: rainbowRotate 3s linear infinite;
}
@keyframes rainbowRotate {
0% { filter: hue-rotate(0deg); }
100% { filter: hue-rotate(360deg); }
}
.color-option span {
font-size: 0.7em;
color: #fff;
text-align: center;
}
.customize-preview {
background: rgba(0, 0, 0, 0.6);
padding: 20px;
border-radius: 15px;
border: 2px solid #ffd700;
display: flex;
flex-direction: column;
align-items: center;
}
.customize-preview h3 {
color: #ffd700;
font-size: 1em;
margin-bottom: 15px;
}
#playerPreviewCanvas {
border: 3px solid #ffd700;
border-radius: 10px;
background: #000;
box-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
}
.save-customize-btn {
padding: 15px 30px;
font-size: 0.9em;
font-family: 'Press Start 2P', cursive;
background: linear-gradient(180deg, #00ff00 0%, #00cc00 100%);
color: #000;
border: 2px solid #00ff00;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 4px 10px rgba(0, 255, 0, 0.3);
align-self: center;
}
.save-customize-btn:hover {
background: linear-gradient(180deg, #00ff88 0%, #00ff00 100%);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(0, 255, 0, 0.5);
}
.save-customize-btn:active {
transform: translateY(0) scale(0.98);
}