Merge branch 'main' of https://www.nakjungit.site/sharedacc520k/WhaleAdventure_VR
# Conflicts: # Assets/My project/Fonts/Pretendard-Black SDF.asset
This commit is contained in:
@@ -405,10 +405,10 @@ void EndRound(int winner, string message)
|
||||
if (dealerWinCount >= targetWinCount)
|
||||
{
|
||||
isMatchOver = true;
|
||||
ShowResult("Final Result\nDealer Wins!");
|
||||
Debug.Log("Final Result: Dealer Wins!");
|
||||
ShowResult("Final Result\nDealer Wins!\nTry Again!");
|
||||
Debug.Log("Final Result: Dealer Wins! Restarting match.");
|
||||
|
||||
StartCoroutine(EndMatchAfterDelay());
|
||||
StartCoroutine(RestartMatchAfterLose());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -439,6 +439,31 @@ IEnumerator EndMatchAfterDelay()
|
||||
onMatchEnded?.Invoke();
|
||||
}
|
||||
|
||||
IEnumerator RestartMatchAfterLose()
|
||||
{
|
||||
if (isEndingMatch)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
isEndingMatch = true;
|
||||
|
||||
yield return new WaitForSeconds(matchEndDelay);
|
||||
|
||||
playerWinCount = 0;
|
||||
dealerWinCount = 0;
|
||||
|
||||
isMatchOver = false;
|
||||
isGameOver = true;
|
||||
isPlayerTurn = false;
|
||||
isEndingMatch = false;
|
||||
|
||||
HideAllWinMarks();
|
||||
StartRound();
|
||||
|
||||
Debug.Log("Blackjack restarted after player lost.");
|
||||
}
|
||||
|
||||
void UpdateScoreUI(bool showDealerFullScore)
|
||||
{
|
||||
int playerScore = CalculateScore(playerCards);
|
||||
|
||||
@@ -5,14 +5,25 @@
|
||||
[RequireComponent(typeof(CharacterVoiceObject))]
|
||||
public class DialogPlayer : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private List<DialogGroup> _dialogGroups;
|
||||
[System.Serializable]
|
||||
public struct RegionGroup
|
||||
{
|
||||
public string Region; // 영역 이름 (NPC마다 자유롭게 지정 — 그룹 이름과 무관)
|
||||
public DialogGroup Group;
|
||||
}
|
||||
|
||||
[Tooltip("영역 이름 ↔ 그 영역에서 재생할 DialogGroup")]
|
||||
[SerializeField] private List<RegionGroup> _regionGroups;
|
||||
|
||||
[Header("Region")]
|
||||
[SerializeField] private string _currentRegion; // 현재 영역 이름. DialogRegion 트리거가 갱신
|
||||
|
||||
[Header("Dialog HUD Placement")] // 씬에서 캐릭터 위치/주변(벽 등)에 맞춰 조절
|
||||
[SerializeField] private float _hudChestHeight = 1.2f; // 화자 발 기준 가슴 높이
|
||||
[SerializeField] private float _hudForwardOffset = 0.5f; // 화자→플레이어 방향으로 띄울 거리
|
||||
[SerializeField] private float _hudLateralOffset = 0f; // 좌우 오프셋 (+ 플레이어 시점 오른쪽)
|
||||
|
||||
private Dictionary<string, DialogGroup> _dialogGroupMap;
|
||||
private Dictionary<string, DialogGroup> _regionMap;
|
||||
private Animator _animator;
|
||||
private int _initialGestureHash;
|
||||
private int _initialExpressionHash;
|
||||
@@ -22,9 +33,9 @@ public class DialogPlayer : MonoBehaviour
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_dialogGroupMap = new Dictionary<string, DialogGroup>();
|
||||
foreach (var g in _dialogGroups)
|
||||
_dialogGroupMap[g.DialogGroupName] = g;
|
||||
_regionMap = new Dictionary<string, DialogGroup>();
|
||||
foreach (var e in _regionGroups)
|
||||
if (e.Group != null) _regionMap[e.Region] = e.Group;
|
||||
|
||||
_animator = GetComponentInChildren<Animator>();
|
||||
|
||||
@@ -41,16 +52,30 @@ private void Awake()
|
||||
|
||||
public async Awaitable Play()
|
||||
{
|
||||
if(_dialogGroups.Count > 0)
|
||||
_ = Play(_dialogGroups[0].DialogGroupName);
|
||||
var region = ResolveRegion();
|
||||
if (region != null)
|
||||
await Play(region);
|
||||
}
|
||||
|
||||
public async Awaitable Play(string groupName)
|
||||
// 현재 영역. 영역이 없거나 매칭 그룹이 없으면 리스트 첫 항목으로 폴백.
|
||||
private string ResolveRegion()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_currentRegion) && _regionMap.ContainsKey(_currentRegion))
|
||||
return _currentRegion;
|
||||
return _regionGroups.Count > 0 ? _regionGroups[0].Region : null;
|
||||
}
|
||||
|
||||
// 영역 전환 (DialogRegion 트리거가 호출). 다음 Play()부터 해당 영역 대화가 재생됨.
|
||||
public void SetRegion(string region) => _currentRegion = region;
|
||||
|
||||
public string CurrentRegion => _currentRegion;
|
||||
|
||||
public async Awaitable Play(string region)
|
||||
{
|
||||
if (IsPlaying) return;
|
||||
if (!_dialogGroupMap.TryGetValue(groupName, out var group))
|
||||
if (!_regionMap.TryGetValue(region, out var group))
|
||||
{
|
||||
Debug.LogWarning($"[DialogPlayer] 그룹 없음: {groupName}");
|
||||
Debug.LogWarning($"[DialogPlayer] 영역 대화 없음: {region}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
27
Assets/02_Scripts/Communication/Dialog/DialogRegion.cs
Normal file
27
Assets/02_Scripts/Communication/Dialog/DialogRegion.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
|
||||
// 영역 트리거. 이 콜라이더(isTrigger) 안으로 NPC(DialogPlayer 보유)가 들어오면
|
||||
// 그 NPC의 현재 영역을 _regionKey로 전환한다.
|
||||
// _regionKey는 해당 영역에서 재생할 DialogGroup의 이름과 일치해야 한다 (예: "Coast", "Hill").
|
||||
//
|
||||
// 주의: OnTriggerEnter가 동작하려면 들어오는 쪽(또는 트리거 쪽)에 Rigidbody가 있어야 하고,
|
||||
// 이 오브젝트의 Collider는 Is Trigger여야 한다.
|
||||
[RequireComponent(typeof(Collider))]
|
||||
public class DialogRegion : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private string _regionKey; // DialogGroup 이름과 일치
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
// 컴포넌트 추가 시 편의상 트리거로 설정
|
||||
var col = GetComponent<Collider>();
|
||||
if (col != null) col.isTrigger = true;
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
var player = other.GetComponentInParent<DialogPlayer>();
|
||||
if (player != null)
|
||||
player.SetRegion(_regionKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fb6dce8c4da85e478d7da770f057cf3
|
||||
@@ -1,18 +0,0 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class RoomMoveButton : MonoBehaviour
|
||||
{
|
||||
[Header("이 버튼을 눌렀을 때 이동할 방 번호 입력")]
|
||||
[SerializeField] private int targetRoomNumber;
|
||||
|
||||
public void OnClickMoveRoom()
|
||||
{
|
||||
if (RoomRouteManager.Instance == null)
|
||||
{
|
||||
Debug.LogError("RoomRouteManager가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
RoomRouteManager.Instance.MoveToRoom(targetRoomNumber);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8eb4311a25437fd468487f681f4662e3
|
||||
@@ -1,92 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
public class RoomRouteDebugTester : MonoBehaviour
|
||||
{
|
||||
private List<RoomRouteManager.RoomData> currentChoices =
|
||||
new List<RoomRouteManager.RoomData>();
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (RoomRouteManager.Instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Keyboard.current == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// C키: 방문 안 한 방 중 랜덤 후보 뽑기
|
||||
if (Keyboard.current.cKey.wasPressedThisFrame)
|
||||
{
|
||||
currentChoices = RoomRouteManager.Instance.GetRandomNextRooms();
|
||||
|
||||
Debug.Log($"현재 선택 가능한 후보 개수: {currentChoices.Count}");
|
||||
|
||||
for (int i = 0; i < currentChoices.Count; i++)
|
||||
{
|
||||
Debug.Log($"{i + 1}번 선택지 → 방 번호: {currentChoices[i].roomNumber}, 씬 이름: {currentChoices[i].sceneName}");
|
||||
}
|
||||
}
|
||||
|
||||
// 1키: 첫 번째 후보 선택
|
||||
if (Keyboard.current.digit1Key.wasPressedThisFrame)
|
||||
{
|
||||
MoveToChoice(0);
|
||||
}
|
||||
|
||||
// 2키: 두 번째 후보 선택
|
||||
if (Keyboard.current.digit2Key.wasPressedThisFrame)
|
||||
{
|
||||
MoveToChoice(1);
|
||||
}
|
||||
|
||||
// T키: 방문 상태 확인
|
||||
if (Keyboard.current.tKey.wasPressedThisFrame)
|
||||
{
|
||||
Debug.Log($"방문한 방 개수: {RoomRouteManager.Instance.VisitedRoomCount} / {RoomRouteManager.Instance.TotalRoomCount}");
|
||||
Debug.Log($"현재 방 번호: {RoomRouteManager.Instance.CurrentRoomNumber}");
|
||||
}
|
||||
|
||||
// X키: 테스트용 방문 기록 초기화
|
||||
if (Keyboard.current.xKey.wasPressedThisFrame)
|
||||
{
|
||||
Debug.Log("방문 기록 초기화");
|
||||
currentChoices.Clear();
|
||||
RoomRouteManager.Instance.ResetVisitedRooms();
|
||||
}
|
||||
|
||||
// F키: 모든 방 방문 후 마지막 씬 이동 테스트
|
||||
if (Keyboard.current.fKey.wasPressedThisFrame)
|
||||
{
|
||||
Debug.Log("마지막 씬 이동 테스트");
|
||||
RoomRouteManager.Instance.MoveToFinalScene();
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveToChoice(int index)
|
||||
{
|
||||
if (currentChoices == null || currentChoices.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("먼저 C키를 눌러 랜덤 후보를 뽑아야 합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= currentChoices.Count)
|
||||
{
|
||||
Debug.LogWarning("해당 번호의 선택지가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
int targetRoomNumber = currentChoices[index].roomNumber;
|
||||
|
||||
Debug.Log($"{index + 1}번 선택지 선택 → 방 {targetRoomNumber} 이동");
|
||||
|
||||
currentChoices.Clear();
|
||||
|
||||
RoomRouteManager.Instance.MoveToRoom(targetRoomNumber);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e414802b02a03e469c541b086f805bb
|
||||
@@ -1,219 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class RoomRouteManager : MonoBehaviour
|
||||
{
|
||||
public static RoomRouteManager Instance;
|
||||
|
||||
[System.Serializable]
|
||||
public class RoomData
|
||||
{
|
||||
[Header("방 번호 입력")]
|
||||
public int roomNumber;
|
||||
|
||||
[Header("이 방에 해당하는 Scene 이름 입력")]
|
||||
public string sceneName;
|
||||
}
|
||||
|
||||
[Header("전체 방 정보 입력")]
|
||||
[SerializeField] private List<RoomData> rooms = new List<RoomData>();
|
||||
|
||||
[Header("시작 방 번호 입력")]
|
||||
[SerializeField] private int startRoomNumber;
|
||||
|
||||
[Header("랜덤 선택지 개수")]
|
||||
[SerializeField] private int randomChoiceCount = 2;
|
||||
|
||||
[Header("모든 방 방문 후 이동할 마지막 Scene 이름")]
|
||||
[SerializeField] private string finalSceneName;
|
||||
|
||||
private int _currentRoomNumber;
|
||||
private readonly HashSet<int> _visitedRooms = new HashSet<int>();
|
||||
|
||||
public int CurrentRoomNumber => _currentRoomNumber;
|
||||
public int VisitedRoomCount => _visitedRooms.Count;
|
||||
public int TotalRoomCount => rooms.Count;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
_currentRoomNumber = startRoomNumber;
|
||||
|
||||
if (startRoomNumber != 0)
|
||||
{
|
||||
_visitedRooms.Add(startRoomNumber);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 방문하지 않은 방 전체 반환
|
||||
public List<RoomData> GetUnvisitedRooms()
|
||||
{
|
||||
List<RoomData> result = new List<RoomData>();
|
||||
|
||||
foreach (RoomData room in rooms)
|
||||
{
|
||||
if (!_visitedRooms.Contains(room.roomNumber))
|
||||
{
|
||||
result.Add(room);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 대화 선택지에 보여줄 랜덤 방 목록 반환
|
||||
public List<RoomData> GetRandomNextRooms()
|
||||
{
|
||||
List<RoomData> unvisitedRooms = GetUnvisitedRooms();
|
||||
List<RoomData> randomRooms = new List<RoomData>();
|
||||
|
||||
int count = Mathf.Min(randomChoiceCount, unvisitedRooms.Count);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int randomIndex = Random.Range(0, unvisitedRooms.Count);
|
||||
|
||||
randomRooms.Add(unvisitedRooms[randomIndex]);
|
||||
unvisitedRooms.RemoveAt(randomIndex);
|
||||
}
|
||||
|
||||
return randomRooms;
|
||||
}
|
||||
|
||||
// 버튼이나 대화 선택지에서 호출
|
||||
public void MoveToRoom(int roomNumber)
|
||||
{
|
||||
if (SceneLoadManager.Instance == null)
|
||||
{
|
||||
Debug.LogError("SceneLoadManager가 씬에 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SceneLoadManager.Instance.IsChangingScene)
|
||||
{
|
||||
Debug.Log("이미 씬 이동 중입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
RoomData targetRoom = GetRoomData(roomNumber);
|
||||
|
||||
if (targetRoom == null)
|
||||
{
|
||||
Debug.LogWarning($"방 정보를 찾을 수 없습니다. 방 번호: {roomNumber}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_visitedRooms.Contains(roomNumber))
|
||||
{
|
||||
Debug.Log($"이미 방문한 방입니다. 방 번호: {roomNumber}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(targetRoom.sceneName))
|
||||
{
|
||||
Debug.LogWarning($"방 {roomNumber}의 Scene 이름이 비어있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
_currentRoomNumber = roomNumber;
|
||||
_visitedRooms.Add(roomNumber);
|
||||
|
||||
SceneLoadManager.Instance.RequestSceneChange(targetRoom.sceneName);
|
||||
}
|
||||
|
||||
// 랜덤 방 하나로 바로 이동하고 싶을 때 사용
|
||||
public void MoveToRandomRoom()
|
||||
{
|
||||
List<RoomData> unvisitedRooms = GetUnvisitedRooms();
|
||||
|
||||
if (unvisitedRooms.Count <= 0)
|
||||
{
|
||||
Debug.Log("방문하지 않은 방이 없습니다.");
|
||||
|
||||
if (IsAllRoomsVisited())
|
||||
{
|
||||
MoveToFinalScene();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int randomIndex = Random.Range(0, unvisitedRooms.Count);
|
||||
RoomData randomRoom = unvisitedRooms[randomIndex];
|
||||
|
||||
MoveToRoom(randomRoom.roomNumber);
|
||||
}
|
||||
|
||||
public bool IsAllRoomsVisited()
|
||||
{
|
||||
return rooms.Count > 0 && _visitedRooms.Count >= rooms.Count;
|
||||
}
|
||||
|
||||
public void MoveToFinalScene()
|
||||
{
|
||||
if (!IsAllRoomsVisited())
|
||||
{
|
||||
Debug.Log("아직 모든 방을 방문하지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SceneLoadManager.Instance == null)
|
||||
{
|
||||
Debug.LogError("SceneLoadManager가 씬에 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (SceneLoadManager.Instance.IsChangingScene)
|
||||
{
|
||||
Debug.Log("이미 씬 이동 중입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(finalSceneName))
|
||||
{
|
||||
Debug.LogWarning("마지막 Scene 이름이 비어있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
SceneLoadManager.Instance.RequestSceneChange(finalSceneName);
|
||||
}
|
||||
|
||||
public bool IsVisitedRoom(int roomNumber)
|
||||
{
|
||||
return _visitedRooms.Contains(roomNumber);
|
||||
}
|
||||
|
||||
public void ResetVisitedRooms()
|
||||
{
|
||||
_visitedRooms.Clear();
|
||||
|
||||
_currentRoomNumber = startRoomNumber;
|
||||
|
||||
if (startRoomNumber != 0)
|
||||
{
|
||||
_visitedRooms.Add(startRoomNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private RoomData GetRoomData(int roomNumber)
|
||||
{
|
||||
foreach (RoomData room in rooms)
|
||||
{
|
||||
if (room.roomNumber == roomNumber)
|
||||
{
|
||||
return room;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a9c2a7a081e65e43806d1ecb3cca28c
|
||||
8
Assets/02_Scripts/Managers/change room manager.meta
Normal file
8
Assets/02_Scripts/Managers/change room manager.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1583d3297de606648b62dde46dc74678
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,55 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class GateOpenZone : MonoBehaviour
|
||||
{
|
||||
[Header("게이트 오픈 관리자")]
|
||||
[SerializeField] private RoomClearGateController roomClearGateController;
|
||||
|
||||
[Header("Player Check")]
|
||||
[SerializeField] private string playerTag = "Player";
|
||||
|
||||
private bool used = false;
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (used)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsPlayer(other))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (roomClearGateController == null)
|
||||
{
|
||||
Debug.LogWarning("RoomClearGateController가 연결되지 않았습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!roomClearGateController.IsRoomCleared)
|
||||
{
|
||||
Debug.Log("아직 방 클리어 전입니다. 게이트를 열지 않습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
used = true;
|
||||
roomClearGateController.OpenClearGate();
|
||||
}
|
||||
|
||||
private bool IsPlayer(Collider other)
|
||||
{
|
||||
if (other.CompareTag(playerTag))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (other.transform.root.CompareTag(playerTag))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6986d649e7cfba74881058e544e2f727
|
||||
@@ -0,0 +1,134 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
public class RandomSceneRouteManager : MonoBehaviour
|
||||
{
|
||||
public static RandomSceneRouteManager Instance;
|
||||
|
||||
[Header("랜덤으로 이동할 방 Scene 이름들")]
|
||||
[SerializeField] private string[] roomSceneNames;
|
||||
|
||||
[Header("모든 방 방문 후 이동할 마지막 Scene 이름")]
|
||||
[SerializeField] private string finalSceneName;
|
||||
|
||||
private readonly HashSet<string> visitedScenes = new HashSet<string>();
|
||||
private bool finalSceneUsed = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetNextSceneName()
|
||||
{
|
||||
string currentSceneName = SceneManager.GetActiveScene().name;
|
||||
|
||||
Debug.Log("현재 씬 이름: " + currentSceneName);
|
||||
|
||||
if (IsRoomScene(currentSceneName))
|
||||
{
|
||||
visitedScenes.Add(currentSceneName);
|
||||
}
|
||||
|
||||
List<string> candidates = new List<string>();
|
||||
|
||||
foreach (string sceneName in roomSceneNames)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(sceneName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string cleanSceneName = sceneName.Trim();
|
||||
|
||||
if (cleanSceneName == currentSceneName)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (visitedScenes.Contains(cleanSceneName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
candidates.Add(cleanSceneName);
|
||||
}
|
||||
|
||||
if (candidates.Count > 0)
|
||||
{
|
||||
int randomIndex = Random.Range(0, candidates.Count);
|
||||
string selectedSceneName = candidates[randomIndex];
|
||||
|
||||
visitedScenes.Add(selectedSceneName);
|
||||
|
||||
Debug.Log("랜덤으로 선택된 다음 씬: " + selectedSceneName);
|
||||
return selectedSceneName;
|
||||
}
|
||||
|
||||
if (!finalSceneUsed && !string.IsNullOrWhiteSpace(finalSceneName))
|
||||
{
|
||||
finalSceneUsed = true;
|
||||
|
||||
string cleanFinalSceneName = finalSceneName.Trim();
|
||||
Debug.Log("모든 방 방문 완료. 마지막 씬으로 이동: " + cleanFinalSceneName);
|
||||
return cleanFinalSceneName;
|
||||
}
|
||||
|
||||
Debug.LogWarning("이동 가능한 다음 씬이 없습니다.");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private bool IsRoomScene(string sceneName)
|
||||
{
|
||||
foreach (string roomSceneName in roomSceneNames)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(roomSceneName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (roomSceneName.Trim() == sceneName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RequestRandomSceneChange()
|
||||
{
|
||||
if (SceneLoadManager.Instance == null)
|
||||
{
|
||||
Debug.LogError("SceneLoadManager가 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
string nextSceneName = GetNextSceneName();
|
||||
|
||||
if (string.IsNullOrEmpty(nextSceneName))
|
||||
{
|
||||
Debug.LogWarning("이동할 다음 씬 이름이 비어있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log("랜덤 씬 이동 요청: " + nextSceneName);
|
||||
SceneLoadManager.Instance.RequestSceneChange(nextSceneName);
|
||||
}
|
||||
|
||||
public void ResetRoute()
|
||||
{
|
||||
visitedScenes.Clear();
|
||||
finalSceneUsed = false;
|
||||
Debug.Log("랜덤 방 방문 기록 초기화");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3fdc82688b5a7144956b337157facc9
|
||||
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class RoomClearGateController : MonoBehaviour
|
||||
{
|
||||
[Header("방 클리어 후 열릴 게이트")]
|
||||
[SerializeField] private RoomExitGate exitGate;
|
||||
|
||||
private bool isRoomCleared = false;
|
||||
private bool gateOpened = false;
|
||||
|
||||
public bool IsRoomCleared => isRoomCleared;
|
||||
|
||||
// 블랙잭 최종 승리 후 호출
|
||||
// 이 함수는 게이트를 바로 열지 않고, "방 클리어 완료" 상태만 저장함
|
||||
public void MarkRoomCleared()
|
||||
{
|
||||
isRoomCleared = true;
|
||||
Debug.Log("방 클리어 완료. 이제 오픈존에 들어가면 게이트가 열립니다.");
|
||||
}
|
||||
|
||||
// 오픈존에 들어갔을 때 호출
|
||||
public void OpenClearGate()
|
||||
{
|
||||
if (!isRoomCleared)
|
||||
{
|
||||
Debug.Log("아직 방 클리어 전이라 게이트를 열 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (gateOpened)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gateOpened = true;
|
||||
|
||||
if (exitGate != null)
|
||||
{
|
||||
exitGate.OpenGate();
|
||||
Debug.Log("방 클리어 게이트 오픈");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Exit Gate가 연결되지 않았습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetClearState()
|
||||
{
|
||||
isRoomCleared = false;
|
||||
gateOpened = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 125cade625c9b644b842b1a4f20527e4
|
||||
326
Assets/02_Scripts/Managers/change room manager/RoomExitGate.cs
Normal file
326
Assets/02_Scripts/Managers/change room manager/RoomExitGate.cs
Normal file
@@ -0,0 +1,326 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
public class RoomExitGate : MonoBehaviour
|
||||
{
|
||||
[Header("Gate Root")]
|
||||
[SerializeField] private GameObject gateVisualRoot;
|
||||
|
||||
[Header("Door")]
|
||||
[SerializeField] private Transform leftDoor;
|
||||
[SerializeField] private Transform rightDoor;
|
||||
|
||||
[Header("Door Open Angle")]
|
||||
[SerializeField] private float leftOpenAngleY = 110f;
|
||||
[SerializeField] private float rightOpenAngleY = -110f;
|
||||
|
||||
[Header("Portal")]
|
||||
[SerializeField] private GameObject portalEffectRoot;
|
||||
[SerializeField] private ParticleSystem[] portalParticles;
|
||||
|
||||
[Header("Open Effect")]
|
||||
[SerializeField] private ParticleSystem openParticle;
|
||||
[SerializeField] private AudioSource openSound;
|
||||
|
||||
[Header("Trigger")]
|
||||
[SerializeField] private Collider gateTrigger;
|
||||
|
||||
[Header("Timing")]
|
||||
[SerializeField] private float openDelay = 1.2f;
|
||||
[SerializeField] private float openDuration = 1.2f;
|
||||
[SerializeField] private float portalShowDelay = 0.3f;
|
||||
|
||||
[Header("Player Check")]
|
||||
[SerializeField] private string playerTag = "Player";
|
||||
|
||||
[Header("Scene Move")]
|
||||
[SerializeField] private bool useRandomScene = true;
|
||||
[SerializeField] private string fallbackSceneName;
|
||||
[SerializeField] private float sceneMoveDelay = 0.2f;
|
||||
|
||||
[Header("Start Setting")]
|
||||
[SerializeField] private bool hideGateOnStart = true;
|
||||
[SerializeField] private bool hidePortalOnStart = true;
|
||||
|
||||
private Quaternion leftClosedRotation;
|
||||
private Quaternion rightClosedRotation;
|
||||
private Quaternion leftOpenRotation;
|
||||
private Quaternion rightOpenRotation;
|
||||
|
||||
private bool isOpened = false;
|
||||
private bool isOpening = false;
|
||||
private bool isEntering = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
CacheDoorRotations();
|
||||
AutoFindPortalParticles();
|
||||
PrepareTrigger();
|
||||
PrepareStartState();
|
||||
}
|
||||
|
||||
private void CacheDoorRotations()
|
||||
{
|
||||
if (leftDoor != null)
|
||||
{
|
||||
leftClosedRotation = leftDoor.localRotation;
|
||||
leftOpenRotation = leftClosedRotation * Quaternion.Euler(0f, leftOpenAngleY, 0f);
|
||||
}
|
||||
|
||||
if (rightDoor != null)
|
||||
{
|
||||
rightClosedRotation = rightDoor.localRotation;
|
||||
rightOpenRotation = rightClosedRotation * Quaternion.Euler(0f, rightOpenAngleY, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoFindPortalParticles()
|
||||
{
|
||||
if ((portalParticles == null || portalParticles.Length == 0) && portalEffectRoot != null)
|
||||
{
|
||||
portalParticles = portalEffectRoot.GetComponentsInChildren<ParticleSystem>(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void PrepareTrigger()
|
||||
{
|
||||
if (gateTrigger != null)
|
||||
{
|
||||
gateTrigger.enabled = false;
|
||||
gateTrigger.isTrigger = true;
|
||||
}
|
||||
|
||||
Rigidbody rb = GetComponent<Rigidbody>();
|
||||
|
||||
if (rb == null)
|
||||
{
|
||||
rb = gameObject.AddComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
rb.isKinematic = true;
|
||||
rb.useGravity = false;
|
||||
}
|
||||
|
||||
private void PrepareStartState()
|
||||
{
|
||||
if (hideGateOnStart && gateVisualRoot != null)
|
||||
{
|
||||
gateVisualRoot.SetActive(false);
|
||||
}
|
||||
|
||||
if (hidePortalOnStart && portalEffectRoot != null)
|
||||
{
|
||||
portalEffectRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenGate()
|
||||
{
|
||||
if (isOpened || isOpening)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(OpenGateRoutine());
|
||||
}
|
||||
|
||||
private IEnumerator OpenGateRoutine()
|
||||
{
|
||||
isOpening = true;
|
||||
|
||||
if (openParticle != null)
|
||||
{
|
||||
openParticle.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
openParticle.Play();
|
||||
}
|
||||
|
||||
if (openSound != null)
|
||||
{
|
||||
openSound.Play();
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(openDelay);
|
||||
|
||||
if (gateVisualRoot != null)
|
||||
{
|
||||
gateVisualRoot.SetActive(true);
|
||||
}
|
||||
|
||||
float timer = 0f;
|
||||
bool portalShown = false;
|
||||
|
||||
while (timer < openDuration)
|
||||
{
|
||||
timer += Time.deltaTime;
|
||||
|
||||
float t = Mathf.Clamp01(timer / openDuration);
|
||||
t = Mathf.SmoothStep(0f, 1f, t);
|
||||
|
||||
if (leftDoor != null)
|
||||
{
|
||||
leftDoor.localRotation = Quaternion.Slerp(leftClosedRotation, leftOpenRotation, t);
|
||||
}
|
||||
|
||||
if (rightDoor != null)
|
||||
{
|
||||
rightDoor.localRotation = Quaternion.Slerp(rightClosedRotation, rightOpenRotation, t);
|
||||
}
|
||||
|
||||
if (!portalShown && timer >= portalShowDelay)
|
||||
{
|
||||
ShowPortal();
|
||||
portalShown = true;
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (leftDoor != null)
|
||||
{
|
||||
leftDoor.localRotation = leftOpenRotation;
|
||||
}
|
||||
|
||||
if (rightDoor != null)
|
||||
{
|
||||
rightDoor.localRotation = rightOpenRotation;
|
||||
}
|
||||
|
||||
ShowPortal();
|
||||
|
||||
if (gateTrigger != null)
|
||||
{
|
||||
gateTrigger.enabled = true;
|
||||
}
|
||||
|
||||
isOpened = true;
|
||||
isOpening = false;
|
||||
|
||||
Debug.Log("게이트 열림 완료");
|
||||
}
|
||||
|
||||
private void ShowPortal()
|
||||
{
|
||||
if (portalEffectRoot != null && !portalEffectRoot.activeSelf)
|
||||
{
|
||||
portalEffectRoot.SetActive(true);
|
||||
}
|
||||
|
||||
if (portalParticles == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ParticleSystem particle in portalParticles)
|
||||
{
|
||||
if (particle != null && !particle.isPlaying)
|
||||
{
|
||||
particle.Play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (!isOpened || isEntering)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsPlayer(other))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
StartCoroutine(EnterGateRoutine());
|
||||
}
|
||||
|
||||
private bool IsPlayer(Collider other)
|
||||
{
|
||||
if (other.CompareTag(playerTag))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (other.transform.root.CompareTag(playerTag))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerator EnterGateRoutine()
|
||||
{
|
||||
isEntering = true;
|
||||
|
||||
yield return new WaitForSeconds(sceneMoveDelay);
|
||||
|
||||
string nextSceneName = GetNextSceneName();
|
||||
|
||||
if (string.IsNullOrEmpty(nextSceneName))
|
||||
{
|
||||
Debug.LogWarning("다음 씬 이름이 비어있습니다.");
|
||||
isEntering = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (SceneLoadManager.Instance == null)
|
||||
{
|
||||
Debug.LogError("SceneLoadManager가 없습니다.");
|
||||
isEntering = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
Debug.Log("게이트 진입, 이동할 씬: " + nextSceneName);
|
||||
SceneLoadManager.Instance.RequestSceneChange(nextSceneName);
|
||||
}
|
||||
|
||||
private string GetNextSceneName()
|
||||
{
|
||||
if (useRandomScene && RandomSceneRouteManager.Instance != null)
|
||||
{
|
||||
return RandomSceneRouteManager.Instance.GetNextSceneName();
|
||||
}
|
||||
|
||||
return fallbackSceneName;
|
||||
}
|
||||
|
||||
public void CloseGateImmediately()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
|
||||
isOpened = false;
|
||||
isOpening = false;
|
||||
isEntering = false;
|
||||
|
||||
if (leftDoor != null)
|
||||
{
|
||||
leftDoor.localRotation = leftClosedRotation;
|
||||
}
|
||||
|
||||
if (rightDoor != null)
|
||||
{
|
||||
rightDoor.localRotation = rightClosedRotation;
|
||||
}
|
||||
|
||||
if (portalEffectRoot != null)
|
||||
{
|
||||
portalEffectRoot.SetActive(false);
|
||||
}
|
||||
|
||||
if (gateTrigger != null)
|
||||
{
|
||||
gateTrigger.enabled = false;
|
||||
}
|
||||
|
||||
if (openParticle != null)
|
||||
{
|
||||
openParticle.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
}
|
||||
|
||||
if (hideGateOnStart && gateVisualRoot != null)
|
||||
{
|
||||
gateVisualRoot.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 317d18e41b8485446ac641a9e1cd4ce2
|
||||
8
Assets/02_Scripts/MazeRoom.meta
Normal file
8
Assets/02_Scripts/MazeRoom.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94051385bfdee2e4d9dc607ec57993c2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Assets/02_Scripts/MazeRoom/SeaCreatureWaypoint.cs
Normal file
51
Assets/02_Scripts/MazeRoom/SeaCreatureWaypoint.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class SeaCreatureWaypoint : MonoBehaviour
|
||||
{
|
||||
[Header("Waypoint Settings")]
|
||||
public Transform[] waypoints;
|
||||
|
||||
[Header("Movement Settings")]
|
||||
public float moveSpeed = 2f;
|
||||
public float rotationSpeed = 5f;
|
||||
public float arriveDistance = 0.2f;
|
||||
|
||||
private int currentWaypoint = 0;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (waypoints == null || waypoints.Length == 0)
|
||||
return;
|
||||
|
||||
Transform target = waypoints[currentWaypoint];
|
||||
|
||||
// 이동
|
||||
transform.position = Vector3.MoveTowards(
|
||||
transform.position,
|
||||
target.position,
|
||||
moveSpeed * Time.deltaTime);
|
||||
|
||||
// 바라보는 방향
|
||||
Vector3 direction = target.position - transform.position;
|
||||
|
||||
if (direction != Vector3.zero)
|
||||
{
|
||||
Quaternion targetRotation = Quaternion.LookRotation(direction);
|
||||
transform.rotation = Quaternion.Slerp(
|
||||
transform.rotation,
|
||||
targetRotation,
|
||||
rotationSpeed * Time.deltaTime);
|
||||
}
|
||||
|
||||
// 도착하면 다음 웨이포인트
|
||||
if (Vector3.Distance(transform.position, target.position) < arriveDistance)
|
||||
{
|
||||
currentWaypoint++;
|
||||
|
||||
if (currentWaypoint >= waypoints.Length)
|
||||
{
|
||||
currentWaypoint = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/02_Scripts/MazeRoom/SeaCreatureWaypoint.cs.meta
Normal file
2
Assets/02_Scripts/MazeRoom/SeaCreatureWaypoint.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb034270e545f1f42be49ca0557e2c46
|
||||
67
Assets/02_Scripts/Npcs/FollowObject.cs
Normal file
67
Assets/02_Scripts/Npcs/FollowObject.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.AI;
|
||||
|
||||
// 대상(보통 플레이어)을 NavMesh 위에서 따라다니는 간단한 동행 스크립트.
|
||||
// 속도/가속/높이(Base Offset) 등은 NavMeshAgent 컴포넌트에서 설정한다.
|
||||
[RequireComponent(typeof(NavMeshAgent))]
|
||||
public class FollowObject : MonoBehaviour
|
||||
{
|
||||
[Header("Target")]
|
||||
[SerializeField] private Transform _target; // 비워두면 Camera.main 사용
|
||||
|
||||
[Header("Follow")]
|
||||
[SerializeField] private float _followDistance = 3.0f; // 이 거리 안이면 멈춤 (= Agent stoppingDistance)
|
||||
[SerializeField] private float _repathInterval = 0.2f; // 목적지 갱신 주기(초)
|
||||
|
||||
[Header("Rotation")]
|
||||
[SerializeField] private bool _lookAtTarget = true; // 켜면 이동방향이 아니라 항상 Target을 바라봄
|
||||
[SerializeField] private float _lookSpeed = 8f; // 바라보기 회전 속도 (0이면 즉시)
|
||||
|
||||
private NavMeshAgent _agent;
|
||||
private float _repathTimer;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_agent = GetComponent<NavMeshAgent>();
|
||||
_agent.stoppingDistance = _followDistance;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
var target = ResolveTarget();
|
||||
if (target == null || !_agent.isOnNavMesh) return;
|
||||
|
||||
// 목적지 갱신 (주기적)
|
||||
_repathTimer -= Time.deltaTime;
|
||||
if (_repathTimer <= 0f)
|
||||
{
|
||||
_repathTimer = _repathInterval;
|
||||
_agent.SetDestination(target.position); // NavMesh가 지면/경사/장애물을 알아서 처리
|
||||
}
|
||||
|
||||
// 회전: 켜면 항상 Target을 바라봄(에이전트 자동회전 끔), 끄면 이동방향을 바라봄(기본)
|
||||
_agent.updateRotation = !_lookAtTarget;
|
||||
if (_lookAtTarget)
|
||||
FaceTarget(target);
|
||||
}
|
||||
|
||||
private void FaceTarget(Transform target)
|
||||
{
|
||||
Vector3 dir = target.position - transform.position;
|
||||
dir.y = 0f; // 수평만 — 위아래로 안 기울게
|
||||
if (dir.sqrMagnitude < 0.0001f) return;
|
||||
|
||||
Quaternion look = Quaternion.LookRotation(dir);
|
||||
transform.rotation = _lookSpeed > 0f
|
||||
? Quaternion.Slerp(transform.rotation, look, _lookSpeed * Time.deltaTime)
|
||||
: look;
|
||||
}
|
||||
|
||||
private Transform ResolveTarget()
|
||||
{
|
||||
if (_target != null) return _target;
|
||||
return Camera.main != null ? Camera.main.transform : null;
|
||||
}
|
||||
|
||||
public void SetTarget(Transform target) => _target = target;
|
||||
}
|
||||
2
Assets/02_Scripts/Npcs/FollowObject.cs.meta
Normal file
2
Assets/02_Scripts/Npcs/FollowObject.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c34c05a7f0add5248b2c4aff05789f4b
|
||||
Reference in New Issue
Block a user