2026-06-19 UI, UI로직
This commit is contained in:
612
Assets/My project/RoomSelect/Scripts/README_KR.md
Normal file
612
Assets/My project/RoomSelect/Scripts/README_KR.md
Normal file
@@ -0,0 +1,612 @@
|
||||
# 방 선택 UI 스크립트 설명서
|
||||
|
||||
이 폴더는 Unity 방 선택 UI에 필요한 기본 스크립트 세트입니다.
|
||||
목표는 다음 흐름을 만드는 것입니다.
|
||||
|
||||
```text
|
||||
방 버튼 클릭
|
||||
→ 오른쪽 상세 패널에 방 이름/설명/상태 표시
|
||||
→ 입장 가능하면 입장하기 버튼 활성화
|
||||
→ 입장하기 클릭
|
||||
→ 씬 이동 또는 같은 씬 안에서 플레이어 위치 이동
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 포함된 스크립트
|
||||
|
||||
```text
|
||||
RoomState.cs
|
||||
RoomData.cs
|
||||
RoomButtonUI.cs
|
||||
RoomDetailUI.cs
|
||||
RoomSelectManager.cs
|
||||
RoomEnterHandler.cs
|
||||
RoomProgressManager.cs
|
||||
RoomSelectOpenClose.cs
|
||||
```
|
||||
|
||||
처음 테스트에 꼭 필요한 것은 아래 6개입니다.
|
||||
|
||||
```text
|
||||
RoomState.cs
|
||||
RoomData.cs
|
||||
RoomButtonUI.cs
|
||||
RoomDetailUI.cs
|
||||
RoomSelectManager.cs
|
||||
RoomEnterHandler.cs
|
||||
```
|
||||
|
||||
추가 기능용 스크립트는 아래 2개입니다.
|
||||
|
||||
```text
|
||||
RoomProgressManager.cs 방 클리어/기억의 조각 조건 관리
|
||||
RoomSelectOpenClose.cs 방 선택 UI 열기/닫기 관리
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Unity 폴더 위치
|
||||
|
||||
Project 창에 아래 폴더를 만들고 스크립트를 넣으세요.
|
||||
|
||||
```text
|
||||
Assets
|
||||
└── My project
|
||||
└── RoomSelect
|
||||
├── Scripts
|
||||
├── Data
|
||||
└── Prefabs
|
||||
```
|
||||
|
||||
스크립트 위치:
|
||||
|
||||
```text
|
||||
Assets/My project/RoomSelect/Scripts
|
||||
```
|
||||
|
||||
방 데이터 asset 위치:
|
||||
|
||||
```text
|
||||
Assets/My project/RoomSelect/Data
|
||||
```
|
||||
|
||||
프리팹 위치:
|
||||
|
||||
```text
|
||||
Assets/My project/RoomSelect/Prefabs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Hierarchy 기본 구조
|
||||
|
||||
방 선택 UI는 아래 구조를 기준으로 만드세요.
|
||||
|
||||
```text
|
||||
RoomSelectCanvas
|
||||
└── RoomSelectUI
|
||||
├── Background
|
||||
├── TitleText
|
||||
├── RoomButtonRoot
|
||||
│ ├── RoomButton_Merchant
|
||||
│ ├── RoomButton_TruthFountain
|
||||
│ ├── RoomButton_FairyGarden
|
||||
│ ├── RoomButton_Workshop
|
||||
│ ├── RoomButton_CatChoir
|
||||
│ ├── RoomButton_GhostShip
|
||||
│ ├── RoomButton_ReverseValley
|
||||
│ ├── RoomButton_Maze
|
||||
│ └── RoomButton_FishingSpot
|
||||
├── DetailPanel
|
||||
│ ├── RoomNameText
|
||||
│ ├── RoomDescriptionText
|
||||
│ ├── RoomStatusText
|
||||
│ └── EnterButton
|
||||
│ └── EnterButtonText
|
||||
├── MemoryProgressArea
|
||||
└── CloseButton
|
||||
└── CloseText
|
||||
|
||||
RoomEnterSystem
|
||||
└── RoomEnterHandler
|
||||
|
||||
RoomProgressSystem
|
||||
└── RoomProgressManager
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 스크립트별 붙이는 위치
|
||||
|
||||
| 스크립트 | 붙이는 위치 | 필수 여부 |
|
||||
|---|---|---|
|
||||
| `RoomState.cs` | 붙이지 않음 | 필수 |
|
||||
| `RoomData.cs` | 붙이지 않음, 데이터 asset 생성용 | 필수 |
|
||||
| `RoomButtonUI.cs` | 각 방 버튼 | 필수 |
|
||||
| `RoomDetailUI.cs` | `DetailPanel` | 필수 |
|
||||
| `RoomSelectManager.cs` | `RoomSelectUI` | 필수 |
|
||||
| `RoomEnterHandler.cs` | `RoomEnterSystem` | 필수 |
|
||||
| `RoomProgressManager.cs` | `RoomProgressSystem` | 선택이지만 추천 |
|
||||
| `RoomSelectOpenClose.cs` | `RoomSelectUI` | 선택이지만 추천 |
|
||||
|
||||
---
|
||||
|
||||
## 5. RoomData asset 만들기
|
||||
|
||||
`RoomData.cs`는 ScriptableObject입니다.
|
||||
씬에 붙이는 스크립트가 아니라, Project 창에서 방 데이터 파일을 만드는 용도입니다.
|
||||
|
||||
만드는 방법:
|
||||
|
||||
```text
|
||||
Project 창 우클릭
|
||||
→ Create
|
||||
→ Adventure
|
||||
→ Room Select
|
||||
→ Room Data
|
||||
```
|
||||
|
||||
9개를 만드세요.
|
||||
|
||||
```text
|
||||
RoomData_Merchant.asset
|
||||
RoomData_TruthFountain.asset
|
||||
RoomData_FairyGarden.asset
|
||||
RoomData_Workshop.asset
|
||||
RoomData_CatChoir.asset
|
||||
RoomData_GhostShip.asset
|
||||
RoomData_ReverseValley.asset
|
||||
RoomData_Maze.asset
|
||||
RoomData_FishingSpot.asset
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. RoomData 입력 예시
|
||||
|
||||
### 상인의 방
|
||||
|
||||
```text
|
||||
Room Id: Merchant
|
||||
Room Name: 상인의 방
|
||||
Description: 수상한 상인이 거래를 제안하는 공간입니다.
|
||||
Scene Name: MerchantRoom
|
||||
Required Memory Pieces: 0
|
||||
Default State: Unlocked
|
||||
Has Memory Piece Reward: Off
|
||||
```
|
||||
|
||||
### 진실의 샘
|
||||
|
||||
```text
|
||||
Room Id: TruthFountain
|
||||
Room Name: 진실의 샘
|
||||
Description: 질문에 따라 피노키오의 코가 반응하는 공간입니다.
|
||||
Scene Name: TruthFountainRoom
|
||||
Required Memory Pieces: 0
|
||||
Default State: Unlocked
|
||||
Has Memory Piece Reward: On
|
||||
```
|
||||
|
||||
### 요정의 정원
|
||||
|
||||
```text
|
||||
Room Id: FairyGarden
|
||||
Room Name: 요정의 정원
|
||||
Description: 푸른 요정의 흔적이 남아 있는 정원입니다.
|
||||
Scene Name: FairyGardenRoom
|
||||
Required Memory Pieces: 2
|
||||
Default State: Locked
|
||||
Has Memory Piece Reward: On
|
||||
```
|
||||
|
||||
### 제페토의 작업실
|
||||
|
||||
```text
|
||||
Room Id: Workshop
|
||||
Room Name: 제페토의 작업실
|
||||
Description: 제페토의 기억과 단서가 남아 있는 작업실입니다.
|
||||
Scene Name: WorkshopRoom
|
||||
Required Memory Pieces: 3
|
||||
Default State: Locked
|
||||
Has Memory Piece Reward: On
|
||||
```
|
||||
|
||||
### 냥아치들의 섬
|
||||
|
||||
```text
|
||||
Room Id: CatChoir
|
||||
Room Name: 냥아치들의 섬
|
||||
Description: 고양이 합창단과 함께 리듬게임을 진행하는 공간입니다.
|
||||
Scene Name: CatChoirRoom
|
||||
Required Memory Pieces: 0
|
||||
Default State: Unlocked
|
||||
Has Memory Piece Reward: On
|
||||
```
|
||||
|
||||
### 저주받은 난파선
|
||||
|
||||
```text
|
||||
Room Id: GhostShip
|
||||
Room Name: 저주받은 난파선
|
||||
Description: 유령 선장과 블랙잭 게임을 벌이는 위험한 난파선입니다.
|
||||
Scene Name: GhostShipRoom
|
||||
Required Memory Pieces: 1
|
||||
Default State: Locked
|
||||
Has Memory Piece Reward: On
|
||||
```
|
||||
|
||||
### 거꾸로 계곡
|
||||
|
||||
```text
|
||||
Room Id: ReverseValley
|
||||
Room Name: 거꾸로 계곡
|
||||
Description: 조작 방향이 반대로 바뀌는 이상한 공간입니다.
|
||||
Scene Name: ReverseValleyRoom
|
||||
Required Memory Pieces: 2
|
||||
Default State: Locked
|
||||
Has Memory Piece Reward: On
|
||||
```
|
||||
|
||||
### 별빛 미로
|
||||
|
||||
```text
|
||||
Room Id: Maze
|
||||
Room Name: 별빛 미로
|
||||
Description: 나침반의 도움을 받아 출구를 찾아야 하는 미로입니다.
|
||||
Scene Name: MazeRoom
|
||||
Required Memory Pieces: 3
|
||||
Default State: Locked
|
||||
Has Memory Piece Reward: On
|
||||
```
|
||||
|
||||
### 기묘한 낚시터
|
||||
|
||||
```text
|
||||
Room Id: FishingSpot
|
||||
Room Name: 기묘한 낚시터
|
||||
Description: 낚시와 정화를 통해 단서를 얻는 공간입니다.
|
||||
Scene Name: FishingSpotRoom
|
||||
Required Memory Pieces: 0
|
||||
Default State: Unlocked
|
||||
Has Memory Piece Reward: On
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. RoomButtonUI 연결 방법
|
||||
|
||||
각 방 버튼에 `RoomButtonUI.cs`를 붙입니다.
|
||||
|
||||
예:
|
||||
|
||||
```text
|
||||
RoomButton_Merchant
|
||||
└── RoomButtonUI
|
||||
```
|
||||
|
||||
Inspector 연결:
|
||||
|
||||
```text
|
||||
Room Data → RoomData_Merchant.asset
|
||||
Button → 자기 자신의 Button 컴포넌트
|
||||
Background Image → 자기 자신의 Image 컴포넌트
|
||||
Room Icon Image → RoomIcon이 있으면 연결, 없으면 비워둠
|
||||
Room Name Text → RoomNameText
|
||||
Lock Icon → LockIcon
|
||||
Clear Icon → ClearIcon
|
||||
```
|
||||
|
||||
상태별 Sprite가 아직 없다면 비워둬도 됩니다.
|
||||
Sprite가 없으면 스크립트가 Color로 버튼 상태를 표시합니다.
|
||||
|
||||
---
|
||||
|
||||
## 8. RoomDetailUI 연결 방법
|
||||
|
||||
`DetailPanel`에 `RoomDetailUI.cs`를 붙입니다.
|
||||
|
||||
```text
|
||||
DetailPanel
|
||||
└── RoomDetailUI
|
||||
```
|
||||
|
||||
Inspector 연결:
|
||||
|
||||
```text
|
||||
Room Name Text → DetailPanel/RoomNameText
|
||||
Room Description Text → DetailPanel/RoomDescriptionText
|
||||
Room Status Text → DetailPanel/RoomStatusText
|
||||
Enter Button → DetailPanel/EnterButton
|
||||
Enter Button Text → DetailPanel/EnterButton/EnterButtonText
|
||||
```
|
||||
|
||||
방을 선택하면 이 패널에 방 정보가 표시됩니다.
|
||||
|
||||
---
|
||||
|
||||
## 9. RoomSelectManager 연결 방법
|
||||
|
||||
`RoomSelectUI`에 `RoomSelectManager.cs`를 붙입니다.
|
||||
|
||||
```text
|
||||
RoomSelectUI
|
||||
└── RoomSelectManager
|
||||
```
|
||||
|
||||
Inspector 연결:
|
||||
|
||||
```text
|
||||
Room Buttons Size: 9
|
||||
Element 0 → RoomButton_Merchant의 RoomButtonUI
|
||||
Element 1 → RoomButton_TruthFountain의 RoomButtonUI
|
||||
Element 2 → RoomButton_FairyGarden의 RoomButtonUI
|
||||
Element 3 → RoomButton_Workshop의 RoomButtonUI
|
||||
Element 4 → RoomButton_CatChoir의 RoomButtonUI
|
||||
Element 5 → RoomButton_GhostShip의 RoomButtonUI
|
||||
Element 6 → RoomButton_ReverseValley의 RoomButtonUI
|
||||
Element 7 → RoomButton_Maze의 RoomButtonUI
|
||||
Element 8 → RoomButton_FishingSpot의 RoomButtonUI
|
||||
|
||||
Detail UI → DetailPanel의 RoomDetailUI
|
||||
Room Enter Handler → RoomEnterSystem의 RoomEnterHandler
|
||||
Room Progress Manager → RoomProgressSystem의 RoomProgressManager
|
||||
```
|
||||
|
||||
`Auto Find Buttons In Children`를 켜면 자식에 있는 `RoomButtonUI`들을 자동으로 찾습니다.
|
||||
그래도 처음에는 직접 연결하는 것을 추천합니다.
|
||||
|
||||
---
|
||||
|
||||
## 10. RoomEnterHandler 연결 방법
|
||||
|
||||
빈 오브젝트를 만들고 `RoomEnterHandler.cs`를 붙입니다.
|
||||
|
||||
```text
|
||||
RoomEnterSystem
|
||||
└── RoomEnterHandler
|
||||
```
|
||||
|
||||
### 방식 A: 씬 이동 방식
|
||||
|
||||
각 방이 별도 씬이라면:
|
||||
|
||||
```text
|
||||
Use Scene Loading: On
|
||||
```
|
||||
|
||||
그리고 각 `RoomData`의 `Scene Name`에 씬 이름을 넣습니다.
|
||||
|
||||
주의:
|
||||
|
||||
```text
|
||||
File → Build Settings → Scenes In Build
|
||||
```
|
||||
|
||||
여기에 씬들이 등록되어 있어야 `SceneManager.LoadScene()`이 정상 작동합니다.
|
||||
|
||||
### 방식 B: 같은 씬 안에서 위치 이동 방식
|
||||
|
||||
VR 테스트 초반에는 이 방식을 추천합니다.
|
||||
|
||||
```text
|
||||
Use Scene Loading: Off
|
||||
Player Root → XR Origin 또는 플레이어 루트 오브젝트
|
||||
Deactivate Other Room Roots → 필요하면 On
|
||||
Room Targets Size → 방 개수만큼 추가
|
||||
```
|
||||
|
||||
Room Target 예시:
|
||||
|
||||
```text
|
||||
Element 0
|
||||
Room Id → CatChoir
|
||||
Spawn Point → CatChoirSpawnPoint
|
||||
Room Root → Room_CatChoir
|
||||
|
||||
Element 1
|
||||
Room Id → FishingSpot
|
||||
Spawn Point → FishingSpotSpawnPoint
|
||||
Room Root → Room_FishingSpot
|
||||
```
|
||||
|
||||
중요:
|
||||
|
||||
```text
|
||||
Room Target의 Room Id는 RoomData의 Room Id와 정확히 같아야 합니다.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. RoomProgressManager 연결 방법
|
||||
|
||||
빈 오브젝트를 만들고 `RoomProgressManager.cs`를 붙입니다.
|
||||
|
||||
```text
|
||||
RoomProgressSystem
|
||||
└── RoomProgressManager
|
||||
```
|
||||
|
||||
Inspector에서:
|
||||
|
||||
```text
|
||||
Current Memory Pieces → 현재 기억의 조각 개수
|
||||
Room Progress Entries → 처음에는 비워둬도 됨
|
||||
```
|
||||
|
||||
방 선택 UI는 `Current Memory Pieces`와 `RoomData.Required Memory Pieces`를 비교해서 잠금 여부를 계산합니다.
|
||||
|
||||
예:
|
||||
|
||||
```text
|
||||
Current Memory Pieces = 0
|
||||
GhostShip Required Memory Pieces = 1
|
||||
→ 저주받은 난파선 잠김
|
||||
|
||||
Current Memory Pieces = 1
|
||||
GhostShip Required Memory Pieces = 1
|
||||
→ 저주받은 난파선 입장 가능
|
||||
```
|
||||
|
||||
기존 `MemoryProgressManager`를 쓰고 있다면, 해당 매니저의 진행도 변경 이벤트에서 아래 함수를 연결하면 됩니다.
|
||||
|
||||
```text
|
||||
RoomProgressManager.SetMemoryPieceCount(int current, int max)
|
||||
```
|
||||
|
||||
또는 `RoomSelectManager`에 직접 연결해도 됩니다.
|
||||
|
||||
```text
|
||||
RoomSelectManager.SetCurrentMemoryPieces(int current, int max)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. RoomSelectOpenClose 연결 방법
|
||||
|
||||
`RoomSelectUI`에 `RoomSelectOpenClose.cs`를 붙입니다.
|
||||
|
||||
```text
|
||||
RoomSelectUI
|
||||
└── RoomSelectOpenClose
|
||||
```
|
||||
|
||||
Inspector 연결:
|
||||
|
||||
```text
|
||||
Room Select Root → RoomSelectUI
|
||||
Room Select Manager → RoomSelectUI의 RoomSelectManager
|
||||
Target Camera → Main Camera 또는 XR Camera
|
||||
Open On Start → 테스트할 때는 On, 나중에는 Off
|
||||
Place In Front Of Camera On Open → On
|
||||
Face Camera On Open → On
|
||||
Distance From Camera → 2.2
|
||||
Vertical Offset → -0.1
|
||||
```
|
||||
|
||||
닫기 버튼 연결:
|
||||
|
||||
```text
|
||||
CloseButton OnClick
|
||||
→ RoomSelectUI
|
||||
→ RoomSelectOpenClose.Close()
|
||||
```
|
||||
|
||||
지도 오브젝트 상호작용 연결:
|
||||
|
||||
```text
|
||||
WhaleMapObject Interact()
|
||||
→ RoomSelectUI
|
||||
→ RoomSelectOpenClose.Open()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. 테스트 순서
|
||||
|
||||
1. 스크립트를 `Assets/My project/RoomSelect/Scripts`에 넣습니다.
|
||||
2. `RoomData` asset 9개를 만듭니다.
|
||||
3. 각 방 버튼에 `RoomButtonUI`를 붙이고 `RoomData`를 연결합니다.
|
||||
4. `DetailPanel`에 `RoomDetailUI`를 붙이고 텍스트/버튼을 연결합니다.
|
||||
5. `RoomSelectUI`에 `RoomSelectManager`를 붙이고 버튼 배열, Detail UI, Enter Handler를 연결합니다.
|
||||
6. `RoomEnterSystem`에 `RoomEnterHandler`를 붙입니다.
|
||||
7. Play를 누릅니다.
|
||||
8. 방 버튼을 클릭합니다.
|
||||
9. 오른쪽 설명 패널이 바뀌는지 확인합니다.
|
||||
10. 입장 가능한 방에서 `입장하기`를 누릅니다.
|
||||
11. Console에 `방 입장` 로그가 뜨면 성공입니다.
|
||||
|
||||
---
|
||||
|
||||
## 14. 자주 생기는 문제
|
||||
|
||||
### 버튼을 눌러도 반응이 없음
|
||||
|
||||
확인할 것:
|
||||
|
||||
```text
|
||||
씬에 EventSystem이 있는가?
|
||||
Canvas에 Graphic Raycaster가 있는가?
|
||||
Button 컴포넌트가 있는가?
|
||||
RoomButtonUI의 Manager가 자동 또는 수동으로 연결되었는가?
|
||||
Button Image의 Raycast Target이 켜져 있는가?
|
||||
```
|
||||
|
||||
### 입장 버튼이 안 켜짐
|
||||
|
||||
확인할 것:
|
||||
|
||||
```text
|
||||
RoomData.Required Memory Pieces가 너무 높지 않은가?
|
||||
RoomProgressManager.Current Memory Pieces가 부족하지 않은가?
|
||||
RoomData.Default State가 Locked로 되어 있지 않은가?
|
||||
```
|
||||
|
||||
### 잠금 해제가 안 됨
|
||||
|
||||
확인할 것:
|
||||
|
||||
```text
|
||||
RoomSelectManager의 Room Progress Manager가 연결되어 있는가?
|
||||
RoomProgressManager.Current Memory Pieces 값이 증가했는가?
|
||||
RoomSelectManager.RefreshAll()이 호출되었는가?
|
||||
```
|
||||
|
||||
### 씬 이동이 안 됨
|
||||
|
||||
확인할 것:
|
||||
|
||||
```text
|
||||
RoomEnterHandler.Use Scene Loading이 On인가?
|
||||
RoomData.Scene Name이 정확한가?
|
||||
Build Settings의 Scenes In Build에 해당 씬이 등록되어 있는가?
|
||||
```
|
||||
|
||||
### 같은 씬 이동이 안 됨
|
||||
|
||||
확인할 것:
|
||||
|
||||
```text
|
||||
RoomEnterHandler.Use Scene Loading이 Off인가?
|
||||
Player Root가 연결되어 있는가?
|
||||
Room Targets의 Room Id가 RoomData.Room Id와 같은가?
|
||||
Spawn Point가 연결되어 있는가?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 15. 추천 개발 순서
|
||||
|
||||
처음에는 복잡한 잠금/이동 기능보다 UI 반응부터 확인하세요.
|
||||
|
||||
```text
|
||||
1. 방 버튼 클릭 → 상세 패널 변경 확인
|
||||
2. 입장 버튼 클릭 → Console 로그 확인
|
||||
3. RoomProgressManager로 잠금 상태 확인
|
||||
4. 기억의 조각 개수 변경 → 잠금 해제 확인
|
||||
5. 같은 씬 안에서 플레이어 위치 이동 연결
|
||||
6. 나중에 필요하면 씬 이동 방식 연결
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. 완성 후 프리팹 저장
|
||||
|
||||
완성되면 아래 오브젝트를 Project 창으로 드래그해서 프리팹으로 저장하세요.
|
||||
|
||||
```text
|
||||
RoomSelectUI
|
||||
```
|
||||
|
||||
저장 위치:
|
||||
|
||||
```text
|
||||
Assets/My project/RoomSelect/Prefabs/UI_RoomSelect.prefab
|
||||
```
|
||||
|
||||
방 버튼 하나도 따로 프리팹으로 저장하면 좋습니다.
|
||||
|
||||
```text
|
||||
Assets/My project/RoomSelect/Prefabs/UI_RoomButton.prefab
|
||||
```
|
||||
7
Assets/My project/RoomSelect/Scripts/README_KR.md.meta
Normal file
7
Assets/My project/RoomSelect/Scripts/README_KR.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df3edf0ae817bd449b34f54a572739c5
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
177
Assets/My project/RoomSelect/Scripts/RoomButtonUI.cs
Normal file
177
Assets/My project/RoomSelect/Scripts/RoomButtonUI.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[RequireComponent(typeof(Button))]
|
||||
public class RoomButtonUI : MonoBehaviour
|
||||
{
|
||||
[Header("Data")]
|
||||
[SerializeField] private RoomData roomData;
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField] private Button button;
|
||||
[SerializeField] private Image backgroundImage;
|
||||
[SerializeField] private Image roomIconImage;
|
||||
[SerializeField] private TMP_Text roomNameText;
|
||||
[SerializeField] private GameObject lockIcon;
|
||||
[SerializeField] private GameObject clearIcon;
|
||||
|
||||
[Header("Sprites")]
|
||||
[SerializeField] private Sprite normalSprite;
|
||||
[SerializeField] private Sprite selectedSprite;
|
||||
[SerializeField] private Sprite lockedSprite;
|
||||
[SerializeField] private Sprite clearedSprite;
|
||||
|
||||
[Header("Colors When Sprite Is Missing")]
|
||||
[SerializeField] private Color normalColor = new Color(0.05f, 0.55f, 0.65f, 0.85f);
|
||||
[SerializeField] private Color selectedColor = new Color(0.75f, 1f, 1f, 0.95f);
|
||||
[SerializeField] private Color lockedColor = new Color(0.15f, 0.18f, 0.22f, 0.75f);
|
||||
[SerializeField] private Color clearedColor = new Color(0.2f, 0.8f, 0.65f, 0.9f);
|
||||
|
||||
private RoomSelectManager manager;
|
||||
private RoomState currentState = RoomState.Unlocked;
|
||||
private bool isSelected;
|
||||
|
||||
public RoomData RoomData => roomData;
|
||||
public RoomState CurrentState => currentState;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
button = GetComponent<Button>();
|
||||
backgroundImage = GetComponent<Image>();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (button == null)
|
||||
{
|
||||
button = GetComponent<Button>();
|
||||
}
|
||||
|
||||
if (backgroundImage == null)
|
||||
{
|
||||
backgroundImage = GetComponent<Image>();
|
||||
}
|
||||
|
||||
if (button != null)
|
||||
{
|
||||
button.onClick.AddListener(HandleClick);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
button.onClick.RemoveListener(HandleClick);
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup(RoomData newRoomData, RoomSelectManager newManager)
|
||||
{
|
||||
roomData = newRoomData;
|
||||
manager = newManager;
|
||||
Refresh(currentState, isSelected);
|
||||
}
|
||||
|
||||
public void SetManager(RoomSelectManager newManager)
|
||||
{
|
||||
manager = newManager;
|
||||
}
|
||||
|
||||
public void Refresh(RoomState state, bool selected)
|
||||
{
|
||||
currentState = state;
|
||||
isSelected = selected;
|
||||
|
||||
if (roomNameText != null)
|
||||
{
|
||||
roomNameText.text = roomData != null ? roomData.RoomName : "방 없음";
|
||||
roomNameText.color = selected ? Color.black : Color.white;
|
||||
}
|
||||
|
||||
if (roomIconImage != null)
|
||||
{
|
||||
bool hasIcon = roomData != null && roomData.RoomIcon != null;
|
||||
roomIconImage.gameObject.SetActive(hasIcon);
|
||||
if (hasIcon)
|
||||
{
|
||||
roomIconImage.sprite = roomData.RoomIcon;
|
||||
}
|
||||
}
|
||||
|
||||
if (lockIcon != null)
|
||||
{
|
||||
lockIcon.SetActive(state == RoomState.Locked);
|
||||
}
|
||||
|
||||
if (clearIcon != null)
|
||||
{
|
||||
clearIcon.SetActive(state == RoomState.Cleared || state == RoomState.MemoryGot);
|
||||
}
|
||||
|
||||
if (button != null)
|
||||
{
|
||||
// 잠긴 방도 클릭 가능하게 둔다. 클릭하면 오른쪽 설명창에서 잠긴 이유를 보여줄 수 있다.
|
||||
button.interactable = true;
|
||||
}
|
||||
|
||||
ApplyVisualState();
|
||||
}
|
||||
|
||||
public void SetSelected(bool selected)
|
||||
{
|
||||
Refresh(currentState, selected);
|
||||
}
|
||||
|
||||
private void HandleClick()
|
||||
{
|
||||
if (manager != null)
|
||||
{
|
||||
manager.SelectRoom(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(RoomButtonUI)}] Manager가 연결되지 않았습니다: {gameObject.name}");
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyVisualState()
|
||||
{
|
||||
if (backgroundImage == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Sprite targetSprite = null;
|
||||
Color targetColor = normalColor;
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
targetSprite = selectedSprite;
|
||||
targetColor = selectedColor;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (currentState)
|
||||
{
|
||||
case RoomState.Locked:
|
||||
targetSprite = lockedSprite;
|
||||
targetColor = lockedColor;
|
||||
break;
|
||||
case RoomState.Cleared:
|
||||
case RoomState.MemoryGot:
|
||||
targetSprite = clearedSprite;
|
||||
targetColor = clearedColor;
|
||||
break;
|
||||
default:
|
||||
targetSprite = normalSprite;
|
||||
targetColor = normalColor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
backgroundImage.sprite = targetSprite;
|
||||
backgroundImage.color = targetSprite != null ? Color.white : targetColor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5601b9bdbbd33e48bf14f39e9715d39
|
||||
57
Assets/My project/RoomSelect/Scripts/RoomData.cs
Normal file
57
Assets/My project/RoomSelect/Scripts/RoomData.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(menuName = "Adventure/Room Select/Room Data", fileName = "RoomData_NewRoom")]
|
||||
public class RoomData : ScriptableObject
|
||||
{
|
||||
[Header("Basic Info")]
|
||||
[SerializeField] private string roomId = "NewRoom";
|
||||
[SerializeField] private string roomName = "새로운 방";
|
||||
[TextArea(3, 6)]
|
||||
[SerializeField] private string description = "방 설명을 입력하세요.";
|
||||
[SerializeField] private Sprite roomIcon;
|
||||
|
||||
[Header("Enter")]
|
||||
[Tooltip("씬 이동 방식을 사용할 때 불러올 씬 이름입니다. 한 씬 안에서 이동만 할 경우 비워둬도 됩니다.")]
|
||||
[SerializeField] private string sceneName = "";
|
||||
|
||||
[Header("Unlock Condition")]
|
||||
[Min(0)]
|
||||
[SerializeField] private int requiredMemoryPieces = 0;
|
||||
[SerializeField] private RoomState defaultState = RoomState.Unlocked;
|
||||
[TextArea(2, 4)]
|
||||
[SerializeField] private string lockReasonOverride = "";
|
||||
|
||||
[Header("Reward Info")]
|
||||
[Tooltip("이 방에서 기억의 조각을 얻을 수 있는지 표시용으로 사용합니다.")]
|
||||
[SerializeField] private bool hasMemoryPieceReward = false;
|
||||
|
||||
public string RoomId => roomId;
|
||||
public string RoomName => roomName;
|
||||
public string Description => description;
|
||||
public Sprite RoomIcon => roomIcon;
|
||||
public string SceneName => sceneName;
|
||||
public int RequiredMemoryPieces => requiredMemoryPieces;
|
||||
public RoomState DefaultState => defaultState;
|
||||
public string LockReasonOverride => lockReasonOverride;
|
||||
public bool HasMemoryPieceReward => hasMemoryPieceReward;
|
||||
|
||||
public bool HasSceneName()
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(sceneName);
|
||||
}
|
||||
|
||||
public string GetLockedMessage(int currentMemoryPieces)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(lockReasonOverride))
|
||||
{
|
||||
return lockReasonOverride;
|
||||
}
|
||||
|
||||
if (requiredMemoryPieces > currentMemoryPieces)
|
||||
{
|
||||
return $"기억의 조각 {requiredMemoryPieces}개 필요";
|
||||
}
|
||||
|
||||
return "아직 입장할 수 없습니다.";
|
||||
}
|
||||
}
|
||||
2
Assets/My project/RoomSelect/Scripts/RoomData.cs.meta
Normal file
2
Assets/My project/RoomSelect/Scripts/RoomData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e58520b2cb6fc2a4e888c5457a44d16b
|
||||
112
Assets/My project/RoomSelect/Scripts/RoomDetailUI.cs
Normal file
112
Assets/My project/RoomSelect/Scripts/RoomDetailUI.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
public class RoomDetailUI : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private TMP_Text roomNameText;
|
||||
[SerializeField] private TMP_Text roomDescriptionText;
|
||||
[SerializeField] private TMP_Text roomStatusText;
|
||||
[SerializeField] private Button enterButton;
|
||||
[SerializeField] private TMP_Text enterButtonText;
|
||||
|
||||
[Header("Default Text")]
|
||||
[SerializeField] private string emptyNameText = "방을 선택하세요";
|
||||
[TextArea(2, 4)]
|
||||
[SerializeField] private string emptyDescriptionText = "이동할 방을 선택하면 설명이 표시됩니다.";
|
||||
[SerializeField] private string emptyStatusText = "상태: -";
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ShowEmpty();
|
||||
}
|
||||
|
||||
public void ShowEmpty()
|
||||
{
|
||||
if (roomNameText != null)
|
||||
{
|
||||
roomNameText.text = emptyNameText;
|
||||
}
|
||||
|
||||
if (roomDescriptionText != null)
|
||||
{
|
||||
roomDescriptionText.text = emptyDescriptionText;
|
||||
}
|
||||
|
||||
if (roomStatusText != null)
|
||||
{
|
||||
roomStatusText.text = emptyStatusText;
|
||||
}
|
||||
|
||||
if (enterButtonText != null)
|
||||
{
|
||||
enterButtonText.text = "입장하기";
|
||||
}
|
||||
|
||||
if (enterButton != null)
|
||||
{
|
||||
enterButton.onClick.RemoveAllListeners();
|
||||
enterButton.interactable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowRoom(RoomData roomData, RoomState state, int currentMemoryPieces, UnityAction enterAction)
|
||||
{
|
||||
if (roomData == null)
|
||||
{
|
||||
ShowEmpty();
|
||||
return;
|
||||
}
|
||||
|
||||
if (roomNameText != null)
|
||||
{
|
||||
roomNameText.text = roomData.RoomName;
|
||||
}
|
||||
|
||||
if (roomDescriptionText != null)
|
||||
{
|
||||
roomDescriptionText.text = roomData.Description;
|
||||
}
|
||||
|
||||
if (roomStatusText != null)
|
||||
{
|
||||
roomStatusText.text = GetStatusText(roomData, state, currentMemoryPieces);
|
||||
}
|
||||
|
||||
bool canEnter = state != RoomState.Locked;
|
||||
|
||||
if (enterButtonText != null)
|
||||
{
|
||||
enterButtonText.text = canEnter ? "입장하기" : "입장 불가";
|
||||
}
|
||||
|
||||
if (enterButton != null)
|
||||
{
|
||||
enterButton.onClick.RemoveAllListeners();
|
||||
enterButton.interactable = canEnter;
|
||||
|
||||
if (canEnter && enterAction != null)
|
||||
{
|
||||
enterButton.onClick.AddListener(enterAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetStatusText(RoomData roomData, RoomState state, int currentMemoryPieces)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case RoomState.Locked:
|
||||
return $"잠김 - {roomData.GetLockedMessage(currentMemoryPieces)}";
|
||||
case RoomState.Cleared:
|
||||
return "클리어 완료";
|
||||
case RoomState.MemoryGot:
|
||||
return "기억의 조각 획득 완료";
|
||||
case RoomState.Unlocked:
|
||||
default:
|
||||
return "입장 가능";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9126dc39f7b9f2741bf103f057a3ef44
|
||||
266
Assets/My project/RoomSelect/Scripts/RoomEnterHandler.cs
Normal file
266
Assets/My project/RoomSelect/Scripts/RoomEnterHandler.cs
Normal file
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
public class RoomEnterHandler : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public class RoomTarget
|
||||
{
|
||||
public string roomId;
|
||||
public Transform spawnPoint;
|
||||
public GameObject roomRoot;
|
||||
public UnityEvent onEnter;
|
||||
}
|
||||
|
||||
[Header("Enter Mode")]
|
||||
[Tooltip("켜면 RoomData의 Scene Name으로 씬을 이동합니다. 끄면 같은 씬 안에서 위치 이동/방 오브젝트 활성화를 합니다.")]
|
||||
[SerializeField] private bool useSceneLoading = false;
|
||||
|
||||
[Header("Same Scene Movement")]
|
||||
[SerializeField] private Transform playerRoot;
|
||||
[SerializeField] private bool deactivateOtherRoomRoots = false;
|
||||
[SerializeField] private List<RoomTarget> roomTargets = new List<RoomTarget>();
|
||||
|
||||
[Header("UI")]
|
||||
[SerializeField] private GameObject roomSelectRoot;
|
||||
[SerializeField] private bool hideRoomSelectOnEnter = true;
|
||||
|
||||
[Header("Room Enter Message UI")]
|
||||
[Tooltip("방 입장 메시지 패널입니다.")]
|
||||
[SerializeField] private GameObject roomEnterMessagePanel;
|
||||
|
||||
[Tooltip("방 입장 메시지를 표시할 TextMeshProUGUI입니다.")]
|
||||
[SerializeField] private TextMeshProUGUI roomEnterMessageText;
|
||||
|
||||
[Tooltip("방 입장 메시지 전체 Root입니다. 보통 RoomEnterMessageCanvas를 넣습니다.")]
|
||||
[SerializeField] private Transform roomEnterMessageRoot;
|
||||
|
||||
[Tooltip("메시지가 화면에 표시되는 시간입니다.")]
|
||||
[SerializeField] private float roomEnterMessageDuration = 2f;
|
||||
|
||||
[Tooltip("{0} 자리에 RoomData의 RoomName이 들어갑니다.")]
|
||||
[SerializeField] private string roomEnterMessageFormat = "{0}에 입장했습니다";
|
||||
|
||||
[Header("Room Enter Message Position")]
|
||||
[Tooltip("메시지를 기준으로 배치할 카메라입니다. XR Origin > Camera Offset > Main Camera를 넣으세요.")]
|
||||
[SerializeField] private Transform messageCameraTarget;
|
||||
|
||||
[Tooltip("켜면 방 입장 메시지가 카메라 앞쪽으로 자동 이동합니다.")]
|
||||
[SerializeField] private bool placeMessageInFrontOfCamera = true;
|
||||
|
||||
[Tooltip("카메라 앞 몇 m 위치에 메시지를 띄울지 정합니다.")]
|
||||
[SerializeField] private float messageDistance = 1.6f;
|
||||
|
||||
[Tooltip("월드 기준 위/아래 위치 보정값입니다. 양수면 위로 올라갑니다.")]
|
||||
[SerializeField] private float messageHeightOffset = 0.25f;
|
||||
|
||||
[Tooltip("글자가 뒤집혀 보이면 켜세요.")]
|
||||
[SerializeField] private bool flipMessageRotation = false;
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent onAnyRoomEntered;
|
||||
|
||||
private Coroutine roomEnterMessageCoroutine;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (messageCameraTarget == null && Camera.main != null)
|
||||
{
|
||||
messageCameraTarget = Camera.main.transform;
|
||||
}
|
||||
|
||||
if (roomEnterMessagePanel != null)
|
||||
{
|
||||
roomEnterMessagePanel.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnterRoom(RoomData roomData)
|
||||
{
|
||||
if (roomData == null)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(RoomEnterHandler)}] RoomData가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hideRoomSelectOnEnter && roomSelectRoot != null)
|
||||
{
|
||||
roomSelectRoot.SetActive(false);
|
||||
}
|
||||
|
||||
if (useSceneLoading)
|
||||
{
|
||||
EnterRoomByScene(roomData);
|
||||
}
|
||||
else
|
||||
{
|
||||
EnterRoomInSameScene(roomData);
|
||||
}
|
||||
|
||||
onAnyRoomEntered?.Invoke();
|
||||
}
|
||||
|
||||
private void EnterRoomByScene(RoomData roomData)
|
||||
{
|
||||
if (!roomData.HasSceneName())
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(RoomEnterHandler)}] Scene Name이 비어 있습니다: {roomData.RoomName}");
|
||||
return;
|
||||
}
|
||||
|
||||
SceneManager.LoadScene(roomData.SceneName);
|
||||
}
|
||||
|
||||
private void EnterRoomInSameScene(RoomData roomData)
|
||||
{
|
||||
RoomTarget target = FindTarget(roomData.RoomId);
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
Debug.Log($"[{nameof(RoomEnterHandler)}] 방 입장 테스트: {roomData.RoomName} / roomId: {roomData.RoomId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (deactivateOtherRoomRoots)
|
||||
{
|
||||
foreach (RoomTarget roomTarget in roomTargets)
|
||||
{
|
||||
if (roomTarget != null && roomTarget.roomRoot != null)
|
||||
{
|
||||
roomTarget.roomRoot.SetActive(roomTarget == target);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target.roomRoot != null)
|
||||
{
|
||||
target.roomRoot.SetActive(true);
|
||||
}
|
||||
|
||||
if (playerRoot != null && target.spawnPoint != null)
|
||||
{
|
||||
CharacterController controller = playerRoot.GetComponent<CharacterController>();
|
||||
bool hadController = controller != null;
|
||||
|
||||
if (hadController)
|
||||
{
|
||||
controller.enabled = false;
|
||||
}
|
||||
|
||||
playerRoot.SetPositionAndRotation(target.spawnPoint.position, target.spawnPoint.rotation);
|
||||
|
||||
if (hadController)
|
||||
{
|
||||
controller.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
target.onEnter?.Invoke();
|
||||
|
||||
ShowRoomEnterMessage(roomData);
|
||||
|
||||
Debug.Log($"[{nameof(RoomEnterHandler)}] 방 입장: {roomData.RoomName}");
|
||||
}
|
||||
|
||||
private RoomTarget FindTarget(string roomId)
|
||||
{
|
||||
return roomTargets.Find(target => target != null && target.roomId == roomId);
|
||||
}
|
||||
|
||||
private void ShowRoomEnterMessage(RoomData roomData)
|
||||
{
|
||||
if (roomData == null)
|
||||
return;
|
||||
|
||||
if (roomEnterMessagePanel == null || roomEnterMessageText == null)
|
||||
return;
|
||||
|
||||
if (roomEnterMessageRoot != null)
|
||||
{
|
||||
roomEnterMessageRoot.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
PlaceRoomEnterMessageInFrontOfCamera();
|
||||
|
||||
string roomName = roomData.RoomName;
|
||||
roomEnterMessageText.text = string.Format(roomEnterMessageFormat, roomName);
|
||||
|
||||
roomEnterMessagePanel.SetActive(true);
|
||||
|
||||
if (roomEnterMessageCoroutine != null)
|
||||
{
|
||||
StopCoroutine(roomEnterMessageCoroutine);
|
||||
}
|
||||
|
||||
roomEnterMessageCoroutine = StartCoroutine(HideRoomEnterMessageAfterDelay());
|
||||
}
|
||||
|
||||
private void PlaceRoomEnterMessageInFrontOfCamera()
|
||||
{
|
||||
if (!placeMessageInFrontOfCamera)
|
||||
return;
|
||||
|
||||
if (roomEnterMessageRoot == null)
|
||||
return;
|
||||
|
||||
if (messageCameraTarget == null)
|
||||
{
|
||||
if (Camera.main != null)
|
||||
{
|
||||
messageCameraTarget = Camera.main.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 카메라가 아래를 보고 있어도 메시지가 바닥으로 들어가지 않게
|
||||
// 카메라 forward에서 Y값을 제거해서 수평 방향만 사용
|
||||
Vector3 flatForward = messageCameraTarget.forward;
|
||||
flatForward.y = 0f;
|
||||
|
||||
if (flatForward.sqrMagnitude < 0.001f)
|
||||
{
|
||||
flatForward = messageCameraTarget.parent != null
|
||||
? messageCameraTarget.parent.forward
|
||||
: Vector3.forward;
|
||||
|
||||
flatForward.y = 0f;
|
||||
}
|
||||
|
||||
flatForward.Normalize();
|
||||
|
||||
Vector3 targetPosition =
|
||||
messageCameraTarget.position +
|
||||
flatForward * messageDistance +
|
||||
Vector3.up * messageHeightOffset;
|
||||
|
||||
roomEnterMessageRoot.position = targetPosition;
|
||||
|
||||
Quaternion targetRotation = Quaternion.LookRotation(flatForward, Vector3.up);
|
||||
|
||||
if (flipMessageRotation)
|
||||
{
|
||||
targetRotation *= Quaternion.Euler(0f, 180f, 0f);
|
||||
}
|
||||
|
||||
roomEnterMessageRoot.rotation = targetRotation;
|
||||
}
|
||||
|
||||
private IEnumerator HideRoomEnterMessageAfterDelay()
|
||||
{
|
||||
yield return new WaitForSeconds(roomEnterMessageDuration);
|
||||
|
||||
if (roomEnterMessagePanel != null)
|
||||
{
|
||||
roomEnterMessagePanel.SetActive(false);
|
||||
}
|
||||
|
||||
roomEnterMessageCoroutine = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e9cc4e5fc56858b47b218364b373d96f
|
||||
159
Assets/My project/RoomSelect/Scripts/RoomProgressManager.cs
Normal file
159
Assets/My project/RoomSelect/Scripts/RoomProgressManager.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
public class RoomProgressManager : MonoBehaviour
|
||||
{
|
||||
[Serializable]
|
||||
public class RoomProgressEntry
|
||||
{
|
||||
public string roomId;
|
||||
public RoomState state = RoomState.Unlocked;
|
||||
public bool memoryPieceCollected;
|
||||
}
|
||||
|
||||
[Header("Memory Piece Progress")]
|
||||
[Min(0)]
|
||||
[SerializeField] private int currentMemoryPieces = 0;
|
||||
|
||||
[Header("Room Progress")]
|
||||
[SerializeField] private List<RoomProgressEntry> roomProgressEntries = new List<RoomProgressEntry>();
|
||||
|
||||
[Header("Events")]
|
||||
public UnityEvent onProgressChanged;
|
||||
|
||||
public int CurrentMemoryPieces => currentMemoryPieces;
|
||||
|
||||
public void SetMemoryPieceCount(int value)
|
||||
{
|
||||
currentMemoryPieces = Mathf.Max(0, value);
|
||||
onProgressChanged?.Invoke();
|
||||
}
|
||||
|
||||
// MemoryProgressManager의 OnProgressChanged(int current, int max) 같은 이벤트와 연결하기 위한 오버로드입니다.
|
||||
public void SetMemoryPieceCount(int current, int max)
|
||||
{
|
||||
SetMemoryPieceCount(current);
|
||||
}
|
||||
|
||||
public RoomState GetRoomState(RoomData roomData)
|
||||
{
|
||||
if (roomData == null)
|
||||
{
|
||||
return RoomState.Locked;
|
||||
}
|
||||
|
||||
RoomProgressEntry entry = FindEntry(roomData.RoomId);
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
if (entry.state == RoomState.Cleared || entry.state == RoomState.MemoryGot)
|
||||
{
|
||||
return entry.state;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMemoryPieces < roomData.RequiredMemoryPieces)
|
||||
{
|
||||
return RoomState.Locked;
|
||||
}
|
||||
|
||||
if (entry != null && entry.state != RoomState.Locked)
|
||||
{
|
||||
return entry.state;
|
||||
}
|
||||
|
||||
if (roomData.DefaultState == RoomState.Locked && currentMemoryPieces >= roomData.RequiredMemoryPieces)
|
||||
{
|
||||
return RoomState.Unlocked;
|
||||
}
|
||||
|
||||
return roomData.DefaultState;
|
||||
}
|
||||
|
||||
public void SetRoomState(string roomId, RoomState state)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(roomId))
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(RoomProgressManager)}] roomId가 비어 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
RoomProgressEntry entry = FindOrCreateEntry(roomId);
|
||||
entry.state = state;
|
||||
|
||||
if (state == RoomState.MemoryGot)
|
||||
{
|
||||
entry.memoryPieceCollected = true;
|
||||
}
|
||||
|
||||
onProgressChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void MarkRoomCleared(RoomData roomData)
|
||||
{
|
||||
if (roomData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetRoomState(roomData.RoomId, RoomState.Cleared);
|
||||
}
|
||||
|
||||
public void MarkMemoryPieceCollected(RoomData roomData)
|
||||
{
|
||||
if (roomData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RoomProgressEntry entry = FindOrCreateEntry(roomData.RoomId);
|
||||
entry.memoryPieceCollected = true;
|
||||
entry.state = RoomState.MemoryGot;
|
||||
onProgressChanged?.Invoke();
|
||||
}
|
||||
|
||||
public bool IsRoomCleared(string roomId)
|
||||
{
|
||||
RoomProgressEntry entry = FindEntry(roomId);
|
||||
return entry != null && (entry.state == RoomState.Cleared || entry.state == RoomState.MemoryGot);
|
||||
}
|
||||
|
||||
public bool HasMemoryPieceCollected(string roomId)
|
||||
{
|
||||
RoomProgressEntry entry = FindEntry(roomId);
|
||||
return entry != null && entry.memoryPieceCollected;
|
||||
}
|
||||
|
||||
public void ResetProgress()
|
||||
{
|
||||
currentMemoryPieces = 0;
|
||||
roomProgressEntries.Clear();
|
||||
onProgressChanged?.Invoke();
|
||||
}
|
||||
|
||||
private RoomProgressEntry FindEntry(string roomId)
|
||||
{
|
||||
return roomProgressEntries.Find(entry => entry.roomId == roomId);
|
||||
}
|
||||
|
||||
private RoomProgressEntry FindOrCreateEntry(string roomId)
|
||||
{
|
||||
RoomProgressEntry entry = FindEntry(roomId);
|
||||
if (entry != null)
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
entry = new RoomProgressEntry
|
||||
{
|
||||
roomId = roomId,
|
||||
state = RoomState.Unlocked,
|
||||
memoryPieceCollected = false
|
||||
};
|
||||
|
||||
roomProgressEntries.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 134358b83ec425543b4fb922e9cabdc1
|
||||
225
Assets/My project/RoomSelect/Scripts/RoomSelectManager.cs
Normal file
225
Assets/My project/RoomSelect/Scripts/RoomSelectManager.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class RoomSelectManager : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private RoomButtonUI[] roomButtons;
|
||||
[SerializeField] private RoomDetailUI detailUI;
|
||||
[SerializeField] private RoomEnterHandler roomEnterHandler;
|
||||
[SerializeField] private RoomProgressManager roomProgressManager;
|
||||
|
||||
[Header("Options")]
|
||||
[SerializeField] private bool autoFindButtonsInChildren = true;
|
||||
[SerializeField] private bool selectFirstUnlockedOnEnable = true;
|
||||
[Min(0)]
|
||||
[SerializeField] private int fallbackCurrentMemoryPieces = 0;
|
||||
|
||||
private RoomButtonUI selectedButton;
|
||||
private RoomData selectedRoomData;
|
||||
|
||||
public RoomData SelectedRoomData => selectedRoomData;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
detailUI = GetComponentInChildren<RoomDetailUI>(true);
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (detailUI == null)
|
||||
{
|
||||
detailUI = GetComponentInChildren<RoomDetailUI>(true);
|
||||
}
|
||||
|
||||
if (autoFindButtonsInChildren && (roomButtons == null || roomButtons.Length == 0))
|
||||
{
|
||||
roomButtons = GetComponentsInChildren<RoomButtonUI>(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
RefreshAll();
|
||||
|
||||
if (selectFirstUnlockedOnEnable)
|
||||
{
|
||||
SelectFirstUnlockedRoom();
|
||||
}
|
||||
else if (detailUI != null)
|
||||
{
|
||||
detailUI.ShowEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshAll()
|
||||
{
|
||||
if (roomButtons == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (RoomButtonUI button in roomButtons)
|
||||
{
|
||||
if (button == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
button.SetManager(this);
|
||||
RoomState state = GetRoomState(button.RoomData);
|
||||
button.Refresh(state, button == selectedButton);
|
||||
}
|
||||
|
||||
if (selectedButton != null)
|
||||
{
|
||||
ShowSelectedRoomDetail();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectRoom(RoomButtonUI button)
|
||||
{
|
||||
if (button == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
selectedButton = button;
|
||||
selectedRoomData = button.RoomData;
|
||||
|
||||
foreach (RoomButtonUI roomButton in roomButtons)
|
||||
{
|
||||
if (roomButton == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
roomButton.Refresh(GetRoomState(roomButton.RoomData), roomButton == selectedButton);
|
||||
}
|
||||
|
||||
ShowSelectedRoomDetail();
|
||||
}
|
||||
|
||||
public void SelectRoomByIndex(int index)
|
||||
{
|
||||
if (roomButtons == null || index < 0 || index >= roomButtons.Length)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(RoomSelectManager)}] 잘못된 방 인덱스입니다: {index}");
|
||||
return;
|
||||
}
|
||||
|
||||
SelectRoom(roomButtons[index]);
|
||||
}
|
||||
|
||||
public void EnterSelectedRoom()
|
||||
{
|
||||
if (selectedRoomData == null)
|
||||
{
|
||||
Debug.LogWarning($"[{nameof(RoomSelectManager)}] 선택된 방이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
RoomState state = GetRoomState(selectedRoomData);
|
||||
if (state == RoomState.Locked)
|
||||
{
|
||||
Debug.Log($"[{nameof(RoomSelectManager)}] 잠긴 방입니다: {selectedRoomData.RoomName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (roomEnterHandler != null)
|
||||
{
|
||||
roomEnterHandler.EnterRoom(selectedRoomData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"[{nameof(RoomSelectManager)}] 방 입장 테스트: {selectedRoomData.RoomName}");
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCurrentMemoryPieces(int current)
|
||||
{
|
||||
fallbackCurrentMemoryPieces = Mathf.Max(0, current);
|
||||
|
||||
if (roomProgressManager != null)
|
||||
{
|
||||
roomProgressManager.SetMemoryPieceCount(current);
|
||||
}
|
||||
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
// MemoryProgressManager의 OnProgressChanged(int current, int max) 이벤트와 연결하기 위한 오버로드입니다.
|
||||
public void SetCurrentMemoryPieces(int current, int max)
|
||||
{
|
||||
SetCurrentMemoryPieces(current);
|
||||
}
|
||||
|
||||
public RoomState GetRoomState(RoomData roomData)
|
||||
{
|
||||
if (roomData == null)
|
||||
{
|
||||
return RoomState.Locked;
|
||||
}
|
||||
|
||||
if (roomProgressManager != null)
|
||||
{
|
||||
return roomProgressManager.GetRoomState(roomData);
|
||||
}
|
||||
|
||||
int currentPieces = GetCurrentMemoryPieces();
|
||||
if (currentPieces < roomData.RequiredMemoryPieces)
|
||||
{
|
||||
return RoomState.Locked;
|
||||
}
|
||||
|
||||
if (roomData.DefaultState == RoomState.Locked && currentPieces >= roomData.RequiredMemoryPieces)
|
||||
{
|
||||
return RoomState.Unlocked;
|
||||
}
|
||||
|
||||
return roomData.DefaultState;
|
||||
}
|
||||
|
||||
public int GetCurrentMemoryPieces()
|
||||
{
|
||||
return roomProgressManager != null ? roomProgressManager.CurrentMemoryPieces : fallbackCurrentMemoryPieces;
|
||||
}
|
||||
|
||||
private void ShowSelectedRoomDetail()
|
||||
{
|
||||
if (detailUI == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RoomState state = GetRoomState(selectedRoomData);
|
||||
detailUI.ShowRoom(selectedRoomData, state, GetCurrentMemoryPieces(), EnterSelectedRoom);
|
||||
}
|
||||
|
||||
private void SelectFirstUnlockedRoom()
|
||||
{
|
||||
if (roomButtons == null || roomButtons.Length == 0)
|
||||
{
|
||||
if (detailUI != null)
|
||||
{
|
||||
detailUI.ShowEmpty();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (RoomButtonUI button in roomButtons)
|
||||
{
|
||||
if (button == null || button.RoomData == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetRoomState(button.RoomData) != RoomState.Locked)
|
||||
{
|
||||
SelectRoom(button);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SelectRoom(roomButtons[0]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5f5a3e84069dd64c8056f5bd0ed41c8
|
||||
100
Assets/My project/RoomSelect/Scripts/RoomSelectOpenClose.cs
Normal file
100
Assets/My project/RoomSelect/Scripts/RoomSelectOpenClose.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class RoomSelectOpenClose : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[SerializeField] private GameObject roomSelectRoot;
|
||||
[SerializeField] private RoomSelectManager roomSelectManager;
|
||||
[SerializeField] private Transform targetCamera;
|
||||
|
||||
[Header("Open Options")]
|
||||
[SerializeField] private bool openOnStart = false;
|
||||
[SerializeField] private bool placeInFrontOfCameraOnOpen = true;
|
||||
[SerializeField] private bool faceCameraOnOpen = true;
|
||||
[SerializeField] private float distanceFromCamera = 2.2f;
|
||||
[SerializeField] private float verticalOffset = -0.1f;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
roomSelectRoot = gameObject;
|
||||
roomSelectManager = GetComponent<RoomSelectManager>();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (roomSelectRoot == null)
|
||||
{
|
||||
roomSelectRoot = gameObject;
|
||||
}
|
||||
|
||||
if (roomSelectManager == null)
|
||||
{
|
||||
roomSelectManager = roomSelectRoot.GetComponent<RoomSelectManager>();
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (openOnStart)
|
||||
{
|
||||
Open();
|
||||
}
|
||||
else
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void Open()
|
||||
{
|
||||
if (targetCamera == null && Camera.main != null)
|
||||
{
|
||||
targetCamera = Camera.main.transform;
|
||||
}
|
||||
|
||||
if (placeInFrontOfCameraOnOpen && targetCamera != null)
|
||||
{
|
||||
Vector3 targetPosition = targetCamera.position + targetCamera.forward * distanceFromCamera;
|
||||
targetPosition.y += verticalOffset;
|
||||
roomSelectRoot.transform.position = targetPosition;
|
||||
}
|
||||
|
||||
if (faceCameraOnOpen && targetCamera != null)
|
||||
{
|
||||
roomSelectRoot.transform.LookAt(targetCamera);
|
||||
roomSelectRoot.transform.Rotate(0f, 180f, 0f);
|
||||
}
|
||||
|
||||
roomSelectRoot.SetActive(true);
|
||||
|
||||
if (roomSelectManager != null)
|
||||
{
|
||||
roomSelectManager.RefreshAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
if (roomSelectRoot != null)
|
||||
{
|
||||
roomSelectRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Toggle()
|
||||
{
|
||||
if (roomSelectRoot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (roomSelectRoot.activeSelf)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
Open();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff1d5678541e82a47a495c8d118f1406
|
||||
7
Assets/My project/RoomSelect/Scripts/RoomState.cs
Normal file
7
Assets/My project/RoomSelect/Scripts/RoomState.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
public enum RoomState
|
||||
{
|
||||
Locked,
|
||||
Unlocked,
|
||||
Cleared,
|
||||
MemoryGot
|
||||
}
|
||||
2
Assets/My project/RoomSelect/Scripts/RoomState.cs.meta
Normal file
2
Assets/My project/RoomSelect/Scripts/RoomState.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5acb48383d9330044a09602affd4fa9d
|
||||
Reference in New Issue
Block a user