using UnityEngine; public class SkillModule : MonoBehaviour { [SerializeField] private int _maxSlots = 4; [SerializeField] private LayerMask _groundLayer; // 바닥 레이캐스트용 private PlayerStateMachine _stateMachine; private Animator _anim; private Transform _transform; private SkillInstance[] _equippedSkills; private ISkillEffect[] _skillEffects; // 차지 private int _chargingSlot = -1; private float _chargeTimer = 0f; // 범위 지정 private int _areaSelectSlot = -1; private GameObject _areaIndicator; public bool IsAreaSelecting => _areaSelectSlot >= 0; private void Awake() { _stateMachine = GetComponent(); _anim = GetComponent(); _transform = transform; _equippedSkills = new SkillInstance[_maxSlots]; _skillEffects = new ISkillEffect[_maxSlots]; } private void Update() { TickCooldowns(); TickCharge(); TickAreaSelect(); } #region 스킬 장착 public void LoadWeaponSkills(WeaponSkillSet skillSet) { for (int i = 0; i < _maxSlots; i++) { if (i < skillSet.Skills.Count && skillSet.Skills[i] != null) { _equippedSkills[i] = new SkillInstance(skillSet.Skills[i]); _skillEffects[i] = ResolveEffect(skillSet.Skills[i].TargetType); } else { _equippedSkills[i] = null; _skillEffects[i] = null; } } ApplyPassives(); } private ISkillEffect ResolveEffect(TargetType targetType) { return targetType switch { TargetType.Self => GetComponent(), TargetType.Single => GetComponent(), TargetType.Area => GetComponent(), TargetType.Projectile => GetComponent(), _ => null }; } #endregion #region 입력 public void SkillInput(int slotIndex, InputState inputState) { if (slotIndex < 0 || slotIndex >= _maxSlots) return; SkillInstance skill = _equippedSkills[slotIndex]; if (skill == null) return; if (skill.Data.SkillType == SkillType.Passive) return; if (inputState == InputState.Started) { // 범위 지정 중에 같은 스킬 키를 다시 누르면 취소 if (_areaSelectSlot == slotIndex) { CancelAreaSelect(); return; } if (!CanUseSkill(skill)) return; if (skill.Data.ActivationType == ActivationType.Instant) { ExecuteSkill(slotIndex); } else if (skill.Data.ActivationType == ActivationType.Charge) { StartCharge(slotIndex); } else if (skill.Data.ActivationType == ActivationType.AreaSelect) { StartAreaSelect(slotIndex); } } else if (inputState == InputState.Canceled) { if (_chargingSlot == slotIndex) { ReleaseCharge(); } } } /// /// 범위 지정 모드 중 마우스 클릭 입력 (InputManager에서 호출) /// public void AreaConfirmInput(InputState inputState) { if (_areaSelectSlot < 0) return; if (inputState == InputState.Started) { ConfirmAreaSelect(); } } #endregion #region 실행 private bool CanUseSkill(SkillInstance skill) { if (skill.IsOnCooldown) return false; PlayerState state = _stateMachine.CurrentState; if (state == PlayerState.Dead || state == PlayerState.Hit || state == PlayerState.Dodge || state == PlayerState.Trans || state == PlayerState.Action) return false; return true; } private void ExecuteSkill(int slotIndex) { SkillInstance skill = _equippedSkills[slotIndex]; ISkillEffect effect = _skillEffects[slotIndex]; _stateMachine.ChangeState(PlayerState.Attack); if (!string.IsNullOrEmpty(skill.Data.AnimTrigger)) _anim.SetTrigger(skill.Data.AnimTrigger); float chargeRatio = 1f; if (skill.Data.ActivationType == ActivationType.Charge) { float maxCharge = skill.CurrentLevelData.ChargeTimeMax; chargeRatio = maxCharge > 0 ? Mathf.Clamp01(_chargeTimer / maxCharge) : 1f; } effect?.Execute(skill, _transform, chargeRatio); skill.StartCooldown(); _chargeTimer = 0f; _chargingSlot = -1; } #endregion #region 차지 private void StartCharge(int slotIndex) { _chargingSlot = slotIndex; _chargeTimer = 0f; _stateMachine.ChangeState(PlayerState.Charge); SkillInstance skill = _equippedSkills[slotIndex]; if (!string.IsNullOrEmpty(skill.Data.AnimTrigger)) _anim.SetTrigger(skill.Data.AnimTrigger); } private void ReleaseCharge() { if (_chargingSlot < 0) return; ExecuteSkill(_chargingSlot); } private void TickCharge() { if (_chargingSlot < 0) return; SkillInstance skill = _equippedSkills[_chargingSlot]; _chargeTimer += Time.deltaTime; if (_chargeTimer >= skill.CurrentLevelData.ChargeTimeMax) { ReleaseCharge(); } } #endregion #region 범위 지정 private void StartAreaSelect(int slotIndex) { _areaSelectSlot = slotIndex; SkillInstance skill = _equippedSkills[slotIndex]; if (skill.Data.AreaIndicatorPrefab != null) { _areaIndicator = Instantiate(skill.Data.AreaIndicatorPrefab); float range = skill.CurrentLevelData.Range; _areaIndicator.transform.localScale = new Vector3(range * 2f, 1f, range * 2f); } } private void TickAreaSelect() { if (_areaSelectSlot < 0 || _areaIndicator == null) return; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 200f, _groundLayer)) { _areaIndicator.transform.position = hit.point; } } private void ConfirmAreaSelect() { if (_areaSelectSlot < 0) return; SkillInstance skill = _equippedSkills[_areaSelectSlot]; ISkillEffect effect = _skillEffects[_areaSelectSlot]; _stateMachine.ChangeState(PlayerState.Attack); if (!string.IsNullOrEmpty(skill.Data.AnimTrigger)) _anim.SetTrigger(skill.Data.AnimTrigger); Vector3 targetPos = _areaIndicator.transform.position; if (effect is AreaEffect areaEffect) { areaEffect.ExecuteAtPosition(skill, _transform, targetPos); } skill.StartCooldown(); Destroy(_areaIndicator); _areaIndicator = null; _areaSelectSlot = -1; } public void CancelAreaSelect() { if (_areaIndicator != null) Destroy(_areaIndicator); _areaIndicator = null; _areaSelectSlot = -1; } #endregion #region 쿨다운 private void TickCooldowns() { foreach (var skill in _equippedSkills) { skill?.TickCooldown(Time.deltaTime); } } #endregion #region 패시브 private void ApplyPassives() { foreach (var skill in _equippedSkills) { if (skill != null && skill.Data.SkillType == SkillType.Passive) { // PlayerStat에 스탯 보정 적용 } } } #endregion #region 외부 접근 public SkillInstance GetSkill(int slotIndex) { if (slotIndex < 0 || slotIndex >= _maxSlots) return null; return _equippedSkills[slotIndex]; } public int MaxSlots => _maxSlots; #endregion }