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**.
|
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)
|
5. [Comment jouer](#comment-jouer)
|
||||||
6. [Architecture du code](#architecture-du-code)
|
6. [Architecture du code](#architecture-du-code)
|
||||||
7. [Documentation technique](#documentation-technique)
|
7. [Documentation technique](#documentation-technique)
|
||||||
|
8. [Systèmes avancés](#systèmes-avancés)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📝 Description
|
## 📝 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
|
- Un personnage jouable personnalisable (couleurs et formes)
|
||||||
- 4 fantômes avec intelligence artificielle
|
- Système de fantômes avec IA avancée et types variés
|
||||||
- 3 labyrinthes différents
|
- 3 labyrinthes différents qui alternent
|
||||||
- Système de niveaux progressifs
|
- 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)
|
- 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
|
### Gameplay
|
||||||
- **Déplacement fluide** : Mouvement pixel par pixel avec interpolation
|
- **Déplacement fluide** : Mouvement pixel par pixel avec interpolation
|
||||||
- **Système de vies** : 3 vies représentées par des cœurs
|
- **Système de vies** : 3 vies représentées par des cœurs animés
|
||||||
- **Points** : Collecte de pastilles (+10 points) et bonus
|
- **Points** : Collecte de pastilles avec système de combo
|
||||||
- **Progression de niveau** : Passer au niveau suivant en mangeant 4 cerises
|
- **Progression de niveau** : Passer au niveau suivant en mangeant 4 cerises
|
||||||
|
- **Mode pause** : Espace ou Échap pour mettre en pause
|
||||||
|
|
||||||
### Bonus
|
### Bonus de base
|
||||||
| Bonus | Points | Effet |
|
| Bonus | Points | Effet | Niveau |
|
||||||
|-------|--------|-------|
|
|-------|--------|-------|--------|
|
||||||
| 🍒 Cerise | +100 | Active la poursuite des fantômes pendant un temps limité. 4 cerises = niveau suivant |
|
| 🍒 Cerise | +100 | Rend les fantômes vulnérables. 4 cerises = niveau suivant | 1+ |
|
||||||
| ⭐ Étoile "L" | +15 | Bonus supplémentaire |
|
| ⭐ É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
|
### 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
|
#### Types de fantômes
|
||||||
- **Vitesse croissante** : Les fantômes accélèrent à chaque niveau
|
| 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
|
### Labyrinthes
|
||||||
- 3 variantes de labyrinthes qui alternent à chaque niveau
|
- 3 variantes de labyrinthes qui alternent à chaque niveau
|
||||||
- Génération dynamique avec remplissage des espaces vides
|
- Génération dynamique avec remplissage des espaces vides
|
||||||
- Randomisation légère pour plus de variété
|
- 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/
|
pacmanludo/
|
||||||
├── index.html # Page HTML principale
|
├── index.html # Page HTML principale avec menu et interface
|
||||||
├── style.css # Feuille de styles
|
├── style.css # Feuille de styles complète (1547 lignes)
|
||||||
├── game.js # Logique du jeu
|
├── game.js # Logique du jeu (2954 lignes)
|
||||||
└── README.md # Documentation
|
└── README.md # Documentation
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -73,32 +141,55 @@ pacmanludo/
|
|||||||
2. **Ouvrir** `index.html` dans un navigateur web moderne
|
2. **Ouvrir** `index.html` dans un navigateur web moderne
|
||||||
3. **Jouer !**
|
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
|
## 🎯 Comment jouer
|
||||||
|
|
||||||
### Contrôles
|
### Contrôles
|
||||||
|
|
||||||
|
#### Clavier
|
||||||
| Touche | Action |
|
| Touche | Action |
|
||||||
|--------|--------|
|
|--------|--------|
|
||||||
| ↑ Flèche Haut | Déplacer vers le haut |
|
| ↑ Flèche Haut | Déplacer vers le haut |
|
||||||
| ↓ Flèche Bas | Déplacer vers le bas |
|
| ↓ Flèche Bas | Déplacer vers le bas |
|
||||||
| ← Flèche Gauche | Déplacer vers la gauche |
|
| ← Flèche Gauche | Déplacer vers la gauche |
|
||||||
| → Flèche Droite | Déplacer vers la droite |
|
| → 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
|
### Objectif
|
||||||
1. Collectez toutes les pastilles blanches pour marquer des points
|
1. Collectez toutes les pastilles blanches pour marquer des points
|
||||||
2. Mangez les cerises pour activer la poursuite et progresser
|
2. Mangez les cerises pour activer la poursuite et progresser
|
||||||
3. Évitez les fantômes !
|
3. Collectez les power-ups pour obtenir des avantages
|
||||||
4. Atteignez le score le plus élevé possible
|
4. Évitez les fantômes (ou mangez-les quand ils sont vulnérables) !
|
||||||
|
5. Atteignez le score le plus élevé possible
|
||||||
|
|
||||||
### Progression
|
### Progression
|
||||||
- **4 cerises mangées** = passage au niveau suivant
|
- **4 cerises mangées** = passage au niveau suivant
|
||||||
- À chaque niveau :
|
- À chaque niveau :
|
||||||
- Pac-Man accélère légèrement (+5% par 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)
|
- 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 :
|
Structure HTML de la page :
|
||||||
|
|
||||||
```html
|
```html
|
||||||
├── main-wrapper
|
├── main-menu (menu principal)
|
||||||
│ ├── container (zone de jeu)
|
│ ├── menuBackgroundCanvas (animation de fond)
|
||||||
│ │ ├── Titre "OULVIC"
|
│ └── menu-container
|
||||||
│ │ ├── Champ nom d'utilisateur
|
│ ├── Titre "OULVIC"
|
||||||
│ │ ├── Barre d'informations (score, niveau, vies, statut)
|
│ ├── Meilleur score
|
||||||
│ │ ├── Canvas du jeu (600x600)
|
│ └── Boutons (Jouer, Personnaliser, Classement, Règles)
|
||||||
│ │ └── Instructions et bouton rejouer
|
├── game-wrapper (interface de jeu)
|
||||||
│ └── leaderboard-container (classement)
|
│ ├── main-wrapper
|
||||||
└── footer (crédits)
|
│ │ ├── 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`
|
### `style.css`
|
||||||
|
|
||||||
| Section | Description |
|
| 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 |
|
| Layout | Flexbox pour disposition responsive |
|
||||||
| Game Info | Styles pour score, niveau, vies |
|
| Game Info | Styles pour score, niveau, vies, combo |
|
||||||
| Canvas | Bordure dorée, ombres |
|
| Canvas | Bordure dorée, ombres, effets de glow |
|
||||||
| Leaderboard | Tableau des scores stylisé |
|
| Leaderboard | Tableau des scores stylisé avec médailles |
|
||||||
| Responsive | Media queries pour mobile |
|
| 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`
|
### `game.js`
|
||||||
|
|
||||||
#### Constantes principales
|
#### Constantes principales
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
CELL_SIZE = 20 // Taille d'une cellule en pixels
|
CELL_SIZE = 25 // Taille d'une cellule en pixels
|
||||||
COLS = 30 // Nombre de colonnes
|
COLS = 30 // Nombre de colonnes
|
||||||
ROWS = 30 // Nombre de lignes
|
ROWS = 30 // Nombre de lignes
|
||||||
|
|
||||||
@ -147,6 +256,27 @@ EMPTY = 0 // Vide
|
|||||||
TUNNEL = 3 // Tunnel (passage fantômes)
|
TUNNEL = 3 // Tunnel (passage fantômes)
|
||||||
BONUS_CHERRY = 4 // Cerise bonus
|
BONUS_CHERRY = 4 // Cerise bonus
|
||||||
BONUS_LUDO = 5 // Étoile 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.
|
Gère le personnage jouable.
|
||||||
|
|
||||||
|
#### Propriétés
|
||||||
|
|
||||||
| Propriété | Type | Description |
|
| Propriété | Type | Description |
|
||||||
|-----------|------|-------------|
|
|-----------|------|-------------|
|
||||||
| `x, y` | number | Position en cellules |
|
| `x, y` | number | Position en cellules |
|
||||||
| `pixelX, pixelY` | number | Position en pixels (mouvement fluide) |
|
| `pixelX, pixelY` | number | Position en pixels (mouvement fluide) |
|
||||||
| `direction` | number | Direction actuelle (0=haut, 1=droite, 2=bas, 3=gauche) |
|
| `direction` | number | Direction actuelle (0=haut, 1=droite, 2=bas, 3=gauche) |
|
||||||
| `nextDirection` | number | Prochaine direction demandée |
|
| `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 |
|
| `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 |
|
| Méthode | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `update()` | Met à jour la position et l'animation |
|
| `update()` | Met à jour la position, l'animation et collecte les objets |
|
||||||
| `canMove(direction)` | Vérifie si le mouvement est possible |
|
| `canMove(direction)` | Vérifie si le mouvement est possible dans une direction |
|
||||||
| `collectDot()` | Collecte les pastilles et bonus |
|
| `collectDot()` | Collecte les pastilles, bonus et active les power-ups |
|
||||||
| `draw()` | Dessine Pac-Man sur le canvas |
|
| `draw()` | Dessine Pac-Man sur le canvas avec la couleur et forme choisies |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Classe `Ghost`
|
### 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 |
|
| Propriété | Type | Description |
|
||||||
|-----------|------|-------------|
|
|-----------|------|-------------|
|
||||||
| `x, y` | number | Position en cellules |
|
| `x, y` | number | Position en cellules |
|
||||||
| `pixelX, pixelY` | number | Position en pixels |
|
| `pixelX, pixelY` | number | Position en pixels |
|
||||||
| `color` | string | Couleur du fantôme |
|
| `color` | string | Couleur du fantôme |
|
||||||
|
| `type` | string | Type de fantôme (normal, hunter, patrol, fast, invisible) |
|
||||||
| `direction` | number | Direction de déplacement |
|
| `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 |
|
| `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 |
|
| Méthode | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `update()` | Met à jour la position et l'IA |
|
| `update()` | Met à jour la position, l'IA et les états spéciaux |
|
||||||
| `updateSpeed()` | Ajuste la vitesse selon le niveau |
|
| `updateSpeed()` | Ajuste la vitesse selon le type et le niveau |
|
||||||
| `canMove(direction)` | Vérifie si le mouvement est possible |
|
| `canMove(direction)` | Vérifie si le mouvement est possible |
|
||||||
| `getDirectionToPacman(dirs)` | Calcule la meilleure direction vers Pac-Man |
|
| `getDirectionToPacman(dirs)` | Calcule la meilleure direction vers Pac-Man avec prédiction |
|
||||||
| `draw()` | Dessine le fantôme (corps + yeux) |
|
| `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`
|
### 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 |
|
| Propriété | Type | Description |
|
||||||
|-----------|------|-------------|
|
|-----------|------|-------------|
|
||||||
| `x, y` | number | Position en cellules |
|
| `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 |
|
| `animation` | number | Animation de pulsation |
|
||||||
|
|
||||||
|
#### Méthodes
|
||||||
|
|
||||||
| Méthode | Description |
|
| Méthode | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `update()` | Met à jour l'animation |
|
| `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 |
|
| Fonction | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `countDots()` | Compte le nombre de pastilles restantes |
|
| `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 |
|
| `fillEmptySpaces()` | Remplit les espaces vides avec des pastilles |
|
||||||
| `randomizeMaze()` | Ajoute des variations aléatoires au labyrinthe |
|
| `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
|
#### Gestion du jeu
|
||||||
|
|
||||||
@ -235,36 +414,96 @@ Gère les objets bonus (cerises, étoiles).
|
|||||||
| `initGame()` | Initialise une nouvelle partie |
|
| `initGame()` | Initialise une nouvelle partie |
|
||||||
| `gameLoop()` | Boucle principale du jeu (requestAnimationFrame) |
|
| `gameLoop()` | Boucle principale du jeu (requestAnimationFrame) |
|
||||||
| `checkCollisions()` | Détecte les collisions Pac-Man/fantômes |
|
| `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) |
|
| `restartCurrentLevel()` | Recommence le niveau actuel (après perte de vie) |
|
||||||
|
| `createGhosts()` | Crée les fantômes selon le niveau et leurs types |
|
||||||
|
|
||||||
#### Interface utilisateur
|
#### Interface utilisateur
|
||||||
|
|
||||||
| Fonction | Description |
|
| Fonction | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `updateLivesDisplay()` | Met à jour l'affichage des vies |
|
| `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 |
|
| `saveScore()` | Sauvegarde le score en localStorage |
|
||||||
| `getScores()` | Récupère les scores depuis 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
|
### Variables globales d'état
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let score = 0; // Score actuel
|
let score = 0; // Score actuel
|
||||||
let level = 1; // Niveau actuel
|
let level = 1; // Niveau actuel
|
||||||
let lives = 3; // Vies restantes
|
let lives = 3; // Vies restantes
|
||||||
let gameRunning = true; // État du jeu
|
let gameRunning = true; // État du jeu
|
||||||
let totalDots = 0; // Pastilles restantes
|
let isPaused = false; // État de pause
|
||||||
let cherriesEaten = 0; // Cerises mangées (niveau actuel)
|
let totalDots = 0; // Pastilles restantes
|
||||||
let isChangingLevel = false; // Flag de transition
|
let cherriesEaten = 0; // Cerises mangées (niveau actuel)
|
||||||
let cherryEatenRecently = false; // Mode poursuite activé
|
let isChangingLevel = false; // Flag de transition
|
||||||
let cherryEatenTimer = 0; // Durée du mode poursuite
|
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
|
### Système de stockage des scores
|
||||||
|
|
||||||
Les scores sont sauvegardés dans le `localStorage` du navigateur sous la clé `pacmanScores`.
|
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"
|
username: "NomJoueur", // Nom saisi ou "Anonyme"
|
||||||
score: 1500, // Score final
|
score: 1500, // Score final
|
||||||
|
level: 5, // Niveau atteint
|
||||||
date: "2025-12-01T..." // Date ISO
|
date: "2025-12-01T..." // Date ISO
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- Maximum **10 scores** conservés
|
- Maximum **10 scores** conservés
|
||||||
- Triés par score décroissant
|
- Triés par score décroissant
|
||||||
|
- Affichage avec médailles pour le top 3 (🥇 🥈 🥉)
|
||||||
---
|
|
||||||
|
|
||||||
### Labyrinthes
|
### Labyrinthes
|
||||||
|
|
||||||
@ -293,8 +532,45 @@ mazeIndex = (level - 1) % 3
|
|||||||
```
|
```
|
||||||
|
|
||||||
Les labyrinthes sont modifiés dynamiquement :
|
Les labyrinthes sont modifiés dynamiquement :
|
||||||
1. `fillEmptySpaces()` - Remplit les zones vides
|
1. `fillEmptySpaces()` - Remplit les zones vides avec des pastilles
|
||||||
2. `randomizeMaze()` - Ajoute de la variété
|
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) |
|
| Fond | `#0a0a0a` (noir) |
|
||||||
| Murs | `#0000ff` (bleu) |
|
| Murs | `#0000ff` (bleu) |
|
||||||
| Pastilles | `#ffffff` (blanc) |
|
| Pastilles | `#ffffff` (blanc) |
|
||||||
| Texte titre | `#ffd700` (or) |
|
| Texte titre | `#ffd700` (or) avec animation arc-en-ciel |
|
||||||
| Cerises | `#ff0000` (rouge) |
|
| 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
|
### Fantômes
|
||||||
- Rouge : `#ff0000`
|
- Rouge : `#ff0000`
|
||||||
- Magenta : `#ff00ff`
|
- Magenta : `#ff00ff`
|
||||||
- Cyan : `#00ffff`
|
- Cyan : `#00ffff`
|
||||||
- Orange : `#ffa500`
|
- Orange : `#ffa500`
|
||||||
|
- Vert : `#00ff00`
|
||||||
|
- Jaune : `#ffff00`
|
||||||
|
|
||||||
### Pac-Man
|
### 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` :
|
Dans `game.js` :
|
||||||
|
|
||||||
```javascript
|
```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;
|
this.baseSpeed = 0.15;
|
||||||
|
|
||||||
// Vitesse de base des fantômes (ligne 277)
|
// Accélération par niveau (ligne ~1504)
|
||||||
this.baseSpeed = 0.1;
|
|
||||||
|
|
||||||
// Accélération par niveau
|
|
||||||
pacman.speed = pacman.baseSpeed * (1 + (level - 1) * 0.05);
|
pacman.speed = pacman.baseSpeed * (1 + (level - 1) * 0.05);
|
||||||
ghost.speed = ghost.baseSpeed * (1 + (level - 1) * 0.2);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Modifier les points
|
### Modifier les points
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Pastille (ligne 213)
|
// Pastille (ligne ~357)
|
||||||
score += 10;
|
let points = 10 * comboMultiplier * (frenzyModeActive ? 3 : 1) * scoreMultiplierValue;
|
||||||
|
|
||||||
// Cerise (ligne 224)
|
// Cerise (ligne ~372)
|
||||||
score += 100;
|
score += 100;
|
||||||
|
|
||||||
// Étoile Ludo (ligne 241)
|
// Étoile Ludo (ligne ~401)
|
||||||
score += 15;
|
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
|
## 📄 Licence
|
||||||
@ -365,5 +705,26 @@ Projet créé par **Ludo** et **Syoul**.
|
|||||||
|
|
||||||
- Inspiration : Pac-Man original (Namco, 1980)
|
- Inspiration : Pac-Man original (Namco, 1980)
|
||||||
- Développement : Ludo & Syoul
|
- 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
|
// Restaurer la couleur originale
|
||||||
ctx.fillStyle = baseFillColor;
|
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 {
|
} else {
|
||||||
// Forme ronde (par défaut)
|
// Forme ronde (par défaut)
|
||||||
if (this.mouthOpen) {
|
if (this.mouthOpen) {
|
||||||
@ -2339,9 +2457,25 @@ class MenuGhost {
|
|||||||
this.size = 30 + Math.random() * 20;
|
this.size = 30 + Math.random() * 20;
|
||||||
this.color = ['#ff0000', '#ff00ff', '#00ffff', '#ffa500'][Math.floor(Math.random() * 4)];
|
this.color = ['#ff0000', '#ff00ff', '#00ffff', '#ffa500'][Math.floor(Math.random() * 4)];
|
||||||
this.animation = Math.random() * Math.PI * 2;
|
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() {
|
update() {
|
||||||
|
if (this.isDestroyed) {
|
||||||
|
this.destroyAnimation += 0.1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.x += this.speedX;
|
this.x += this.speedX;
|
||||||
this.y += this.speedY;
|
this.y += this.speedY;
|
||||||
this.animation += 0.05;
|
this.animation += 0.05;
|
||||||
@ -2360,6 +2494,33 @@ class MenuGhost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
draw() {
|
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.save();
|
||||||
menuCtx.translate(this.x, this.y);
|
menuCtx.translate(this.x, this.y);
|
||||||
|
|
||||||
@ -2440,6 +2601,13 @@ function animateMenu() {
|
|||||||
ghost.draw();
|
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);
|
menuAnimationFrame = requestAnimationFrame(animateMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2488,6 +2656,25 @@ if (mainMenu) {
|
|||||||
menuObserver.observe(mainMenu, { attributes: true, attributeFilter: ['style'] });
|
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 ===
|
// === GESTION DU MENU ===
|
||||||
// Afficher le jeu
|
// Afficher le jeu
|
||||||
playBtn.addEventListener('click', () => {
|
playBtn.addEventListener('click', () => {
|
||||||
@ -2748,6 +2935,120 @@ function startPreviewAnimation() {
|
|||||||
};
|
};
|
||||||
ctx.fillStyle = colorMap[selectedColor] || '#ffd700';
|
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 {
|
} else {
|
||||||
// Dessiner un cercle
|
// Dessiner un cercle
|
||||||
if (mouthOpen) {
|
if (mouthOpen) {
|
||||||
|
|||||||
@ -287,6 +287,10 @@
|
|||||||
<div class="shape-preview triangle-preview"></div>
|
<div class="shape-preview triangle-preview"></div>
|
||||||
<span>Triangle</span>
|
<span>Triangle</span>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<div class="customize-preview">
|
<div class="customize-preview">
|
||||||
|
|||||||
17
style.css
17
style.css
@ -156,7 +156,7 @@ body::after {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
pointer-events: none;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-container {
|
.menu-container {
|
||||||
@ -1539,6 +1539,21 @@ footer {
|
|||||||
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.3));
|
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 {
|
.shape-option span {
|
||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
|||||||
Reference in New Issue
Block a user