From bb48246a96e77a8afc140ef7cb7031a2d3fa4670 Mon Sep 17 00:00:00 2001 From: HaeinLEE Date: Thu, 15 May 2025 09:45:55 +0900 Subject: [PATCH] =?UTF-8?q?[Fix]=20=EB=A9=94=EC=9D=B8=20=EC=B6=A9=EB=8F=8C?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/JAY/Scripts/PlayerController.cs | 564 +++++------------- .../UI/InteractionAnimationPanelController.cs | 37 +- ...InteractionAnimationPanelController.cs.bak | 159 +++++ Assets/Scripts/Tutorial/TutorialManager.cs | 15 +- .../Scripts/Tutorial/TutorialManager.cs.bak | 152 +++++ .../Tutorial/TutorialManager.cs.bak.meta | 2 + 6 files changed, 488 insertions(+), 441 deletions(-) create mode 100644 Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs.bak create mode 100644 Assets/Scripts/Tutorial/TutorialManager.cs.bak create mode 100644 Assets/Scripts/Tutorial/TutorialManager.cs.bak.meta diff --git a/Assets/JAY/Scripts/PlayerController.cs b/Assets/JAY/Scripts/PlayerController.cs index 7c2e1c34..f1a5954a 100644 --- a/Assets/JAY/Scripts/PlayerController.cs +++ b/Assets/JAY/Scripts/PlayerController.cs @@ -1,458 +1,186 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Runtime.CompilerServices; -using UnityEditor.TextCore.Text; using UnityEngine; using UnityEngine.SceneManagement; -public enum PlayerState { None, Idle, Move, Win, Hit, Dead } - -public class PlayerController : CharacterBase, IObserver +public partial class GameManager : Singleton,ISaveable { - // 외부 접근 가능 변수 - [Header("Attach Points")] - [SerializeField] private Transform rightHandTransform; - [SerializeField] private CameraShake cameraShake; - [SerializeField] private GameObject normalModel; // char_body : 일상복 - [SerializeField] private GameObject battleModel; // warrior_1 : 전투복 - [SerializeField] private Transform dashEffectAnchor; // 대시 이펙트 위치 + // 게임 진행 상태 + private int currentDay = 1; // 날짜 + public int CurrentDay => currentDay; + private int maxDays = GameConstants.maxDays; + + private int stageLevel = 1; // 스테이지 정보 + public int StageLevel => stageLevel; - // 내부에서만 사용하는 변수 - private PlayerHitEffectController hitEffectController; - private CharacterController _characterController; - private bool _isBattle; - private GameObject weapon; - private WeaponController _weaponController; - public WeaponController WeaponController => _weaponController; + private int tryStageCount = 0; + public int TryStageCount => tryStageCount; - private IPlayerState _currentStateClass { get; set; } - private IPlayerAction _currentAction; - public IPlayerAction CurrentAction => _currentAction; + // 날짜 변경 이벤트, 추후에 UI 상의 날짜를 변경할 때 사용 + public event Action OnDayChanged; - // 강화 관련 - private float attackPowerLevel; - private float moveSpeedLevel; - private float dashCoolLevel; - public float attackSpeedLevel; + private ChatWindowController chatWindowController; // 대화창 컨트롤러 - // 상태 관련 - private PlayerStateIdle _playerStateIdle; - private PlayerStateMove _playerStateMove; - private PlayerStateWin _playerStateWin; - private PlayerStateHit _playerStateHit; - private PlayerStateDead _playerStateDead; + //패널 관련 + private PanelManager panelManager; + public PanelManager PanelManager => panelManager; - //대시 쿨타임 관련 - [SerializeField] private float dashCooldownDuration = 1.5f; - private float dashCooldownTimer = 0f; - public bool IsDashOnCooldown => dashCooldownTimer > 0f; - public float DashCooldownRatio => dashCooldownTimer / dashCooldownDuration; + private TutorialManager tutorialManager; - // 행동 관련 - private PlayerActionAttack _attackAction; - private PlayerActionDash _actionDash; - - // 외부에서도 사용하는 변수 - public FixedJoystick Joystick { get; private set; } - public PlayerState CurrentState { get; private set; } - private Dictionary _playerStates; - public Animator PlayerAnimator { get; private set; } - public CharacterController CharacterController => _characterController; - public bool IsBattle => _isBattle; - public Transform DashEffectAnchor => dashEffectAnchor; - - [Header("대시, 어택 터치 연출용")] - [SerializeField] private DungeonPanelController dungeonPanelController; - - private void Awake() + private void Start() { - if (Joystick == null) - { - Joystick = FindObjectOfType(); - } + // 오디오 초기화 + InitializeAudio(); + + //패널 매니저 생성 + panelManager = Instantiate(Resources.Load("Prefabs/PanelManager")).GetComponent(); + } - // isBattle 초기화 (임시) - bool isHousingScene = SceneManager.GetActiveScene().name.Contains("Housing"); - _isBattle = !isHousingScene; - - AssignCharacterController(); - AssignAnimator(); + #region 대화 관련 + + public void StartNPCDialogue(GamePhase phase) // intro, gameplay, end 존재 + { + StartCoroutine(StartNPCDialogueCoroutine(phase)); } - protected override void Start() + private IEnumerator StartNPCDialogueCoroutine(GamePhase phase) { - base.Start(); - - hitEffectController = GetComponentInChildren(); - - PlayerInit(); - - //강화 수치 적용 - attackPowerLevel = 1 + (float)UpgradeManager.Instance.upgradeStat.CurrentUpgradeLevel(StatType.AttackPower) / 2; - moveSpeedLevel = 1 + (float)UpgradeManager.Instance.upgradeStat.CurrentUpgradeLevel(StatType.MoveSpeed) / 2; - dashCoolLevel = (float)UpgradeManager.Instance.upgradeStat.CurrentUpgradeLevel(StatType.DashCoolDown)/5; - attackSpeedLevel = (float)UpgradeManager.Instance.upgradeStat.CurrentUpgradeLevel(StatType.AttackSpeed)/10; + if (chatWindowController == null) + { + yield return new WaitForSeconds(0.5f); // 씬 전환 대기 + chatWindowController = FindObjectOfType(); + } + + chatWindowController.SetGamePhase(phase); + } - attackPower *= attackPowerLevel; - moveSpeed *= moveSpeedLevel; - dashCooldownDuration -= dashCoolLevel; + public void DirectStartDialogue() + { + if (chatWindowController == null) chatWindowController = FindObjectOfType(); + chatWindowController.SetGamePhase(GamePhase.Gameplay); + } + + #endregion + + //일시 정지 + public void PauseGame() + { + Time.timeScale = 0; + } + + public void ResumeGame() + { + Time.timeScale = 1; } - private void Update() + // 이벤트 할당(PlayerStats Start에서 호출) + public void SetEvents() { - if (CurrentState != PlayerState.None) + PlayerStats.Instance.OnDayEnded += AdvanceDay; // 날짜 변경 + PlayerStats.Instance.ZeroReputation += ZeroReputationEnd; // 평판 0 엔딩 + } + + // 날짜 진행 + public void AdvanceDay() + { + currentDay++; + OnDayChanged?.Invoke(currentDay); + + // 최대 일수 도달 체크 + if (currentDay > maxDays) // 8일차에 검사 { - _playerStates[CurrentState].Update(); + TriggerTimeEnding(); } + } + + public void ChangeToMainScene() + { + SceneManager.LoadScene("Main"); + } + + public void ChangeToGameScene() + { + tryStageCount++; // 던전 시도 횟수 증가 + var switchingPanel = PanelManager.GetPanel("SwitchingPanel").GetComponent(); + switchingPanel.FadeAndSceneLoad("ReDungeon"); // 던전 Scene + InteractionController interactionController = FindObjectOfType(); + interactionController.ReSetAfterWorkEvent(); + HandleSceneAudio("Dungeon"); + } + + public void ChangeToHomeScene(bool isNewStart = false) + { + var switchingPanel = PanelManager.GetPanel("SwitchingPanel").GetComponent(); + switchingPanel.FadeAndSceneLoad("ReHousing"); // Home Scene + HandleSceneAudio("Housing"); - //대시 쿨타임 진행 - if (dashCooldownTimer > 0f) - dashCooldownTimer -= Time.deltaTime; + if(isNewStart) // 아예 메인에서 시작 시 튜토리얼 출력 + StartNPCDialogue(GamePhase.Intro); // StartCoroutine(StartTutorialCoroutine()); - // Hit 상태거나 게임 끝났을 땐 땐 입력 무시 - if (CurrentState == PlayerState.Hit || CurrentState == PlayerState.Dead || CurrentState == PlayerState.Win) - return; + if (tryStageCount >= 3) FailEnd(); // 엔딩 + } + + public IEnumerator StartTutorialCoroutine() + { + yield return new WaitForSeconds(0.5f); - // 대시 우선 입력 처리 - if (Input.GetKeyDown(KeyCode.Space)) - { - dungeonPanelController.DashTouchMotion(); - StartDashAction(); - return; - } + if(tutorialManager == null) + tutorialManager = FindObjectOfType(); - // 공격 입력 처리 - if (Input.GetKeyDown(KeyCode.X) && (_currentAction == null || !_currentAction.IsActive) - && (CurrentState != PlayerState.Win && CurrentState != PlayerState.Dead)) - { - dungeonPanelController.AttackTouchMotion(); - GameManager.Instance.PlayPlayerAttackSound(); - StartAttackAction(); - } + PlayerStats.Instance.HideBubble(); + tutorialManager.StartTutorial(() => PlayerStats.Instance.ShowBubble()); + } + + // TODO: Open Setting Panel 등 Panel 처리 + + protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode) + { + // TODO: 씬 로드 시 동작 구현. ex: BGM 변경 - // 액션 업데이트 - if (_currentAction != null && _currentAction.IsActive) - { - _currentAction.UpdateAction(); - } + // UI용 Canvas 찾기 + // _canvas = GameObject.FindObjectOfType(); } private void OnDestroy() { - OnGetHit -= HandlePlayerHit; + if(PlayerStats.Instance != null) + PlayerStats.Instance.OnDayEnded -= AdvanceDay; // 이벤트 구독 해제 + } + + private void OnApplicationQuit() + { + // TODO: 게임 종료 시 로직 추가 } - #region 초기화 관련 - - private void PlayerInit() + public void ApplySaveData(Save save) { - // 상태 초기화 - _playerStateIdle = new PlayerStateIdle(); - _playerStateMove = new PlayerStateMove(); - _playerStateHit = new PlayerStateHit(); - _playerStateWin = new PlayerStateWin(); - _playerStateDead = new PlayerStateDead(); - - _playerStates = new Dictionary + if (save?.dungeonSave != null) { - { PlayerState.Idle, _playerStateIdle }, - { PlayerState.Move, _playerStateMove }, - { PlayerState.Hit, _playerStateHit }, - { PlayerState.Win, _playerStateWin }, - { PlayerState.Dead, _playerStateDead }, + stageLevel = Mathf.Clamp(save.dungeonSave.stageLevel,1,2); + tryStageCount = Mathf.Clamp(save.dungeonSave.tryStageCount,0,3); + } + + if (save?.homeSave != null) + { + currentDay = Mathf.Clamp(save.homeSave.currentDay,1,maxDays); + } + } + + public Save ExtractSaveData() + { + return new Save + { + dungeonSave = new DungeonSave() + { + stageLevel = Mathf.Clamp(this.stageLevel,1,2), + tryStageCount = Mathf.Clamp(this.tryStageCount,0,3), + }, + + homeSave = new HomeSave + { + currentDay = Mathf.Clamp(this.currentDay,1,maxDays), + } }; - - _attackAction = new PlayerActionAttack(); - _actionDash = new PlayerActionDash(); - - OnGetHit -= HandlePlayerHit; - OnGetHit += HandlePlayerHit; - - SetState(PlayerState.Idle); - - InstantiateWeapon(); } - - private void InstantiateWeapon() - { - if (weapon == null) - { - GameObject weaponObject = Resources.Load("Player/Weapon/Chopstick"); - weapon = Instantiate(weaponObject, rightHandTransform); - _weaponController = weapon?.GetComponent(); - _weaponController?.Subscribe(this); - weapon?.SetActive(_isBattle); - } - } - - /// - /// 애니메이션 초기화 - /// - private void InitializeAnimatorParameters() - { - if (PlayerAnimator == null) return; - - SafeSetBool("Walk", false); - SafeSetBool("Run", false); - // SafeSetBool(Dead, false); - SafeResetTrigger("Bore"); - SafeResetTrigger("GetHit"); - PlayerAnimator.Rebind(); // 레이어 초기화 - // PlayerAnimator.Update(0f); // 즉시 반영 - } - - #endregion - - #region 애니메이션 파라미터 관련 - - public void SafeSetBool(string paramName, bool value) - { - if (PlayerAnimator == null) return; - - foreach (var param in PlayerAnimator.parameters) - { - if (param.name == paramName && param.type == AnimatorControllerParameterType.Bool) - { - PlayerAnimator.SetBool(paramName, value); - break; - } - } - } - - private void SafeResetTrigger(string triggerName) - { - if (PlayerAnimator == null) return; - - foreach (var param in PlayerAnimator.parameters) - { - if (param.name == triggerName && param.type == AnimatorControllerParameterType.Trigger) - { - PlayerAnimator.ResetTrigger(triggerName); - break; - } - } - } - - #endregion - - #region 상태, 동작 변화 관련 - - public void SetState(PlayerState state) - { - if (CurrentState != PlayerState.None) - { - _playerStates[CurrentState].Exit(); - } - CurrentState = state; - _currentStateClass = _playerStates[state]; - _currentStateClass.Enter(this); - } - - - public void StartAttackAction() - { - if (!_isBattle) return; - - _currentAction = _attackAction; - _currentAction.StartAction(this); - } - - public void StartDashAction() - { - if (!_isBattle) return; - - // 쿨타임 중이면 무시 - if (IsDashOnCooldown) - { - Debug.Log("대시 쿨타임 중"); - return; - } - - // 만약 공격 중이면 강제로 공격 종료 - if (_currentAction == _attackAction && _attackAction.IsActive) - { - _attackAction.EndAction(); // 애니메이션도 중단 - _weaponController.AttackEnd(); - } - - // 기존 대시 중이면 중복 실행 안 함 - if (_actionDash.IsActive) - return; - - _currentAction = _actionDash; - _actionDash.StartAction(this); - - // 쿨타임 시작 - dashCooldownTimer = dashCooldownDuration; - } - - public void OnActionEnded(IPlayerAction action) - { - if (_currentAction == action) _currentAction = null; - } - - /// - /// 전투, 일상 모드 플레이어 프리팹에 따라 애니메이터 가져오기 - /// - private void AssignAnimator() - { - PlayerAnimator = _isBattle - ? battleModel.GetComponent() - : normalModel.GetComponent(); - - InitializeAnimatorParameters(); - } - - /// - /// 전투, 일상 모드 플레이어 프리팹에 따라 Character Controller 가져오기 - /// - private void AssignCharacterController() - { - _characterController = _isBattle - ? battleModel.GetComponent() - : normalModel.GetComponent(); - } - - #endregion - - #region 공격 관련 - - public void SwitchBattleMode() - { - _isBattle = !_isBattle; - - // 복장 전환 - normalModel.SetActive(!_isBattle); - battleModel.SetActive(_isBattle); - - // Animator, Character Controller 다시 참조 (복장에 붙은 걸로) - AssignAnimator(); - AssignCharacterController(); - - // 무기도 전투모드에만 - weapon.SetActive(_isBattle); - } - - public void PlayAttackEffect() - { - if (_attackAction == null) return; - - // 현재 콤보 단계 (1~4) - int comboStep = _attackAction.CurrentComboStep; - - // 홀수면 기본 방향 (오→왼), 짝수면 반전 (왼→오) - bool isMirror = comboStep % 2 != 0; - - Vector3 basePos = CharacterController.transform.position; - Vector3 forward = CharacterController.transform.forward; - - float forwardPos = CurrentState == PlayerState.Move ? 1f : 0.2f; - - // 이펙트 위치: 위로 0.5 + 앞으로 약간 - Vector3 pos = basePos + Vector3.up * 0.5f + forward * forwardPos; - - Quaternion rot = Quaternion.LookRotation(forward, Vector3.up) * Quaternion.Euler(0, 90, 0); - GameObject effect = EffectManager.Instance.PlayEffect(pos, rot, EffectManager.EffectType.Attack); - - // 반전이 필요한 경우, X축 스케일 -1 - if (isMirror && effect != null) - { - Vector3 scale = effect.transform.localScale; - scale.z *= -1; - effect.transform.localScale = scale; - } - } - - public void OnAttackButtonPressed() - { - if ((_currentAction == null || !_currentAction.IsActive) && - CurrentState != PlayerState.Win && CurrentState != PlayerState.Dead) - { - GameManager.Instance.PlayPlayerAttackSound(); - StartAttackAction(); - } - else if (_currentAction is PlayerActionAttack attackAction) - { - attackAction.OnComboInput(); - } - } - - #endregion - - #region 대시 관련 - - public Vector3 GetMoveDirectionOrForward() - { - Vector3 dir = new Vector3(Joystick.Horizontal, 0, Joystick.Vertical); - return dir.sqrMagnitude > 0.01f ? dir.normalized : transform.forward; - } - - public void OnDashButtonPressed() - { - if (!_actionDash.IsActive && CurrentState != PlayerState.Win && CurrentState != PlayerState.Dead) - { - StartDashAction(); - } - } - - #endregion - - #region IObserver 관련 - - public void OnNext(GameObject value) - { - float playerAttackPower = _weaponController.AttackPower * attackPower; - - if (value.CompareTag("Enemy")) // 적이 Enemy일 때만 공격 처리 - { - var enemyController = value.transform.GetComponent(); - if (enemyController != null) - { - enemyController.TakeDamage(playerAttackPower); - } - } - } - - public void OnError(Exception error) - { - } - - public void OnCompleted() - { - _weaponController.Unsubscribe(this); - } - - #endregion - - #region 피격 관련 - - // TODO: Editor에서 확인하기 위한 테스트용 메서드 - public void HandlePlayerHit() - { - if (CurrentState == PlayerState.Dead) return; - - SetState(PlayerState.Hit); - } - - private void HandlePlayerHit(CharacterBase character) - { - if (character != this) return; - if (CurrentState == PlayerState.Dead) return; - - GameManager.Instance.PlayPlayerHitSound(); - SetState(PlayerState.Hit); - } - - public void PlayHitEffect() - { - hitEffectController?.PlayHitEffect(); - } - - public void ShakeCamera() - { - cameraShake?.Shake(); - } - - #endregion } diff --git a/Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs b/Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs index b7559aea..ab58f281 100644 --- a/Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs +++ b/Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs @@ -5,6 +5,7 @@ using System.Text; using TMPro; using UnityEngine; using UnityEngine.UI; +using Random = UnityEngine.Random; public class InteractionAnimationPanelController : MonoBehaviour { @@ -33,8 +34,9 @@ public class InteractionAnimationPanelController : MonoBehaviour public void ShowAnimationPanel(ActionType actionType, string animationText) { PlayerStats.Instance.HideBubble(); - - if (actionType == ActionType.Sleep && !PlayerStats.Instance.HasWorkedToday) // 결근 + if (actionType == ActionType.Sleep && !PlayerStats.Instance.HasWorkedToday + && PlayerStats.Instance.LastAction != ActionType.TeamDinner + && PlayerStats.Instance.LastAction != ActionType.OvertimeWork) // 결근 { _isAbsenceToday = true; } @@ -89,7 +91,7 @@ public class InteractionAnimationPanelController : MonoBehaviour /// 패널이 2초후 자동으로 닫히거나 터치시 닫히도록 합니다. /// /// - private IEnumerator AutoHidePanel(ActionType actionType) + private IEnumerator AutoHidePanel(ActionType actionType, bool isTutorial = false) { float startTime = Time.time; while (Time.time - startTime < animationDuration) @@ -105,11 +107,21 @@ public class InteractionAnimationPanelController : MonoBehaviour GameManager.Instance.StopInteractionSound(actionType); //패널 닫고 애니메이션 null처리 - HidePanel(); + HidePanel(isTutorial); _autoHideCoroutine = null; + + if (actionType == ActionType.Housework) + { + var chance = 0.7f; + + if (Random.value < chance) + { + UpgradeManager.Instance.StartUpgradeInHome(); + } + } } - private void HidePanel() + private void HidePanel(bool isTutorial = false) { panel.SetActive(false); @@ -124,16 +136,19 @@ public class InteractionAnimationPanelController : MonoBehaviour StopCoroutine(_autoHideCoroutine); _autoHideCoroutine = null; } - + if (_isAbsenceToday) // 결근한 경우 { PlayerStats.Instance.PerformAbsent(); return; } - - // 패널 닫히고 결근 체크, 상호작용 패널과 결근 엔딩 채팅창이 겹치지 않기 위함 - PlayerStats.Instance.CheckAbsent(); - PlayerStats.Instance.ShowBubble(); + + if (!isTutorial) // 튜토리얼 시 결근과 말풍선 오류로 인해 조건문 추가 + { + // 패널 닫히고 결근 체크, 상호작용 패널과 결근 엔딩 채팅창이 겹치지 않기 위함 + PlayerStats.Instance.CheckAbsent(); + PlayerStats.Instance.ShowBubble(); + } } public void TutorialSleepAnimation() @@ -153,7 +168,7 @@ public class InteractionAnimationPanelController : MonoBehaviour animator.Play("Sleep"); _textAnimCoroutine = StartCoroutine(TextDotsAnimation()); - _autoHideCoroutine = StartCoroutine(AutoHidePanel(ActionType.Sleep)); + _autoHideCoroutine = StartCoroutine(AutoHidePanel(ActionType.Sleep, true)); } } \ No newline at end of file diff --git a/Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs.bak b/Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs.bak new file mode 100644 index 00000000..b7559aea --- /dev/null +++ b/Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs.bak @@ -0,0 +1,159 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +public class InteractionAnimationPanelController : MonoBehaviour +{ + [SerializeField] private GameObject panel; + [SerializeField] private Image doingImage; + [SerializeField] private TMP_Text doingText; + [SerializeField] private Animator animator; + [SerializeField] private float animationDuration = 2.0f; + + private Coroutine _textAnimCoroutine; + private Coroutine _autoHideCoroutine; + private Canvas _parentCanvas; + + private bool _isAbsenceToday = false; + + public void SetDoingText(string text) + { + doingText.text = text; + } + + public bool IsPanelActive() + { + return panel.activeSelf; + } + + public void ShowAnimationPanel(ActionType actionType, string animationText) + { + PlayerStats.Instance.HideBubble(); + + if (actionType == ActionType.Sleep && !PlayerStats.Instance.HasWorkedToday) // 결근 + { + _isAbsenceToday = true; + } + + // 1) 패널 활성화 + panel.SetActive(true); + // 2) 기존 코루틴 정리 + if (_textAnimCoroutine != null) StopCoroutine(_textAnimCoroutine); + if (_autoHideCoroutine != null) StopCoroutine(_autoHideCoroutine); + + // 3) 텍스트 및 애니메이션 세팅 + doingText.text = animationText; + + switch (actionType) + { + case ActionType.Sleep: + animator.Play("Sleep"); + break; + case ActionType.Work: + animator.Play("Go2Work"); + break; + case ActionType.Eat: + animator.Play("Meal"); + break; + case ActionType.Dungeon: + animator.Play("Dungeon"); + break; + case ActionType.Housework: + animator.Play("Laundry"); + break; + } + _textAnimCoroutine = StartCoroutine(TextDotsAnimation()); + _autoHideCoroutine = StartCoroutine(AutoHidePanel(actionType)); + } + + private IEnumerator TextDotsAnimation() + { + var tempText = doingText.text; + float startTime = Time.time; + while (Time.time - startTime < 3) + { + for (int i = 0; i < 3; i++) + { + yield return new WaitForSeconds(0.3f); + doingText.text = tempText + new string('.', i + 1); + } + yield return new WaitForSeconds(0.3f); + } + } + + /// + /// 패널이 2초후 자동으로 닫히거나 터치시 닫히도록 합니다. + /// + /// + private IEnumerator AutoHidePanel(ActionType actionType) + { + float startTime = Time.time; + while (Time.time - startTime < animationDuration) + { + if (Input.touchCount > 0 || Input.GetMouseButtonDown(0)) + { + break; + } + yield return null; + } + //로테이션 초기화 + doingImage.rectTransform.localRotation = Quaternion.identity; + + GameManager.Instance.StopInteractionSound(actionType); + //패널 닫고 애니메이션 null처리 + HidePanel(); + _autoHideCoroutine = null; + } + + private void HidePanel() + { + panel.SetActive(false); + + if (_textAnimCoroutine != null) + { + StopCoroutine(_textAnimCoroutine); + _textAnimCoroutine = null; + } + + if (_autoHideCoroutine != null) + { + StopCoroutine(_autoHideCoroutine); + _autoHideCoroutine = null; + } + + if (_isAbsenceToday) // 결근한 경우 + { + PlayerStats.Instance.PerformAbsent(); + return; + } + + // 패널 닫히고 결근 체크, 상호작용 패널과 결근 엔딩 채팅창이 겹치지 않기 위함 + PlayerStats.Instance.CheckAbsent(); + PlayerStats.Instance.ShowBubble(); + } + + public void TutorialSleepAnimation() + { + _parentCanvas = FindObjectOfType(typeof(Canvas)) as Canvas; + + HousingConstants.interactions.TryGetValue(ActionType.Sleep, out var interactionTexts); + + // 1) 패널 활성화 + panel.SetActive(true); + // 2) 기존 코루틴 정리 + if (_textAnimCoroutine != null) StopCoroutine(_textAnimCoroutine); + if (_autoHideCoroutine != null) StopCoroutine(_autoHideCoroutine); + + // 3) 텍스트 및 애니메이션 세팅 + doingText.text = interactionTexts.AnimationText; + animator.Play("Sleep"); + + _textAnimCoroutine = StartCoroutine(TextDotsAnimation()); + _autoHideCoroutine = StartCoroutine(AutoHidePanel(ActionType.Sleep)); + } +} + \ No newline at end of file diff --git a/Assets/Scripts/Tutorial/TutorialManager.cs b/Assets/Scripts/Tutorial/TutorialManager.cs index 0fef1105..5546e9d7 100644 --- a/Assets/Scripts/Tutorial/TutorialManager.cs +++ b/Assets/Scripts/Tutorial/TutorialManager.cs @@ -24,10 +24,6 @@ public class TutorialManager : MonoBehaviour private Canvas overlayCanvas; // RectTransformUtility를 위한 Canvas private Action onTutorialComplete; - public void Start( ) - { - StartTutorial(null); - } public void StartTutorial(Action onTutorialEnd, int panelIndex = 0) { if(parentCanvas == null) @@ -45,9 +41,9 @@ public class TutorialManager : MonoBehaviour if (_tutorialPanelController != null) { onTutorialComplete = onTutorialEnd; - overlay.alpha = 1f; - overlay.blocksRaycasts = true; - RunStep(firstStep); + overlay.alpha = 1f; + overlay.blocksRaycasts = true; + RunStep(firstStep); } else Debug.Log("패널 생성 실패, 튜토리얼 진행이 불가능합니다."); } @@ -97,15 +93,10 @@ public class TutorialManager : MonoBehaviour if (RectTransformUtility.RectangleContainsScreenPoint( targetRt, screenPos, overlayCanvas.worldCamera)) { - Debug.Log("타겟 터치"); targetRt = null; _tutorialPanelController.HideTouchTarget(step.touchTargetIndex); done = true; } - else - { - Debug.Log("타겟이 아닌 곳 터치"); - } } } diff --git a/Assets/Scripts/Tutorial/TutorialManager.cs.bak b/Assets/Scripts/Tutorial/TutorialManager.cs.bak new file mode 100644 index 00000000..0fef1105 --- /dev/null +++ b/Assets/Scripts/Tutorial/TutorialManager.cs.bak @@ -0,0 +1,152 @@ +using System; +using UnityEngine; +using TMPro; +using UnityEngine.UI; +using System.Collections; +using Unity.VisualScripting; +using UnityEngine.Serialization; +using UnityEngine.Events; + +public class TutorialManager : MonoBehaviour +{ + [SerializeField] private TutorialStep firstStep; // 인스펙터에서 첫 단계 드래그 + + [Header("튜토리얼 패널 생성")] + [SerializeField] private GameObject[] tutorialPanelPrefabs; + [FormerlySerializedAs("parentObject")] public Canvas parentCanvas; + + private GameObject _tutorialPanelObject; + private TutorialPanelController _tutorialPanelController; + + private Coroutine _runningCoroutine; + private CanvasGroup overlay; // 화면 암전 및 입력 차단 + private RectTransform targetRt; + private Canvas overlayCanvas; // RectTransformUtility를 위한 Canvas + private Action onTutorialComplete; + + public void Start( ) + { + StartTutorial(null); + } + public void StartTutorial(Action onTutorialEnd, int panelIndex = 0) + { + if(parentCanvas == null) + parentCanvas = FindObjectOfType(typeof(Canvas)) as Canvas; + + if (parentCanvas != null) + { + overlayCanvas = parentCanvas as Canvas; + + _tutorialPanelObject = Instantiate(tutorialPanelPrefabs[panelIndex], parentCanvas.GameObject().transform); + overlay = _tutorialPanelObject.GetComponent(); + _tutorialPanelController = _tutorialPanelObject.GetComponent(); + } + + if (_tutorialPanelController != null) + { + onTutorialComplete = onTutorialEnd; + overlay.alpha = 1f; + overlay.blocksRaycasts = true; + RunStep(firstStep); + } + else Debug.Log("패널 생성 실패, 튜토리얼 진행이 불가능합니다."); + } + + private void RunStep(TutorialStep step) + { + if (_runningCoroutine != null) StopCoroutine(_runningCoroutine); + _runningCoroutine = StartCoroutine(RunStepCoroutine(step)); + } + + private IEnumerator RunStepCoroutine(TutorialStep step) + { + // 단계 시작 이벤트 + step.onStepBegin?.Invoke(); + // 메시지 갱신 + _tutorialPanelController.setTutorialText(step.message); + + float elapsed = 0f; + bool done = false; + + //터치해야 할 위치가 있는지 체크 + if (step.touchTargetIndex >= 0) + { + _tutorialPanelController.ShowTouchTarget(step.touchTargetIndex); + targetRt = _tutorialPanelController.touchTargets[step.touchTargetIndex].GetComponent(); + } + + if (step.imageIndex >= 0) + { + _tutorialPanelController.ShowImage(step.imageIndex); + } + + while (!done) + { + // 1) 영역 터치 체크 + if (targetRt != null) + { + // 클릭 또는 터치 이벤트 + bool pressed = Input.GetMouseButtonDown(0) || Input.touchCount > 0; + if (pressed) + { + Vector2 screenPos = Input.touchCount > 0 + ? Input.GetTouch(0).position + : (Vector2)Input.mousePosition; + + // 터치 위치가 지정 RectTransform 안에 있는지 검사 + if (RectTransformUtility.RectangleContainsScreenPoint( + targetRt, screenPos, overlayCanvas.worldCamera)) + { + Debug.Log("타겟 터치"); + targetRt = null; + _tutorialPanelController.HideTouchTarget(step.touchTargetIndex); + done = true; + } + else + { + Debug.Log("타겟이 아닌 곳 터치"); + } + } + } + + // 타임아웃 체크 + if (step.timeout > 0f && elapsed >= step.timeout) + done = true; + + elapsed += Time.deltaTime; + yield return null; + } + + // 단계 완료 이벤트 + step.onStepComplete?.Invoke(); + + if (step.imageIndex >= 0) + { + _tutorialPanelController.HideImage(step.imageIndex); + } + + // 다음 단계로 + if (step.nextStep != null) + RunStep(step.nextStep); + else + EndTutorial(); + } + + private void EndTutorial() + { + _tutorialPanelController.setTutorialText(""); + overlay.alpha = 0f; + overlay.blocksRaycasts = false; + + if(onTutorialComplete!=null) + { + onTutorialComplete?.Invoke(); + onTutorialComplete = null; + } + + if (_tutorialPanelObject == null) + return; + Destroy(_tutorialPanelObject); + _tutorialPanelController = null; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Tutorial/TutorialManager.cs.bak.meta b/Assets/Scripts/Tutorial/TutorialManager.cs.bak.meta new file mode 100644 index 00000000..2a5fe0a8 --- /dev/null +++ b/Assets/Scripts/Tutorial/TutorialManager.cs.bak.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 23ceb52967652ad4c9179bc5b6cd1cbf \ No newline at end of file