2026-04-13 스킬시스템 진행중
This commit is contained in:
@@ -44,8 +44,5 @@ public class Item : UseableEntry
|
||||
public float IntervalDamage;
|
||||
public float IntervalDamageTime;
|
||||
|
||||
public override void Use()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
public override IUseableRuntime CreateRuntime() => new ItemInstance(this);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
public class ItemInstance
|
||||
public class ItemInstance : IUseableRuntime
|
||||
{
|
||||
public Item Data; // 원본 ScriptableObject 참조 (이름, 아이콘 등 불변 데이터)
|
||||
|
||||
@@ -18,4 +18,9 @@ public ItemInstance(Item sourceData, int stack = 1)
|
||||
this.EnhancementLevel = -1;// 기본 강화 수치 (-1은 강화수치가 없는 아이템)
|
||||
this.Durability = -1; // 기본 내구도 (-1은 내구도가 없는 아이템)
|
||||
}
|
||||
|
||||
public void Execute(UseContext ctx)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,16 @@ public class InputManager : MonoBehaviour
|
||||
public event Action OnNormalAttackEvent;
|
||||
public event Action OnHeavyAttackEvent;
|
||||
public event Action OnInteractionEvent;
|
||||
public event Action OnKeyDown_SlotQEvent;
|
||||
public event Action OnKeyDown_SlotEEvent;
|
||||
public event Action OnKeyDown_SlotREvent;
|
||||
public event Action OnKeyDown_SlotTEvent;
|
||||
public event Action OnKeyDown_SlotFEvent;
|
||||
public event Action OnKeyDown_SlotGEvent;
|
||||
public event Action<InputState> OnKeyDown_SlotQEvent;
|
||||
public event Action<InputState> OnKeyDown_SlotEEvent;
|
||||
public event Action<InputState> OnKeyDown_SlotREvent;
|
||||
public event Action<InputState> OnKeyDown_SlotTEvent;
|
||||
public event Action<InputState> OnKeyDown_SlotFEvent;
|
||||
public event Action<InputState> OnKeyDown_SlotGEvent;
|
||||
public event Action<InputState> OnAreaConfirmEvent; // Remote 스킬 범위 확정
|
||||
|
||||
// 범위 지정 중 기본 공격 차단용 플래그 (SkillModule이 제어)
|
||||
public bool SuppressNormalAttack { get; set; }
|
||||
|
||||
|
||||
//키조작
|
||||
@@ -119,6 +123,8 @@ public void SetCharacterInputMap(string mapName)
|
||||
BindActionCharacter("UseSlot_F",OnKeyDown_Slot);
|
||||
BindActionCharacter("UseSlot_G",OnKeyDown_Slot);
|
||||
|
||||
BindActionCharacter("AreaConfirm", OnAreaConfirm);
|
||||
|
||||
BindActionCharacter("Interaction", OnInteraction);
|
||||
|
||||
BindActionCharacter("OnkeyDown_IKey", OnKeyDown_IKey);
|
||||
@@ -224,6 +230,7 @@ private void OnDodge(InputAction.CallbackContext ctx)
|
||||
|
||||
private void OnNormalAttack(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (SuppressNormalAttack) return;
|
||||
OnNormalAttackEvent?.Invoke();
|
||||
}
|
||||
|
||||
@@ -238,11 +245,40 @@ private void OnInteraction(InputAction.CallbackContext ctx)
|
||||
OnInteractionEvent?.Invoke();
|
||||
}
|
||||
|
||||
private void OnAreaConfirm(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if (ctx.started)
|
||||
OnAreaConfirmEvent?.Invoke(InputState.Started);
|
||||
}
|
||||
|
||||
private void OnKeyDown_Slot(InputAction.CallbackContext ctx)
|
||||
{
|
||||
if(ctx.started)
|
||||
if (ctx.started)
|
||||
{
|
||||
|
||||
if (ctx.action.name == "UseSlot_Q")
|
||||
{
|
||||
OnKeyDown_SlotQEvent?.Invoke(InputState.Started);
|
||||
}
|
||||
else if (ctx.action.name == "UseSlot_E")
|
||||
{
|
||||
OnKeyDown_SlotEEvent?.Invoke(InputState.Started);
|
||||
}
|
||||
else if (ctx.action.name == "UseSlot_R")
|
||||
{
|
||||
OnKeyDown_SlotREvent?.Invoke(InputState.Started);
|
||||
}
|
||||
else if (ctx.action.name == "UseSlot_T")
|
||||
{
|
||||
OnKeyDown_SlotTEvent?.Invoke(InputState.Started);
|
||||
}
|
||||
else if (ctx.action.name == "UseSlot_F")
|
||||
{
|
||||
OnKeyDown_SlotFEvent?.Invoke(InputState.Started);
|
||||
}
|
||||
else if (ctx.action.name == "UseSlot_G")
|
||||
{
|
||||
OnKeyDown_SlotGEvent?.Invoke(InputState.Started);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -109,6 +109,14 @@ public void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
InputManager.Instance.OnLookEvent += CurrentCharacterController.LookInput;
|
||||
InputManager.Instance.OnDodgeEvent += CurrentCharacterController.DodgeInput;
|
||||
|
||||
//스킬 범위 확정 (Remote 스킬)
|
||||
SkillModule sm = CurrentCharacter.GetComponent<SkillModule>();
|
||||
if (sm != null)
|
||||
{
|
||||
InputManager.Instance.OnAreaConfirmEvent -= sm.AreaConfirmInput;
|
||||
InputManager.Instance.OnAreaConfirmEvent += sm.AreaConfirmInput;
|
||||
}
|
||||
|
||||
//공격매핑
|
||||
//InputManager.Instance.OnNormalAttackEvent;
|
||||
//InputManager.Instance.OnHeavyAttackEvent;
|
||||
@@ -134,6 +142,40 @@ private void ApplyCharacterInfo(PlayerCharacterController pcc, UserCharacter uc)
|
||||
pcc.PlayerCharacterIdentity.IsDefaultControl = uc.DefaultControl;
|
||||
}
|
||||
|
||||
public void BindSlotAction(string slotName,Action<InputState> slotUseAction)
|
||||
{
|
||||
if (slotName == "UseSlot_Q")
|
||||
{
|
||||
InputManager.Instance.OnKeyDown_SlotQEvent -= slotUseAction;
|
||||
InputManager.Instance.OnKeyDown_SlotQEvent += slotUseAction;
|
||||
}
|
||||
else if (slotName == "UseSlot_E")
|
||||
{
|
||||
InputManager.Instance.OnKeyDown_SlotEEvent -= slotUseAction;
|
||||
InputManager.Instance.OnKeyDown_SlotEEvent += slotUseAction;
|
||||
}
|
||||
else if (slotName == "UseSlot_R")
|
||||
{
|
||||
InputManager.Instance.OnKeyDown_SlotREvent -= slotUseAction;
|
||||
InputManager.Instance.OnKeyDown_SlotREvent += slotUseAction;
|
||||
}
|
||||
else if (slotName == "UseSlot_T")
|
||||
{
|
||||
InputManager.Instance.OnKeyDown_SlotTEvent -= slotUseAction;
|
||||
InputManager.Instance.OnKeyDown_SlotTEvent += slotUseAction;
|
||||
}
|
||||
else if (slotName == "UseSlot_F")
|
||||
{
|
||||
InputManager.Instance.OnKeyDown_SlotFEvent -= slotUseAction;
|
||||
InputManager.Instance.OnKeyDown_SlotFEvent += slotUseAction;
|
||||
}
|
||||
else if (slotName == "UseSlot_G")
|
||||
{
|
||||
InputManager.Instance.OnKeyDown_SlotGEvent -= slotUseAction;
|
||||
InputManager.Instance.OnKeyDown_SlotGEvent += slotUseAction;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if(InputManager.Instance != null)
|
||||
|
||||
@@ -116,10 +116,45 @@ private void Awake()
|
||||
_renderers = GetComponentsInChildren<Renderer>();
|
||||
}
|
||||
|
||||
// 범위 지정 중 회전모드 복구용
|
||||
private PlayerRotationMode _rotationModeBeforeAreaSelect;
|
||||
|
||||
private SkillModule _skillModule;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_stateMachine.SetMaxJumpCount(_maxJumpCount);
|
||||
SetCursorLockState(true);
|
||||
|
||||
// 범위 지정 이벤트 구독 — 회전모드만 토글 (이동은 영향 없음)
|
||||
_skillModule = GetComponent<SkillModule>();
|
||||
if (_skillModule != null)
|
||||
{
|
||||
_skillModule.OnAreaSelectStarted += HandleAreaSelectStarted;
|
||||
_skillModule.OnAreaSelectEnded += HandleAreaSelectEnded;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_skillModule != null)
|
||||
{
|
||||
_skillModule.OnAreaSelectStarted -= HandleAreaSelectStarted;
|
||||
_skillModule.OnAreaSelectEnded -= HandleAreaSelectEnded;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAreaSelectStarted()
|
||||
{
|
||||
_rotationModeBeforeAreaSelect = RotationMode;
|
||||
RotationMode = PlayerRotationMode.CameraDecoupled;
|
||||
SetCursorLockState(false); // 마우스 자유 이동
|
||||
}
|
||||
|
||||
private void HandleAreaSelectEnded()
|
||||
{
|
||||
RotationMode = _rotationModeBeforeAreaSelect;
|
||||
SetCursorLockState(true);
|
||||
}
|
||||
|
||||
public void PlayerStart()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class Weapon : MonoBehaviour
|
||||
{
|
||||
private WeaponType weaponType;
|
||||
public WeaponType WType;
|
||||
}
|
||||
|
||||
@@ -65,16 +65,16 @@ public class SkillData : UseableEntry
|
||||
[Header("레벨별 수치")]
|
||||
public SkillLevelData[] Levels;
|
||||
|
||||
[Header("키 액션 설정")]
|
||||
public string InputActionName;
|
||||
|
||||
public SkillLevelData GetLevelData(int level)
|
||||
{
|
||||
int idx = Mathf.Clamp(level - 1, 0, Levels.Length - 1);
|
||||
return Levels[idx];
|
||||
}
|
||||
|
||||
public override void Use()
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
public override IUseableRuntime CreateRuntime() => new SkillInstance(this);
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
@@ -90,6 +90,7 @@ public class SkillLevelData
|
||||
public float ChannelDuration;
|
||||
public float TickInterval;
|
||||
public float TickDamage;
|
||||
public float ActionDuration; // Attack 상태 지속 시간 (스킬 발동 후 Idle 복귀까지)
|
||||
}
|
||||
|
||||
public enum SkillType { Active, Passive }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
public class SkillInstance
|
||||
public class SkillInstance : IUseableRuntime
|
||||
{
|
||||
public SkillData Data { get; private set; }
|
||||
public int Level { get; set; } = 1;
|
||||
@@ -23,4 +23,12 @@ public void TickCooldown(float deltaTime)
|
||||
if (CooldownTimer > 0f)
|
||||
CooldownTimer -= deltaTime;
|
||||
}
|
||||
|
||||
public void Execute(UseContext ctx)
|
||||
{
|
||||
SkillModule skillModule = ctx.Caster.GetComponent<SkillModule>(); //사용자(캐스터)의 스킬 모듈
|
||||
if (skillModule == null) return;
|
||||
|
||||
skillModule.SkillInputByData(Data, ctx.UseInputState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
public abstract class UseableEntry : ScriptableObject
|
||||
public abstract class UseableEntry : ScriptableObject
|
||||
{
|
||||
[Header("기본 정보")]
|
||||
public string EntryName;
|
||||
[TextArea] public string EntryDesc;
|
||||
public Sprite Icon;
|
||||
|
||||
public abstract void Use();
|
||||
|
||||
public abstract IUseableRuntime CreateRuntime();
|
||||
}
|
||||
|
||||
public interface IUseableRuntime
|
||||
{
|
||||
public void Execute(UseContext ctx);
|
||||
}
|
||||
|
||||
public struct UseContext
|
||||
{
|
||||
public GameObject Caster;
|
||||
public GameObject Target;
|
||||
public InputState UseInputState;
|
||||
}
|
||||
Reference in New Issue
Block a user