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

This commit is contained in:
2026-04-07 18:05:40 +09:00
parent 42f92020c7
commit 0844a07902
1283 changed files with 71803 additions and 6 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b956bf81ee4833341acdf7059a1d938b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -28,6 +28,9 @@ public class SkillData : ScriptableObject
[Header("이펙트")]
public GameObject EffectPrefab;
[Header("범위 지정 (AreaSelect용)")]
public GameObject AreaIndicatorPrefab;
[Header("레벨별 수치")]
public SkillLevelData[] Levels;
@@ -50,5 +53,5 @@ public class SkillLevelData
}
public enum SkillType { Active, Passive }
public enum ActivationType { Instant, Charge }
public enum ActivationType { Instant, Charge, AreaSelect }
public enum TargetType { Self, Single, Area, Projectile }

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e8aa8aab15385b846adf08d005daa382

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ac616ef280c255c448061b78e6e0d2d5

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c055da5732c2e1d4ebf43e41d419b8dd
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -22,4 +22,22 @@ public void Execute(SkillInstance skill, Transform caster, float chargeRatio)
Debug.Log($"[범위] {hit.name}에게 {finalDamage} 데미지");
}
}
public void ExecuteAtPosition(SkillInstance skill, Transform caster, Vector3 targetPos)
{
SkillLevelData levelData = skill.CurrentLevelData;
if (skill.Data.EffectPrefab != null)
{
Instantiate(skill.Data.EffectPrefab, targetPos, Quaternion.identity);
}
Collider[] hits = Physics.OverlapSphere(targetPos, levelData.Range);
foreach (Collider hit in hits)
{
if (hit.transform == caster) continue;
Debug.Log($"[범위지정] {hit.name}에게 {levelData.Damage} 데미지");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d5746059337329945a31ec9d01642e50

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 20d041c01752a8c4c92afe47384dd2ee

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7bafae519de550c48a5e64ca631a8859

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 96115cf6a2e3f694fab06c6c3c08a446

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: abfe13628584cc9429a5ef30dfd56f57

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c0f8bfa2f5004744aad5b6585118b1ad

View File

@@ -0,0 +1,296 @@
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<PlayerStateMachine>();
_anim = GetComponent<Animator>();
_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<BuffEffect>(),
TargetType.Single => GetComponent<DamageEffect>(),
TargetType.Area => GetComponent<AreaEffect>(),
TargetType.Projectile => GetComponent<ProjectileEffect>(),
_ => 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();
}
}
}
/// <summary>
/// 범위 지정 모드 중 마우스 클릭 입력 (InputManager에서 호출)
/// </summary>
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
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 57617c4607df14449bbf0853cd718274