Files
Colosseum/Assets/Scripts/Skills/SkillEffect.cs
dal4segno eff23471d7 스킬 시스템 구현
- 애니메이션 이벤트 기반 스킬 시스템 추가
  - SkillData: 스킬 데이터 (클립, 쿨타임, 효과 목록)
  - SkillController: 스킬 실행 및 애니메이션 제어
  - AnimatorOverrideController로 단일 State에서 다양한 스킬 재생

- 스킬 효과 시스템
  - DamageEffect, HealEffect, BuffEffect
  - KnockbackEffect, SoundEffect, SpawnEffect
  - 범위 공격 및 팀 구분 지원

- Team 컴포넌트로 아군/적 구분

- 스킬 중 이동 제한
  - IsPlayingAnimation으로 애니메이션 종료까지 이동 불가
  - OnSkillEnd 호출 시 다음 스킬 시전 가능

- 입력 시스템에 스킬 슬롯 6개 추가

- 애니메이션 에셋 추가 및 정리
  - AnimationSwordCombat 패키지 추가 (검 공격 애니메이션)
  - PlayerAnimationController에 Skill 상태 추가
  - External_Used 폴더 구조 정리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 12:08:52 +09:00

135 lines
3.9 KiB
C#

using UnityEngine;
using Colosseum;
namespace Colosseum.Skills
{
/// <summary>
/// 스킬 효과의 기본 클래스.
/// 모든 효과는 이 클래스를 상속받아 구현합니다.
/// 효과 발동 타이밍은 애니메이션 이벤트(OnEffect)로 제어합니다.
/// </summary>
public abstract class SkillEffect : ScriptableObject
{
[Header("대상 설정")]
[Tooltip("Self: 시전자, Area: 범위 내 대상")]
[SerializeField] protected TargetType targetType = TargetType.Self;
[Header("Area Settings (TargetType이 Area일 때)")]
[Tooltip("범위 내에서 공격할 대상 필터")]
[SerializeField] protected TargetTeam targetTeam = TargetTeam.Enemy;
[SerializeField] protected AreaCenterType areaCenter = AreaCenterType.Caster;
[Min(0.1f)] [SerializeField] protected float areaRadius = 3f;
[SerializeField] protected LayerMask targetLayers;
// Properties
public TargetType TargetType => targetType;
public TargetTeam TargetTeam => targetTeam;
/// <summary>
/// 스킬 시전 시 호출
/// </summary>
public void ExecuteOnCast(GameObject caster)
{
switch (targetType)
{
case TargetType.Self:
ApplyEffect(caster, caster);
break;
case TargetType.Area:
ExecuteArea(caster);
break;
}
}
/// <summary>
/// 투사체 충돌 시 호출
/// </summary>
public void ExecuteOnHit(GameObject caster, GameObject hitTarget)
{
if (IsValidTarget(caster, hitTarget))
{
ApplyEffect(caster, hitTarget);
}
}
/// <summary>
/// 실제 효과 적용 (상속받은 클래스에서 구현)
/// </summary>
protected abstract void ApplyEffect(GameObject caster, GameObject target);
/// <summary>
/// 충돌한 대상이 유효한 타겟인지 확인
/// </summary>
public bool IsValidTarget(GameObject caster, GameObject target)
{
if (target == null) return false;
// 레이어 체크
if (targetLayers.value != 0 && (targetLayers.value & (1 << target.layer)) == 0)
return false;
// 팀 체크
return IsCorrectTeam(caster, target);
}
private bool IsCorrectTeam(GameObject caster, GameObject target)
{
bool isSameTeam = Team.IsSameTeam(caster, target);
return targetTeam switch
{
TargetTeam.Enemy => !isSameTeam,
TargetTeam.Ally => isSameTeam,
TargetTeam.All => true,
_ => true
};
}
private void ExecuteArea(GameObject caster)
{
Vector3 center = GetAreaCenter(caster);
Collider[] hits = Physics.OverlapSphere(center, areaRadius, targetLayers);
foreach (var hit in hits)
{
if (hit.gameObject == caster) continue;
if (!IsCorrectTeam(caster, hit.gameObject)) continue;
ApplyEffect(caster, hit.gameObject);
}
}
private Vector3 GetAreaCenter(GameObject caster)
{
if (caster == null) return Vector3.zero;
return areaCenter switch
{
AreaCenterType.Caster => caster.transform.position,
AreaCenterType.CasterForward => caster.transform.position + caster.transform.forward * areaRadius,
_ => caster.transform.position
};
}
}
public enum TargetType
{
Self,
Area
}
public enum TargetTeam
{
Enemy,
Ally,
All
}
public enum AreaCenterType
{
Caster,
CasterForward
}
}