카테고리 없음

[최종프로젝트] Unity 본캠프 76일차

julesnow 2025. 7. 29. 05:42

기존코드

 

BaseRoom.cs

using System;

using System.Collections.Generic;

using System.Linq;

using UnityEngine.Tilemaps;

using UnityEngine;





public abstract class BaseRoom: MonoBehaviour

{

public Bounds RoomBounds => new (transform.position, _areaCollider.size);

public Transform TPPoint => tpPoint;

public Vector2Int Cell { get; set; }



public bool IsDiscovered => isDiscovered;

public bool IsCleared => isCleared;



/// <summary>

/// 상속받아서 해당 방 클리어 조건을 설정

/// </summary>

protected abstract bool ClearCondition { get; }

protected FloorManager FloorManager { get; private set; }




[SerializeField] private Transform tpPoint;

[SerializeField] private Transform roomTilemapRoot;

[SerializeField] private Tilemap minimapTilemap;





private bool isDiscovered;

private bool isCleared;


private Dictionary<Dir4, BaseRoom> _connectedRooms = new();

private RoomPath[] _paths;

private Tilemap[] _roomTilemaps;

private BoxCollider2D _areaCollider;



private RoomLightHandler _lightHandler;



protected virtual void Awake()

{

_areaCollider = GetComponent<BoxCollider2D>();

_paths = GetComponentsInChildren<RoomPath>();

_roomTilemaps = roomTilemapRoot.GetComponentsInChildren<Tilemap>();



TryGetComponent(out _lightHandler);



Dir4[] dirArray = (Dir4[])Enum.GetValues(typeof(Dir4));


for (int i = 0; i < _paths.Length; i++)

{

_paths[i].Init(dirArray[i]);

}


minimapTilemap.gameObject.SetActive(false);

}





public virtual void Init(FloorManager floorManager)

{

FloorManager = floorManager;


foreach (var path in _paths)

{

foreach (var roomKvp in _connectedRooms)

{

if (roomKvp.Key == path.LookDir)

{

path.ActivePath(floorManager,this, _roomTilemaps);

break;

}

}

}

}




public void ConnectRoom(BaseRoom connectRoom, Vector2Int targetDir)

{

Dir4 dir = Dir4.Up;


if (targetDir == Vector2Int.up)

{

dir = Dir4.Up;

}

else if (targetDir == Vector2Int.down)

{

dir = Dir4.Down;

}

else if (targetDir == Vector2Int.right)

{

dir = Dir4.Right;

}

else if (targetDir == Vector2Int.left)

{

dir = Dir4.Left;

}



_connectedRooms[dir] = connectRoom;

}





public virtual void OnEnter()

{

CheckClearCondition();

EnableMinimapTilemaps();


_lightHandler?.TurnOnLights();

}


public virtual void OnExit()

{

DisableMinimapTilemaps();


_lightHandler?.TurnOffLights();

}





public void Discover() => isDiscovered = true;





protected virtual void ClearRoom()

{

isCleared = true;


DisablePathBarriers();

}




protected void CheckClearCondition()

{

if (IsCleared) return;


if (ClearCondition)

{

ClearRoom();

}

else

{

EnablePathBarriers();

}

}




protected void EnablePathBarriers()

{

foreach (var path in _paths) path.EnableBarrier();

}



protected void DisablePathBarriers()

{

foreach (var path in _paths) path.DisableBarrier();

}





// 임시 수정

public void EnableMinimapTilemaps()

{

minimapTilemap.gameObject.SetActive(true);



foreach (var path in _paths)

{

if (path.gameObject.activeInHierarchy)

{

path.ShowMinimapTilemap();

}

}

}



protected void DisableMinimapTilemaps()

{

minimapTilemap.gameObject.SetActive(false);



foreach (var path in _paths)

{

if (path.gameObject.activeInHierarchy)

{

path.HideMinimapTilemap();

}

}

}

}

 

FloorMapUI.cs

using System; 
using UnityEngine; 
using UnityEngine.UI; 
using System.Collections.Generic; 
using System.Linq; 
using UnityEngine.Serialization; 

public class FloorMapUI: BaseUI 
{ 
public override CanvasLayer Layer => CanvasLayer.Popup; 
public override bool Enabled => gameObject.activeSelf; 

[FormerlySerializedAs("uiRoomIconPrefab")] [FormerlySerializedAs("roomUIPrefab")] [SerializeField] private FloorMapUIRoomSlot uiRoomSlotPrefab; 
[SerializeField] private ScrollRect scrollRect; 

private PlayerController _playerController; 
private FloorManager _floorManager; 
private Rect _roomUIsTotalRect; 
private List<FloorMapUIRoomSlot> _createdRoomUIs = new(); 
private FloorMapUIRoomSlot _curEnteredUIRoomSlot; 


public override void Enable() 
{ 
gameObject.SetActive(true); 
_playerController.InputController.EnableFloorMapInputs(); 
_playerController.InputController.OnClosePopupUI += Disable; 
} 

public override void Disable() 
{ 
gameObject.SetActive(false); 

_playerController.InputController.EnablePlayerInputs(); 
} 

public void Init(FloorManager floorManager) 
{ 
_floorManager = floorManager; 

_playerController = floorManager.Player; 

foreach (var roomUI in _createdRoomUIs) 
{ 
Destroy(roomUI.gameObject); 
} 
_roomUIsTotalRect = Rect.zero; 
_createdRoomUIs.Clear(); 

foreach (var room in _floorManager.FloorState.TotalRooms) 
{ 
var icon = Instantiate(uiRoomSlotPrefab, scrollRect.content.transform); 
icon.Init(floorManager, room, this); 
icon.gameObject.SetActive(false); 
_createdRoomUIs.Add(icon); 
} 
} 


public void EnableDiscoveredRoomUI(BaseRoom discoveredRoom) 
{ 
var newRoomUI = _createdRoomUIs.FirstOrDefault(ui => ui.LinkedRoom == discoveredRoom); 
newRoomUI.gameObject.SetActive(true); 
var newRoomUIRect = newRoomUI.RectTransform.rect; 
newRoomUIRect.center = newRoomUI.RectTransform.anchoredPosition; 

float xMin = newRoomUIRect.xMin; 
float yMin = newRoomUIRect.yMin; 
float xMax = newRoomUIRect.xMax; 
float yMax = newRoomUIRect.yMax; 
if (_roomUIsTotalRect == Rect.zero) 
{ 
_roomUIsTotalRect = newRoomUI.RectTransform.rect; 
} 
else 
{ 
xMin = Mathf.Min(_roomUIsTotalRect.xMin, xMin); 
yMin = Mathf.Min(_roomUIsTotalRect.yMin, yMin); 
xMax = Mathf.Max(_roomUIsTotalRect.xMax, xMax); 
yMax = Mathf.Max(_roomUIsTotalRect.yMax, yMax); 
_roomUIsTotalRect = Rect.MinMaxRect(xMin, yMin, xMax, yMax); 
} 
Vector2 boundingBoxCenter = new Vector2((xMin + xMax) / 2f, (yMin + yMax) / 2f); 

foreach (var roomUI in _createdRoomUIs) 
{ 
roomUI.RectTransform.anchoredPosition -= boundingBoxCenter; 
} 
scrollRect.content.sizeDelta = _roomUIsTotalRect.size * 2.5f; 
} 


public void SetEnteredUI(BaseRoom enteredRoom) 
{ 
foreach (var roomUI in _createdRoomUIs) 
{ 
if (roomUI.LinkedRoom == enteredRoom) 
{ 
if (_curEnteredUIRoomSlot != null) 
{ 
_curEnteredUIRoomSlot.OnExit(); 
} 
_curEnteredUIRoomSlot = roomUI; 
_curEnteredUIRoomSlot.OnEnter(); 

scrollRect.content.anchoredPosition = roomUI.RectTransform.anchoredPosition * -1f; 
break; 
} 
} 
} 
}

 

FloorMinimapUI.cs

FloorMinimapUI.cs

using System;

using UnityEngine;

using UnityEngine.UI;



public class FloorMinimapUI: BaseUI

{

public override CanvasLayer Layer => CanvasLayer.HUD;


public override bool Enabled => gameObject.activeSelf;


[SerializeField] private RawImage _minimapRawImage;

[SerializeField] private Vector2 mapMin = new Vector2(-30, -30);

[SerializeField] private Vector2 mapMax = new Vector2(30, 30);




private FloorMinimapCamera _floorMinimapCamera;

private RectTransform _miniMapRectTransform;

private Vector2 _worldMin;

private Vector2 _worldMax;







public override void Enable() => gameObject.SetActive(true);

public override void Disable() => gameObject.SetActive(false);




public void Init(FloorManager floorManager)

{

_floorMinimapCamera = floorManager.FloorMinimapCamera;

_floorMinimapCamera.Init(floorManager.Player);

}





public void SetClampBounds(Bounds roomBounds)

{

_floorMinimapCamera.SetClampBounds(roomBounds.min, roomBounds.max);

}




public void LocateMiniMapCamera(Vector3 position)

{

if (_floorMinimapCamera != null)

_floorMinimapCamera.MoveTo(position);

}



private void AdjustMiniMapSize(Vector3Int tilemapSize)

{

float scaleFactor = 10f;

_miniMapRectTransform.sizeDelta = new Vector2(tilemapSize.x, tilemapSize.y) * scaleFactor;

}




private void Update()

{

if (_miniMapRectTransform == null) return;

}

private Vector2 WorldToMiniMap(Vector3 worldPos)

{

float x = (worldPos.x - _worldMin.x) / (_worldMax.x - _worldMin.x) * _miniMapRectTransform.sizeDelta.x;

float y = (worldPos.y - _worldMin.y) / (_worldMax.y - _worldMin.y) * _miniMapRectTransform.sizeDelta.y;

return new Vector2(x, y);

}



}

 

FloorMapUIRoomSlot.cs 

using System; 
using UnityEngine; 
using UnityEngine.UI; 

public class FloorMapUIRoomSlot: MonoBehaviour 
{ 
public RectTransform RectTransform => _rectTransform; 
public BaseRoom LinkedRoom => _linkedRoom; 

private FloorManager _floorManager; 
private FloorMapUI _floorMapUI; 
private BaseRoom _linkedRoom; 
private Button _button; 
private RectTransform _rectTransform; 

private float _roomSpacingRate = 0.1f; 
private float _roomSizeRate = 0.08f; 


public void Init(FloorManager floorManager, BaseRoom room, FloorMapUI floorMapUI) 
{ 
_button = GetComponentInChildren<Button>(); 
_rectTransform = GetComponent<RectTransform>(); 

_floorManager = floorManager; 
_linkedRoom = room; 
_floorMapUI = floorMapUI; 
// 위치 계산 
var camera = Camera.main; 
var screenPointCenter = camera.WorldToScreenPoint(room.RoomBounds.center); 
_rectTransform.anchoredPosition = screenPointCenter * _roomSpacingRate; 
///크기 계산 
var screenPointMin = camera.WorldToScreenPoint(room.RoomBounds.min); 
var screenPointMax = camera.WorldToScreenPoint(room.RoomBounds.max); 
float screenWidth = Mathf.Abs(screenPointMax.x - screenPointMin.x); 
float screenHeight = Mathf.Abs(screenPointMax.y - screenPointMin.y); 
_rectTransform.sizeDelta = new Vector2(screenWidth, screenHeight) * _roomSizeRate; 
_button.interactable = false; 
_button.onClick.AddListener(() => TeleportToRoom(_floorManager.Player)); 
} 

public void OnEnter() => _button.interactable = false; 
public void OnExit() => _button.interactable = true; 
public void TeleportToRoom(PlayerController player) 
{ 
if (!_linkedRoom.IsCleared) 
return; 

player.transform.position = _linkedRoom.TPPoint.position; 
_floorManager.EnterRoom(_linkedRoom); 
_floorMapUI.SetEnteredUI(_linkedRoom); 
} 
}

 기능 추가

BaseRoom.cs

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Tilemaps;
using UnityEngine;

public enum RoomType
{
    Basic,
    Start,
    Boss,
    Shop,
    Bless,
    End
}
public abstract class BaseRoom: MonoBehaviour
{
    public Bounds RoomBounds => new (transform.position, _areaCollider.size);
    public Transform TPPoint => tpPoint;
    public Vector2Int Cell { get; set; }

    public bool IsDiscovered => isDiscovered;
    public bool IsCleared => isCleared;

    /// <summary>
    /// 상속받아서 해당 방 클리어 조건을 설정
    /// </summary>
    protected abstract bool ClearCondition { get; }
    protected FloorManager FloorManager { get; private set; }
  

    [SerializeField] private Transform tpPoint;
    [SerializeField] private Transform roomTilemapRoot;
    [SerializeField] private Tilemap minimapTilemap;
    [field: SerializeField] public RoomType RoomType { get; private set; } = RoomType.Basic;


    private bool isDiscovered;
    private bool isCleared;
    
    private Dictionary<Dir4, BaseRoom> _connectedRooms = new();
    private RoomPath[] _paths;
    private Tilemap[] _roomTilemaps;
    private BoxCollider2D _areaCollider;

    private RoomLightHandler _lightHandler;

    protected virtual void Awake()
    {
        _areaCollider = GetComponent<BoxCollider2D>();
        _paths = GetComponentsInChildren<RoomPath>();
        _roomTilemaps = roomTilemapRoot.GetComponentsInChildren<Tilemap>();

        TryGetComponent(out _lightHandler);

        Dir4[] dirArray = (Dir4[])Enum.GetValues(typeof(Dir4));
        
        for (int i = 0; i < _paths.Length; i++)
        {
            _paths[i].Init(dirArray[i]);
        }
        
        minimapTilemap.gameObject.SetActive(false);
    }


    public virtual void Init(FloorManager floorManager)
    {
        FloorManager = floorManager;
        
        foreach (var path in _paths)
        {
            foreach (var roomKvp in _connectedRooms)
            {
                if (roomKvp.Key == path.LookDir)
                {
                    path.ActivePath(floorManager,this, _roomTilemaps);
                    break;
                }
            }
        }
    }

    
    public void ConnectRoom(BaseRoom connectRoom, Vector2Int targetDir)
    {
        Dir4 dir = Dir4.Up;
        
        if (targetDir == Vector2Int.up)
        {
            dir = Dir4.Up;
        }
        else if (targetDir == Vector2Int.down)
        {
            dir = Dir4.Down;
        }
        else if (targetDir == Vector2Int.right)
        {
            dir = Dir4.Right;
        }
        else if (targetDir == Vector2Int.left)
        {
            dir = Dir4.Left;
        }

        _connectedRooms[dir] = connectRoom;
    }
    

    
    public virtual void OnEnter()
    {
        CheckClearCondition();
        EnableMinimapTilemaps();
        
        _lightHandler?.TurnOnLights();
    }
    
    public virtual void OnExit()
    {
        DisableMinimapTilemaps();
        
        _lightHandler?.TurnOffLights();
    }


    public void Discover() => isDiscovered = true;

    
    
    protected virtual void ClearRoom()
    {
        isCleared = true;
        
        DisablePathBarriers();
    }
    

    protected void CheckClearCondition()
    {
        if (IsCleared) return;
        
        if (ClearCondition)
        {
            ClearRoom();
        }
        else
        {
            EnablePathBarriers();
        }
    }
    

    protected void EnablePathBarriers()
    {
        foreach (var path in _paths) path.EnableBarrier();
    }

    protected void DisablePathBarriers()
    {
        foreach (var path in _paths) path.DisableBarrier();
    }


    // 임시 수정
    public void EnableMinimapTilemaps()
    {
        minimapTilemap.gameObject.SetActive(true);

        foreach (var path in _paths)
        {
            if (path.gameObject.activeInHierarchy)
            {
                path.ShowMinimapTilemap();
            }
        }
    }
    
    
    protected void DisableMinimapTilemaps()
    {
        minimapTilemap.gameObject.SetActive(false);

        foreach (var path in _paths)
        {
            if (path.gameObject.activeInHierarchy)
            {
                path.HideMinimapTilemap();
            }
        }
    }
}

 

FloorMapUI.cs

using System;
using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Serialization;

public class FloorMapUI: BaseUI
{
    public override CanvasLayer Layer => CanvasLayer.Popup;
    public override bool Enabled => gameObject.activeSelf;

    [SerializeField] private FloorMapUIRoomSlot uiRoomSlotPrefab;
    [SerializeField] private ScrollRect scrollRect;

    private PlayerController _playerController;
    private FloorManager _floorManager;
    private Rect _roomUIsTotalRect;
    private List<FloorMapUIRoomSlot> _createdRoomUIs = new();
    private FloorMapUIRoomSlot _curEnteredUIRoomSlot;
    private FloorMinimapUI _floorMinimapUI;
    private FloorMapUIRoomSlot _selectedRoomSlot;

    public override void Enable()
    {
        gameObject.SetActive(true);
        
        _playerController.InputController.EnableFloorMapInputs();
        _playerController.InputController.OnClosePopupUI += Disable;
        
        // 미니맵 테두리 끄기
        if (_floorMinimapUI == null) _floorMinimapUI = UIManager.Instance.GetUI<FloorMinimapUI>();
        _floorMinimapUI?.HideBorder();

        // 모든 방 UI 상태 업데이트
        UpdateAllRoomSlots();
    }

    public override void Disable()
    {
        gameObject.SetActive(false);

        _playerController.InputController.EnablePlayerInputs();
        
        // 미니맵 테두리 다시 켜기
        if (_floorMinimapUI != null) _floorMinimapUI.ShowBorder();
    }
     public void Init(FloorManager floorManager)
    {
        _floorManager = floorManager;
        _playerController = floorManager.Player;

        foreach (var roomUI in _createdRoomUIs)
        {
            Destroy(roomUI.gameObject);
        }
        
        _roomUIsTotalRect = Rect.zero;
        _createdRoomUIs.Clear();

        foreach (var room in _floorManager.FloorState.TotalRooms)
        {
            var icon = Instantiate(uiRoomSlotPrefab, scrollRect.content.transform);
            icon.Init(floorManager, room, this);
            icon.gameObject.SetActive(false);
            
            _createdRoomUIs.Add(icon);
        }
    }

    public void EnableDiscoveredRoomUI(BaseRoom discoveredRoom)
    {
        var newRoomUI = _createdRoomUIs.FirstOrDefault(ui => ui.LinkedRoom == discoveredRoom);
        
        // 수정: newRoomUI가 null일 경우를 대비한 방어 코드
        if (newRoomUI == null) return;
        
        newRoomUI.gameObject.SetActive(true);
        
        var newRoomUIRect = newRoomUI.RectTransform.rect;
        newRoomUIRect.center = newRoomUI.RectTransform.anchoredPosition;

        float xMin = newRoomUIRect.xMin;
        float yMin = newRoomUIRect.yMin;
        float xMax = newRoomUIRect.xMax;
        float yMax = newRoomUIRect.yMax;
        
        if (_roomUIsTotalRect == Rect.zero)
        {
            _roomUIsTotalRect = newRoomUIRect;
        }
        else
        {
            xMin = Mathf.Min(_roomUIsTotalRect.xMin, xMin);
            yMin = Mathf.Min(_roomUIsTotalRect.yMin, yMin);
            xMax = Mathf.Max(_roomUIsTotalRect.xMax, xMax);
            yMax = Mathf.Max(_roomUIsTotalRect.yMax, yMax);
        
            _roomUIsTotalRect = Rect.MinMaxRect(xMin, yMin, xMax, yMax);
        }
        
        Vector2 boundingBoxCenter = _roomUIsTotalRect.center;

        foreach (var roomUI in _createdRoomUIs)
        {
            if(roomUI.gameObject.activeInHierarchy)
                roomUI.RectTransform.anchoredPosition -= boundingBoxCenter;
        }
        
        scrollRect.content.sizeDelta = _roomUIsTotalRect.size * 1.5f; // 스크롤 영역 약간의 여유분
    }

    public void SetEnteredUI(BaseRoom enteredRoom)
    {
        _curEnteredUIRoomSlot = _createdRoomUIs.FirstOrDefault(ui => ui.LinkedRoom == enteredRoom);
        
        UpdateAllRoomSlots();

        if (_curEnteredUIRoomSlot != null)
        {
            scrollRect.content.anchoredPosition = _curEnteredUIRoomSlot.RectTransform.anchoredPosition * -1f;
        }
    }
    
    public void UpdateAllRoomSlots()
    {
        foreach (var roomUI in _createdRoomUIs)
        {
            if (roomUI.gameObject.activeInHierarchy)
            {
                roomUI.UpdateVisuals();
            }
        }
    }

    public void SelectRoomSlot(FloorMapUIRoomSlot slot)
    {
        if (_selectedRoomSlot != null)
        {
            _selectedRoomSlot.SetSelected(false);
        }
        
        _selectedRoomSlot = slot;

        if (_selectedRoomSlot != null)
        {
            _selectedRoomSlot.SetSelected(true);
        }
    }
}

 

FloorMinimapUI.cs

using System;
using UnityEngine;
using UnityEngine.UI;

public class FloorMinimapUI: BaseUI
{
    public override CanvasLayer Layer => CanvasLayer.HUD;
    
    public override bool Enabled => gameObject.activeSelf;
    
    [SerializeField] private RawImage _minimapRawImage;
    [SerializeField] private Vector2 mapMin = new Vector2(-30, -30); 
    [SerializeField] private Vector2 mapMax = new Vector2(30, 30);
    [SerializeField] private Image _borderImage;
    
    private FloorMinimapCamera _floorMinimapCamera;
    private RectTransform _miniMapRectTransform;
    private Vector2 _worldMin;
    private Vector2 _worldMax;



    public override void Enable() =>  gameObject.SetActive(true);
    public override void Disable() => gameObject.SetActive(false);
    
    
    public void ShowBorder()
    {
        if (_borderImage != null) _borderImage.enabled = true;
    }

    public void HideBorder()
    {
        if (_borderImage != null) _borderImage.enabled = false;
    }

    private void Start()
    {
        ShowBorder(); // 게임 시작 시 기본적으로 테두리 켜기
    }
    
    public void Init(FloorManager floorManager)
    {
        _floorMinimapCamera = floorManager.FloorMinimapCamera;
        _floorMinimapCamera.Init(floorManager.Player);
    }
    
    

    public void SetClampBounds(Bounds roomBounds)
    {
        _floorMinimapCamera.SetClampBounds(roomBounds.min, roomBounds.max);
    }
    

    public void LocateMiniMapCamera(Vector3 position)
    {
        if (_floorMinimapCamera != null)
            _floorMinimapCamera.MoveTo(position);
    }
    
    
    private void AdjustMiniMapSize(Vector3Int tilemapSize)
    {
        float scaleFactor = 10f;
        _miniMapRectTransform.sizeDelta = new Vector2(tilemapSize.x, tilemapSize.y) * scaleFactor;
    }
    

    private void Update()
    {
        if (_miniMapRectTransform == null) return;
    }
    private Vector2 WorldToMiniMap(Vector3 worldPos)
    {
        float x = (worldPos.x - _worldMin.x) / (_worldMax.x - _worldMin.x) * _miniMapRectTransform.sizeDelta.x;
        float y = (worldPos.y - _worldMin.y) / (_worldMax.y - _worldMin.y) * _miniMapRectTransform.sizeDelta.y;
        return new Vector2(x, y);
    }

}

 

 

FloorMapUIRoomSlot.cs

using System;
using UnityEngine;
using UnityEngine.UI;

public class FloorMapUIRoomSlot: MonoBehaviour
{
    public RectTransform RectTransform => _rectTransform;
    public BaseRoom LinkedRoom => _linkedRoom;
    
    [SerializeField] private Image _backgroundImage;      // 방 배경 (회색/연한 회색)
    [SerializeField] private Image _borderImage;          // 선택 테두리 (라임색)
    [SerializeField] private Image _specialIconImage;     // 보스, 상점 등 특수 아이콘
    [SerializeField] private GameObject _playerIcon;      // 플레이어 위치 표시 아이콘
    
    [SerializeField] private Color _normalColor = new Color(0.3f, 0.3f, 0.3f); // 진한 회색
    [SerializeField] private Color _currentColor = new Color(0.5f, 0.5f, 0.5f); // 연한 회색
    [SerializeField] private Color _limeColor = Color.green; // 라임색

    private FloorManager _floorManager;
    private FloorMapUI _floorMapUI;
    private BaseRoom _linkedRoom;
    private Button _button;
    private RectTransform _rectTransform;
    private bool _isSelected;

    private readonly float _roomSpacingRate = 0.1f;
    private readonly float _roomSizeRate = 0.08f;

    public void Init(FloorManager floorManager, BaseRoom room, FloorMapUI floorMapUI)
    {
        _button = GetComponentInChildren<Button>();
        _rectTransform = GetComponent<RectTransform>();

        _floorManager = floorManager;
        _linkedRoom = room;
        _floorMapUI = floorMapUI;
        
        // 위치 계산
        var camera = Camera.main;
        var screenPointCenter = camera.WorldToScreenPoint(room.RoomBounds.center);
        _rectTransform.anchoredPosition = screenPointCenter * _roomSpacingRate;
            
        
        ///크기 계산
        var screenPointMin = camera.WorldToScreenPoint(room.RoomBounds.min);
        var screenPointMax = camera.WorldToScreenPoint(room.RoomBounds.max);
        float screenWidth  = Mathf.Abs(screenPointMax.x - screenPointMin.x);
        float screenHeight = Mathf.Abs(screenPointMax.y - screenPointMin.y);
        _rectTransform.sizeDelta = new Vector2(screenWidth, screenHeight) * _roomSizeRate;
        
        // 버튼 리스너 연결
        _button.onClick.RemoveAllListeners(); // 기존 리스너 제거
        _button.onClick.AddListener(OnSlotClicked);
        
        UpdateVisuals();
    }
   
    private void OnSlotClicked()
    {
        _floorMapUI.SelectRoomSlot(this);
        
        if (_linkedRoom.IsCleared)
        {
            TeleportToRoom();
        }
        else
        {
            UIManager.Instance.ShowToast("아직 클리어하지 않은 방입니다.");
        }
    }

    public void SetSelected(bool selected)
    {
        _isSelected = selected;
        UpdateVisuals();
    }
    
    public void UpdateVisuals()
    {
        if (_linkedRoom == null || _floorManager == null) return;
        
        // FloorManager에 CurrentRoom 프로퍼티 추가 필요
        bool isCurrentRoom = (_floorManager.CurrentRoom == _linkedRoom);
        _playerIcon.SetActive(isCurrentRoom);

        _backgroundImage.color = isCurrentRoom ? _currentColor : _normalColor;
        
        _borderImage.enabled = _isSelected;
        _borderImage.color = _limeColor;
        
        // TODO: 특수 방 아이콘 로직 추가 (아래 주석 해제 및 스프라이트 할당 필요)
        /*
        _specialIconImage.enabled = true;
        switch(_linkedRoom.RoomType)
        {
            case RoomType.Boss:
                _specialIconImage.sprite = YourBossIconSprite;
                break;
            case RoomType.Shop:
                _specialIconImage.sprite = YourShopIconSprite;
                break;
            default:
                _specialIconImage.enabled = false;
                break;
        }
        */

        // 클리어된 방만 버튼 활성화
        _button.interactable = _linkedRoom.IsCleared;
    }

    // TeleportToRoom 메서드는 기존 로직 유지
    public void TeleportToRoom()
    {
        if (!_linkedRoom.IsCleared) return;

        _floorManager.Player.transform.position = _linkedRoom.TPPoint.position;
        _floorManager.EnterRoom(_linkedRoom);
        _floorMapUI.Disable();
    }
    
    // public void OnEnter() => _button.interactable = false;
    // public void OnExit()  => _button.interactable = true;
    
    
    // public void TeleportToRoom(PlayerController player)
    // {
    //     if (!_linkedRoom.IsCleared)
    //         return;
    //
    //     player.transform.position = _linkedRoom.TPPoint.position;
    //     
    //     _floorManager.EnterRoom(_linkedRoom);
    //     _floorMapUI.SetEnteredUI(_linkedRoom);
    // }
}

1. 맵 아이콘이 훨씬 똑똑해졌어요 (FloorMapUIRoomSlot.cs) 🗺️✨

가장 큰 변화는 맵에 표시되는 각 방의 아이콘(UI 슬롯)이 대대적으로 업그레이드된 점입니다.

  • 이전 코드: 각 방 아이콘은 단순히 사각형 버튼이었습니다. 현재 내가 있는 방의 버튼만 비활성화될 뿐, 특별한 정보가 없었죠.
  • 바뀐 코드: 이제 각 방 아이콘은 다양한 시각적 정보를 제공합니다.
    • 플레이어 위치 표시: 현재 플레이어가 있는 방에는 플레이어 아이콘(_playerIcon)이 표시됩니다.
    • 현재 방 강조: 플레이어가 있는 방의 배경색(_backgroundImage)을 다른 방과 다르게 표시하여 눈에 잘 띄게 합니다.
    • 선택 기능 추가: 맵에서 방을 클릭하면 라임색 테두리(_borderImage)가 생겨 어떤 방을 선택했는지 명확하게 보여줍니다.
    • 특수 방 아이콘 (준비 중): 보스 방, 상점 방 등은 나중에 특별한 아이콘(_specialIconImage)으로 표시할 수 있도록 기능이 추가되었습니다.

2. '방 종류(RoomType)' 개념 도입 (BaseRoom.cs) 🏷️

위에서 설명한 '특수 방 아이콘' 기능을 구현하기 위해, 각 방에 '종류'를 설정할 수 있는 기능이 추가되었습니다.

  • 이전 코드: 모든 방은 그냥 '방'이었습니다. 시작 방인지 보스 방인지 구분할 방법이 없었죠.
  • 바뀐 코드: RoomType이라는 새로운 개념이 도입되었습니다.
    • enum RoomType을 새로 만들어 Start, Boss, Shop 등 방의 종류를 정의했습니다.
    • 이제 BaseRoom 스크립트에서 각 방이 어떤 종류인지 설정할 수 있습니다. 이를 통해 "이 방은 보스 방이니까 보스 아이콘을 표시해줘!" 같은 작업이 가능해졌습니다.

3. 맵 사용 방식 개선 (FloorMapUI.cs & FloorMapUIRoomSlot.cs) 🖱️👆

전체 맵을 열었을 때의 조작 방식과 로직이 더 편리하고 직관적으로 바뀌었습니다.

  • 이전 코드:
    • 맵을 열면 현재 방 UI(_curEnteredUIRoomSlot)의 버튼이 비활성화되고, 다른 방으로 이동하면 다시 활성화되는 방식이었습니다.
    • 순간이동 로직이 조금 복잡하게 얽혀 있었습니다.
  • 바뀐 코드:
    • 선택과 순간이동 분리: 이제 맵에서 방을 클릭하면 먼저 선택(SelectRoomSlot) 상태가 됩니다.
    • 클리어한 방으로만 순간이동: 클리어가 완료된 방을 선택하고 클릭하면 TeleportToRoom 함수가 실행되어 순간이동합니다. 만약 클리어하지 않은 방을 클릭하면 "아직 클리어하지 않은 방입니다" 라는 안내 메시지가 뜹니다.
    • 중심 정렬 개선: 새로운 방이 발견될 때마다 전체 맵 아이콘들의 위치를 재계산하여 항상 맵의 중앙에 오도록 하는 로직이 더 안정적으로 개선되었습니다.
    • 순간이동 후 맵 닫기: 방으로 순간이동하면 열려 있던 전체 맵이 자동으로 닫혀 바로 게임을 이어서 할 수 있습니다.

4. 미니맵 UI 연동 (FloorMinimapUI.cs & FloorMapUI.cs) 🖼️

사소하지만 깔끔한 UI를 위한 개선이 있었습니다.

  • 이전 코드: 전체 맵을 열어도 화면 구석의 미니맵은 그대로 보였습니다.
  • 바뀐 코드: 전체 맵(FloorMapUI)을 열면, 화면 구석에 있는 미니맵의 테두리(_borderImage)가 잠시 사라집니다. 전체 맵을 닫으면 테두리가 다시 나타납니다. 이를 통해 화면이 더 깔끔해 보이는 효과를 줍니다.

요약

기능 이전 코드 바뀐 코드
맵 아이콘 단순한 회색 버튼 플레이어 위치, 현재 방, 선택 테두리, 특수 아이콘 등 다양한 정보 표시
방 데이터 종류 구분 없음 RoomType 추가로 보스, 상점 등 방 종류 구분 가능
맵 조작 현재 방 버튼만 비활성화 클릭으로 방 선택, 클리어된 방으로만 순간이동, 안내 메시지 추가
UI 연동 미니맵과 별개로 동작 전체 맵 열면 미니맵 테두리 숨김 처리로 깔끔함 증대

1. BaseRoom.cs (방의 기본 정보)

가장 근본적인 데이터 구조에 변화가 생겼습니다.

📄 추가된 코드

public enum RoomType
{
    Basic,
    Start,
    Boss,
    Shop,
    Bless,
    End
}

// ...

[field: SerializeField] public RoomType RoomType { get; private set; } = RoomType.Basic;
  • public enum RoomType: enum(열거형)은 프로그래머가 특정 값들의 목록을 직접 만드는 기능입니다. 여기서는 방의 종류를 Basic(일반), Start(시작), Boss(보스) 등으로 미리 정의했습니다. 이렇게 하면 숫자로 방 종류 = 1 이라고 쓰는 것보다 방 종류 = RoomType.Boss 라고 쓰는 것이 훨씬 이해하기 쉽습니다.
  • [field: SerializeField] public RoomType ...:
    • [SerializeField]: 이 코드를 변수 위에 붙이면, private(비공개) 변수여도 유니티 에디터 화면에서는 값을 직접 설정할 수 있게 됩니다. 개발자가 코드를 열지 않고도 각 방의 종류를 보스방, 상점방 등으로 쉽게 바꿀 수 있게 만들죠.
    • public RoomType ...: 이 방의 RoomType이 무엇인지 다른 스크립트에서 읽을 수 있도록 public으로 선언했습니다.
    • = RoomType.Basic: 별도로 설정하지 않으면 모든 방은 기본적으로 Basic(일반) 방이 됩니다.

⭐ 핵심: 이제 모든 방은 '종류'라는 데이터를 가지게 되었고, 이를 통해 맵에서 보스 아이콘 등을 표시할 근거가 마련되었습니다.


2. FloorMapUIRoomSlot.cs (맵에 표시되는 각 방 아이콘)

가장 많은 변화가 일어난 파일입니다. 겉모습과 작동 방식이 완전히 새로워졌습니다.

📄 추가된 코드 (UI 변수)

[SerializeField] private Image _backgroundImage;      // 방 배경
[SerializeField] private Image _borderImage;          // 선택 테두리
[SerializeField] private Image _specialIconImage;     // 특수 아이콘
[SerializeField] private GameObject _playerIcon;      // 플레이어 위치 표시 아이콘

[SerializeField] private Color _normalColor = ...; // 진한 회색
[SerializeField] private Color _currentColor = ...; // 연한 회색
[SerializeField] private Color _limeColor = ...; // 라임색
  • UI 연결 변수들: 맵 아이콘을 구성하는 각 UI 요소(배경 이미지, 테두리, 아이콘 등)를 유니티 에디터에서 연결하기 위한 변수들입니다.
  • 색상 변수들: 맵 아이콘의 상태에 따라 색을 바꾸기 위해 미리 색상 값을 정의해 두었습니다.

📄 변경된 코드 (OnSlotClicked와 UpdateVisuals 등)

  • OnSlotClicked() (새로운 메소드): 이 아이콘(버튼)을 클릭했을 때 호출되는 모든 로직이 여기 담겨있습니다.
    1. _floorMapUI.SelectRoomSlot(this);: 전체 맵 관리자에게 "내가 방금 선택됐어!"라고 알립니다.
    2. if (_linkedRoom.IsCleared): 연결된 방이 클리어되었는지 확인합니다.
    3. TeleportToRoom();: 클리어되었다면 순간이동을 실행합니다.
    4. else { ... }: 클리어되지 않았다면 "아직 클리어하지 않은 방입니다" 라는 메시지를 띄웁니다.
  • UpdateVisuals() (새로운 메소드): 이 아이콘의 모든 겉모습을 결정하는 가장 중요한 부분입니다.
    1. _playerIcon.SetActive(...): 현재 플레이어가 있는 방이라면 플레이어 아이콘을 켭니다.
    2. _backgroundImage.color = ...: 현재 방이라면 배경을 밝은 회색으로, 아니면 어두운 회색으로 바꿉니다.
    3. _borderImage.enabled = _isSelected;: 이 아이콘이 '선택'된 상태라면 라임색 테두리를 켭니다.
    4. _button.interactable = _linkedRoom.IsCleared;: 방이 클리어된 상태여야만 버튼이 활성화되도록 변경되었습니다. (순간이동 가능 상태)
  • TeleportToRoom() (수정된 메소드):
    • _floorMapUI.Disable();: 순간이동을 한 후에, 열려있던 전체 맵 UI를 자동으로 닫아주는 편의 기능이 추가되었습니다.
  • OnEnter(), OnExit() (삭제된 메소드):
    • 기존 코드에서 현재 방에 들어왔는지 나갔는지에 따라 버튼을 끄고 켜던 복잡한 로직이었습니다.
    • 이제 UpdateVisuals() 메소드가 모든 상태를 한 번에 관리하므로, 이 두 메소드는 필요 없어져서 주석 처리(사실상 삭제)되었습니다.

⭐ 핵심: 방 아이콘이 스스로의 상태(현재 방, 선택된 방, 클리어된 방)를 파악하고, 그에 맞춰 플레이어 아이콘, 배경색, 테두리 등을 종합적으로 보여주는 '스마트한' UI 부품이 되었습니다.


3. FloorMapUI.cs (전체 맵 관리자)

개별 아이콘들을 총괄하고 맵의 전반적인 동작을 관리합니다.

📄 추가/변경된 코드

  • _floorMinimapUI, _selectedRoomSlot: 미니맵 UI와 '현재 선택된 방 아이콘'이 무엇인지 기억하기 위한 변수가 추가되었습니다.
  • Enable() / Disable():
    • _floorMinimapUI?.HideBorder();: 전체 맵을 켤 때(Enable) 미니맵의 테두리를 숨깁니다.
    • _floorMinimapUI.ShowBorder();: 전체 맵을 끌 때(Disable) 미니맵 테두리를 다시 보여줍니다.
    • UpdateAllRoomSlots(): 맵을 켤 때 모든 방 아이콘들의 상태를 최신으로 업데이트하라는 명령을 내립니다.
  • SelectRoomSlot(FloorMapUIRoomSlot slot) (새로운 메소드):
    1. 이전에 선택했던 방 아이콘(_selectedRoomSlot)이 있다면, 선택 해제(SetSelected(false))시킵니다.
    2. 새로 클릭된 방 아이콘(slot)을 _selectedRoomSlot으로 기억합니다.
    3. 새로 선택된 아이콘을 선택 상태(SetSelected(true))로 만듭니다.
  • SetEnteredUI(BaseRoom enteredRoom):
    • 코드가 훨씬 간결해졌습니다. 플레이어가 특정 방에 들어갔을 때, UpdateAllRoomSlots()를 호출하여 모든 아이콘의 모습을 갱신하고, 스크롤 위치만 현재 방으로 옮겨주는 역할만 합니다.

⭐ 핵심: '선택'이라는 개념을 관리하고, 미니맵 UI와 연동하며, 맵 전체의 상태를 업데이트하는 컨트롤 타워 역할을 합니다.


4. FloorMinimapUI.cs (화면 구석의 미니맵)

가장 간단한 변화입니다.

📄 추가된 코드

[SerializeField] private Image _borderImage;

public void ShowBorder()
{
    if (_borderImage != null) _borderImage.enabled = true;
}

public void HideBorder()
{
    if (_borderImage != null) _borderImage.enabled = false;
}
  • _borderImage 변수를 추가해서 유니티 에디터에서 미니맵의 테두리 이미지를 연결할 수 있게 했습니다.
  • ShowBorder()와 HideBorder()라는 두 메소드는 이 테두리 이미지를 껐다 켰다 하는 매우 단순한 역할을 합니다. 위에서 설명한 FloorMapUI가 이 함수들을 호출하여 테두리를 제어합니다.

⭐ 핵심: 다른 UI(전체 맵)가 필요에 따라 자신의 테두리를 끄고 켤 수 있도록 외부 제어 기능을 추가했습니다.