2026-04-13 스킬시스템 진행중

This commit is contained in:
2026-04-13 01:42:32 +09:00
parent ba93dc6d2f
commit b2cceb5b27
20 changed files with 1243 additions and 37 deletions

View File

@@ -1,10 +1,17 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class SkillModule : MonoBehaviour
{
[SerializeField] private int _maxSlots = 4;
[SerializeField] private LayerMask _groundLayer; // 바닥 레이캐스트용
// 범위 지정 시작/종료 이벤트 (외부에서 RotationMode/UI 등 반응)
public event Action OnAreaSelectStarted;
public event Action OnAreaSelectEnded;
private PlayerStateMachine _stateMachine;
private Animator _anim;
private Transform _transform;
@@ -37,6 +44,18 @@ public class SkillModule : MonoBehaviour
public bool IsCasting => _castingSlot >= 0;
public bool IsChanneling => _channelingSlot >= 0;
[SerializeField] private List<WeaponSkillSet> _weaponSkills;
[SerializeField] private Weapon _equippedWeapon;
private Dictionary<WeaponType, WeaponSkillSet> _dicSkills = new Dictionary<WeaponType, WeaponSkillSet>();
// 슬롯 이름 → 런타임 매핑 (Dispatcher 테이블)
private Dictionary<string, IUseableRuntime> _skillBindData = new Dictionary<string, IUseableRuntime>();
// 퀵슬롯에 바인딩할 슬롯 이름 목록 (스킬용)
private static readonly string[] SkillSlotNames =
{ "UseSlot_Q", "UseSlot_E", "UseSlot_R", "UseSlot_T" };
private void Awake()
{
_stateMachine = GetComponent<PlayerStateMachine>();
@@ -47,6 +66,55 @@ private void Awake()
_skillEffects = new ISkillEffect[_maxSlots];
}
private void Start()
{
// 무기별 스킬셋 테이블 구성
foreach (WeaponSkillSet wss in _weaponSkills)
{
_dicSkills[wss.WeaponType] = wss;
}
// 현재 장착 무기의 스킬셋 로드 (무기 미장착 시 스킬 없음)
if (_equippedWeapon != null
&& _dicSkills.TryGetValue(_equippedWeapon.WType, out WeaponSkillSet out_wss)
&& out_wss != null)
{
LoadWeaponSkills(out_wss);
RegisterSkillsToQuickslot(out_wss);
}
// 각 슬롯에 dispatcher를 한 번만 바인딩 (이후 내용물만 _skillBindData로 교체)
foreach (string slotName in SkillSlotNames)
{
string captured = slotName;
GameManager.Instance.Level.BindSlotAction(captured, (state) => DispatchSlot(captured, state));
}
}
private void DispatchSlot(string slotName, InputState state)
{
if (!_skillBindData.TryGetValue(slotName, out IUseableRuntime runtime) || runtime == null)
return;
runtime.Execute(new UseContext
{
Caster = gameObject,
Target = null,
UseInputState = state
});
}
private void RegisterSkillsToQuickslot(WeaponSkillSet wss)
{
for (int i = 0; i < SkillSlotNames.Length; i++)
{
if (i < wss.Skills.Count && wss.Skills[i] != null)
_skillBindData[SkillSlotNames[i]] = wss.Skills[i].CreateRuntime();
else
_skillBindData.Remove(SkillSlotNames[i]);
}
}
private void Update()
{
TickCooldowns();
@@ -149,9 +217,6 @@ public void AreaConfirmInput(InputState inputState)
#endregion
#region
/// <summary>
/// ActivationType에 따라 적절한 흐름을 시작
/// </summary>
private void BeginActivation(int slotIndex)
{
SkillInstance skill = _equippedSkills[slotIndex];
@@ -220,6 +285,22 @@ private void ExecuteSkill(int slotIndex)
skill.StartCooldown();
_chargeTimer = 0f;
_chargingSlot = -1;
// ActionDuration 경과 후 Idle 복귀
float duration = skill.CurrentLevelData.ActionDuration;
if (duration > 0f)
{
_ = Util.RunDelayed(duration, () =>
{
if (this != null && _stateMachine != null
&& _stateMachine.CurrentState == PlayerState.Attack)
_stateMachine.ChangeState(PlayerState.Idle);
}, default);
}
else
{
_stateMachine.ChangeState(PlayerState.Idle);
}
}
private void ClearRemoteTarget()
@@ -416,13 +497,21 @@ private void StartAreaSelect(int slotIndex)
float range = skill.CurrentLevelData.Range;
_areaIndicator.transform.localScale = new Vector3(range * 2f, range * 2f, range * 2f);
}
// 범위 지정 중에는 좌클릭이 확정 용도로 쓰이므로 기본 공격 차단
InputManager.Instance.SuppressNormalAttack = true;
// 이벤트 발행 — 구독자(PlayerCharacterController 등)가 회전모드 등 처리
OnAreaSelectStarted?.Invoke();
}
private void TickAreaSelect()
{
if (_areaSelectSlot < 0 || _areaIndicator == null) return;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Mouse.current == null) return;
Vector2 mousePos = Mouse.current.position.ReadValue();
Ray ray = Camera.main.ScreenPointToRay(mousePos);
if (Physics.Raycast(ray, out RaycastHit hit, 200f, _groundLayer))
{
_areaIndicator.transform.position = hit.point;
@@ -444,6 +533,10 @@ private void ConfirmAreaSelect()
_areaIndicator = null;
_areaSelectSlot = -1;
// 범위 지정 종료 → 기본 공격 복구
InputManager.Instance.SuppressNormalAttack = false;
OnAreaSelectEnded?.Invoke();
// ActivationType에 따라 흐름 시작
BeginActivation(slotIndex);
}
@@ -455,6 +548,10 @@ public void CancelAreaSelect()
_areaIndicator = null;
_areaSelectSlot = -1;
// 범위 지정 종료 → 기본 공격 복구
InputManager.Instance.SuppressNormalAttack = false;
OnAreaSelectEnded?.Invoke();
}
#endregion
@@ -488,6 +585,23 @@ public SkillInstance GetSkill(int slotIndex)
return _equippedSkills[slotIndex];
}
public void SkillInputByData(SkillData data, InputState inputState)
{
int slotIndex = FindSlotByData(data);
if (slotIndex < 0) return;
SkillInput(slotIndex, inputState);
}
private int FindSlotByData(SkillData data)
{
for (int i = 0; i < _maxSlots; i++)
{
if (_equippedSkills[i] != null && _equippedSkills[i].Data == data)
return i;
}
return -1;
}
public int MaxSlots => _maxSlots;
#endregion
}