기존코드
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() (새로운 메소드): 이 아이콘(버튼)을 클릭했을 때 호출되는 모든 로직이 여기 담겨있습니다.
- _floorMapUI.SelectRoomSlot(this);: 전체 맵 관리자에게 "내가 방금 선택됐어!"라고 알립니다.
- if (_linkedRoom.IsCleared): 연결된 방이 클리어되었는지 확인합니다.
- TeleportToRoom();: 클리어되었다면 순간이동을 실행합니다.
- else { ... }: 클리어되지 않았다면 "아직 클리어하지 않은 방입니다" 라는 메시지를 띄웁니다.
- UpdateVisuals() (새로운 메소드): 이 아이콘의 모든 겉모습을 결정하는 가장 중요한 부분입니다.
- _playerIcon.SetActive(...): 현재 플레이어가 있는 방이라면 플레이어 아이콘을 켭니다.
- _backgroundImage.color = ...: 현재 방이라면 배경을 밝은 회색으로, 아니면 어두운 회색으로 바꿉니다.
- _borderImage.enabled = _isSelected;: 이 아이콘이 '선택'된 상태라면 라임색 테두리를 켭니다.
- _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) (새로운 메소드):
- 이전에 선택했던 방 아이콘(_selectedRoomSlot)이 있다면, 선택 해제(SetSelected(false))시킵니다.
- 새로 클릭된 방 아이콘(slot)을 _selectedRoomSlot으로 기억합니다.
- 새로 선택된 아이콘을 선택 상태(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(전체 맵)가 필요에 따라 자신의 테두리를 끄고 켤 수 있도록 외부 제어 기능을 추가했습니다.