DEG-178-돌발이벤트패널-버그수정 #63
@ -1,458 +1,186 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using UnityEditor.TextCore.Text;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
public enum PlayerState { None, Idle, Move, Win, Hit, Dead }
|
public partial class GameManager : Singleton<GameManager>,ISaveable
|
||||||
|
|
||||||
public class PlayerController : CharacterBase, IObserver<GameObject>
|
|
||||||
{
|
{
|
||||||
// 외부 접근 가능 변수
|
// 게임 진행 상태
|
||||||
[Header("Attach Points")]
|
private int currentDay = 1; // 날짜
|
||||||
[SerializeField] private Transform rightHandTransform;
|
public int CurrentDay => currentDay;
|
||||||
[SerializeField] private CameraShake cameraShake;
|
private int maxDays = GameConstants.maxDays;
|
||||||
[SerializeField] private GameObject normalModel; // char_body : 일상복
|
|
||||||
[SerializeField] private GameObject battleModel; // warrior_1 : 전투복
|
|
||||||
[SerializeField] private Transform dashEffectAnchor; // 대시 이펙트 위치
|
|
||||||
|
|
||||||
// 내부에서만 사용하는 변수
|
private int stageLevel = 1; // 스테이지 정보
|
||||||
private PlayerHitEffectController hitEffectController;
|
public int StageLevel => stageLevel;
|
||||||
private CharacterController _characterController;
|
|
||||||
private bool _isBattle;
|
|
||||||
private GameObject weapon;
|
|
||||||
private WeaponController _weaponController;
|
|
||||||
public WeaponController WeaponController => _weaponController;
|
|
||||||
|
|
||||||
private IPlayerState _currentStateClass { get; set; }
|
private int tryStageCount = 0;
|
||||||
private IPlayerAction _currentAction;
|
public int TryStageCount => tryStageCount;
|
||||||
public IPlayerAction CurrentAction => _currentAction;
|
|
||||||
|
|
||||||
// 강화 관련
|
// 날짜 변경 이벤트, 추후에 UI 상의 날짜를 변경할 때 사용
|
||||||
private float attackPowerLevel;
|
public event Action<int> OnDayChanged;
|
||||||
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;
|
|
||||||
|
|
||||||
//대시 쿨타임 관련
|
//패널 관련
|
||||||
[SerializeField] private float dashCooldownDuration = 1.5f;
|
private PanelManager panelManager;
|
||||||
private float dashCooldownTimer = 0f;
|
public PanelManager PanelManager => panelManager;
|
||||||
public bool IsDashOnCooldown => dashCooldownTimer > 0f;
|
|
||||||
public float DashCooldownRatio => dashCooldownTimer / dashCooldownDuration;
|
|
||||||
|
|
||||||
// 행동 관련
|
private TutorialManager tutorialManager;
|
||||||
private PlayerActionAttack _attackAction;
|
|
||||||
private PlayerActionDash _actionDash;
|
|
||||||
|
|
||||||
// 외부에서도 사용하는 변수
|
private void Start()
|
||||||
public FixedJoystick Joystick { get; private set; }
|
|
||||||
public PlayerState CurrentState { get; private set; }
|
|
||||||
private Dictionary<PlayerState, IPlayerState> _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()
|
|
||||||
{
|
{
|
||||||
if (Joystick == null)
|
// 오디오 초기화
|
||||||
{
|
InitializeAudio();
|
||||||
Joystick = FindObjectOfType<FixedJoystick>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// isBattle 초기화 (임시)
|
//패널 매니저 생성
|
||||||
bool isHousingScene = SceneManager.GetActiveScene().name.Contains("Housing");
|
panelManager = Instantiate(Resources.Load<GameObject>("Prefabs/PanelManager")).GetComponent<PanelManager>();
|
||||||
_isBattle = !isHousingScene;
|
|
||||||
|
|
||||||
AssignCharacterController();
|
|
||||||
AssignAnimator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Start()
|
#region 대화 관련
|
||||||
|
|
||||||
|
public void StartNPCDialogue(GamePhase phase) // intro, gameplay, end 존재
|
||||||
{
|
{
|
||||||
base.Start();
|
StartCoroutine(StartNPCDialogueCoroutine(phase));
|
||||||
|
|
||||||
hitEffectController = GetComponentInChildren<PlayerHitEffectController>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
attackPower *= attackPowerLevel;
|
|
||||||
moveSpeed *= moveSpeedLevel;
|
|
||||||
dashCooldownDuration -= dashCoolLevel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private IEnumerator StartNPCDialogueCoroutine(GamePhase phase)
|
||||||
{
|
{
|
||||||
if (CurrentState != PlayerState.None)
|
if (chatWindowController == null)
|
||||||
{
|
{
|
||||||
_playerStates[CurrentState].Update();
|
yield return new WaitForSeconds(0.5f); // 씬 전환 대기
|
||||||
|
chatWindowController = FindObjectOfType<ChatWindowController>();
|
||||||
}
|
}
|
||||||
|
|
||||||
//대시 쿨타임 진행
|
chatWindowController.SetGamePhase(phase);
|
||||||
if (dashCooldownTimer > 0f)
|
}
|
||||||
dashCooldownTimer -= Time.deltaTime;
|
|
||||||
|
|
||||||
// Hit 상태거나 게임 끝났을 땐 땐 입력 무시
|
public void DirectStartDialogue()
|
||||||
if (CurrentState == PlayerState.Hit || CurrentState == PlayerState.Dead || CurrentState == PlayerState.Win)
|
{
|
||||||
return;
|
if (chatWindowController == null) chatWindowController = FindObjectOfType<ChatWindowController>();
|
||||||
|
chatWindowController.SetGamePhase(GamePhase.Gameplay);
|
||||||
|
}
|
||||||
|
|
||||||
// 대시 우선 입력 처리
|
#endregion
|
||||||
if (Input.GetKeyDown(KeyCode.Space))
|
|
||||||
|
//일시 정지
|
||||||
|
public void PauseGame()
|
||||||
|
{
|
||||||
|
Time.timeScale = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeGame()
|
||||||
|
{
|
||||||
|
Time.timeScale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이벤트 할당(PlayerStats Start에서 호출)
|
||||||
|
public void SetEvents()
|
||||||
|
{
|
||||||
|
PlayerStats.Instance.OnDayEnded += AdvanceDay; // 날짜 변경
|
||||||
|
PlayerStats.Instance.ZeroReputation += ZeroReputationEnd; // 평판 0 엔딩
|
||||||
|
}
|
||||||
|
|
||||||
|
// 날짜 진행
|
||||||
|
public void AdvanceDay()
|
||||||
|
{
|
||||||
|
currentDay++;
|
||||||
|
OnDayChanged?.Invoke(currentDay);
|
||||||
|
|
||||||
|
// 최대 일수 도달 체크
|
||||||
|
if (currentDay > maxDays) // 8일차에 검사
|
||||||
{
|
{
|
||||||
dungeonPanelController.DashTouchMotion();
|
TriggerTimeEnding();
|
||||||
StartDashAction();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 공격 입력 처리
|
public void ChangeToMainScene()
|
||||||
if (Input.GetKeyDown(KeyCode.X) && (_currentAction == null || !_currentAction.IsActive)
|
{
|
||||||
&& (CurrentState != PlayerState.Win && CurrentState != PlayerState.Dead))
|
SceneManager.LoadScene("Main");
|
||||||
{
|
}
|
||||||
dungeonPanelController.AttackTouchMotion();
|
|
||||||
GameManager.Instance.PlayPlayerAttackSound();
|
|
||||||
StartAttackAction();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 액션 업데이트
|
public void ChangeToGameScene()
|
||||||
if (_currentAction != null && _currentAction.IsActive)
|
{
|
||||||
{
|
tryStageCount++; // 던전 시도 횟수 증가
|
||||||
_currentAction.UpdateAction();
|
var switchingPanel = PanelManager.GetPanel("SwitchingPanel").GetComponent<SwitchingPanelController>();
|
||||||
}
|
switchingPanel.FadeAndSceneLoad("ReDungeon"); // 던전 Scene
|
||||||
|
InteractionController interactionController = FindObjectOfType<InteractionController>();
|
||||||
|
interactionController.ReSetAfterWorkEvent();
|
||||||
|
HandleSceneAudio("Dungeon");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ChangeToHomeScene(bool isNewStart = false)
|
||||||
|
{
|
||||||
|
var switchingPanel = PanelManager.GetPanel("SwitchingPanel").GetComponent<SwitchingPanelController>();
|
||||||
|
switchingPanel.FadeAndSceneLoad("ReHousing"); // Home Scene
|
||||||
|
HandleSceneAudio("Housing");
|
||||||
|
|
||||||
|
if(isNewStart) // 아예 메인에서 시작 시 튜토리얼 출력
|
||||||
|
StartNPCDialogue(GamePhase.Intro); // StartCoroutine(StartTutorialCoroutine());
|
||||||
|
|
||||||
|
if (tryStageCount >= 3) FailEnd(); // 엔딩
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator StartTutorialCoroutine()
|
||||||
|
{
|
||||||
|
yield return new WaitForSeconds(0.5f);
|
||||||
|
|
||||||
|
if(tutorialManager == null)
|
||||||
|
tutorialManager = FindObjectOfType<TutorialManager>();
|
||||||
|
|
||||||
|
PlayerStats.Instance.HideBubble();
|
||||||
|
tutorialManager.StartTutorial(() => PlayerStats.Instance.ShowBubble());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Open Setting Panel 등 Panel 처리
|
||||||
|
|
||||||
|
protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||||
|
{
|
||||||
|
// TODO: 씬 로드 시 동작 구현. ex: BGM 변경
|
||||||
|
|
||||||
|
// UI용 Canvas 찾기
|
||||||
|
// _canvas = GameObject.FindObjectOfType<Canvas>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
OnGetHit -= HandlePlayerHit;
|
if(PlayerStats.Instance != null)
|
||||||
|
PlayerStats.Instance.OnDayEnded -= AdvanceDay; // 이벤트 구독 해제
|
||||||
}
|
}
|
||||||
|
|
||||||
#region 초기화 관련
|
private void OnApplicationQuit()
|
||||||
|
|
||||||
private void PlayerInit()
|
|
||||||
{
|
{
|
||||||
// 상태 초기화
|
// TODO: 게임 종료 시 로직 추가
|
||||||
_playerStateIdle = new PlayerStateIdle();
|
}
|
||||||
_playerStateMove = new PlayerStateMove();
|
|
||||||
_playerStateHit = new PlayerStateHit();
|
|
||||||
_playerStateWin = new PlayerStateWin();
|
|
||||||
_playerStateDead = new PlayerStateDead();
|
|
||||||
|
|
||||||
_playerStates = new Dictionary<PlayerState, IPlayerState>
|
public void ApplySaveData(Save save)
|
||||||
|
{
|
||||||
|
if (save?.dungeonSave != null)
|
||||||
{
|
{
|
||||||
{ PlayerState.Idle, _playerStateIdle },
|
stageLevel = Mathf.Clamp(save.dungeonSave.stageLevel,1,2);
|
||||||
{ PlayerState.Move, _playerStateMove },
|
tryStageCount = Mathf.Clamp(save.dungeonSave.tryStageCount,0,3);
|
||||||
{ PlayerState.Hit, _playerStateHit },
|
}
|
||||||
{ PlayerState.Win, _playerStateWin },
|
|
||||||
{ PlayerState.Dead, _playerStateDead },
|
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<GameObject>("Player/Weapon/Chopstick");
|
|
||||||
weapon = Instantiate(weaponObject, rightHandTransform);
|
|
||||||
_weaponController = weapon?.GetComponent<WeaponController>();
|
|
||||||
_weaponController?.Subscribe(this);
|
|
||||||
weapon?.SetActive(_isBattle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 애니메이션 초기화
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 전투, 일상 모드 플레이어 프리팹에 따라 애니메이터 가져오기
|
|
||||||
/// </summary>
|
|
||||||
private void AssignAnimator()
|
|
||||||
{
|
|
||||||
PlayerAnimator = _isBattle
|
|
||||||
? battleModel.GetComponent<Animator>()
|
|
||||||
: normalModel.GetComponent<Animator>();
|
|
||||||
|
|
||||||
InitializeAnimatorParameters();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 전투, 일상 모드 플레이어 프리팹에 따라 Character Controller 가져오기
|
|
||||||
/// </summary>
|
|
||||||
private void AssignCharacterController()
|
|
||||||
{
|
|
||||||
_characterController = _isBattle
|
|
||||||
? battleModel.GetComponent<CharacterController>()
|
|
||||||
: normalModel.GetComponent<CharacterController>();
|
|
||||||
}
|
|
||||||
|
|
||||||
#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<EnemyController>();
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
BIN
Assets/LIN/Prefabs/SuddenEventPanel.prefab
(Stored with Git LFS)
BIN
Assets/LIN/Prefabs/SuddenEventPanel.prefab
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/LIN/Prefabs/TutorialExamplePanel.prefab
(Stored with Git LFS)
BIN
Assets/LIN/Prefabs/TutorialExamplePanel.prefab
(Stored with Git LFS)
Binary file not shown.
@ -22,6 +22,11 @@ public class InteractionController : MonoBehaviour
|
|||||||
PlayerStats.Instance.SetHousingCanvasController(housingCanvasController);
|
PlayerStats.Instance.SetHousingCanvasController(housingCanvasController);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReSetAfterWorkEvent()
|
||||||
|
{
|
||||||
|
PlayerStats.Instance.OnWorked -= SuddenAfterWorkEventHappen;
|
||||||
|
}
|
||||||
|
|
||||||
// 상호작용 가능한 사물 범위에 들어올 때
|
// 상호작용 가능한 사물 범위에 들어올 때
|
||||||
private void OnTriggerEnter(Collider other)
|
private void OnTriggerEnter(Collider other)
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,7 @@ public enum AfterWorkEventType
|
|||||||
public static class HousingConstants
|
public static class HousingConstants
|
||||||
{
|
{
|
||||||
//돌발 이벤트 확률 계산
|
//돌발 이벤트 확률 계산
|
||||||
public static int AFTER_WORK_DENOMINATOR = 2;
|
public static int AFTER_WORK_DENOMINATOR = 3;
|
||||||
//돌발 이벤트 보여줄 시간
|
//돌발 이벤트 보여줄 시간
|
||||||
public static float SUDDENEVENT_IAMGE_SHOW_TIME = 4.0f;
|
public static float SUDDENEVENT_IAMGE_SHOW_TIME = 4.0f;
|
||||||
//전환효과(Switching) 패널 애니메이션 시간
|
//전환효과(Switching) 패널 애니메이션 시간
|
||||||
|
@ -5,6 +5,7 @@ using System.Text;
|
|||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
using Random = UnityEngine.Random;
|
||||||
|
|
||||||
public class InteractionAnimationPanelController : MonoBehaviour
|
public class InteractionAnimationPanelController : MonoBehaviour
|
||||||
{
|
{
|
||||||
@ -33,8 +34,9 @@ public class InteractionAnimationPanelController : MonoBehaviour
|
|||||||
public void ShowAnimationPanel(ActionType actionType, string animationText)
|
public void ShowAnimationPanel(ActionType actionType, string animationText)
|
||||||
{
|
{
|
||||||
PlayerStats.Instance.HideBubble();
|
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;
|
_isAbsenceToday = true;
|
||||||
}
|
}
|
||||||
@ -89,7 +91,7 @@ public class InteractionAnimationPanelController : MonoBehaviour
|
|||||||
/// 패널이 2초후 자동으로 닫히거나 터치시 닫히도록 합니다.
|
/// 패널이 2초후 자동으로 닫히거나 터치시 닫히도록 합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private IEnumerator AutoHidePanel(ActionType actionType)
|
private IEnumerator AutoHidePanel(ActionType actionType, bool isTutorial = false)
|
||||||
{
|
{
|
||||||
float startTime = Time.time;
|
float startTime = Time.time;
|
||||||
while (Time.time - startTime < animationDuration)
|
while (Time.time - startTime < animationDuration)
|
||||||
@ -105,11 +107,21 @@ public class InteractionAnimationPanelController : MonoBehaviour
|
|||||||
|
|
||||||
GameManager.Instance.StopInteractionSound(actionType);
|
GameManager.Instance.StopInteractionSound(actionType);
|
||||||
//패널 닫고 애니메이션 null처리
|
//패널 닫고 애니메이션 null처리
|
||||||
HidePanel();
|
HidePanel(isTutorial);
|
||||||
_autoHideCoroutine = null;
|
_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);
|
panel.SetActive(false);
|
||||||
|
|
||||||
@ -131,9 +143,12 @@ public class InteractionAnimationPanelController : MonoBehaviour
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 패널 닫히고 결근 체크, 상호작용 패널과 결근 엔딩 채팅창이 겹치지 않기 위함
|
if (!isTutorial) // 튜토리얼 시 결근과 말풍선 오류로 인해 조건문 추가
|
||||||
PlayerStats.Instance.CheckAbsent();
|
{
|
||||||
PlayerStats.Instance.ShowBubble();
|
// 패널 닫히고 결근 체크, 상호작용 패널과 결근 엔딩 채팅창이 겹치지 않기 위함
|
||||||
|
PlayerStats.Instance.CheckAbsent();
|
||||||
|
PlayerStats.Instance.ShowBubble();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TutorialSleepAnimation()
|
public void TutorialSleepAnimation()
|
||||||
@ -141,7 +156,19 @@ public class InteractionAnimationPanelController : MonoBehaviour
|
|||||||
_parentCanvas = FindObjectOfType(typeof(Canvas)) as Canvas;
|
_parentCanvas = FindObjectOfType(typeof(Canvas)) as Canvas;
|
||||||
|
|
||||||
HousingConstants.interactions.TryGetValue(ActionType.Sleep, out var interactionTexts);
|
HousingConstants.interactions.TryGetValue(ActionType.Sleep, out var interactionTexts);
|
||||||
ShowAnimationPanel(ActionType.Sleep, interactionTexts.AnimationText);
|
|
||||||
|
// 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, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
159
Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs.bak
Normal file
159
Assets/LIN/Scripts/UI/InteractionAnimationPanelController.cs.bak
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 패널이 2초후 자동으로 닫히거나 터치시 닫히도록 합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
BIN
Assets/Prefabs/ReHousing/SuddenEventPanel.prefab
(Stored with Git LFS)
Normal file
BIN
Assets/Prefabs/ReHousing/SuddenEventPanel.prefab
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Assets/Prefabs/ReHousing/TutorialManager.prefab
(Stored with Git LFS)
Normal file
BIN
Assets/Prefabs/ReHousing/TutorialManager.prefab
(Stored with Git LFS)
Normal file
Binary file not shown.
7
Assets/Prefabs/ReHousing/TutorialManager.prefab.meta
Normal file
7
Assets/Prefabs/ReHousing/TutorialManager.prefab.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d6becab55ba8d2a4985851dd3e1c4a83
|
||||||
|
PrefabImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
BIN
Assets/Prefabs/ReHousing/TutorialPanel.prefab
(Stored with Git LFS)
Normal file
BIN
Assets/Prefabs/ReHousing/TutorialPanel.prefab
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -26,6 +26,8 @@ public partial class GameManager : Singleton<GameManager>,ISaveable
|
|||||||
private PanelManager panelManager;
|
private PanelManager panelManager;
|
||||||
public PanelManager PanelManager => panelManager;
|
public PanelManager PanelManager => panelManager;
|
||||||
|
|
||||||
|
private TutorialManager tutorialManager;
|
||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
// 오디오 초기화
|
// 오디오 초기화
|
||||||
@ -92,21 +94,44 @@ public partial class GameManager : Singleton<GameManager>,ISaveable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ChangeToMainScene()
|
||||||
|
{
|
||||||
|
SceneManager.LoadScene("Main");
|
||||||
|
}
|
||||||
|
|
||||||
public void ChangeToGameScene()
|
public void ChangeToGameScene()
|
||||||
{
|
{
|
||||||
tryStageCount++; // 던전 시도 횟수 증가
|
tryStageCount++; // 던전 시도 횟수 증가
|
||||||
SceneManager.LoadScene("ReDungeon"); // 던전 Scene
|
InteractionController interactionController = FindObjectOfType<InteractionController>();
|
||||||
|
interactionController.ReSetAfterWorkEvent();
|
||||||
|
var switchingPanel = PanelManager.GetPanel("SwitchingPanel").GetComponent<SwitchingPanelController>();
|
||||||
|
switchingPanel.FadeAndSceneLoad("ReDungeon"); // 던전 Scene
|
||||||
HandleSceneAudio("Dungeon");
|
HandleSceneAudio("Dungeon");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ChangeToHomeScene()
|
public void ChangeToHomeScene(bool isNewStart = false)
|
||||||
{
|
{
|
||||||
SceneManager.LoadScene("ReHousing"); // Home Scene
|
var switchingPanel = PanelManager.GetPanel("SwitchingPanel").GetComponent<SwitchingPanelController>();
|
||||||
|
switchingPanel.FadeAndSceneLoad("ReHousing"); // Home Scene
|
||||||
HandleSceneAudio("Housing");
|
HandleSceneAudio("Housing");
|
||||||
|
|
||||||
|
if(isNewStart) // 아예 메인에서 시작 시 튜토리얼 출력
|
||||||
|
StartNPCDialogue(GamePhase.Intro); // StartCoroutine(StartTutorialCoroutine());
|
||||||
|
|
||||||
if (tryStageCount >= 3) FailEnd(); // 엔딩
|
if (tryStageCount >= 3) FailEnd(); // 엔딩
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerator StartTutorialCoroutine()
|
||||||
|
{
|
||||||
|
yield return new WaitForSeconds(0.5f);
|
||||||
|
|
||||||
|
if(tutorialManager == null)
|
||||||
|
tutorialManager = FindObjectOfType<TutorialManager>();
|
||||||
|
|
||||||
|
PlayerStats.Instance.HideBubble();
|
||||||
|
tutorialManager.StartTutorial(() => PlayerStats.Instance.ShowBubble());
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Open Setting Panel 등 Panel 처리
|
// TODO: Open Setting Panel 등 Panel 처리
|
||||||
|
|
||||||
protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
protected override void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||||
@ -133,6 +158,7 @@ public partial class GameManager : Singleton<GameManager>,ISaveable
|
|||||||
if (save?.dungeonSave != null)
|
if (save?.dungeonSave != null)
|
||||||
{
|
{
|
||||||
stageLevel = Mathf.Clamp(save.dungeonSave.stageLevel,1,2);
|
stageLevel = Mathf.Clamp(save.dungeonSave.stageLevel,1,2);
|
||||||
|
tryStageCount = Mathf.Clamp(save.dungeonSave.tryStageCount,0,3);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (save?.homeSave != null)
|
if (save?.homeSave != null)
|
||||||
@ -148,6 +174,7 @@ public partial class GameManager : Singleton<GameManager>,ISaveable
|
|||||||
dungeonSave = new DungeonSave()
|
dungeonSave = new DungeonSave()
|
||||||
{
|
{
|
||||||
stageLevel = Mathf.Clamp(this.stageLevel,1,2),
|
stageLevel = Mathf.Clamp(this.stageLevel,1,2),
|
||||||
|
tryStageCount = Mathf.Clamp(this.tryStageCount,0,3),
|
||||||
},
|
},
|
||||||
|
|
||||||
homeSave = new HomeSave
|
homeSave = new HomeSave
|
||||||
|
143
Assets/Scripts/Tutorial/TutorialManager.cs
Normal file
143
Assets/Scripts/Tutorial/TutorialManager.cs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
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 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<CanvasGroup>();
|
||||||
|
_tutorialPanelController = _tutorialPanelObject.GetComponent<TutorialPanelController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<RectTransform>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
targetRt = null;
|
||||||
|
_tutorialPanelController.HideTouchTarget(step.touchTargetIndex);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 타임아웃 체크
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
2
Assets/Scripts/Tutorial/TutorialManager.cs.bak.meta
Normal file
2
Assets/Scripts/Tutorial/TutorialManager.cs.bak.meta
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 23ceb52967652ad4c9179bc5b6cd1cbf
|
@ -12,8 +12,8 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: b22d834cf5e26e647be215074940d40e, type: 3}
|
m_Script: {fileID: 11500000, guid: b22d834cf5e26e647be215074940d40e, type: 3}
|
||||||
m_Name: TutorialStep1
|
m_Name: TutorialStep1
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
message: "\uC9D1\uC5D0\uC11C\uB3C4 \uC2AC\uB77C\uC774\uB354\uB97C \uC870\uC791\uD574
|
message: "\uC2AC\uB77C\uC774\uB354\uB97C \uC870\uC791\uD574 \uCE90\uB9AD\uD130\uB97C
|
||||||
\uCE90\uB9AD\uD130\uB97C \uC6C0\uC9C1\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
\uC6C0\uC9C1\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
||||||
timeout: 0
|
timeout: 0
|
||||||
onStepBegin:
|
onStepBegin:
|
||||||
m_PersistentCalls:
|
m_PersistentCalls:
|
@ -13,8 +13,8 @@ MonoBehaviour:
|
|||||||
m_Name: TutorialStep2
|
m_Name: TutorialStep2
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
message: "\uCE68\uB300, \uB0C9\uC7A5\uACE0, \uD604\uAD00 \uADF8\uB9AC\uACE0 \uC8FC\uBC29\uC5D0
|
message: "\uCE68\uB300, \uB0C9\uC7A5\uACE0, \uD604\uAD00 \uADF8\uB9AC\uACE0 \uC8FC\uBC29\uC5D0
|
||||||
\uAC00\uAE4C\uC774 \uAC00\uBA74 \uADF8\uC5D0 \uC5B4\uC6B8\uB9AC\uB294 \uC0C1\uD638\uC791\uC6A9\uC744
|
\uAC00\uAE4C\uC774 \uAC00\uBA74\n\uC0C1\uD638\uC791\uC6A9\uC744 \uD560 \uC218
|
||||||
\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
\uC788\uC2B5\uB2C8\uB2E4."
|
||||||
timeout: 0
|
timeout: 0
|
||||||
onStepBegin:
|
onStepBegin:
|
||||||
m_PersistentCalls:
|
m_PersistentCalls:
|
Loading…
x
Reference in New Issue
Block a user