using UnityEngine; using System.Collections.Generic; using System.Linq; public enum BuildingMode { PlacingWalls, PlacingDoor, AddingDoorToExistingWall } public class RoomBuilder : MonoBehaviour { [Header("Building Settings")] [SerializeField] private float snapTolerance = 1f; [SerializeField] private Material wallMaterial; [SerializeField] private Material previewMaterial; [SerializeField] private float wallHeight = 3f; [SerializeField] private float wallThickness = 0.2f; [Header("Door Settings")] [SerializeField] private float doorWidth = 1.5f; [SerializeField] private Material doorFrameMaterial; [Header("Visual Feedback")] [SerializeField] private GameObject wallPointPrefab; [SerializeField] private LineRenderer previewLine; private List currentRoomPoints = new List(); private List wallSegments = new List(); private List wallPointMarkers = new List(); private bool isBuildingRoom = false; private bool isPlacingDoor = false; private BuildingMode currentBuildingMode = BuildingMode.PlacingWalls; private Camera playerCamera; private float lastRoomCompletionTime = 0f; private const float ROOM_DETECTION_COOLDOWN = 1.0f; // 1 second cooldown private GameObject previewWall; private Room currentRoom; // Events public System.Action OnRoomCompleted; public System.Action OnBuildingError; private void Start() { playerCamera = Camera.main; if (previewLine == null) { CreatePreviewLine(); } } private void Update() { if (GameManager.Instance != null && !GameManager.Instance.IsBuilding()) return; HandleInput(); UpdatePreview(); } private void OnGUI() { // Show building mode information if (GameManager.Instance != null && GameManager.Instance.IsBuilding()) { GUIStyle style = new GUIStyle(); style.fontSize = 16; style.normal.textColor = Color.white; string modeText = ""; string instructionText = ""; if (isBuildingRoom) { modeText = "Building Room"; instructionText = "Left-click to place wall points | Right-click to complete room"; } else if (isPlacingDoor) { modeText = "Placing Door"; instructionText = "Click on a wall to place door"; } else if (currentBuildingMode == BuildingMode.AddingDoorToExistingWall) { modeText = "Adding Door to Wall"; instructionText = "Click on existing wall to add door | ESC to cancel | Tab to toggle modes"; } else { modeText = "Ready to Build"; instructionText = "Left-click to start room | Tab to add door to existing wall | Auto-detects enclosed rooms"; } GUI.Label(new Rect(10, 10, 400, 30), $"Mode: {modeText}", style); GUI.Label(new Rect(10, 35, 400, 30), instructionText, style); } } #region Input Handling private void HandleInput() { // Only handle building inputs when we're supposed to be building if (GameManager.Instance == null || !GameManager.Instance.IsBuilding()) return; // Building mode switching - use Tab instead of D to avoid camera conflict if (Input.GetKeyDown(KeyCode.Tab) && !isBuildingRoom && !isPlacingDoor) { currentBuildingMode = BuildingMode.AddingDoorToExistingWall; Debug.Log("Switched to door placement mode - Click on existing walls to add doors"); } else if (Input.GetKeyDown(KeyCode.Tab) && currentBuildingMode == BuildingMode.AddingDoorToExistingWall) { currentBuildingMode = BuildingMode.PlacingWalls; Debug.Log("Switched to wall building mode"); } if (Input.GetMouseButtonDown(0)) // Left click { Debug.Log($"Mouse click detected - isPlacingDoor={isPlacingDoor}, currentBuildingMode={currentBuildingMode}"); if (currentBuildingMode == BuildingMode.AddingDoorToExistingWall) { TryAddDoorToExistingWall(); } else if (isPlacingDoor) { TryPlaceDoor(); } else { TryPlaceWallPoint(); } } else if (Input.GetMouseButtonDown(1)) // Right click { if (isBuildingRoom) { // Instead of forcing completion, try to auto-complete smartly // Only allow manual completion if we have a valid room shape if (currentRoomPoints.Count >= 3) { TryCompleteRoom(); } else { OnBuildingError?.Invoke("Need at least 3 points to complete room manually"); } } } else if (Input.GetKeyDown(KeyCode.Escape)) { if (currentBuildingMode == BuildingMode.AddingDoorToExistingWall) { currentBuildingMode = BuildingMode.PlacingWalls; Debug.Log("Cancelled door placement mode"); } else { CancelBuilding(); } } } private Vector3 GetMouseWorldPosition() { Ray ray = playerCamera.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit)) { return hit.point; } else { // Fallback: project to ground plane (y = 0) Plane groundPlane = new Plane(Vector3.up, Vector3.zero); if (groundPlane.Raycast(ray, out float distance)) { return ray.GetPoint(distance); } } return Vector3.zero; } #endregion #region Wall Building private void TryPlaceWallPoint() { Vector3 mousePos = GetMouseWorldPosition(); if (!isBuildingRoom) { // Check if we're starting near an existing wall Vector3 snapPoint = FindWallSnapPoint(mousePos); if (snapPoint != Vector3.zero) { Debug.Log($"Starting room from existing wall at: {snapPoint}"); StartRoom(snapPoint); } else { StartRoom(mousePos); } } else { // Check if we're close to the first point (completing the room) if (Vector3.Distance(mousePos, currentRoomPoints[0]) < snapTolerance) { AutoCompleteRoom(); return; } // Check if we're clicking on an existing wall to complete the room Vector3 wallSnapPoint = FindWallSnapPoint(mousePos); if (wallSnapPoint != Vector3.zero) { Debug.Log($"Completing room by connecting to existing wall at: {wallSnapPoint}"); AddWallPoint(wallSnapPoint); // If we started from an existing wall and now connect to another existing wall, // we can complete the room with just 2 points (wall-to-wall connection) if (currentRoomPoints.Count >= 2) { Debug.Log("Wall-to-wall connection detected, triggering comprehensive room detection"); // Instead of AutoCompleteRoom, use the comprehensive room detection CheckForNewEnclosedRoomsGlobal(); return; } } AddWallPoint(mousePos); // Auto-check if room is now enclosed (only for non-wall-snap cases) if (IsRoomEnclosed(currentRoomPoints)) { AutoCompleteRoom(); } // ADDITIONAL: Force completion check if we have enough points and walls if (currentRoomPoints.Count >= 3) { Debug.Log("Checking for forced room completion with sufficient points"); // Get all walls and see if we have enough to complete a room List allWalls = new List(); GameObject[] allGameObjects = FindObjectsByType(FindObjectsSortMode.None); foreach (GameObject obj in allGameObjects) { if (obj != null && obj.GetComponent() != null && (obj.name.Contains("Wall") || obj.name.Contains("wall"))) { allWalls.Add(obj); } } if (allWalls.Count >= 4) // Enough walls for a room { Debug.Log("Sufficient walls detected, forcing room completion"); CheckForNewEnclosedRoomsGlobal(); } } } } private void AutoCompleteRoom() { // Check if room already has a door if (DoesRoomHaveDoor()) { Debug.Log("Room already has a door, completing without door placement."); CompleteRoomWithoutDoor(); } else { Debug.Log("Room completed, needs door placement."); CompleteRoom(); } } private Vector3 FindWallSnapPoint(Vector3 clickPosition) { float snapDistance = 2.0f; // Distance to snap to existing walls GameObject closestWall = null; float closestDistance = float.MaxValue; // Check all existing wall segments foreach (GameObject wall in wallSegments) { if (wall == null || wall.name == "Door Opening" || wall.name == "Door Frame Post") continue; Renderer wallRenderer = wall.GetComponent(); if (wallRenderer == null) continue; // Check distance to wall bounds Bounds bounds = wallRenderer.bounds; float distance = bounds.SqrDistance(clickPosition); if (distance < closestDistance && distance < snapDistance * snapDistance) { closestWall = wall; closestDistance = distance; } } if (closestWall != null) { // Find the closest point on the wall surface to snap to Transform wallTransform = closestWall.transform; Vector3 wallScale = wallTransform.localScale; Vector3 wallCenter = wallTransform.position; // Calculate wall endpoints based on wall's forward direction (length direction) Vector3 wallDirection = wallTransform.forward; float wallLength = wallScale.z; // Length is in Z direction after LookRotation Vector3 wallStart = wallCenter - wallDirection * wallLength / 2f; Vector3 wallEnd = wallCenter + wallDirection * wallLength / 2f; // Project click position onto the wall line to find closest point Vector3 wallVector = wallEnd - wallStart; Vector3 clickVector = clickPosition - wallStart; float t = Mathf.Clamp01(Vector3.Dot(clickVector, wallVector) / wallVector.sqrMagnitude); Vector3 snapPoint = wallStart + t * wallVector; // Ensure snap point is at ground level snapPoint.y = 0f; Debug.Log($"Found wall snap point: {snapPoint} on wall from {wallStart} to {wallEnd}"); return snapPoint; } return Vector3.zero; // No wall found to snap to } private bool DoesRoomHaveDoor() { // Check if any wall segments in current room area have door openings or door frames foreach (GameObject segment in wallSegments) { if (segment != null && (segment.name == "Door Opening" || segment.name == "Door Frame Post")) { return true; } } return false; } private bool IsRoomEnclosed(List points) { // Check if the current points form a closed shape if (points.Count < 3) return false; // Check if we're close to starting point or clicked on existing wall Vector3 lastPoint = points[points.Count - 1]; Vector3 firstPoint = points[0]; return Vector3.Distance(lastPoint, firstPoint) < snapTolerance * 2f; } private void CompleteRoomWithoutDoor() { // Complete the room without entering door placement mode if (currentRoomPoints.Count > 2) { Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1]; Vector3 firstPoint = currentRoomPoints[0]; if (Vector3.Distance(lastPoint, firstPoint) > 0.1f) { CreateWallSegment(lastPoint, firstPoint); } } Debug.Log("Room completed without additional door (existing door found)."); // Clean up any very short or overlapping wall segments CleanupWallSegments(); // Finalize the room immediately FinishRoom(); // Reset building state ResetBuildingState(); } private void StartRoom(Vector3 startPoint) { Debug.Log("Starting room construction"); isBuildingRoom = true; currentRoomPoints.Clear(); currentRoomPoints.Add(startPoint); // Create visual marker for start point if (wallPointPrefab != null) { GameObject marker = Instantiate(wallPointPrefab, startPoint, Quaternion.identity); marker.GetComponent().material.color = Color.green; // Start point is green wallPointMarkers.Add(marker); } } private void AddWallPoint(Vector3 newPoint) { currentRoomPoints.Add(newPoint); // Create wall segment from previous point to new point CreateWallSegment(currentRoomPoints[currentRoomPoints.Count - 2], newPoint); // Create visual marker if (wallPointPrefab != null) { GameObject marker = Instantiate(wallPointPrefab, newPoint, Quaternion.identity); wallPointMarkers.Add(marker); } Debug.Log($"Added wall point {currentRoomPoints.Count}: {newPoint}"); // Check if this wall placement has created any new enclosed rooms CheckForNewEnclosedRooms(); // Additional comprehensive check if we have enough points if (currentRoomPoints.Count >= 3) { Debug.Log("Also triggering global room detection as backup"); CheckForNewEnclosedRoomsGlobal(); } // SIMPLE FALLBACK: If we have 3+ points and are near existing walls, try to complete if (currentRoomPoints.Count >= 3) { Vector3 firstPoint = currentRoomPoints[0]; Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1]; // Simple distance check - if start and end are close to existing walls, complete the room if (IsNearExistingWall(firstPoint) && IsNearExistingWall(lastPoint)) { Debug.Log("Simple room completion: Start and end points near existing walls"); ForceSimpleRoomCompletion(); } } } private bool IsNearExistingWall(Vector3 point) { GameObject[] allObjects = FindObjectsByType(FindObjectsSortMode.None); foreach (GameObject obj in allObjects) { if (obj != null && obj.name.Contains("Wall") && obj.GetComponent() != null) { if (Vector3.Distance(point, obj.transform.position) < 3.0f) { return true; } } } return false; } private void ForceSimpleRoomCompletion() { Debug.Log("Forcing simple room completion"); // Create a simple room from current points if (currentRoomPoints.Count >= 3) { List roomArea = new List(currentRoomPoints); // Check if it has a door bool hasDoor = DoesAreaHaveDoor(roomArea); if (!hasDoor) { Debug.Log("Simple room has no door - forcing door placement"); ForceRoomDoorPlacement(roomArea); } else { Debug.Log("Simple room has door - completing"); CompleteDetectedRoom(roomArea); } } } private void TryCompleteRoom() { if (currentRoomPoints.Count < 3) { OnBuildingError?.Invoke("Room needs at least 3 points"); return; } Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1]; Vector3 firstPoint = currentRoomPoints[0]; // Check if there's a clear line to the first point if (IsValidClosingWall(lastPoint, firstPoint)) { CompleteRoom(); } else { OnBuildingError?.Invoke("Cannot complete room: No clear line to start point"); } } private void CompleteRoom() { // Add final wall segment to close the room if (currentRoomPoints.Count > 2) { Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1]; Vector3 firstPoint = currentRoomPoints[0]; if (Vector3.Distance(lastPoint, firstPoint) > 0.1f) { CreateWallSegment(lastPoint, firstPoint); } } Debug.Log("Room walls completed. Now place a door."); // Clean up any very short or overlapping wall segments CleanupWallSegments(); // Start door placement phase isBuildingRoom = false; isPlacingDoor = true; // Change marker colors to indicate door placement phase foreach (var marker in wallPointMarkers) { if (marker != null) marker.GetComponent().material.color = Color.blue; } } private void CleanupWallSegments() { // Remove any walls that are too short or overlapping for (int i = wallSegments.Count - 1; i >= 0; i--) { if (wallSegments[i] == null) continue; Transform wallTransform = wallSegments[i].transform; Vector3 wallScale = wallTransform.localScale; // Remove walls that are too short (likely overlapping or error segments) if (wallScale.z < 0.2f) // Z is the length dimension { Debug.Log($"Removing short wall segment: {wallScale.z}"); DestroyImmediate(wallSegments[i]); wallSegments.RemoveAt(i); } } } private bool IsValidClosingWall(Vector3 from, Vector3 to) { // For now, just check distance (can be improved with obstacle checking) return Vector3.Distance(from, to) < 20f; // Max wall length } private void CreateWallSegment(Vector3 from, Vector3 to) { // Ensure points are at ground level (y = 0) for consistency from.y = 0f; to.y = 0f; Vector3 center = (from + to) / 2f; Vector3 direction = to - from; float length = direction.magnitude; // Skip very short wall segments to prevent overlapping if (length < 0.1f) { Debug.LogWarning($"Skipping very short wall segment: {length}"); return; } GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube); wall.name = "Wall Segment"; // Anchor wall to floor - position at ground level with wall extending upward wall.transform.position = new Vector3(center.x, wallHeight / 2f, center.z); wall.transform.rotation = Quaternion.LookRotation(direction); wall.transform.localScale = new Vector3(wallThickness, wallHeight, length); if (wallMaterial != null) { wall.GetComponent().material = wallMaterial; } // Add collider for pathfinding obstacles but make non-clickable Collider wallCollider = wall.GetComponent(); if (wallCollider != null) { wallCollider.isTrigger = false; // Walls should block navigation but not be clickable for UI wall.layer = LayerMask.NameToLayer("Default"); // Ensure it's on a non-UI layer } wallSegments.Add(wall); // After adding any wall, check if it might have completed an enclosed room // (but only if we're not already in a specific building mode) if (!isPlacingDoor && currentBuildingMode != BuildingMode.AddingDoorToExistingWall) { CheckForNewEnclosedRoomsGlobal(); } } #endregion #region Door Placement private void TryPlaceDoor() { Vector3 mousePos = GetMouseWorldPosition(); Debug.Log($"Trying to place door at mouse position: {mousePos}"); // Find the closest wall segment GameObject closestWall = FindClosestWallSegment(mousePos); if (closestWall != null) { Debug.Log($"Found wall for door placement: {closestWall.name}"); PlaceDoor(closestWall, mousePos); FinishRoom(); // This should reset the building state // Extra safety: force reset building state immediately Debug.Log("Building state reset."); return; // Exit immediately after placing door } else { Debug.LogWarning("No suitable wall found for door placement"); OnBuildingError?.Invoke("Click closer to a wall to place the door (not too close to corners)"); } } private void TryAddDoorToExistingWall() { Vector3 mousePos = GetMouseWorldPosition(); Debug.Log($"Trying to add door to existing wall at: {mousePos}"); // Find the closest existing wall segment (not door-related) GameObject targetWall = null; float closestDistance = float.MaxValue; foreach (GameObject wall in wallSegments) { if (wall == null || wall.name == "Door Opening" || wall.name == "Door Frame Post") continue; Renderer wallRenderer = wall.GetComponent(); if (wallRenderer == null) continue; Bounds bounds = wallRenderer.bounds; float distance = bounds.SqrDistance(mousePos); if (distance < closestDistance && distance < 16f) // Max click distance squared { targetWall = wall; closestDistance = distance; } } if (targetWall != null) { Debug.Log($"Adding door to existing wall: {targetWall.name}"); PlaceDoor(targetWall, mousePos); // Switch back to normal building mode currentBuildingMode = BuildingMode.PlacingWalls; Debug.Log("Door added successfully, switched back to wall building mode"); } else { OnBuildingError?.Invoke("Click closer to an existing wall to add a door"); } } private GameObject FindClosestWallSegment(Vector3 position) { GameObject closest = null; float closestDistance = float.MaxValue; foreach (GameObject wall in wallSegments) { if (wall == null) continue; // Get the wall bounds Renderer wallRenderer = wall.GetComponent(); if (wallRenderer == null) continue; Bounds bounds = wallRenderer.bounds; float distance = bounds.SqrDistance(position); // Check if click is within reasonable distance and not too close to wall ends if (distance < closestDistance && distance < 16f) // Max click distance squared (4^2) { // Additional check: make sure we're not too close to wall endpoints Transform wallTransform = wall.transform; Vector3 wallDirection = wallTransform.forward; float wallLength = wallTransform.localScale.z; Vector3 wallStart = wallTransform.position - wallDirection * wallLength / 2f; Vector3 wallEnd = wallTransform.position + wallDirection * wallLength / 2f; float distToStart = Vector3.Distance(position, wallStart); float distToEnd = Vector3.Distance(position, wallEnd); // Only allow door placement if not too close to wall ends if (distToStart > doorWidth * 0.75f && distToEnd > doorWidth * 0.75f) { closestDistance = distance; closest = wall; } } } return closest; } private void PlaceDoor(GameObject wallSegment, Vector3 clickPosition) { // Create door opening by modifying the wall Transform wallTransform = wallSegment.transform; Vector3 wallScale = wallTransform.localScale; Vector3 wallCenter = wallTransform.position; // Calculate wall endpoints correctly based on wall's forward direction (length direction) Vector3 wallDirection = wallTransform.forward; float wallLength = wallScale.z; // Length is in Z direction after LookRotation Vector3 wallStart = wallCenter - wallDirection * wallLength / 2f; Vector3 wallEnd = wallCenter + wallDirection * wallLength / 2f; // Project click position onto the wall line to find closest point Vector3 wallVector = wallEnd - wallStart; Vector3 clickVector = clickPosition - wallStart; float t = Mathf.Clamp01(Vector3.Dot(clickVector, wallVector) / wallVector.sqrMagnitude); Vector3 doorPosition = wallStart + t * wallVector; // Validate door position - ensure it's not too close to wall ends float distFromStart = Vector3.Distance(doorPosition, wallStart); float distFromEnd = Vector3.Distance(doorPosition, wallEnd); float minDistance = doorWidth * 0.6f; if (distFromStart < minDistance || distFromEnd < minDistance) { // Adjust door position to be away from wall ends if (distFromStart < distFromEnd) { doorPosition = wallStart + wallDirection * minDistance; } else { doorPosition = wallEnd - wallDirection * minDistance; } } Debug.Log($"Door placed at: {doorPosition} on wall from {wallStart} to {wallEnd}"); // Remove original wall and create two wall segments with a gap wallSegments.Remove(wallSegment); DestroyImmediate(wallSegment); CreateWallWithDoor(wallStart, wallEnd, doorPosition); } private void CreateWallWithDoor(Vector3 wallStart, Vector3 wallEnd, Vector3 doorPosition) { Vector3 wallDirection = (wallEnd - wallStart).normalized; float doorHalfWidth = doorWidth / 2f; Vector3 door1End = doorPosition - wallDirection * doorHalfWidth; Vector3 door2Start = doorPosition + wallDirection * doorHalfWidth; // Create wall segment before door (if long enough) float leftSegmentLength = Vector3.Distance(wallStart, door1End); if (leftSegmentLength > 0.5f) { CreateWallSegment(wallStart, door1End); } // Create wall segment after door (if long enough) float rightSegmentLength = Vector3.Distance(door2Start, wallEnd); if (rightSegmentLength > 0.5f) { CreateWallSegment(door2Start, wallEnd); } // Create door frame markers (simplified) CreateDoorFrame(doorPosition, wallDirection); } private void CreateDoorFrame(Vector3 position, Vector3 direction) { Debug.Log($"Creating door frame at position: {position}, direction: {direction}"); // Create simple door frame markers - two posts on sides of the opening float frameHeight = wallHeight * 0.85f; // Door frame slightly lower than wall // Fix rotation: Use the direction itself (not perpendicular) for door post positioning // This positions posts along the wall direction, not perpendicular to it Vector3 postDirection = direction.normalized; // Position posts precisely at the door opening edges along the wall Vector3 leftPost = position - postDirection * doorWidth / 2f; Vector3 rightPost = position + postDirection * doorWidth / 2f; // Ensure door posts are exactly at ground level like walls leftPost.y = 0f; rightPost.y = 0f; Debug.Log($"Door posts positioned at: Left={leftPost}, Right={rightPost}, Height={frameHeight}"); CreateDoorPost(leftPost, frameHeight); CreateDoorPost(rightPost, frameHeight); // Create a subtle ground-level door opening indicator GameObject doorOpening = new GameObject("Door Opening"); doorOpening.transform.position = new Vector3(position.x, 0.01f, position.z); // Just above ground // Add a simple flat rectangle for the door opening - perpendicular to wall direction GameObject indicator = GameObject.CreatePrimitive(PrimitiveType.Cube); indicator.transform.SetParent(doorOpening.transform); indicator.transform.localPosition = Vector3.zero; // Rotate indicator to be perpendicular to the wall (across the door opening) Vector3 perpendicular = Vector3.Cross(direction, Vector3.up).normalized; indicator.transform.localRotation = Quaternion.LookRotation(perpendicular); indicator.transform.localScale = new Vector3(doorWidth, 0.02f, 0.1f); // Thin flat indicator Renderer indicatorRenderer = indicator.GetComponent(); if (indicatorRenderer != null) { indicatorRenderer.material.color = new Color(0.2f, 0.8f, 0.2f, 0.7f); // Semi-transparent green } indicator.name = "Door Indicator"; // Make door indicator non-clickable by removing collider Collider indicatorCollider = indicator.GetComponent(); if (indicatorCollider != null) { DestroyImmediate(indicatorCollider); } wallSegments.Add(doorOpening); } private void CreateDoorPost(Vector3 position, float height) { GameObject post = GameObject.CreatePrimitive(PrimitiveType.Cube); post.name = "Door Frame Post"; // Position door posts to match wall anchoring (anchored to floor) // Ensure Y position matches how walls are positioned at wallHeight/2 post.transform.position = new Vector3(position.x, height / 2f, position.z); post.transform.localScale = new Vector3(0.15f, height, 0.15f); // Slightly thinner posts if (doorFrameMaterial != null) { post.GetComponent().material = doorFrameMaterial; } else { post.GetComponent().material.color = new Color(0.4f, 0.2f, 0.05f); // Darker brown } // Make door posts non-clickable but solid Collider postCollider = post.GetComponent(); if (postCollider != null) { postCollider.isTrigger = false; // Keep solid for visual barrier post.layer = LayerMask.NameToLayer("Default"); // Non-clickable layer } wallSegments.Add(post); } private void FinishRoom() { // Use the stored room data if we have it (from auto-detection), otherwise create new if (currentRoom == null) { currentRoom = new Room { roomPoints = new List(currentRoomPoints), wallObjects = new List(wallSegments), isComplete = true, roomType = RoomType.Generic }; } else { // Update the existing room with completion status currentRoom.isComplete = true; currentRoom.hasEntrance = true; } lastRoomCompletionTime = Time.time; // Set completion time for cooldown Debug.Log($"Room completed with door placed"); // Clean up visual markers foreach (GameObject marker in wallPointMarkers) { if (marker != null) DestroyImmediate(marker); } // Notify completion OnRoomCompleted?.Invoke(currentRoom); // Reset building state ResetBuildingState(); } #endregion #region Preview and Visual Feedback private void UpdatePreview() { if (!isBuildingRoom || currentRoomPoints.Count == 0) { if (previewLine != null) previewLine.enabled = false; return; } Vector3 mousePos = GetMouseWorldPosition(); // Update preview line if (previewLine != null) { previewLine.enabled = true; previewLine.positionCount = 2; previewLine.SetPosition(0, currentRoomPoints[currentRoomPoints.Count - 1]); previewLine.SetPosition(1, mousePos); // Change color based on snap distance to first point if (currentRoomPoints.Count > 2 && Vector3.Distance(mousePos, currentRoomPoints[0]) < snapTolerance) { previewLine.material.color = Color.green; // Can close room } else { previewLine.material.color = Color.white; // Normal building } } } private void CreatePreviewLine() { GameObject previewObj = new GameObject("Preview Line"); previewLine = previewObj.AddComponent(); previewLine.material = new Material(Shader.Find("Sprites/Default")); previewLine.startColor = Color.white; previewLine.endColor = Color.white; previewLine.startWidth = 0.1f; previewLine.enabled = false; } #endregion #region Utility Methods private void CancelBuilding() { if (isBuildingRoom || isPlacingDoor) { Debug.Log("Cancelling room construction"); // Clean up foreach (GameObject wall in wallSegments) { if (wall != null) DestroyImmediate(wall); } foreach (GameObject marker in wallPointMarkers) { if (marker != null) DestroyImmediate(marker); } // Reset state isBuildingRoom = false; isPlacingDoor = false; currentRoomPoints.Clear(); wallSegments.Clear(); wallPointMarkers.Clear(); if (previewLine != null) previewLine.enabled = false; } } public bool IsBuilding() { return isBuildingRoom || isPlacingDoor; } private void ResetBuildingState() { Debug.Log($"ResetBuildingState() called - Before: isBuildingRoom={isBuildingRoom}, isPlacingDoor={isPlacingDoor}, currentBuildingMode={currentBuildingMode}"); // Reset all building-related state variables isBuildingRoom = false; isPlacingDoor = false; currentBuildingMode = BuildingMode.PlacingWalls; currentRoomPoints.Clear(); wallSegments.Clear(); wallPointMarkers.Clear(); if (previewLine != null) previewLine.enabled = false; Debug.Log($"ResetBuildingState() completed - After: isBuildingRoom={isBuildingRoom}, isPlacingDoor={isPlacingDoor}, currentBuildingMode={currentBuildingMode}"); } private void CheckForNewEnclosedRooms() { Debug.Log("CheckForNewEnclosedRooms() called"); // Skip room detection if we just completed a room recently (prevents immediate re-detection) if (Time.time - lastRoomCompletionTime < ROOM_DETECTION_COOLDOWN) { Debug.Log("Skipping room detection due to cooldown period"); return; } // Skip if we're already placing a door if (isPlacingDoor) { Debug.Log("Skipping room detection - already placing door"); return; } // Get all wall segments from multiple sources to ensure we find ALL walls List allWalls = new List(); // Method 1: Include walls from our wallSegments list List roomBuilderWalls = wallSegments.Where(wall => wall != null && wall.name == "Wall Segment" && wall.GetComponent() != null ).ToList(); allWalls.AddRange(roomBuilderWalls); // Method 2: Find all GameObjects that look like walls in the scene GameObject[] allGameObjects = FindObjectsByType(FindObjectsSortMode.None); foreach (GameObject obj in allGameObjects) { if (obj != null && obj.GetComponent() != null && (obj.name.Contains("Wall") || obj.name.Contains("wall")) && !allWalls.Contains(obj)) { allWalls.Add(obj); } } Debug.Log($"Found {roomBuilderWalls.Count} walls from RoomBuilder, {allWalls.Count} total walls in scene"); // Debug: List all walls found foreach (var wall in allWalls) { if (wall != null) { Debug.Log($"Wall found: {wall.name} at position {wall.transform.position}"); } } // Find enclosed areas using the wall network List> enclosedAreas = FindEnclosedAreas(allWalls); Debug.Log($"FindEnclosedAreas returned {enclosedAreas.Count} areas"); foreach (var area in enclosedAreas) { Debug.Log($"Checking area with {area.Count} points"); if (IsValidRoom(area)) { // Check if this room has any doors bool hasDoor = DoesAreaHaveDoor(area); Debug.Log($"Valid room found with {area.Count} points. Has door: {hasDoor}"); if (!hasDoor) { Debug.Log($"Found new enclosed room without door - forcing door placement"); ForceRoomDoorPlacement(area); return; // Handle one room at a time } else { Debug.Log($"Found enclosed room with existing door - completing room"); CompleteDetectedRoom(area); } } else { Debug.Log($"Area not valid as room (too small or insufficient points)"); } } } private List> FindEnclosedAreas(List walls) { Debug.Log("FindEnclosedAreas called"); List> enclosedAreas = new List>(); // Instead of just checking current room points, do a comprehensive wall network analysis if (walls.Count >= 3) // Need at least 3 walls to form a room { Debug.Log("Attempting comprehensive wall network analysis"); // Use the global polygon detection method enclosedAreas = DetectClosedPolygonsFromWalls(walls); } // Also check if current room points form a potential room (original logic) if (currentRoomPoints.Count >= 3) { Debug.Log("Checking current room points for completion"); Vector3 firstPoint = currentRoomPoints[0]; Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1]; // Check if there are existing walls that could complete the loop foreach (GameObject wall in walls) { if (CouldWallCompleteRoom(wall, firstPoint, lastPoint)) { Debug.Log("Found wall that could complete current room"); List completedRoom = new List(currentRoomPoints); enclosedAreas.Add(completedRoom); break; } } } Debug.Log($"FindEnclosedAreas returning {enclosedAreas.Count} areas"); return enclosedAreas; } private bool CouldWallCompleteRoom(GameObject wall, Vector3 roomStart, Vector3 roomEnd) { if (wall == null) return false; // Get wall endpoints Transform wallTransform = wall.transform; Vector3 wallScale = wallTransform.localScale; Vector3 wallCenter = wallTransform.position; Vector3 wallDirection = wallTransform.forward; float wallLength = wallScale.z; Vector3 wallStart = wallCenter - wallDirection * wallLength / 2f; Vector3 wallEnd = wallCenter + wallDirection * wallLength / 2f; // Check if either end of the room is close to either end of this wall float tolerance = snapTolerance * 2f; return (Vector3.Distance(roomStart, wallStart) < tolerance || Vector3.Distance(roomStart, wallEnd) < tolerance) && (Vector3.Distance(roomEnd, wallStart) < tolerance || Vector3.Distance(roomEnd, wallEnd) < tolerance); } private bool IsValidRoom(List area) { if (area.Count < 3) return false; // Calculate area - rooms should have minimum area float area2D = CalculatePolygonArea(area); return area2D > 4f; // Minimum room area } private float CalculatePolygonArea(List points) { if (points.Count < 3) return 0f; float area = 0f; for (int i = 0; i < points.Count; i++) { Vector3 current = points[i]; Vector3 next = points[(i + 1) % points.Count]; area += (current.x * next.z - next.x * current.z); } return Mathf.Abs(area) / 2f; } private bool DoesAreaHaveDoor(List area) { // Check if there are any door openings or door frame posts within the room area foreach (GameObject segment in wallSegments) { if (segment != null && (segment.name == "Door Opening" || segment.name == "Door Frame Post")) { Vector3 doorPos = segment.transform.position; if (IsPointInPolygon(doorPos, area)) { return true; } } } return false; } private bool IsPointInPolygon(Vector3 point, List polygon) { // Simple ray casting algorithm for 2D point-in-polygon test bool inside = false; for (int i = 0, j = polygon.Count - 1; i < polygon.Count; j = i++) { Vector3 pi = polygon[i]; Vector3 pj = polygon[j]; if (((pi.z > point.z) != (pj.z > point.z)) && (point.x < (pj.x - pi.x) * (point.z - pi.z) / (pj.z - pi.z) + pi.x)) { inside = !inside; } } return inside; } private void ForceRoomDoorPlacement(List roomArea) { Debug.Log("Forcing door placement for enclosed room"); // Switch to door placement mode isBuildingRoom = false; isPlacingDoor = true; currentBuildingMode = BuildingMode.PlacingDoor; // Store the room data currentRoom = new Room { roomPoints = roomArea, isComplete = false }; // Clear current room points since we're now in door mode currentRoomPoints.Clear(); OnBuildingError?.Invoke("Room detected! Click on a wall to place a door."); } private void CompleteDetectedRoom(List roomArea) { Debug.Log("Completing room with existing door"); // Create and complete the room Room detectedRoom = new Room { roomPoints = roomArea, isComplete = true, hasEntrance = true }; // Fire completion event OnRoomCompleted?.Invoke(detectedRoom); // Reset building state ResetBuildingState(); } private void CheckForNewEnclosedRoomsGlobal() { // This is a more comprehensive room detection that works with all existing walls // It finds enclosed areas using all wall segments, not just current room points List actualWalls = wallSegments.Where(wall => wall != null && wall.name == "Wall Segment" && wall.GetComponent() != null ).ToList(); if (actualWalls.Count < 3) return; // Need at least 3 walls to form a room Debug.Log($"Checking for enclosed rooms with {actualWalls.Count} walls"); // Use a more sophisticated approach to detect closed polygons List> detectedRooms = DetectClosedPolygonsFromWalls(actualWalls); Debug.Log($"Found {detectedRooms.Count} potential rooms"); foreach (var roomArea in detectedRooms) { Debug.Log($"Checking room with {roomArea.Count} points, area: {CalculatePolygonArea(roomArea)}"); if (IsValidRoom(roomArea)) { bool hasDoor = DoesAreaHaveDoor(roomArea); Debug.Log($"Valid room found. Has door: {hasDoor}"); if (!hasDoor) { Debug.Log($"Detected new enclosed room without door - forcing door placement"); ForceRoomDoorPlacement(roomArea); return; // Handle one room at a time } else { Debug.Log($"Room already has door - completing"); CompleteDetectedRoom(roomArea); return; } } } Debug.Log("No valid enclosed rooms detected"); } private List> DetectClosedPolygonsFromWalls(List walls) { List> closedPolygons = new List>(); Debug.Log($"Starting simplified room detection with {walls.Count} walls"); // Simple approach: Look for rectangular rooms formed by connecting existing walls // This is more reliable than complex graph algorithms for your use case // Check if current room points connect existing walls to form a room if (currentRoomPoints.Count >= 2) { Vector3 firstPoint = currentRoomPoints[0]; Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1]; Debug.Log($"Checking if room points form enclosed area: {currentRoomPoints.Count} points"); Debug.Log($"First point: {firstPoint}, Last point: {lastPoint}"); // Check if we can find existing walls that connect our start and end points bool startsOnWall = IsPointOnAnyWall(firstPoint, walls); bool endsOnWall = IsPointOnAnyWall(lastPoint, walls); Debug.Log($"First point on wall: {startsOnWall}, Last point on wall: {endsOnWall}"); if (startsOnWall && endsOnWall && currentRoomPoints.Count >= 3) { // Check if there's a path of existing walls that could complete the room if (CanWallsCompleteRoom(firstPoint, lastPoint, walls)) { Debug.Log("Found completed room using wall connections!"); List completedRoom = new List(currentRoomPoints); closedPolygons.Add(completedRoom); } } } Debug.Log($"Simplified detection found {closedPolygons.Count} rooms"); return closedPolygons; } private bool IsPointOnAnyWall(Vector3 point, List walls) { float tolerance = 2.0f; // Distance tolerance for "on wall" foreach (GameObject wall in walls) { if (wall == null) continue; // Check if point is close to this wall Bounds wallBounds = wall.GetComponent().bounds; if (wallBounds.SqrDistance(point) < tolerance * tolerance) { Debug.Log($"Point {point} is near wall {wall.name}"); return true; } } return false; } private bool CanWallsCompleteRoom(Vector3 startPoint, Vector3 endPoint, List walls) { // Simple check: if we have walls forming the outer boundary and our points connect them, // we likely have a completed room // Count how many walls we have that could form room boundaries int boundaryWalls = 0; foreach (GameObject wall in walls) { if (wall != null && wall.name.Contains("Wall")) { boundaryWalls++; } } Debug.Log($"Found {boundaryWalls} potential boundary walls"); // If we have sufficient boundary walls and our points connect them, assume room completion return boundaryWalls >= 4; // At least 4 walls needed for a room } private Vector3 RoundVector3(Vector3 vector, float precision) { return new Vector3( Mathf.Round(vector.x / precision) * precision, Mathf.Round(vector.y / precision) * precision, Mathf.Round(vector.z / precision) * precision ); } private List FindSmallestCycle(Dictionary> graph, Vector3 start, HashSet globalVisited) { if (globalVisited.Contains(start)) return null; // DFS to find the shortest cycle starting from this point HashSet pathVisited = new HashSet(); List path = new List(); if (DFSFindCycle(graph, start, start, pathVisited, path)) { return new List(path); } return null; } private bool DFSFindCycle(Dictionary> graph, Vector3 current, Vector3 target, HashSet pathVisited, List path) { // Check if we found a cycle back to start - use exact equality since we're using rounded coordinates if (path.Count > 0 && path.Count >= 3) { // Check if current point connects back to target in the graph if (graph.ContainsKey(current) && graph[current].Contains(target)) { Debug.Log($"Found cycle! Path length: {path.Count}"); return true; } } if (path.Count > 6) return false; // Limit search depth to avoid infinite loops pathVisited.Add(current); path.Add(current); if (graph.ContainsKey(current)) { foreach (var neighbor in graph[current]) { // Don't revisit the previous node (except when completing cycle) if (path.Count == 1 || (!pathVisited.Contains(neighbor)) || (path.Count >= 3 && neighbor.Equals(target))) { if (DFSFindCycle(graph, neighbor, target, pathVisited, path)) return true; } } } pathVisited.Remove(current); path.RemoveAt(path.Count - 1); return false; } #endregion } // Data classes [System.Serializable] public class Room { public List roomPoints = new List(); public List wallObjects = new List(); public bool isComplete = false; public RoomType roomType = RoomType.Generic; public Vector3 doorPosition; public bool hasEntrance = false; public bool hasReception = false; } public enum RoomType { Generic, Reception, GuestRoom, Restaurant, Kitchen, Storage, Bathroom, Lobby }