sync_salles
This commit is contained in:
parent
2a9f76b054
commit
4dd29d59d7
147
Patchs/sync_salles/README_SYNC_SALLES.md
Normal file
147
Patchs/sync_salles/README_SYNC_SALLES.md
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# Sync multi des nouvelles salles (GestionnaireMultiSalles v2.0)
|
||||||
|
|
||||||
|
## Contexte
|
||||||
|
|
||||||
|
Ton dernier test a révélé que j'avais synchronisé le mauvais système :
|
||||||
|
- Tu utilises **`GestionnaireMultiSalles`** (achat "Nouvelle salle Ouest/Est/Nord/Sud" au Terminal)
|
||||||
|
- J'avais synchronisé **`AgrandissementSalle`** (panneau mural, qui n'est plus utilisé)
|
||||||
|
|
||||||
|
Ce pack corrige ça.
|
||||||
|
|
||||||
|
## Ce qui change
|
||||||
|
|
||||||
|
Un seul fichier patché : **`GestionnaireMultiSalles.cs` v2.0**
|
||||||
|
|
||||||
|
Changements :
|
||||||
|
1. La classe passe de `MonoBehaviour` à **`NetworkBehaviour`**
|
||||||
|
2. Ajout d'une **`SyncList<string>`** qui contient les créations de salles (ex: `"1_Ouest"`, `"2_Nord"`)
|
||||||
|
3. Ajout d'un **`[ClientRpc] RpcLancerConstructionSalle`** qui broadcast l'ordre aux clients
|
||||||
|
4. `CreerNouvelleSalle` côté serveur :
|
||||||
|
- Enregistre dans la SyncList (pour rejoin)
|
||||||
|
- Broadcast le Rpc aux clients
|
||||||
|
5. Chaque instance (serveur + clients) exécute l'animation **localement** via `AnimationCreationSalleLocal`
|
||||||
|
6. Nouvelle méthode `OnStartClient` : les clients qui rejoignent en cours de partie relisent la SyncList et rejouent toutes les salles en mode **instantané** (pas d'animation)
|
||||||
|
7. Le code de génération est refactorisé : la partie "construire la géométrie de la salle + couloir" est extraite dans `CreerSalleGeometrie` pour être réutilisée par le flux normal ET le rejoin
|
||||||
|
8. Gardes `DedicatedServerMode.IsDedicatedServer` ajoutées partout où du Material/Light est créé (pas de shaders côté serveur)
|
||||||
|
|
||||||
|
## Setup Inspector
|
||||||
|
|
||||||
|
**Important** : `GestionnaireMultiSalles` est maintenant un `NetworkBehaviour`, donc il a besoin d'un `NetworkIdentity` sur son GameObject.
|
||||||
|
|
||||||
|
### Étapes
|
||||||
|
|
||||||
|
1. Dans la scène `Datacenter_01`, trouver le GameObject qui porte le composant `GestionnaireMultiSalles`
|
||||||
|
2. Si ce GameObject n'a **pas** de `NetworkIdentity` :
|
||||||
|
- `Add Component` → `NetworkIdentity`
|
||||||
|
- Dans l'Inspector du `NetworkIdentity`, **ne coche rien** (ni "Server Only" ni "Local Player Authority" — c'est un objet serveur autoritaire qui synchronise via la SyncList)
|
||||||
|
3. Sauver la scène (`Ctrl+S`)
|
||||||
|
|
||||||
|
### Note sur les autres composants
|
||||||
|
|
||||||
|
Tu peux **retirer** maintenant :
|
||||||
|
- `SynchronisationAgrandissement` (de la scène)
|
||||||
|
- `PlayerAgrandissementBridge` (du prefab joueur)
|
||||||
|
- Les 2 fichiers `.cs` correspondants (si tu veux nettoyer)
|
||||||
|
|
||||||
|
Puisque `AgrandissementSalle` n'est plus utilisé dans ta version actuelle, les 2 scripts que j'avais livrés hier ne servent plus à rien. Tu peux les garder dans le code mort s'il te semble possible d'y revenir plus tard.
|
||||||
|
|
||||||
|
## Actions à faire
|
||||||
|
|
||||||
|
1. **Remplacer** `Assets/Scripts/GestionnaireMultiSalles.cs` par la nouvelle version
|
||||||
|
2. **Vérifier / ajouter** `NetworkIdentity` sur le GameObject qui porte ce composant
|
||||||
|
3. **Sauver la scène**
|
||||||
|
4. **Build client Windows** (pour pouvoir tester)
|
||||||
|
5. **Build Dedicated Server Linux** et déployer sur la Debian
|
||||||
|
|
||||||
|
## Test à faire
|
||||||
|
|
||||||
|
### Test 1 — Solo (éditeur Play)
|
||||||
|
1. Lancer l'éditeur en Play (pas host)
|
||||||
|
2. Aller au Terminal, acheter "Nouvelle salle Ouest"
|
||||||
|
3. **Attendu** : le mur explose, couloir apparaît, salle Ouest se construit comme avant
|
||||||
|
|
||||||
|
### Test 2 — Host + Client
|
||||||
|
1. Éditeur en Host, un build Windows connecté en client
|
||||||
|
2. Le **host** achète "Nouvelle salle Est"
|
||||||
|
3. **Attendu** : les DEUX voient la même animation + couloir + salle créée
|
||||||
|
|
||||||
|
### Test 3 — Client déclencheur
|
||||||
|
1. Host + Client
|
||||||
|
2. Le **client** achète "Nouvelle salle Nord"
|
||||||
|
3. **Attendu** : les DEUX voient la même animation
|
||||||
|
|
||||||
|
### Test 4 — Dedicated + 2 clients
|
||||||
|
1. Dedicated sur Debian + 2 clients Windows
|
||||||
|
2. Un client achète "Nouvelle salle Sud"
|
||||||
|
3. **Attendu** : les 2 clients voient l'animation synchrone, le serveur logue la création
|
||||||
|
|
||||||
|
### Test 5 — Rejoin
|
||||||
|
1. Dedicated + 1 client
|
||||||
|
2. Le client achète "Nouvelle salle Ouest"
|
||||||
|
3. Un **2e client** se connecte APRÈS
|
||||||
|
4. **Attendu** : le 2e client voit la nouvelle salle + le couloir déjà construits (rejeu instantané, pas d'anim)
|
||||||
|
|
||||||
|
## Diagnostic
|
||||||
|
|
||||||
|
Dans les logs, tu devrais voir :
|
||||||
|
|
||||||
|
**Côté serveur** :
|
||||||
|
```
|
||||||
|
[MultiSalles] Salle enregistree dans SyncList : 1_Ouest (total : 1)
|
||||||
|
[MultiSalles] Debut construction locale salle Ouest depuis Salle 1 (instantane=False)
|
||||||
|
[MultiSalles] Mur DC_MurEst percé avec morceaux latéraux
|
||||||
|
[MultiSalles] Nouvelle salle creee localement : Salle 2 (15x10m) direction Ouest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Côté client** :
|
||||||
|
```
|
||||||
|
[MultiSalles] Rpc recu : construction salle Ouest depuis parent id=1
|
||||||
|
[MultiSalles] Debut construction locale salle Ouest depuis Salle 1 (instantane=False)
|
||||||
|
[MultiSalles] Mur DC_MurEst percé avec morceaux latéraux
|
||||||
|
[MultiSalles] Nouvelle salle creee localement : Salle 2 (15x10m) direction Ouest
|
||||||
|
```
|
||||||
|
|
||||||
|
**Si rien ne se passe côté client** malgré le Rpc envoyé :
|
||||||
|
- Vérifier que le GameObject qui porte `GestionnaireMultiSalles` a bien un `NetworkIdentity`
|
||||||
|
- Vérifier dans Mirror Inspector que le NetworkBehaviour est bien enregistré
|
||||||
|
|
||||||
|
**Si "Salle parente id=X introuvable localement, skip"** :
|
||||||
|
- Problème de timing : le client n'a pas encore sa salle 1 enregistrée
|
||||||
|
- Pas grave dans 99% des cas (la salle 1 s'enregistre au démarrage via `SalleDatacenter.Start()`), mais si ça arrive à un client qui rejoint tardivement, le rejeu va skipper proprement
|
||||||
|
|
||||||
|
## Ce qui n'est PAS encore synchronisé
|
||||||
|
|
||||||
|
- **Les équipements placés dans les nouvelles salles** : quand un joueur pose une baie dans la salle 2, ça passe par `BoutiqueReseau.TraiterPlacementBaieServeur` qui utilise bien `NetworkServer.Spawn`, donc **c'est déjà synchronisé**. Pas de travail.
|
||||||
|
- **L'état "mur percé" dans la SyncList** : je ne track pas séparément les percements de mur, je track la création de salle. Le percement est fait automatiquement quand la salle est créée (dans `AnimationCreationSalleLocal`). Donc pas de divergence possible.
|
||||||
|
- **L'ordre des salles créées** : la SyncList conserve l'ordre d'insertion. Si un client rejoint, il rejoue dans l'ordre → pas de conflit.
|
||||||
|
|
||||||
|
## Architecture en un schéma
|
||||||
|
|
||||||
|
```
|
||||||
|
Client achete "Nouvelle salle Ouest" au Terminal
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
BoutiqueReseau.DemanderCommandeSalle
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
PlayerBoutiqueCommande.CmdCommanderSalle (deja en place, aucun changement)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[SERVEUR] BoutiqueReseau.TraiterCommandeSalleServeur
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[SERVEUR] GestionnaireMultiSalles.CreerNouvelleSalle
|
||||||
|
│
|
||||||
|
├── Ajoute "1_Ouest" a _sallesSyncList (→ sync auto aux clients)
|
||||||
|
│
|
||||||
|
└── RpcLancerConstructionSalle(1, "Ouest")
|
||||||
|
│
|
||||||
|
├── [CLIENT 1] StartCoroutine(AnimationCreationSalleLocal(1, "Ouest", false))
|
||||||
|
├── [CLIENT 2] StartCoroutine(AnimationCreationSalleLocal(1, "Ouest", false))
|
||||||
|
└── [HOST si applicable] skip (deja joue cote serveur)
|
||||||
|
|
||||||
|
[NOUVEAU CLIENT qui rejoint plus tard]
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
OnStartClient : lit _sallesSyncList → rejoue chaque entree en mode instantane=true
|
||||||
|
```
|
||||||
888
Patchs/sync_salles/Scripts_Modifies/GestionnaireMultiSalles.cs
Normal file
888
Patchs/sync_salles/Scripts_Modifies/GestionnaireMultiSalles.cs
Normal file
@ -0,0 +1,888 @@
|
|||||||
|
// GestionnaireMultiSalles.cs - v2.0 Multi-compatible
|
||||||
|
|
||||||
|
using UnityEngine;
|
||||||
|
using Mirror;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gestionnaire central des salles du datacenter.
|
||||||
|
/// NetworkBehaviour singleton — a placer sur un GameObject persistant dans la scene
|
||||||
|
/// AVEC un NetworkIdentity.
|
||||||
|
///
|
||||||
|
/// v2.0 : Synchronisation multijoueur via SyncList + ClientRpc
|
||||||
|
/// - Le serveur est autoritaire : il valide l'achat, enregistre la salle creee
|
||||||
|
/// dans _sallesSyncList (synchronise automatiquement aux clients)
|
||||||
|
/// - Chaque client execute l'animation en local grace a RpcLancerConstructionSalle
|
||||||
|
/// - Un client qui rejoint en cours de partie recoit la SyncList et rejoue
|
||||||
|
/// toutes les salles deja construites en mode instantane (pas d'animation)
|
||||||
|
///
|
||||||
|
/// Gere :
|
||||||
|
/// - Le registre de toutes les salles (salle d'origine + extensions)
|
||||||
|
/// - La creation de nouvelles salles (percement mur, couloir, nouvelle salle)
|
||||||
|
/// - L'attribution d'IDs et noms uniques
|
||||||
|
/// - La synchronisation reseau en multi
|
||||||
|
///
|
||||||
|
/// Workflow (multi) :
|
||||||
|
/// 1. Client achete au Terminal → DemanderCommandeSalle → CmdCommanderSalle (deja en place)
|
||||||
|
/// 2. Serveur : TraiterCommandeSalleServeur → CreerNouvelleSalle → valide, enregistre
|
||||||
|
/// dans la SyncList, emet RpcLancerConstructionSalle(parentId, direction)
|
||||||
|
/// 3. Chaque instance (serveur + clients) execute AnimationCreationSalleLocal
|
||||||
|
/// avec les memes parametres → meme geometrie partout
|
||||||
|
/// 4. Un client qui rejoint : OnStartClient rejoue la SyncList en mode instantane
|
||||||
|
/// </summary>
|
||||||
|
public class GestionnaireMultiSalles : NetworkBehaviour
|
||||||
|
{
|
||||||
|
public static GestionnaireMultiSalles Instance { get; private set; }
|
||||||
|
|
||||||
|
[Header("Configuration couloirs")]
|
||||||
|
[Tooltip("Longueur du couloir entre deux salles (en mètres)")]
|
||||||
|
public float longueurCouloir = 3f;
|
||||||
|
[Tooltip("Largeur du couloir (ouverture dans le mur)")]
|
||||||
|
public float largeurCouloir = 4f;
|
||||||
|
|
||||||
|
[Header("Configuration nouvelles salles")]
|
||||||
|
[Tooltip("Dimensions par défaut d'une nouvelle salle")]
|
||||||
|
public float nouvelleSalleLongueur = 15f;
|
||||||
|
public float nouvelleSalleLargeur = 10f;
|
||||||
|
[Tooltip("Prix d'une nouvelle salle")]
|
||||||
|
public float prixNouvelleSalle = 50000f;
|
||||||
|
|
||||||
|
[Header("Animation")]
|
||||||
|
public int nombreDebris = 20;
|
||||||
|
public float forceExplosion = 4f;
|
||||||
|
|
||||||
|
[Header("État")]
|
||||||
|
public List<SalleInfo> salles = new List<SalleInfo>();
|
||||||
|
|
||||||
|
private int _nextId = 1;
|
||||||
|
|
||||||
|
// ==================== SYNC MULTI ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Liste synchronisee des creations de salles effectuees cote serveur.
|
||||||
|
/// Format des entrees : "parentId_direction" (ex: "1_Ouest", "2_Nord").
|
||||||
|
/// Les clients qui rejoignent en cours de partie rejouent ces entrees en
|
||||||
|
/// mode instantane pour reconstruire l'etat du monde.
|
||||||
|
/// </summary>
|
||||||
|
private readonly SyncList<string> _sallesSyncList = new SyncList<string>();
|
||||||
|
|
||||||
|
// ==================== SALLE INFO ====================
|
||||||
|
|
||||||
|
[System.Serializable]
|
||||||
|
public class SalleInfo
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public string nom;
|
||||||
|
public SalleDatacenter salle;
|
||||||
|
public Vector3 positionMonde;
|
||||||
|
public int salleParenteId; // -1 pour la salle d'origine
|
||||||
|
public string directionDepuisParent; // "Nord", "Sud", "Est", "Ouest"
|
||||||
|
|
||||||
|
public SalleInfo(int id, string nom, SalleDatacenter salle, Vector3 pos, int parentId, string direction)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
this.nom = nom;
|
||||||
|
this.salle = salle;
|
||||||
|
this.positionMonde = pos;
|
||||||
|
this.salleParenteId = parentId;
|
||||||
|
this.directionDepuisParent = direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== LIFECYCLE ====================
|
||||||
|
|
||||||
|
void Awake()
|
||||||
|
{
|
||||||
|
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnStartClient()
|
||||||
|
{
|
||||||
|
base.OnStartClient();
|
||||||
|
|
||||||
|
// Ne pas rejouer sur le host (il est deja a jour, c'est lui le serveur)
|
||||||
|
if (NetworkServer.active) return;
|
||||||
|
|
||||||
|
// Rejouer immediatement les entrees deja presentes dans la SyncList
|
||||||
|
if (_sallesSyncList.Count > 0)
|
||||||
|
{
|
||||||
|
Debug.Log($"[MultiSalles] Client rejoint : rejeu de {_sallesSyncList.Count} salle(s) deja construite(s)");
|
||||||
|
foreach (string id in _sallesSyncList)
|
||||||
|
{
|
||||||
|
RejouerCreationSalleInstantane(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== ENREGISTREMENT ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enregistre une salle existante (appelé par SalleDatacenter.Start() pour la salle d'origine).
|
||||||
|
/// </summary>
|
||||||
|
public SalleInfo EnregistrerSalle(SalleDatacenter salle, string nom = null, int parentId = -1, string direction = "")
|
||||||
|
{
|
||||||
|
// Vérifie si déjà enregistrée
|
||||||
|
foreach (var s in salles)
|
||||||
|
if (s.salle == salle) return s;
|
||||||
|
|
||||||
|
int id = _nextId++;
|
||||||
|
string nomSalle = nom ?? ("Salle " + id);
|
||||||
|
SalleInfo info = new SalleInfo(id, nomSalle, salle, salle.transform.position, parentId, direction);
|
||||||
|
salles.Add(info);
|
||||||
|
Debug.Log($"[MultiSalles] Salle enregistrée : {nomSalle} (ID={id}) @ {salle.transform.position}");
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== ACCESSEURS ====================
|
||||||
|
|
||||||
|
public SalleInfo GetSalleParId(int id)
|
||||||
|
{
|
||||||
|
foreach (var s in salles)
|
||||||
|
if (s.id == id) return s;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SalleInfo GetSalleParRef(SalleDatacenter salle)
|
||||||
|
{
|
||||||
|
foreach (var s in salles)
|
||||||
|
if (s.salle == salle) return s;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SalleInfo> GetToutesSalles() => salles;
|
||||||
|
|
||||||
|
public int GetNombreSalles() => salles.Count;
|
||||||
|
|
||||||
|
// ==================== VÉRIFICATIONS ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Vérifie si une direction est disponible pour une salle donnée.
|
||||||
|
/// </summary>
|
||||||
|
public bool DirectionDisponible(SalleDatacenter salleParente, string direction)
|
||||||
|
{
|
||||||
|
SalleInfo info = GetSalleParRef(salleParente);
|
||||||
|
if (info == null) return false;
|
||||||
|
|
||||||
|
// Vérifie qu'aucune salle n'est déjà connectée dans cette direction
|
||||||
|
foreach (var s in salles)
|
||||||
|
if (s.salleParenteId == info.id && s.directionDepuisParent == direction)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retourne les directions disponibles pour une salle.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> GetDirectionsDisponibles(SalleDatacenter salle)
|
||||||
|
{
|
||||||
|
List<string> dispo = new List<string>();
|
||||||
|
string[] toutes = { "Nord", "Sud", "Est", "Ouest" };
|
||||||
|
foreach (string dir in toutes)
|
||||||
|
if (DirectionDisponible(salle, dir))
|
||||||
|
dispo.Add(dir);
|
||||||
|
return dispo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== CRÉATION NOUVELLE SALLE ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// v2.0 : Point d'entree serveur pour creer une salle.
|
||||||
|
/// Cette methode est appelee cote serveur par BoutiqueReseau.TraiterCommandeSalleServeur
|
||||||
|
/// (qui est dans un flux Cmd venant du client). Elle valide, enregistre dans la SyncList
|
||||||
|
/// pour le rejoin, puis broadcast a tous les clients pour qu'ils executent l'animation
|
||||||
|
/// en local.
|
||||||
|
///
|
||||||
|
/// En mode solo (pas de Mirror actif), execute directement en local.
|
||||||
|
/// </summary>
|
||||||
|
public bool CreerNouvelleSalle(SalleDatacenter salleParente, string direction)
|
||||||
|
{
|
||||||
|
SalleInfo parentInfo = GetSalleParRef(salleParente);
|
||||||
|
if (parentInfo == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[MultiSalles] Salle parente non enregistrée !");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DirectionDisponible(salleParente, direction))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MultiSalles] Direction {direction} déjà occupée !");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode solo : execution directe
|
||||||
|
if (!NetworkServer.active && !NetworkClient.active)
|
||||||
|
{
|
||||||
|
Debug.Log($"[MultiSalles] Mode solo : creation directe de la salle {direction} depuis {parentInfo.nom}");
|
||||||
|
StartCoroutine(AnimationCreationSalleLocal(parentInfo.id, direction, false));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode multi : SEUL LE SERVEUR PEUT CREER. Si on n'est pas serveur, refuser.
|
||||||
|
// (cette methode n'est censee etre appelee que depuis TraiterCommandeSalleServeur
|
||||||
|
// qui tourne sur le serveur apres une Cmd client)
|
||||||
|
if (!NetworkServer.active)
|
||||||
|
{
|
||||||
|
Debug.LogError("[MultiSalles] CreerNouvelleSalle appelee cote client ! Cela doit passer par une Cmd.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enregistrer dans la SyncList pour les futurs joueurs qui rejoignent
|
||||||
|
string id = ConstruireId(parentInfo.id, direction);
|
||||||
|
if (!_sallesSyncList.Contains(id))
|
||||||
|
{
|
||||||
|
_sallesSyncList.Add(id);
|
||||||
|
Debug.Log($"[MultiSalles] Salle enregistree dans SyncList : {id} (total : {_sallesSyncList.Count})");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast aux clients : chacun va executer l'animation en local
|
||||||
|
RpcLancerConstructionSalle(parentInfo.id, direction);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Version avec achat intégré — appelée par la boutique.
|
||||||
|
/// </summary>
|
||||||
|
public bool AcheterEtCreerSalle(SalleDatacenter salleParente, string direction)
|
||||||
|
{
|
||||||
|
if (GameEconomy.Instance != null && !GameEconomy.Instance.modeSandbox)
|
||||||
|
{
|
||||||
|
if (!GameEconomy.Instance.TenterAchat(prixNouvelleSalle))
|
||||||
|
{
|
||||||
|
Debug.Log("[MultiSalles] Fonds insuffisants !");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CreerNouvelleSalle(salleParente, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crée une nouvelle salle IMMÉDIATEMENT (sans animation, sans coroutine).
|
||||||
|
/// Utilisé par le SaveManager lors du chargement d'une sauvegarde.
|
||||||
|
/// Retourne la SalleDatacenter créée, ou null en cas d'erreur.
|
||||||
|
/// </summary>
|
||||||
|
public SalleDatacenter CreerNouvelleSalleImmediat(SalleDatacenter salleParente, string direction)
|
||||||
|
{
|
||||||
|
SalleInfo parentInfo = GetSalleParRef(salleParente);
|
||||||
|
if (parentInfo == null)
|
||||||
|
{
|
||||||
|
Debug.LogError("[MultiSalles] Salle parente non enregistrée pour création immédiate !");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution immediate locale (pas de reseau, juste la geometrie)
|
||||||
|
return CreerSalleGeometrie(parentInfo, direction, instantane: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== RPC CLIENTS ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// v2.0 : Diffuse a tous les clients (+ le host) pour lancer l'animation de construction
|
||||||
|
/// en local. Chaque client execute sa propre copie avec les memes parametres.
|
||||||
|
/// </summary>
|
||||||
|
[ClientRpc]
|
||||||
|
private void RpcLancerConstructionSalle(int parentId, string direction)
|
||||||
|
{
|
||||||
|
// Sur le host, le serveur a deja joue l'animation (c'est lui qui a valide).
|
||||||
|
// On evite de la rejouer.
|
||||||
|
if (NetworkServer.active && isClient) return;
|
||||||
|
|
||||||
|
Debug.Log($"[MultiSalles] Rpc recu : construction salle {direction} depuis parent id={parentId}");
|
||||||
|
StartCoroutine(AnimationCreationSalleLocal(parentId, direction, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Execute l'animation ET la construction geometrique en local.
|
||||||
|
/// Cote serveur OU cote client : chacun genere sa propre copie.
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerator AnimationCreationSalleLocal(int parentId, string direction, bool instantane)
|
||||||
|
{
|
||||||
|
SalleInfo parentInfo = GetSalleParId(parentId);
|
||||||
|
if (parentInfo == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MultiSalles] Salle parente id={parentId} introuvable localement, skip");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parentInfo.salle == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MultiSalles] SalleDatacenter de la salle parente id={parentId} est null, skip");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifier qu'on n'a pas deja construit cette salle localement (protection idempotence)
|
||||||
|
foreach (var s in salles)
|
||||||
|
{
|
||||||
|
if (s.salleParenteId == parentId && s.directionDepuisParent == direction)
|
||||||
|
{
|
||||||
|
Debug.Log($"[MultiSalles] Salle {direction} depuis parent {parentId} deja construite localement, skip");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SalleDatacenter salleParente = parentInfo.salle;
|
||||||
|
Debug.Log($"[MultiSalles] Debut construction locale salle {direction} depuis {parentInfo.nom} (instantane={instantane})");
|
||||||
|
|
||||||
|
// === PHASE 1 : Percement du mur (anime ou instantane) ===
|
||||||
|
if (instantane || DedicatedServerMode.IsDedicatedServer)
|
||||||
|
{
|
||||||
|
SupprimerMur(salleParente, direction);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return StartCoroutine(PercerMur(salleParente, direction));
|
||||||
|
}
|
||||||
|
|
||||||
|
// === PHASE 2 : Construction geometrique de la nouvelle salle ===
|
||||||
|
// Attente courte pour laisser les debris jouer (anime seulement)
|
||||||
|
if (!instantane && !DedicatedServerMode.IsDedicatedServer)
|
||||||
|
yield return new WaitForSeconds(0.5f);
|
||||||
|
|
||||||
|
CreerSalleGeometrie(parentInfo, direction, instantane);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// v2.0 : Methode centrale qui cree la geometrie (couloir + salle + percement mur nouvelle salle).
|
||||||
|
/// Pas de coroutine ici : appel direct, retourne immediatement la SalleDatacenter creee.
|
||||||
|
/// Utilisee par :
|
||||||
|
/// - AnimationCreationSalleLocal (apres l'animation de percement)
|
||||||
|
/// - CreerNouvelleSalleImmediat (SaveManager)
|
||||||
|
/// </summary>
|
||||||
|
private SalleDatacenter CreerSalleGeometrie(SalleInfo parentInfo, string direction, bool instantane)
|
||||||
|
{
|
||||||
|
SalleDatacenter salleParente = parentInfo.salle;
|
||||||
|
Vector3 origineParente = salleParente.transform.position;
|
||||||
|
float pL = salleParente.longueur;
|
||||||
|
float pW = salleParente.largeur;
|
||||||
|
float ep = salleParente.epaisseurMur;
|
||||||
|
|
||||||
|
Vector3 origineNouvelle = CalculerPositionNouvelleSalle(origineParente, pL, pW, ep, direction);
|
||||||
|
float nouvL = nouvelleSalleLongueur;
|
||||||
|
float nouvW = nouvelleSalleLargeur;
|
||||||
|
|
||||||
|
// Pour Est/Ouest la nouvelle salle garde la même largeur (= même Z)
|
||||||
|
// Pour Nord/Sud la nouvelle salle garde la même longueur (= même X)
|
||||||
|
if (direction == "Est" || direction == "Ouest")
|
||||||
|
nouvW = pW;
|
||||||
|
else
|
||||||
|
nouvL = pL;
|
||||||
|
|
||||||
|
// Couloir
|
||||||
|
Vector3 debutCouloir, finCouloir;
|
||||||
|
CalculerPositionsCouloir(origineParente, pL, pW, ep, direction, out debutCouloir, out finCouloir);
|
||||||
|
GenererCouloir(debutCouloir, finCouloir, direction, salleParente);
|
||||||
|
|
||||||
|
// Nouvelle salle (nom base sur parentId+direction pour garantir meme nom partout)
|
||||||
|
GameObject nouvelleSalleObj = new GameObject($"SalleDatacenter_p{parentInfo.id}_{direction}");
|
||||||
|
nouvelleSalleObj.transform.position = origineNouvelle;
|
||||||
|
|
||||||
|
SalleDatacenter nouvelleSalle = nouvelleSalleObj.AddComponent<SalleDatacenter>();
|
||||||
|
CopierParametresSalle(salleParente, nouvelleSalle);
|
||||||
|
nouvelleSalle.longueur = nouvL;
|
||||||
|
nouvelleSalle.largeur = nouvW;
|
||||||
|
|
||||||
|
// Note : GenererSalle() va appeler Start() de SalleDatacenter qui va
|
||||||
|
// tenter de s'auto-enregistrer. L'enregistrement se fait la si c'est la premiere fois.
|
||||||
|
nouvelleSalle.GenererSalle();
|
||||||
|
|
||||||
|
// Percer le mur de la nouvelle salle cote couloir
|
||||||
|
string dirOpposee = GetDirectionOpposee(direction);
|
||||||
|
SupprimerMur(nouvelleSalle, dirOpposee);
|
||||||
|
|
||||||
|
// Enregistrement local
|
||||||
|
SalleInfo newInfo = EnregistrerSalle(nouvelleSalle, null, parentInfo.id, direction);
|
||||||
|
Debug.Log($"[MultiSalles] Nouvelle salle creee localement : {newInfo.nom} ({nouvL}x{nouvW}m) direction {direction}");
|
||||||
|
|
||||||
|
// Audio (cote client seulement)
|
||||||
|
if (!DedicatedServerMode.IsDedicatedServer && AudioManager.Instance != null)
|
||||||
|
AudioManager.Instance.JouerNotification();
|
||||||
|
|
||||||
|
// Notifier la boutique que la construction est terminee
|
||||||
|
if (OnConstructionTerminee != null) OnConstructionTerminee(direction, newInfo.nom);
|
||||||
|
|
||||||
|
return nouvelleSalle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rejouer une entree de la SyncList en mode instantane (pour un client qui rejoint).
|
||||||
|
/// </summary>
|
||||||
|
private void RejouerCreationSalleInstantane(string id)
|
||||||
|
{
|
||||||
|
int parentId;
|
||||||
|
string direction;
|
||||||
|
if (!DecomposerId(id, out parentId, out direction))
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MultiSalles] Id mal forme : {id}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lancer la coroutine en mode instantane
|
||||||
|
StartCoroutine(AnimationCreationSalleLocal(parentId, direction, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Événement déclenché quand une salle est construite.
|
||||||
|
/// Paramètres : direction, nom de la salle.
|
||||||
|
/// </summary>
|
||||||
|
public System.Action<string, string> OnConstructionTerminee;
|
||||||
|
|
||||||
|
// ==================== CALCULS POSITION ====================
|
||||||
|
|
||||||
|
Vector3 CalculerPositionNouvelleSalle(Vector3 origineParente, float pL, float pW, float ep, string direction)
|
||||||
|
{
|
||||||
|
float corridor = longueurCouloir + ep * 2; // mur parent + couloir + mur nouvelle salle
|
||||||
|
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case "Nord":
|
||||||
|
return new Vector3(origineParente.x, origineParente.y, origineParente.z + pW + corridor);
|
||||||
|
case "Sud":
|
||||||
|
float nouvW_sud = nouvelleSalleLargeur;
|
||||||
|
// Pour Nord/Sud, on utilise la même longueur X que le parent
|
||||||
|
return new Vector3(origineParente.x, origineParente.y, origineParente.z - nouvW_sud - corridor);
|
||||||
|
case "Est":
|
||||||
|
return new Vector3(origineParente.x + pL + corridor, origineParente.y, origineParente.z);
|
||||||
|
case "Ouest":
|
||||||
|
float nouvL_ouest = nouvelleSalleLongueur;
|
||||||
|
return new Vector3(origineParente.x - nouvL_ouest - corridor, origineParente.y, origineParente.z);
|
||||||
|
default:
|
||||||
|
return origineParente;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CalculerPositionsCouloir(Vector3 origineParente, float pL, float pW, float ep, string direction,
|
||||||
|
out Vector3 debut, out Vector3 fin)
|
||||||
|
{
|
||||||
|
// Le couloir commence à la face EXTÉRIEURE du mur de la salle parente
|
||||||
|
// et se termine à la face EXTÉRIEURE du mur de la nouvelle salle.
|
||||||
|
// Les murs sont percés, donc le couloir doit couvrir l'épaisseur des deux murs + l'espace entre.
|
||||||
|
// Position = face extérieure du mur parent → face extérieure du mur nouvelle salle
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case "Nord":
|
||||||
|
debut = origineParente + new Vector3(pL / 2f, 0, pW);
|
||||||
|
fin = debut + new Vector3(0, 0, longueurCouloir + ep * 2);
|
||||||
|
break;
|
||||||
|
case "Sud":
|
||||||
|
debut = origineParente + new Vector3(pL / 2f, 0, 0);
|
||||||
|
fin = debut + new Vector3(0, 0, -(longueurCouloir + ep * 2));
|
||||||
|
break;
|
||||||
|
case "Est":
|
||||||
|
debut = origineParente + new Vector3(pL, 0, pW / 2f);
|
||||||
|
fin = debut + new Vector3(longueurCouloir + ep * 2, 0, 0);
|
||||||
|
break;
|
||||||
|
case "Ouest":
|
||||||
|
debut = origineParente + new Vector3(0, 0, pW / 2f);
|
||||||
|
fin = debut + new Vector3(-(longueurCouloir + ep * 2), 0, 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
debut = fin = origineParente;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== PERCEMENT MUR ====================
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Perce un mur en le remplaçant par deux morceaux latéraux de chaque côté de l'ouverture.
|
||||||
|
/// L'ouverture fait largeurCouloir de large, centrée sur le mur.
|
||||||
|
/// </summary>
|
||||||
|
IEnumerator PercerMur(SalleDatacenter salle, string direction)
|
||||||
|
{
|
||||||
|
string nomMur = "DC_Mur" + direction;
|
||||||
|
Transform murT = null;
|
||||||
|
|
||||||
|
foreach (Transform child in salle.GetComponentsInChildren<Transform>())
|
||||||
|
{
|
||||||
|
if (child.name == nomMur) { murT = child; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (murT == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[MultiSalles] Mur {nomMur} introuvable — peut-être déjà percé");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sauvegarder les infos du mur avant destruction
|
||||||
|
Vector3 murPos = murT.position;
|
||||||
|
Vector3 murScale = murT.localScale;
|
||||||
|
Transform murParent = murT.parent;
|
||||||
|
|
||||||
|
// Cote serveur : pas de debris animes (pas de shader)
|
||||||
|
Material matMur = null;
|
||||||
|
if (!DedicatedServerMode.IsDedicatedServer)
|
||||||
|
{
|
||||||
|
matMur = new Material(Shader.Find("Standard"));
|
||||||
|
matMur.color = salle.couleurMur;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation debris (cote client uniquement)
|
||||||
|
List<GameObject> debris = null;
|
||||||
|
if (!DedicatedServerMode.IsDedicatedServer)
|
||||||
|
{
|
||||||
|
Vector3 dirVec = GetDirectionVector(direction);
|
||||||
|
debris = new List<GameObject>();
|
||||||
|
|
||||||
|
for (int i = 0; i < nombreDebris; i++)
|
||||||
|
{
|
||||||
|
GameObject d = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
d.name = "Debris_" + i;
|
||||||
|
float rx = Random.Range(-murScale.x / 3f, murScale.x / 3f);
|
||||||
|
float ry = Random.Range(0f, salle.hauteurMurs);
|
||||||
|
d.transform.position = murPos + murT.right * rx + Vector3.up * ry + Random.insideUnitSphere * 0.1f;
|
||||||
|
float sz = Random.Range(0.08f, 0.2f);
|
||||||
|
d.transform.localScale = new Vector3(sz, sz * Random.Range(0.5f, 1.5f), sz * Random.Range(0.3f, 0.8f));
|
||||||
|
d.transform.rotation = Random.rotation;
|
||||||
|
d.GetComponent<Renderer>().material = matMur;
|
||||||
|
Rigidbody rb = d.AddComponent<Rigidbody>();
|
||||||
|
rb.mass = Random.Range(0.5f, 1.5f);
|
||||||
|
rb.AddForce((dirVec + Vector3.up * Random.Range(0.2f, 0.5f) + Random.insideUnitSphere * 0.3f) * forceExplosion, ForceMode.Impulse);
|
||||||
|
rb.AddTorque(Random.insideUnitSphere * 2f, ForceMode.Impulse);
|
||||||
|
debris.Add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supprimer le mur original
|
||||||
|
Destroy(murT.gameObject);
|
||||||
|
|
||||||
|
// Attente + nettoyage debris (cote client uniquement)
|
||||||
|
if (!DedicatedServerMode.IsDedicatedServer)
|
||||||
|
{
|
||||||
|
yield return new WaitForSeconds(2f);
|
||||||
|
if (debris != null)
|
||||||
|
{
|
||||||
|
foreach (var d in debris)
|
||||||
|
if (d != null) Destroy(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer les deux morceaux de mur latéraux
|
||||||
|
CreerMorceauxMurLateraux(salle, direction, murPos, murScale, murParent, matMur);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remplace un mur supprimé par deux morceaux de chaque côté de l'ouverture.
|
||||||
|
/// Utilisé à la fois pour la salle parente (après animation) et la nouvelle salle (immédiat).
|
||||||
|
/// </summary>
|
||||||
|
public void SupprimerMur(SalleDatacenter salle, string direction)
|
||||||
|
{
|
||||||
|
string nomMur = "DC_Mur" + direction;
|
||||||
|
Transform murT = null;
|
||||||
|
|
||||||
|
foreach (Transform child in salle.GetComponentsInChildren<Transform>())
|
||||||
|
{
|
||||||
|
if (child.name == nomMur) { murT = child; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (murT == null) return;
|
||||||
|
|
||||||
|
Vector3 murPos = murT.position;
|
||||||
|
Vector3 murScale = murT.localScale;
|
||||||
|
Transform murParent = murT.parent;
|
||||||
|
|
||||||
|
Material matMur = null;
|
||||||
|
if (!DedicatedServerMode.IsDedicatedServer)
|
||||||
|
{
|
||||||
|
matMur = new Material(Shader.Find("Standard"));
|
||||||
|
matMur.color = salle.couleurMur;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestroyImmediate car on est souvent dans la même frame que GenererSalle
|
||||||
|
DestroyImmediate(murT.gameObject);
|
||||||
|
|
||||||
|
CreerMorceauxMurLateraux(salle, direction, murPos, murScale, murParent, matMur);
|
||||||
|
Debug.Log($"[MultiSalles] Mur {nomMur} percé avec morceaux latéraux");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crée deux morceaux de mur de chaque côté de l'ouverture du couloir.
|
||||||
|
/// </summary>
|
||||||
|
void CreerMorceauxMurLateraux(SalleDatacenter salle, string direction,
|
||||||
|
Vector3 murPos, Vector3 murScale, Transform murParent, Material matMur)
|
||||||
|
{
|
||||||
|
float hauteurMurs = salle.hauteurMurs;
|
||||||
|
float ep = salle.epaisseurMur;
|
||||||
|
|
||||||
|
// Déterminer la longueur totale du mur et l'axe
|
||||||
|
bool estNordSud = (direction == "Nord" || direction == "Sud");
|
||||||
|
float longueurMur = estNordSud ? murScale.x : murScale.z; // longueur du mur original
|
||||||
|
float demiOuverture = largeurCouloir / 2f;
|
||||||
|
float longueurMorceau = (longueurMur - largeurCouloir) / 2f;
|
||||||
|
|
||||||
|
if (longueurMorceau <= 0.01f) return; // pas de place pour les morceaux
|
||||||
|
|
||||||
|
for (int cote = 0; cote < 2; cote++)
|
||||||
|
{
|
||||||
|
float signe = (cote == 0) ? -1f : 1f;
|
||||||
|
float offset = signe * (demiOuverture + longueurMorceau / 2f);
|
||||||
|
|
||||||
|
Vector3 morceauPos = murPos;
|
||||||
|
Vector3 morceauScale = murScale;
|
||||||
|
|
||||||
|
if (estNordSud)
|
||||||
|
{
|
||||||
|
morceauPos.x += offset;
|
||||||
|
morceauScale.x = longueurMorceau;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
morceauPos.z += offset;
|
||||||
|
morceauScale.z = longueurMorceau;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameObject morceau = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
morceau.name = $"DC_Mur{direction}_Morceau_{cote}";
|
||||||
|
morceau.transform.SetParent(murParent);
|
||||||
|
morceau.transform.position = morceauPos;
|
||||||
|
morceau.transform.localScale = morceauScale;
|
||||||
|
if (morceau.GetComponent<Renderer>() != null && matMur != null)
|
||||||
|
morceau.GetComponent<Renderer>().sharedMaterial = matMur;
|
||||||
|
morceau.isStatic = true;
|
||||||
|
// Le BoxCollider est déjà ajouté par CreatePrimitive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== COULOIR ====================
|
||||||
|
|
||||||
|
GameObject GenererCouloir(Vector3 debut, Vector3 fin, string direction, SalleDatacenter salleRef)
|
||||||
|
{
|
||||||
|
GameObject couloir = new GameObject($"Couloir_{direction}_{salles.Count + 1}");
|
||||||
|
|
||||||
|
float hauteurSol = salleRef.hauteurSol;
|
||||||
|
float hauteurMurs = salleRef.hauteurMurs;
|
||||||
|
float hauteurPlafond = salleRef.hauteurPlafond;
|
||||||
|
float epaisseurDalle = salleRef.epaisseurDalle;
|
||||||
|
|
||||||
|
// Materiels : skip cote serveur
|
||||||
|
Material matSol = null, matMur = null, matPlafond = null, matNeon = null;
|
||||||
|
if (!DedicatedServerMode.IsDedicatedServer)
|
||||||
|
{
|
||||||
|
matSol = new Material(Shader.Find("Standard"));
|
||||||
|
matSol.color = salleRef.couleurDalleSol;
|
||||||
|
matSol.SetFloat("_Metallic", 0.1f);
|
||||||
|
matSol.SetFloat("_Glossiness", 0.3f);
|
||||||
|
|
||||||
|
matMur = new Material(Shader.Find("Standard"));
|
||||||
|
matMur.color = salleRef.couleurMur;
|
||||||
|
|
||||||
|
matPlafond = new Material(Shader.Find("Standard"));
|
||||||
|
matPlafond.color = salleRef.couleurPlafondDalle;
|
||||||
|
|
||||||
|
matNeon = new Material(Shader.Find("Standard"));
|
||||||
|
matNeon.color = salleRef.couleurNeon;
|
||||||
|
matNeon.EnableKeyword("_EMISSION");
|
||||||
|
matNeon.SetColor("_EmissionColor", salleRef.couleurNeon * 2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calcul dimensions
|
||||||
|
Vector3 centre = (debut + fin) / 2f;
|
||||||
|
bool estNordSud = (direction == "Nord" || direction == "Sud");
|
||||||
|
|
||||||
|
float couloirLong = longueurCouloir;
|
||||||
|
float couloirLarg = largeurCouloir;
|
||||||
|
|
||||||
|
float solX, solZ, murLong;
|
||||||
|
if (estNordSud)
|
||||||
|
{
|
||||||
|
solX = couloirLarg;
|
||||||
|
solZ = couloirLong;
|
||||||
|
murLong = couloirLong;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
solX = couloirLong;
|
||||||
|
solZ = couloirLarg;
|
||||||
|
murLong = couloirLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sol
|
||||||
|
GameObject sol = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
sol.name = "Couloir_Sol";
|
||||||
|
sol.transform.SetParent(couloir.transform);
|
||||||
|
sol.transform.position = centre + Vector3.up * hauteurSol;
|
||||||
|
sol.transform.localScale = new Vector3(solX, epaisseurDalle, solZ);
|
||||||
|
if (sol.GetComponent<Renderer>() != null && matSol != null)
|
||||||
|
sol.GetComponent<Renderer>().sharedMaterial = matSol;
|
||||||
|
sol.isStatic = true;
|
||||||
|
|
||||||
|
// Sol collider — sommet au niveau de la surface de marche (TOUJOURS cree, serveur et client)
|
||||||
|
GameObject solCol = new GameObject("Couloir_SolCollider");
|
||||||
|
solCol.transform.SetParent(couloir.transform);
|
||||||
|
float solSommet = hauteurSol + epaisseurDalle / 2f + 0.01f;
|
||||||
|
float solBase = -2f;
|
||||||
|
float solEp = solSommet - solBase;
|
||||||
|
solCol.transform.position = centre + Vector3.up * ((solSommet + solBase) / 2f);
|
||||||
|
BoxCollider sc = solCol.AddComponent<BoxCollider>();
|
||||||
|
sc.size = new Vector3(solX + 0.5f, solEp, solZ + 0.5f);
|
||||||
|
int layerSol = LayerMask.NameToLayer("Sol");
|
||||||
|
if (layerSol >= 0) solCol.layer = layerSol;
|
||||||
|
|
||||||
|
// Murs latéraux
|
||||||
|
float mY = hauteurMurs / 2f;
|
||||||
|
float ep = salleRef.epaisseurMur;
|
||||||
|
|
||||||
|
if (estNordSud)
|
||||||
|
{
|
||||||
|
// Murs gauche et droit (le long de X)
|
||||||
|
GameObject murG = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
murG.name = "Couloir_MurG";
|
||||||
|
murG.transform.SetParent(couloir.transform);
|
||||||
|
murG.transform.position = centre + new Vector3(-couloirLarg / 2f - ep / 2f, mY, 0);
|
||||||
|
murG.transform.localScale = new Vector3(ep, hauteurMurs, solZ);
|
||||||
|
if (murG.GetComponent<Renderer>() != null && matMur != null)
|
||||||
|
murG.GetComponent<Renderer>().sharedMaterial = matMur;
|
||||||
|
murG.isStatic = true;
|
||||||
|
murG.AddComponent<BoxCollider>();
|
||||||
|
|
||||||
|
GameObject murD = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
murD.name = "Couloir_MurD";
|
||||||
|
murD.transform.SetParent(couloir.transform);
|
||||||
|
murD.transform.position = centre + new Vector3(couloirLarg / 2f + ep / 2f, mY, 0);
|
||||||
|
murD.transform.localScale = new Vector3(ep, hauteurMurs, solZ);
|
||||||
|
if (murD.GetComponent<Renderer>() != null && matMur != null)
|
||||||
|
murD.GetComponent<Renderer>().sharedMaterial = matMur;
|
||||||
|
murD.isStatic = true;
|
||||||
|
murD.AddComponent<BoxCollider>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Murs avant et arrière (le long de Z)
|
||||||
|
GameObject murAv = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
murAv.name = "Couloir_MurAv";
|
||||||
|
murAv.transform.SetParent(couloir.transform);
|
||||||
|
murAv.transform.position = centre + new Vector3(0, mY, -couloirLarg / 2f - ep / 2f);
|
||||||
|
murAv.transform.localScale = new Vector3(solX, hauteurMurs, ep);
|
||||||
|
if (murAv.GetComponent<Renderer>() != null && matMur != null)
|
||||||
|
murAv.GetComponent<Renderer>().sharedMaterial = matMur;
|
||||||
|
murAv.isStatic = true;
|
||||||
|
murAv.AddComponent<BoxCollider>();
|
||||||
|
|
||||||
|
GameObject murAr = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
murAr.name = "Couloir_MurAr";
|
||||||
|
murAr.transform.SetParent(couloir.transform);
|
||||||
|
murAr.transform.position = centre + new Vector3(0, mY, couloirLarg / 2f + ep / 2f);
|
||||||
|
murAr.transform.localScale = new Vector3(solX, hauteurMurs, ep);
|
||||||
|
if (murAr.GetComponent<Renderer>() != null && matMur != null)
|
||||||
|
murAr.GetComponent<Renderer>().sharedMaterial = matMur;
|
||||||
|
murAr.isStatic = true;
|
||||||
|
murAr.AddComponent<BoxCollider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plafond
|
||||||
|
GameObject plafond = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
plafond.name = "Couloir_Plafond";
|
||||||
|
plafond.transform.SetParent(couloir.transform);
|
||||||
|
plafond.transform.position = centre + Vector3.up * hauteurMurs;
|
||||||
|
plafond.transform.localScale = new Vector3(solX + ep * 2, 0.1f, solZ + ep * 2);
|
||||||
|
if (plafond.GetComponent<Renderer>() != null && matPlafond != null)
|
||||||
|
plafond.GetComponent<Renderer>().sharedMaterial = matPlafond;
|
||||||
|
plafond.isStatic = true;
|
||||||
|
|
||||||
|
// Neon central + Light : SEULEMENT cote client (serveur n'a pas besoin de lumiere)
|
||||||
|
if (!DedicatedServerMode.IsDedicatedServer)
|
||||||
|
{
|
||||||
|
GameObject neon = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
|
neon.name = "Couloir_Neon";
|
||||||
|
neon.transform.SetParent(couloir.transform);
|
||||||
|
neon.transform.position = centre + Vector3.up * (hauteurPlafond);
|
||||||
|
float neonL = estNordSud ? 0.5f : murLong * 0.6f;
|
||||||
|
float neonW = estNordSud ? murLong * 0.6f : 0.5f;
|
||||||
|
neon.transform.localScale = new Vector3(neonL, 0.03f, neonW);
|
||||||
|
if (neon.GetComponent<Renderer>() != null && matNeon != null)
|
||||||
|
neon.GetComponent<Renderer>().sharedMaterial = matNeon;
|
||||||
|
neon.isStatic = true;
|
||||||
|
Collider nc = neon.GetComponent<Collider>();
|
||||||
|
if (nc != null) Destroy(nc);
|
||||||
|
|
||||||
|
GameObject lightObj = new GameObject("Couloir_Light");
|
||||||
|
lightObj.transform.SetParent(couloir.transform);
|
||||||
|
lightObj.transform.position = centre + Vector3.up * (hauteurPlafond - 0.1f);
|
||||||
|
Light light = lightObj.AddComponent<Light>();
|
||||||
|
light.type = LightType.Point;
|
||||||
|
light.color = salleRef.couleurLumiere;
|
||||||
|
light.intensity = salleRef.intensiteLumiere;
|
||||||
|
light.range = 6f;
|
||||||
|
light.shadows = LightShadows.Hard;
|
||||||
|
lightObj.isStatic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return couloir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== HELPERS ====================
|
||||||
|
|
||||||
|
void CopierParametresSalle(SalleDatacenter source, SalleDatacenter dest)
|
||||||
|
{
|
||||||
|
dest.hauteurSol = source.hauteurSol;
|
||||||
|
dest.hauteurPlafond = source.hauteurPlafond;
|
||||||
|
dest.hauteurMurs = source.hauteurMurs;
|
||||||
|
dest.tailleDalle = source.tailleDalle;
|
||||||
|
dest.epaisseurDalle = source.epaisseurDalle;
|
||||||
|
dest.espacementDalles = source.espacementDalles;
|
||||||
|
dest.frequenceDalleVentilee = source.frequenceDalleVentilee;
|
||||||
|
dest.tailleDallePlafond = source.tailleDallePlafond;
|
||||||
|
dest.epaisseurPlafond = source.epaisseurPlafond;
|
||||||
|
dest.frequenceNeon = source.frequenceNeon;
|
||||||
|
dest.intensiteLumiere = source.intensiteLumiere;
|
||||||
|
dest.couleurLumiere = source.couleurLumiere;
|
||||||
|
dest.epaisseurMur = source.epaisseurMur;
|
||||||
|
dest.nbRangees = source.nbRangees;
|
||||||
|
dest.emplacementsParRangee = source.emplacementsParRangee;
|
||||||
|
dest.espaceAlleeFroide = source.espaceAlleeFroide;
|
||||||
|
dest.espaceAlleeChaude = source.espaceAlleeChaude;
|
||||||
|
dest.largeurEmplacement = source.largeurEmplacement;
|
||||||
|
dest.profondeurEmplacement = source.profondeurEmplacement;
|
||||||
|
dest.margeDebutRangee = source.margeDebutRangee;
|
||||||
|
dest.espaceBaiesDansRangee = source.espaceBaiesDansRangee;
|
||||||
|
dest.decalageZRangees = source.decalageZRangees;
|
||||||
|
dest.couleurDalleSol = source.couleurDalleSol;
|
||||||
|
dest.couleurDalleVentilee = source.couleurDalleVentilee;
|
||||||
|
dest.couleurPiedestal = source.couleurPiedestal;
|
||||||
|
dest.couleurSousSol = source.couleurSousSol;
|
||||||
|
dest.couleurPlafondDalle = source.couleurPlafondDalle;
|
||||||
|
dest.couleurNeon = source.couleurNeon;
|
||||||
|
dest.couleurMur = source.couleurMur;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetDirectionOpposee(string direction)
|
||||||
|
{
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case "Nord": return "Sud";
|
||||||
|
case "Sud": return "Nord";
|
||||||
|
case "Est": return "Ouest";
|
||||||
|
case "Ouest": return "Est";
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 GetDirectionVector(string direction)
|
||||||
|
{
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case "Nord": return Vector3.forward;
|
||||||
|
case "Sud": return Vector3.back;
|
||||||
|
case "Est": return Vector3.right;
|
||||||
|
case "Ouest": return Vector3.left;
|
||||||
|
default: return Vector3.forward;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== HELPERS ID ====================
|
||||||
|
|
||||||
|
private static string ConstruireId(int parentId, string direction) => $"{parentId}_{direction}";
|
||||||
|
|
||||||
|
private static bool DecomposerId(string id, out int parentId, out string direction)
|
||||||
|
{
|
||||||
|
parentId = -1;
|
||||||
|
direction = "";
|
||||||
|
if (string.IsNullOrEmpty(id)) return false;
|
||||||
|
int sep = id.IndexOf('_');
|
||||||
|
if (sep <= 0 || sep >= id.Length - 1) return false;
|
||||||
|
if (!int.TryParse(id.Substring(0, sep), out parentId)) return false;
|
||||||
|
direction = id.Substring(sep + 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user