texture zebre plus sourie menu
This commit is contained in:
531
README.md
531
README.md
@ -1,4 +1,4 @@
|
||||
# 🎮 Oulvic - Jeu de type Pac-Man
|
||||
# 🎮 OULVIC - Jeu de type Pac-Man
|
||||
|
||||
Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul**.
|
||||
|
||||
@ -13,19 +13,25 @@ Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul
|
||||
5. [Comment jouer](#comment-jouer)
|
||||
6. [Architecture du code](#architecture-du-code)
|
||||
7. [Documentation technique](#documentation-technique)
|
||||
8. [Systèmes avancés](#systèmes-avancés)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Description
|
||||
|
||||
**Oulvic** est un clone de Pac-Man développé entièrement en HTML5, CSS3 et JavaScript vanilla. Le jeu utilise l'élément `<canvas>` pour le rendu graphique et propose une expérience de jeu complète avec :
|
||||
**OULVIC** est un clone de Pac-Man développé entièrement en HTML5, CSS3 et JavaScript vanilla. Le jeu utilise l'élément `<canvas>` pour le rendu graphique et propose une expérience de jeu complète avec :
|
||||
|
||||
- Un personnage jouable aux couleurs changeantes
|
||||
- 4 fantômes avec intelligence artificielle
|
||||
- 3 labyrinthes différents
|
||||
- Système de niveaux progressifs
|
||||
- Un personnage jouable personnalisable (couleurs et formes)
|
||||
- Système de fantômes avec IA avancée et types variés
|
||||
- 3 labyrinthes différents qui alternent
|
||||
- Système de niveaux progressifs avec difficulté croissante
|
||||
- Power-ups et bonus multiples
|
||||
- Système de combo et mode Frenzy
|
||||
- Zones spéciales (téléportation, bonus, danger)
|
||||
- Classement des scores (localStorage)
|
||||
- Interface responsive
|
||||
- Interface responsive avec support mobile
|
||||
- Menu principal animé
|
||||
- Mode plein écran
|
||||
|
||||
---
|
||||
|
||||
@ -33,25 +39,87 @@ Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul
|
||||
|
||||
### Gameplay
|
||||
- **Déplacement fluide** : Mouvement pixel par pixel avec interpolation
|
||||
- **Système de vies** : 3 vies représentées par des cœurs
|
||||
- **Points** : Collecte de pastilles (+10 points) et bonus
|
||||
- **Système de vies** : 3 vies représentées par des cœurs animés
|
||||
- **Points** : Collecte de pastilles avec système de combo
|
||||
- **Progression de niveau** : Passer au niveau suivant en mangeant 4 cerises
|
||||
- **Mode pause** : Espace ou Échap pour mettre en pause
|
||||
|
||||
### Bonus
|
||||
| Bonus | Points | Effet |
|
||||
|-------|--------|-------|
|
||||
| 🍒 Cerise | +100 | Active la poursuite des fantômes pendant un temps limité. 4 cerises = niveau suivant |
|
||||
| ⭐ Étoile "L" | +15 | Bonus supplémentaire |
|
||||
### Bonus de base
|
||||
| Bonus | Points | Effet | Niveau |
|
||||
|-------|--------|-------|--------|
|
||||
| 🍒 Cerise | +100 | Rend les fantômes vulnérables. 4 cerises = niveau suivant | 1+ |
|
||||
| ⭐ Étoile "L" | +200 | Bonus supplémentaire | 1+ |
|
||||
|
||||
### Power-ups (Niveau 2+)
|
||||
| Power-up | Effet | Durée | Niveau |
|
||||
|----------|-------|-------|--------|
|
||||
| ⚡ Étoile de vitesse | Pacman devient 2x plus rapide | 10 secondes | 2+ |
|
||||
| 🛡 Bouclier | Protège d'une collision avec un fantôme | 15 secondes | 2+ |
|
||||
| 💣 Bombe | Repousse tous les fantômes proches | Instantané | 3+ |
|
||||
| ✨ Multiplicateur | Double tous les points | 30 secondes | 3+ |
|
||||
|
||||
### Système de Combo
|
||||
- Collectez les pastilles rapidement pour créer un combo
|
||||
- Chaque combo augmente votre multiplicateur de score (jusqu'à x5)
|
||||
- Le combo se réinitialise si vous attendez plus de 3 secondes entre deux collectes
|
||||
- Points de base : 10 × combo × Frenzy × multiplicateur
|
||||
|
||||
### Mode Frenzy
|
||||
- S'active automatiquement toutes les **30 secondes**
|
||||
- Tous les fantômes deviennent vulnérables pendant **10 secondes**
|
||||
- Score multiplié par **3** pendant la durée du mode
|
||||
- Indicateur visuel avec animation
|
||||
|
||||
### Fantômes
|
||||
- **4 fantômes** avec couleurs distinctes (rouge, magenta, cyan, orange)
|
||||
- **IA adaptative** : Les fantômes poursuivent Pac-Man après qu'une cerise soit mangée
|
||||
- **Vitesse croissante** : Les fantômes accélèrent à chaque niveau
|
||||
|
||||
#### Types de fantômes
|
||||
| Type | Description | Niveau |
|
||||
|------|-------------|--------|
|
||||
| 👤 Normal | Poursuit directement le joueur | 1+ |
|
||||
| 🎯 Chasseur | Poursuit agressivement avec prédiction de mouvement | 2+ |
|
||||
| 🛡 Patrouilleur | Bloque les passages stratégiques et patrouille | 3+ |
|
||||
| ⚡ Rapide | 1.5x plus rapide que les autres fantômes | 4+ |
|
||||
| 👻 Invisible | Devient invisible 50% du temps, difficile à voir | 5+ |
|
||||
|
||||
#### Nombre de fantômes par niveau
|
||||
- **Niveau 1-2** : 4 fantômes
|
||||
- **Niveau 3-4** : 5 fantômes
|
||||
- **Niveau 5-9** : 6 fantômes
|
||||
- **Niveau 10+** : 7 fantômes
|
||||
|
||||
#### Comportement
|
||||
- **Poursuite** : Les fantômes poursuivent activement le joueur
|
||||
- **Vulnérabilité** : Après avoir mangé une cerise, les fantômes deviennent vulnérables et fuient
|
||||
- **Manger un fantôme vulnérable** : +200 points
|
||||
- **Prédiction** : Les chasseurs prédisent la position future du joueur
|
||||
|
||||
### Zones spéciales (Niveau 4+)
|
||||
| Zone | Couleur | Effet |
|
||||
|------|---------|-------|
|
||||
| Téléportation | Violet | Vous téléporte à un endroit aléatoire |
|
||||
| Bonus | Doré | Double les points dans cette zone |
|
||||
| Danger | Rouge | Ralentit Pacman temporairement |
|
||||
|
||||
### Labyrinthes
|
||||
- 3 variantes de labyrinthes qui alternent à chaque niveau
|
||||
- Génération dynamique avec remplissage des espaces vides
|
||||
- Randomisation légère pour plus de variété
|
||||
- Zones spéciales ajoutées selon le niveau
|
||||
|
||||
### Personnalisation
|
||||
- **8 couleurs** : Jaune, Rouge, Bleu, Vert, Violet, Orange, Rose, Arc-en-ciel (animé)
|
||||
- **2 formes** : Rond (classique) ou Triangle
|
||||
- Sauvegarde des préférences dans localStorage
|
||||
- Aperçu en temps réel dans le menu de personnalisation
|
||||
|
||||
### Interface
|
||||
- **Menu principal** avec animation de fond
|
||||
- **Classement** en temps réel pendant le jeu
|
||||
- **Indicateurs visuels** pour les power-ups actifs
|
||||
- **Compteur de pastilles** restantes
|
||||
- **Timer de poursuite** quand les fantômes sont vulnérables
|
||||
- **Mode plein écran** disponible
|
||||
- **Contrôles tactiles** pour mobile
|
||||
|
||||
---
|
||||
|
||||
@ -59,9 +127,9 @@ Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul
|
||||
|
||||
```
|
||||
pacmanludo/
|
||||
├── index.html # Page HTML principale
|
||||
├── style.css # Feuille de styles
|
||||
├── game.js # Logique du jeu
|
||||
├── index.html # Page HTML principale avec menu et interface
|
||||
├── style.css # Feuille de styles complète (1547 lignes)
|
||||
├── game.js # Logique du jeu (2954 lignes)
|
||||
└── README.md # Documentation
|
||||
```
|
||||
|
||||
@ -73,32 +141,55 @@ pacmanludo/
|
||||
2. **Ouvrir** `index.html` dans un navigateur web moderne
|
||||
3. **Jouer !**
|
||||
|
||||
Aucune dépendance externe n'est requise.
|
||||
Aucune dépendance externe n'est requise. Le jeu fonctionne entièrement côté client.
|
||||
|
||||
**Navigateurs supportés :**
|
||||
- Chrome/Edge (recommandé)
|
||||
- Firefox
|
||||
- Safari
|
||||
- Opéra
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Comment jouer
|
||||
|
||||
### Contrôles
|
||||
|
||||
#### Clavier
|
||||
| Touche | Action |
|
||||
|--------|--------|
|
||||
| ↑ Flèche Haut | Déplacer vers le haut |
|
||||
| ↓ Flèche Bas | Déplacer vers le bas |
|
||||
| ← Flèche Gauche | Déplacer vers la gauche |
|
||||
| → Flèche Droite | Déplacer vers la droite |
|
||||
| Espace / Échap | Mettre en pause / Reprendre |
|
||||
|
||||
#### Mobile
|
||||
- Utilisez les boutons directionnels tactiles affichés en bas de l'écran
|
||||
|
||||
### Objectif
|
||||
1. Collectez toutes les pastilles blanches pour marquer des points
|
||||
2. Mangez les cerises pour activer la poursuite et progresser
|
||||
3. Évitez les fantômes !
|
||||
4. Atteignez le score le plus élevé possible
|
||||
3. Collectez les power-ups pour obtenir des avantages
|
||||
4. Évitez les fantômes (ou mangez-les quand ils sont vulnérables) !
|
||||
5. Atteignez le score le plus élevé possible
|
||||
|
||||
### Progression
|
||||
- **4 cerises mangées** = passage au niveau suivant
|
||||
- À chaque niveau :
|
||||
- Pac-Man accélère légèrement (+5% par niveau)
|
||||
- Les fantômes deviennent plus rapides (+20% par niveau)
|
||||
- Les fantômes deviennent plus rapides (vitesse fixe x1.4)
|
||||
- Le labyrinthe change (cycle de 3 labyrinthes)
|
||||
- Nouveaux types de fantômes apparaissent
|
||||
- De nouveaux power-ups deviennent disponibles
|
||||
|
||||
### Système de score
|
||||
| Action | Points de base | Multiplicateurs |
|
||||
|--------|----------------|-----------------|
|
||||
| Pastille | 10 | × combo × Frenzy × multiplicateur |
|
||||
| Fantôme vulnérable | 200 | × Frenzy × multiplicateur |
|
||||
| Cerise | 100 | × Frenzy × multiplicateur |
|
||||
| Étoile Ludo | 200 | × Frenzy × multiplicateur |
|
||||
|
||||
---
|
||||
|
||||
@ -109,34 +200,52 @@ Aucune dépendance externe n'est requise.
|
||||
Structure HTML de la page :
|
||||
|
||||
```html
|
||||
├── main-wrapper
|
||||
│ ├── container (zone de jeu)
|
||||
│ │ ├── Titre "OULVIC"
|
||||
│ │ ├── Champ nom d'utilisateur
|
||||
│ │ ├── Barre d'informations (score, niveau, vies, statut)
|
||||
│ │ ├── Canvas du jeu (600x600)
|
||||
│ │ └── Instructions et bouton rejouer
|
||||
│ └── leaderboard-container (classement)
|
||||
└── footer (crédits)
|
||||
├── main-menu (menu principal)
|
||||
│ ├── menuBackgroundCanvas (animation de fond)
|
||||
│ └── menu-container
|
||||
│ ├── Titre "OULVIC"
|
||||
│ ├── Meilleur score
|
||||
│ └── Boutons (Jouer, Personnaliser, Classement, Règles)
|
||||
├── game-wrapper (interface de jeu)
|
||||
│ ├── main-wrapper
|
||||
│ │ ├── container (zone de jeu)
|
||||
│ │ │ ├── game-header
|
||||
│ │ │ ├── user-input-section
|
||||
│ │ │ ├── game-info (score, niveau, vies, pastilles)
|
||||
│ │ │ ├── power-ups-display
|
||||
│ │ │ ├── frenzy-indicator
|
||||
│ │ │ ├── pursuit-indicator
|
||||
│ │ │ ├── gameCanvas (750x750)
|
||||
│ │ │ ├── mobile-controls
|
||||
│ │ │ └── game-overlay (Game Over)
|
||||
│ │ └── game-leaderboard (classement en temps réel)
|
||||
│ └── leaderboard-container (classement complet)
|
||||
├── leaderboard-modal (modal classement)
|
||||
├── rules-modal (modal règles)
|
||||
└── customize-modal (modal personnalisation)
|
||||
```
|
||||
|
||||
### `style.css`
|
||||
|
||||
| Section | Description |
|
||||
|---------|-------------|
|
||||
| Reset & Body | Styles de base, fond sombre avec effets |
|
||||
| Reset & Body | Styles de base, fond sombre avec effets, curseur personnalisé |
|
||||
| Animations | Animations CSS (neonFlicker, rainbow, heartbeat, etc.) |
|
||||
| Menu Principal | Styles du menu avec animation de fond |
|
||||
| Layout | Flexbox pour disposition responsive |
|
||||
| Game Info | Styles pour score, niveau, vies |
|
||||
| Canvas | Bordure dorée, ombres |
|
||||
| Leaderboard | Tableau des scores stylisé |
|
||||
| Responsive | Media queries pour mobile |
|
||||
| Game Info | Styles pour score, niveau, vies, combo |
|
||||
| Canvas | Bordure dorée, ombres, effets de glow |
|
||||
| Leaderboard | Tableau des scores stylisé avec médailles |
|
||||
| Power-ups | Indicateurs visuels pour les power-ups actifs |
|
||||
| Responsive | Media queries pour mobile et tablette |
|
||||
| Modals | Styles pour les modales (règles, classement, personnalisation) |
|
||||
|
||||
### `game.js`
|
||||
|
||||
#### Constantes principales
|
||||
|
||||
```javascript
|
||||
CELL_SIZE = 20 // Taille d'une cellule en pixels
|
||||
CELL_SIZE = 25 // Taille d'une cellule en pixels
|
||||
COLS = 30 // Nombre de colonnes
|
||||
ROWS = 30 // Nombre de lignes
|
||||
|
||||
@ -147,6 +256,27 @@ EMPTY = 0 // Vide
|
||||
TUNNEL = 3 // Tunnel (passage fantômes)
|
||||
BONUS_CHERRY = 4 // Cerise bonus
|
||||
BONUS_LUDO = 5 // Étoile bonus
|
||||
BONUS_SPEED = 6 // Étoile de vitesse
|
||||
BONUS_SHIELD = 7 // Bouclier
|
||||
BONUS_BOMB = 8 // Bombe
|
||||
BONUS_MULTIPLIER = 9 // Multiplicateur
|
||||
|
||||
// Zones spéciales
|
||||
ZONE_TELEPORT = 10 // Zone de téléportation
|
||||
ZONE_BONUS = 11 // Zone bonus
|
||||
ZONE_DANGER = 12 // Zone danger
|
||||
|
||||
// Types de fantômes
|
||||
GHOST_NORMAL = 'normal'
|
||||
GHOST_HUNTER = 'hunter'
|
||||
GHOST_PATROL = 'patrol'
|
||||
GHOST_FAST = 'fast'
|
||||
GHOST_INVISIBLE = 'invisible'
|
||||
|
||||
// Constantes de gameplay
|
||||
COMBO_TIMEOUT = 3000 // 3 secondes pour maintenir le combo
|
||||
FRENZY_INTERVAL = 30000 // 30 secondes entre chaque mode Frenzy
|
||||
FRENZY_DURATION = 10000 // 10 secondes de durée du mode Frenzy
|
||||
```
|
||||
|
||||
---
|
||||
@ -157,62 +287,111 @@ BONUS_LUDO = 5 // Étoile bonus
|
||||
|
||||
Gère le personnage jouable.
|
||||
|
||||
#### Propriétés
|
||||
|
||||
| Propriété | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `x, y` | number | Position en cellules |
|
||||
| `pixelX, pixelY` | number | Position en pixels (mouvement fluide) |
|
||||
| `direction` | number | Direction actuelle (0=haut, 1=droite, 2=bas, 3=gauche) |
|
||||
| `nextDirection` | number | Prochaine direction demandée |
|
||||
| `speed` | number | Vitesse de déplacement |
|
||||
| `speed` | number | Vitesse de déplacement (modifiable par power-ups) |
|
||||
| `baseSpeed` | number | Vitesse de base (0.25) |
|
||||
| `mouthAngle` | number | Animation de la bouche |
|
||||
| `colorAnimation` | number | Animation des couleurs HSL |
|
||||
| `mouthOpen` | boolean | État de la bouche (ouverte/fermée) |
|
||||
| `colorAnimation` | number | Animation des couleurs HSL (pour arc-en-ciel) |
|
||||
| `color` | string | Couleur du joueur (sauvegardée) |
|
||||
| `shape` | string | Forme du joueur ('round' ou 'triangle') |
|
||||
|
||||
#### Méthodes
|
||||
|
||||
| Méthode | Description |
|
||||
|---------|-------------|
|
||||
| `update()` | Met à jour la position et l'animation |
|
||||
| `canMove(direction)` | Vérifie si le mouvement est possible |
|
||||
| `collectDot()` | Collecte les pastilles et bonus |
|
||||
| `draw()` | Dessine Pac-Man sur le canvas |
|
||||
| `update()` | Met à jour la position, l'animation et collecte les objets |
|
||||
| `canMove(direction)` | Vérifie si le mouvement est possible dans une direction |
|
||||
| `collectDot()` | Collecte les pastilles, bonus et active les power-ups |
|
||||
| `draw()` | Dessine Pac-Man sur le canvas avec la couleur et forme choisies |
|
||||
|
||||
---
|
||||
|
||||
### Classe `Ghost`
|
||||
|
||||
Gère les fantômes ennemis.
|
||||
Gère les fantômes ennemis avec IA avancée.
|
||||
|
||||
#### Propriétés
|
||||
|
||||
| Propriété | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `x, y` | number | Position en cellules |
|
||||
| `pixelX, pixelY` | number | Position en pixels |
|
||||
| `color` | string | Couleur du fantôme |
|
||||
| `type` | string | Type de fantôme (normal, hunter, patrol, fast, invisible) |
|
||||
| `direction` | number | Direction de déplacement |
|
||||
| `speed` | number | Vitesse (augmente avec le niveau) |
|
||||
| `speed` | number | Vitesse (ajustée selon le type et le niveau) |
|
||||
| `baseSpeed` | number | Vitesse de base (0.15) |
|
||||
| `moveCounter` | number | Compteur pour les changements de direction |
|
||||
| `moveInterval` | number | Intervalle entre changements de direction |
|
||||
| `isVulnerable` | boolean | État de vulnérabilité |
|
||||
| `vulnerableTimer` | number | Durée restante de vulnérabilité |
|
||||
| `isInvisible` | boolean | État d'invisibilité (pour GHOST_INVISIBLE) |
|
||||
| `invisibleTimer` | number | Timer pour l'invisibilité |
|
||||
| `patrolTarget` | object | Cible de patrouille (pour GHOST_PATROL) |
|
||||
| `patrolIndex` | number | Index de patrouille |
|
||||
|
||||
#### Méthodes
|
||||
|
||||
| Méthode | Description |
|
||||
|---------|-------------|
|
||||
| `update()` | Met à jour la position et l'IA |
|
||||
| `updateSpeed()` | Ajuste la vitesse selon le niveau |
|
||||
| `update()` | Met à jour la position, l'IA et les états spéciaux |
|
||||
| `updateSpeed()` | Ajuste la vitesse selon le type et le niveau |
|
||||
| `canMove(direction)` | Vérifie si le mouvement est possible |
|
||||
| `getDirectionToPacman(dirs)` | Calcule la meilleure direction vers Pac-Man |
|
||||
| `draw()` | Dessine le fantôme (corps + yeux) |
|
||||
| `getDirectionToPacman(dirs)` | Calcule la meilleure direction vers Pac-Man avec prédiction |
|
||||
| `getDirectionAwayFromPacman(dirs)` | Calcule la direction pour fuir Pac-Man (quand vulnérable) |
|
||||
| `getPatrolDirection(dirs)` | Calcule la direction de patrouille (pour GHOST_PATROL) |
|
||||
| `draw()` | Dessine le fantôme (corps + yeux, avec effets spéciaux) |
|
||||
|
||||
---
|
||||
|
||||
### Classe `Bonus`
|
||||
|
||||
Gère les objets bonus (cerises, étoiles).
|
||||
Gère les objets bonus (cerises, étoiles, power-ups).
|
||||
|
||||
#### Propriétés
|
||||
|
||||
| Propriété | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `x, y` | number | Position en cellules |
|
||||
| `type` | number | Type de bonus (CHERRY ou LUDO) |
|
||||
| `type` | number | Type de bonus (BONUS_CHERRY, BONUS_LUDO, etc.) |
|
||||
| `animation` | number | Animation de pulsation |
|
||||
|
||||
#### Méthodes
|
||||
|
||||
| Méthode | Description |
|
||||
|---------|-------------|
|
||||
| `update()` | Met à jour l'animation |
|
||||
| `draw()` | Dessine le bonus avec effet de scale |
|
||||
| `draw()` | Dessine le bonus avec effet de scale et animation |
|
||||
|
||||
---
|
||||
|
||||
### Classe `MenuGhost`
|
||||
|
||||
Gère les fantômes animés dans le menu principal.
|
||||
|
||||
#### Propriétés
|
||||
|
||||
| Propriété | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `x, y` | number | Position en pixels |
|
||||
| `vx, vy` | number | Vitesse en pixels |
|
||||
| `color` | string | Couleur du fantôme |
|
||||
| `size` | number | Taille du fantôme |
|
||||
|
||||
#### Méthodes
|
||||
|
||||
| Méthode | Description |
|
||||
|---------|-------------|
|
||||
| `update()` | Met à jour la position |
|
||||
| `draw(ctx)` | Dessine le fantôme sur le canvas du menu |
|
||||
|
||||
---
|
||||
|
||||
@ -223,10 +402,10 @@ Gère les objets bonus (cerises, étoiles).
|
||||
| Fonction | Description |
|
||||
|----------|-------------|
|
||||
| `countDots()` | Compte le nombre de pastilles restantes |
|
||||
| `drawMaze()` | Dessine le labyrinthe complet |
|
||||
| `drawMaze()` | Dessine le labyrinthe complet avec zones spéciales |
|
||||
| `fillEmptySpaces()` | Remplit les espaces vides avec des pastilles |
|
||||
| `randomizeMaze()` | Ajoute des variations aléatoires au labyrinthe |
|
||||
| `placeBonuses()` | Place les bonus aux positions définies |
|
||||
| `placeBonuses()` | Place les bonus aux positions définies selon le niveau |
|
||||
|
||||
#### Gestion du jeu
|
||||
|
||||
@ -235,36 +414,96 @@ Gère les objets bonus (cerises, étoiles).
|
||||
| `initGame()` | Initialise une nouvelle partie |
|
||||
| `gameLoop()` | Boucle principale du jeu (requestAnimationFrame) |
|
||||
| `checkCollisions()` | Détecte les collisions Pac-Man/fantômes |
|
||||
| `nextLevel()` | Passe au niveau suivant |
|
||||
| `nextLevel()` | Passe au niveau suivant avec transition |
|
||||
| `restartCurrentLevel()` | Recommence le niveau actuel (après perte de vie) |
|
||||
| `createGhosts()` | Crée les fantômes selon le niveau et leurs types |
|
||||
|
||||
#### Interface utilisateur
|
||||
|
||||
| Fonction | Description |
|
||||
|----------|-------------|
|
||||
| `updateLivesDisplay()` | Met à jour l'affichage des vies |
|
||||
| `updateLeaderboard()` | Rafraîchit le classement |
|
||||
| `updateLeaderboard()` | Rafraîchit le classement principal |
|
||||
| `updateGameLeaderboard()` | Rafraîchit le classement en jeu |
|
||||
| `updateMenuLeaderboard()` | Rafraîchit le classement du menu |
|
||||
| `saveScore()` | Sauvegarde le score en localStorage |
|
||||
| `getScores()` | Récupère les scores depuis localStorage |
|
||||
| `showGameOver()` | Affiche l'overlay Game Over |
|
||||
| `hideGameOver()` | Cache l'overlay Game Over |
|
||||
| `confirmUsername()` | Confirme le nom d'utilisateur |
|
||||
| `resetUsernameInput()` | Réinitialise le champ nom d'utilisateur |
|
||||
|
||||
#### Personnalisation
|
||||
|
||||
| Fonction | Description |
|
||||
|----------|-------------|
|
||||
| `updateColorSelection()` | Met à jour la sélection de couleur |
|
||||
| `updateShapeSelection()` | Met à jour la sélection de forme |
|
||||
| `updatePlayerPreview()` | Met à jour l'aperçu du joueur |
|
||||
| `startPreviewAnimation()` | Démarre l'animation de l'aperçu |
|
||||
| `stopPreviewAnimation()` | Arrête l'animation de l'aperçu |
|
||||
|
||||
#### Menu et animations
|
||||
|
||||
| Fonction | Description |
|
||||
|----------|-------------|
|
||||
| `animateMenu()` | Anime le menu principal |
|
||||
| `startMenuAnimation()` | Démarre l'animation du menu |
|
||||
| `stopMenuAnimation()` | Arrête l'animation du menu |
|
||||
| `resizeMenuCanvas()` | Redimensionne le canvas du menu |
|
||||
| `updateBestScore()` | Met à jour l'affichage du meilleur score |
|
||||
|
||||
#### Plein écran
|
||||
|
||||
| Fonction | Description |
|
||||
|----------|-------------|
|
||||
| `toggleFullscreen()` | Active/désactive le mode plein écran |
|
||||
| `updateFullscreenButton()` | Met à jour le bouton plein écran |
|
||||
| `isFullscreen()` | Vérifie si on est en mode plein écran |
|
||||
|
||||
---
|
||||
|
||||
### Variables globales d'état
|
||||
|
||||
```javascript
|
||||
let score = 0; // Score actuel
|
||||
let level = 1; // Niveau actuel
|
||||
let lives = 3; // Vies restantes
|
||||
let gameRunning = true; // État du jeu
|
||||
let totalDots = 0; // Pastilles restantes
|
||||
let cherriesEaten = 0; // Cerises mangées (niveau actuel)
|
||||
let isChangingLevel = false; // Flag de transition
|
||||
let cherryEatenRecently = false; // Mode poursuite activé
|
||||
let cherryEatenTimer = 0; // Durée du mode poursuite
|
||||
let score = 0; // Score actuel
|
||||
let level = 1; // Niveau actuel
|
||||
let lives = 3; // Vies restantes
|
||||
let gameRunning = true; // État du jeu
|
||||
let isPaused = false; // État de pause
|
||||
let totalDots = 0; // Pastilles restantes
|
||||
let cherriesEaten = 0; // Cerises mangées (niveau actuel)
|
||||
let isChangingLevel = false; // Flag de transition
|
||||
let cherryEatenRecently = false; // Mode poursuite activé
|
||||
let cherryEatenTimer = 0; // Durée du mode poursuite
|
||||
|
||||
// Système de combo
|
||||
let comboCount = 0; // Nombre de pastilles dans le combo
|
||||
let comboMultiplier = 1; // Multiplicateur actuel (max x5)
|
||||
let lastDotTime = 0; // Temps de la dernière pastille collectée
|
||||
|
||||
// Power-ups actifs
|
||||
let speedBoostActive = false; // État du boost de vitesse
|
||||
let speedBoostTimer = 0; // Durée restante
|
||||
let shieldActive = false; // État du bouclier
|
||||
let shieldTimer = 0; // Durée restante
|
||||
let scoreMultiplierActive = false; // État du multiplicateur
|
||||
let scoreMultiplierTimer = 0; // Durée restante
|
||||
let scoreMultiplierValue = 1; // Valeur du multiplicateur (x2)
|
||||
|
||||
// Mode Frenzy
|
||||
let frenzyModeActive = false; // État du mode Frenzy
|
||||
let frenzyTimer = 0; // Durée restante
|
||||
let frenzyCooldown = 0; // Cooldown avant le prochain Frenzy
|
||||
|
||||
// Zones spéciales
|
||||
let specialZones = []; // Zones spéciales actives
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Systèmes avancés
|
||||
|
||||
### Système de stockage des scores
|
||||
|
||||
Les scores sont sauvegardés dans le `localStorage` du navigateur sous la clé `pacmanScores`.
|
||||
@ -274,14 +513,14 @@ Les scores sont sauvegardés dans le `localStorage` du navigateur sous la clé `
|
||||
{
|
||||
username: "NomJoueur", // Nom saisi ou "Anonyme"
|
||||
score: 1500, // Score final
|
||||
level: 5, // Niveau atteint
|
||||
date: "2025-12-01T..." // Date ISO
|
||||
}
|
||||
```
|
||||
|
||||
- Maximum **10 scores** conservés
|
||||
- Triés par score décroissant
|
||||
|
||||
---
|
||||
- Affichage avec médailles pour le top 3 (🥇 🥈 🥉)
|
||||
|
||||
### Labyrinthes
|
||||
|
||||
@ -293,8 +532,45 @@ mazeIndex = (level - 1) % 3
|
||||
```
|
||||
|
||||
Les labyrinthes sont modifiés dynamiquement :
|
||||
1. `fillEmptySpaces()` - Remplit les zones vides
|
||||
2. `randomizeMaze()` - Ajoute de la variété
|
||||
1. `fillEmptySpaces()` - Remplit les zones vides avec des pastilles
|
||||
2. `randomizeMaze()` - Ajoute de la variété en modifiant certains murs
|
||||
3. `placeBonuses()` - Place les bonus et zones spéciales selon le niveau
|
||||
|
||||
### Système de fruits
|
||||
|
||||
Les fruits changent selon le niveau :
|
||||
- Niveau 1 : Cerise rouge
|
||||
- Niveau 2 : Banane
|
||||
- Niveau 3 : Orange
|
||||
- Niveau 4 : Pomme
|
||||
- Niveau 5 : Raisin
|
||||
- Niveau 6 : Fraise
|
||||
- Niveau 7+ : Ananas (rotation)
|
||||
|
||||
### IA des fantômes
|
||||
|
||||
#### Fantôme Normal
|
||||
- Poursuit directement le joueur
|
||||
- Changement de direction périodique
|
||||
|
||||
#### Chasseur (Niveau 2+)
|
||||
- Prédit la position future du joueur (3-4 cases d'avance)
|
||||
- Poursuite plus agressive
|
||||
- Pas de restriction sur le retour en arrière
|
||||
|
||||
#### Patrouilleur (Niveau 3+)
|
||||
- Si proche du joueur (< 12 cases) : poursuit
|
||||
- Si loin : patrouille vers les zones centrales
|
||||
- Bloque les passages stratégiques
|
||||
|
||||
#### Rapide (Niveau 4+)
|
||||
- Vitesse 1.5x supérieure aux autres
|
||||
- Même comportement de poursuite que Normal
|
||||
|
||||
#### Invisible (Niveau 5+)
|
||||
- Devient invisible 50% du temps
|
||||
- Timer alternant toutes les 5 secondes
|
||||
- Difficile à voir et à éviter
|
||||
|
||||
---
|
||||
|
||||
@ -307,18 +583,34 @@ Les labyrinthes sont modifiés dynamiquement :
|
||||
| Fond | `#0a0a0a` (noir) |
|
||||
| Murs | `#0000ff` (bleu) |
|
||||
| Pastilles | `#ffffff` (blanc) |
|
||||
| Texte titre | `#ffd700` (or) |
|
||||
| Texte titre | `#ffd700` (or) avec animation arc-en-ciel |
|
||||
| Cerises | `#ff0000` (rouge) |
|
||||
| Cœurs | `#ff0000` (rouge) |
|
||||
| Cœurs | `#ff0000` (rouge) avec animation heartbeat |
|
||||
| Zones téléportation | `rgba(255, 0, 255, 0.3)` (violet) |
|
||||
| Zones bonus | `rgba(255, 215, 0, 0.3)` (doré) |
|
||||
| Zones danger | `rgba(255, 0, 0, 0.3)` (rouge) |
|
||||
|
||||
### Fantômes
|
||||
- Rouge : `#ff0000`
|
||||
- Magenta : `#ff00ff`
|
||||
- Cyan : `#00ffff`
|
||||
- Orange : `#ffa500`
|
||||
- Vert : `#00ff00`
|
||||
- Jaune : `#ffff00`
|
||||
|
||||
### Pac-Man
|
||||
- Couleur animée en HSL (arc-en-ciel)
|
||||
- Couleur animée en HSL (arc-en-ciel) par défaut
|
||||
- 8 couleurs disponibles : Jaune, Rouge, Bleu, Vert, Violet, Orange, Rose, Arc-en-ciel
|
||||
- 2 formes : Rond (classique) ou Triangle
|
||||
|
||||
### Animations CSS
|
||||
- `neonFlicker` : Effet de scintillement néon pour le titre
|
||||
- `rainbow` : Animation arc-en-ciel pour le texte
|
||||
- `heartbeat` : Animation de pulsation pour les cœurs
|
||||
- `canvasGlow` : Effet de lueur pour le canvas
|
||||
- `pulse` : Animation de pulsation pour les indicateurs
|
||||
- `fadeIn` : Animation d'apparition
|
||||
- `shake` : Animation de secousse pour Game Over
|
||||
|
||||
---
|
||||
|
||||
@ -329,30 +621,78 @@ Les labyrinthes sont modifiés dynamiquement :
|
||||
Dans `game.js` :
|
||||
|
||||
```javascript
|
||||
// Vitesse de base de Pac-Man (ligne 145)
|
||||
// Vitesse de base de Pac-Man (ligne ~265)
|
||||
this.baseSpeed = 0.25;
|
||||
|
||||
// Vitesse de base des fantômes (ligne ~575)
|
||||
this.baseSpeed = 0.15;
|
||||
|
||||
// Vitesse de base des fantômes (ligne 277)
|
||||
this.baseSpeed = 0.1;
|
||||
|
||||
// Accélération par niveau
|
||||
// Accélération par niveau (ligne ~1504)
|
||||
pacman.speed = pacman.baseSpeed * (1 + (level - 1) * 0.05);
|
||||
ghost.speed = ghost.baseSpeed * (1 + (level - 1) * 0.2);
|
||||
```
|
||||
|
||||
### Modifier les points
|
||||
|
||||
```javascript
|
||||
// Pastille (ligne 213)
|
||||
score += 10;
|
||||
// Pastille (ligne ~357)
|
||||
let points = 10 * comboMultiplier * (frenzyModeActive ? 3 : 1) * scoreMultiplierValue;
|
||||
|
||||
// Cerise (ligne 224)
|
||||
// Cerise (ligne ~372)
|
||||
score += 100;
|
||||
|
||||
// Étoile Ludo (ligne 241)
|
||||
score += 15;
|
||||
// Étoile Ludo (ligne ~401)
|
||||
let points = 200 * (frenzyModeActive ? 3 : 1) * scoreMultiplierValue;
|
||||
|
||||
// Fantôme vulnérable (ligne ~1298)
|
||||
score += 200;
|
||||
```
|
||||
|
||||
### Modifier les durées
|
||||
|
||||
```javascript
|
||||
// Combo timeout (ligne ~230)
|
||||
const COMBO_TIMEOUT = 3000; // 3 secondes
|
||||
|
||||
// Mode Frenzy (lignes ~245-246)
|
||||
const FRENZY_INTERVAL = 30000; // 30 secondes
|
||||
const FRENZY_DURATION = 10000; // 10 secondes
|
||||
|
||||
// Power-ups (lignes ~408, 415, 436)
|
||||
speedBoostTimer = 600; // 10 secondes à 60 FPS
|
||||
shieldTimer = 900; // 15 secondes
|
||||
scoreMultiplierTimer = 1800; // 30 secondes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Responsive Design
|
||||
|
||||
Le jeu est entièrement responsive avec :
|
||||
- **Desktop** : Interface complète avec classement à droite
|
||||
- **Tablette** : Layout adapté, classement en dessous
|
||||
- **Mobile** : Contrôles tactiles, layout vertical, classement en dessous
|
||||
|
||||
Media queries :
|
||||
- `@media (max-width: 1200px)` : Layout vertical
|
||||
- `@media (max-width: 700px)` : Optimisations mobile
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Dépannage
|
||||
|
||||
### Le jeu ne démarre pas
|
||||
- Vérifiez que vous utilisez un navigateur moderne
|
||||
- Ouvrez la console (F12) pour voir les erreurs
|
||||
- Vérifiez que tous les fichiers sont présents
|
||||
|
||||
### Les scores ne se sauvegardent pas
|
||||
- Vérifiez que le localStorage est activé dans votre navigateur
|
||||
- Videz le cache si nécessaire
|
||||
|
||||
### Le mode plein écran ne fonctionne pas
|
||||
- Certains navigateurs nécessitent une interaction utilisateur
|
||||
- Essayez de cliquer sur le bouton après le chargement de la page
|
||||
|
||||
---
|
||||
|
||||
## 📄 Licence
|
||||
@ -365,5 +705,26 @@ Projet créé par **Ludo** et **Syoul**.
|
||||
|
||||
- Inspiration : Pac-Man original (Namco, 1980)
|
||||
- Développement : Ludo & Syoul
|
||||
- Police : Press Start 2P (Google Fonts)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Changelog
|
||||
|
||||
### Version actuelle
|
||||
- ✅ Système de combo
|
||||
- ✅ Mode Frenzy automatique
|
||||
- ✅ Power-ups multiples (vitesse, bouclier, bombe, multiplicateur)
|
||||
- ✅ Types de fantômes variés avec IA avancée
|
||||
- ✅ Zones spéciales (téléportation, bonus, danger)
|
||||
- ✅ Personnalisation complète du joueur
|
||||
- ✅ Menu principal animé
|
||||
- ✅ Classement en temps réel
|
||||
- ✅ Mode plein écran
|
||||
- ✅ Support mobile avec contrôles tactiles
|
||||
- ✅ Système de pause
|
||||
- ✅ Fruits variés selon le niveau
|
||||
|
||||
---
|
||||
|
||||
**Amusez-vous bien avec OULVIC ! 🎮👻**
|
||||
|
||||
301
game.js
301
game.js
@ -550,6 +550,124 @@ class Pacman {
|
||||
|
||||
// Restaurer la couleur originale
|
||||
ctx.fillStyle = baseFillColor;
|
||||
} else if (this.shape === 'zebre') {
|
||||
// Dessiner un zèbre plus réaliste
|
||||
const bodyWidth = size * 1.8;
|
||||
const bodyHeight = size * 1.4;
|
||||
|
||||
ctx.save();
|
||||
|
||||
// Corps (ellipse allongée)
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, size * 0.2, bodyWidth, bodyHeight, 0, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Rayures noires horizontales sur le corps
|
||||
ctx.fillStyle = '#000000';
|
||||
const stripeCount = 8;
|
||||
const stripeSpacing = (bodyHeight * 2) / stripeCount;
|
||||
|
||||
for (let i = 0; i < stripeCount; i++) {
|
||||
if (i % 2 === 1) { // Alterner les rayures
|
||||
const yPos = -bodyHeight + (i * stripeSpacing);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, yPos + size * 0.2, bodyWidth * 0.95, stripeSpacing * 0.5, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// Tête (cercle légèrement plus petit au-dessus du corps)
|
||||
const headSize = size * 0.9;
|
||||
ctx.beginPath();
|
||||
if (this.mouthOpen) {
|
||||
ctx.arc(0, -bodyHeight - headSize * 0.2, headSize, 0.15, Math.PI * 2 - 0.15);
|
||||
ctx.lineTo(0, -bodyHeight - headSize * 0.2);
|
||||
ctx.closePath();
|
||||
} else {
|
||||
ctx.arc(0, -bodyHeight - headSize * 0.2, headSize, 0, Math.PI * 2);
|
||||
}
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Rayures sur la tête (verticales)
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(-headSize * 0.4, -bodyHeight - headSize * 0.5, headSize * 0.15, headSize * 0.6);
|
||||
ctx.fillRect(headSize * 0.25, -bodyHeight - headSize * 0.5, headSize * 0.15, headSize * 0.6);
|
||||
|
||||
// Oreilles pointues
|
||||
ctx.beginPath();
|
||||
// Oreille gauche
|
||||
ctx.moveTo(-headSize * 0.6, -bodyHeight - headSize * 0.8);
|
||||
ctx.lineTo(-headSize * 0.8, -bodyHeight - headSize * 1.4);
|
||||
ctx.lineTo(-headSize * 0.4, -bodyHeight - headSize * 0.9);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// Rayure sur l'oreille gauche
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-headSize * 0.65, -bodyHeight - headSize * 0.95);
|
||||
ctx.lineTo(-headSize * 0.75, -bodyHeight - headSize * 1.3);
|
||||
ctx.lineTo(-headSize * 0.55, -bodyHeight - headSize * 1.0);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Oreille droite
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(headSize * 0.6, -bodyHeight - headSize * 0.8);
|
||||
ctx.lineTo(headSize * 0.8, -bodyHeight - headSize * 1.4);
|
||||
ctx.lineTo(headSize * 0.4, -bodyHeight - headSize * 0.9);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// Rayure sur l'oreille droite
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(headSize * 0.65, -bodyHeight - headSize * 0.95);
|
||||
ctx.lineTo(headSize * 0.75, -bodyHeight - headSize * 1.3);
|
||||
ctx.lineTo(headSize * 0.55, -bodyHeight - headSize * 1.0);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Yeux
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.15, 0, Math.PI * 2);
|
||||
ctx.arc(headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.15, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Pupilles
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.08, 0, Math.PI * 2);
|
||||
ctx.arc(headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.08, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Reflets dans les yeux
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-headSize * 0.32, -bodyHeight - headSize * 0.32, headSize * 0.03, 0, Math.PI * 2);
|
||||
ctx.arc(headSize * 0.28, -bodyHeight - headSize * 0.32, headSize * 0.03, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Queue (petite touffe)
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.arc(bodyWidth * 0.6, bodyHeight * 0.6, size * 0.2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
} else {
|
||||
// Forme ronde (par défaut)
|
||||
if (this.mouthOpen) {
|
||||
@ -2339,9 +2457,25 @@ class MenuGhost {
|
||||
this.size = 30 + Math.random() * 20;
|
||||
this.color = ['#ff0000', '#ff00ff', '#00ffff', '#ffa500'][Math.floor(Math.random() * 4)];
|
||||
this.animation = Math.random() * Math.PI * 2;
|
||||
this.isDestroyed = false;
|
||||
this.destroyAnimation = 0;
|
||||
}
|
||||
|
||||
// Vérifier si le curseur touche le fantôme
|
||||
isPointInside(mouseX, mouseY) {
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(mouseX - this.x, 2) +
|
||||
Math.pow(mouseY - this.y, 2)
|
||||
);
|
||||
return distance < this.size * 0.7;
|
||||
}
|
||||
|
||||
update() {
|
||||
if (this.isDestroyed) {
|
||||
this.destroyAnimation += 0.1;
|
||||
return;
|
||||
}
|
||||
|
||||
this.x += this.speedX;
|
||||
this.y += this.speedY;
|
||||
this.animation += 0.05;
|
||||
@ -2360,6 +2494,33 @@ class MenuGhost {
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (this.isDestroyed) {
|
||||
// Animation de destruction (particules qui s'éparpillent)
|
||||
if (this.destroyAnimation > 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
menuCtx.save();
|
||||
menuCtx.globalAlpha = 1 - (this.destroyAnimation / 2);
|
||||
|
||||
// Dessiner des particules qui s'éparpillent
|
||||
const particleCount = 8;
|
||||
for (let i = 0; i < particleCount; i++) {
|
||||
const angle = (Math.PI * 2 / particleCount) * i + this.destroyAnimation * 2;
|
||||
const distance = this.destroyAnimation * 30;
|
||||
const particleX = this.x + Math.cos(angle) * distance;
|
||||
const particleY = this.y + Math.sin(angle) * distance;
|
||||
|
||||
menuCtx.fillStyle = this.color;
|
||||
menuCtx.beginPath();
|
||||
menuCtx.arc(particleX, particleY, 4, 0, Math.PI * 2);
|
||||
menuCtx.fill();
|
||||
}
|
||||
|
||||
menuCtx.restore();
|
||||
return;
|
||||
}
|
||||
|
||||
menuCtx.save();
|
||||
menuCtx.translate(this.x, this.y);
|
||||
|
||||
@ -2440,6 +2601,13 @@ function animateMenu() {
|
||||
ghost.draw();
|
||||
});
|
||||
|
||||
// Remplacer les fantômes complètement détruits
|
||||
for (let i = 0; i < menuGhosts.length; i++) {
|
||||
if (menuGhosts[i].isDestroyed && menuGhosts[i].destroyAnimation > 2) {
|
||||
menuGhosts[i] = new MenuGhost();
|
||||
}
|
||||
}
|
||||
|
||||
menuAnimationFrame = requestAnimationFrame(animateMenu);
|
||||
}
|
||||
|
||||
@ -2488,6 +2656,25 @@ if (mainMenu) {
|
||||
menuObserver.observe(mainMenu, { attributes: true, attributeFilter: ['style'] });
|
||||
}
|
||||
|
||||
// Gestion de la destruction des fantômes avec le curseur
|
||||
let mouseX = 0;
|
||||
let mouseY = 0;
|
||||
|
||||
menuCanvas.addEventListener('mousemove', (e) => {
|
||||
const rect = menuCanvas.getBoundingClientRect();
|
||||
mouseX = e.clientX - rect.left;
|
||||
mouseY = e.clientY - rect.top;
|
||||
|
||||
// Vérifier les collisions avec les fantômes
|
||||
menuGhosts.forEach(ghost => {
|
||||
if (!ghost.isDestroyed && ghost.isPointInside(mouseX, mouseY)) {
|
||||
// Détruire le fantôme
|
||||
ghost.isDestroyed = true;
|
||||
ghost.destroyAnimation = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// === GESTION DU MENU ===
|
||||
// Afficher le jeu
|
||||
playBtn.addEventListener('click', () => {
|
||||
@ -2748,6 +2935,120 @@ function startPreviewAnimation() {
|
||||
};
|
||||
ctx.fillStyle = colorMap[selectedColor] || '#ffd700';
|
||||
}
|
||||
} else if (selectedShape === 'zebre') {
|
||||
// Dessiner un zèbre plus réaliste
|
||||
const bodyWidth = size * 1.8;
|
||||
const bodyHeight = size * 1.4;
|
||||
|
||||
// Corps (ellipse allongée)
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, size * 0.2, bodyWidth, bodyHeight, 0, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Rayures noires horizontales sur le corps
|
||||
ctx.fillStyle = '#000000';
|
||||
const stripeCount = 8;
|
||||
const stripeSpacing = (bodyHeight * 2) / stripeCount;
|
||||
|
||||
for (let i = 0; i < stripeCount; i++) {
|
||||
if (i % 2 === 1) { // Alterner les rayures
|
||||
const yPos = -bodyHeight + (i * stripeSpacing);
|
||||
ctx.beginPath();
|
||||
ctx.ellipse(0, yPos + size * 0.2, bodyWidth * 0.95, stripeSpacing * 0.5, 0, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// Tête (cercle légèrement plus petit au-dessus du corps)
|
||||
const headSize = size * 0.9;
|
||||
ctx.beginPath();
|
||||
if (mouthOpen) {
|
||||
ctx.arc(0, -bodyHeight - headSize * 0.2, headSize, 0.15, Math.PI * 2 - 0.15);
|
||||
ctx.lineTo(0, -bodyHeight - headSize * 0.2);
|
||||
ctx.closePath();
|
||||
} else {
|
||||
ctx.arc(0, -bodyHeight - headSize * 0.2, headSize, 0, Math.PI * 2);
|
||||
}
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
|
||||
// Rayures sur la tête (verticales)
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.fillRect(-headSize * 0.4, -bodyHeight - headSize * 0.5, headSize * 0.15, headSize * 0.6);
|
||||
ctx.fillRect(headSize * 0.25, -bodyHeight - headSize * 0.5, headSize * 0.15, headSize * 0.6);
|
||||
|
||||
// Oreilles pointues
|
||||
ctx.beginPath();
|
||||
// Oreille gauche
|
||||
ctx.moveTo(-headSize * 0.6, -bodyHeight - headSize * 0.8);
|
||||
ctx.lineTo(-headSize * 0.8, -bodyHeight - headSize * 1.4);
|
||||
ctx.lineTo(-headSize * 0.4, -bodyHeight - headSize * 0.9);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// Rayure sur l'oreille gauche
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-headSize * 0.65, -bodyHeight - headSize * 0.95);
|
||||
ctx.lineTo(-headSize * 0.75, -bodyHeight - headSize * 1.3);
|
||||
ctx.lineTo(-headSize * 0.55, -bodyHeight - headSize * 1.0);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Oreille droite
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(headSize * 0.6, -bodyHeight - headSize * 0.8);
|
||||
ctx.lineTo(headSize * 0.8, -bodyHeight - headSize * 1.4);
|
||||
ctx.lineTo(headSize * 0.4, -bodyHeight - headSize * 0.9);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
// Rayure sur l'oreille droite
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(headSize * 0.65, -bodyHeight - headSize * 0.95);
|
||||
ctx.lineTo(headSize * 0.75, -bodyHeight - headSize * 1.3);
|
||||
ctx.lineTo(headSize * 0.55, -bodyHeight - headSize * 1.0);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
// Yeux
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.15, 0, Math.PI * 2);
|
||||
ctx.arc(headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.15, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Pupilles
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.08, 0, Math.PI * 2);
|
||||
ctx.arc(headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.08, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Reflets dans les yeux
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-headSize * 0.32, -bodyHeight - headSize * 0.32, headSize * 0.03, 0, Math.PI * 2);
|
||||
ctx.arc(headSize * 0.28, -bodyHeight - headSize * 0.32, headSize * 0.03, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// Queue (petite touffe)
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.arc(bodyWidth * 0.6, bodyHeight * 0.6, size * 0.2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
} else {
|
||||
// Dessiner un cercle
|
||||
if (mouthOpen) {
|
||||
|
||||
@ -287,6 +287,10 @@
|
||||
<div class="shape-preview triangle-preview"></div>
|
||||
<span>Triangle</span>
|
||||
</div>
|
||||
<div class="shape-option" data-shape="zebre" data-name="Zèbre">
|
||||
<div class="shape-preview zebre-preview"></div>
|
||||
<span>Zèbre</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customize-preview">
|
||||
|
||||
17
style.css
17
style.css
@ -156,7 +156,7 @@ body::after {
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
@ -1539,6 +1539,21 @@ footer {
|
||||
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.3));
|
||||
}
|
||||
|
||||
.zebre-preview {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: repeating-linear-gradient(
|
||||
90deg,
|
||||
#ffffff 0px,
|
||||
#ffffff 8px,
|
||||
#000000 8px,
|
||||
#000000 16px
|
||||
);
|
||||
border: 3px solid #fff;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.shape-option span {
|
||||
font-size: 0.7em;
|
||||
color: #fff;
|
||||
|
||||
Reference in New Issue
Block a user