Files
Colosseum/Assets/_Game/Scripts/Skills/SkillData.cs
dal4segno a94daf7968 feat: 젬 분류 사양과 장착 UI 반영
- 옵시디언 기준의 역할/발동 타입 분류를 스킬·젬 데이터와 장착 검증 로직에 반영
- 젬 보관 UI와 퀵슬롯 표시를 새 분류 및 실제 마나/쿨타임 계산 기준으로 갱신
- 테스트 스킬/젬 자산을 에디터 메뉴로 동기화하고 Unity 컴파일 및 플레이 검증 완료
2026-03-26 16:18:45 +09:00

235 lines
9.1 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
namespace Colosseum.Skills
{
/// <summary>
/// 젬 장착 조건에서 사용하는 기반 스킬 분류입니다.
/// 하나의 스킬이 여러 분류를 동시에 가질 수 있습니다.
/// </summary>
[Flags]
public enum SkillBaseType
{
None = 0,
Attack = 1 << 0,
Defense = 1 << 1,
Support = 1 << 2,
Control = 1 << 3,
Mobility = 1 << 4,
Utility = 1 << 5,
All = Attack | Defense | Support | Control | Mobility | Utility,
}
/// <summary>
/// 스킬의 역할 분류입니다.
/// 젬 장착 조건에는 비트 마스크 형태로도 사용합니다.
/// </summary>
[Flags]
public enum SkillRoleType
{
None = 0,
Attack = 1 << 0,
Defense = 1 << 1,
Support = 1 << 2,
All = Attack | Defense | Support,
}
/// <summary>
/// 스킬의 발동 타입 분류입니다.
/// </summary>
[Flags]
public enum SkillActivationType
{
None = 0,
Instant = 1 << 0,
Buff = 1 << 1,
All = Instant | Buff,
}
/// <summary>
/// 스킬 데이터. 스킬의 기본 정보와 효과 목록을 관리합니다.
/// </summary>
[CreateAssetMenu(fileName = "NewSkill", menuName = "Colosseum/Skill")]
public class SkillData : ScriptableObject
{
[Header("기본 정보")]
[SerializeField] private string skillName;
[TextArea(2, 4)]
[SerializeField] private string description;
[SerializeField] private Sprite icon;
[Header("스킬 분류")]
[Tooltip("이 스킬의 주 역할입니다.")]
[SerializeField] private SkillRoleType skillRole = SkillRoleType.Attack;
[Tooltip("이 스킬의 발동 타입입니다.")]
[SerializeField] private SkillActivationType activationType = SkillActivationType.Instant;
[Header("레거시 기반 스킬 분류")]
[Tooltip("기존 테스트 데이터와의 호환을 위한 기반 분류입니다.")]
[SerializeField] private SkillBaseType baseTypes = SkillBaseType.None;
[Header("애니메이션")]
[Tooltip("기본 Animator Controller의 'Skill' 상태에 덮어씌워질 클립")]
[SerializeField] private AnimationClip skillClip;
[Tooltip("종료 애니메이션 (선택)")]
[SerializeField] private AnimationClip endClip;
[Tooltip("애니메이션 재생 속도 (1 = 기본, 2 = 2배속)")]
[Min(0.1f)] [SerializeField] private float animationSpeed = 1f;
[Header("루트 모션")]
[Tooltip("애니메이션의 이동/회전 데이터를 캐릭터에 적용")]
[SerializeField] private bool useRootMotion = false;
[Tooltip("루트 모션 적용 시 Y축 이동 무시 (중력과 충돌)")]
[SerializeField] private bool ignoreRootMotionY = true;
[Tooltip("스킬 시전 시 대상 위치로 점프 이동 (UseRootMotion + IgnoreRootMotionY=false 필요)")]
[SerializeField] private bool jumpToTarget = false;
[Header("행동 제한")]
[Tooltip("시전 중 이동 입력 차단 여부")]
[SerializeField] private bool blockMovementWhileCasting = true;
[Tooltip("시전 중 점프 입력 차단 여부")]
[SerializeField] private bool blockJumpWhileCasting = true;
[Tooltip("시전 중 다른 스킬 입력 차단 여부")]
[SerializeField] private bool blockOtherSkillsWhileCasting = true;
[Header("쿨타임 & 비용")]
[Min(0f)] [SerializeField] private float cooldown = 1f;
[Min(0f)] [SerializeField] private float manaCost = 0f;
[Header("젬 슬롯")]
[Tooltip("이 스킬에 장착 가능한 젬 슬롯 수")]
[Min(0)] [SerializeField] private int maxGemSlotCount = 2;
[Header("효과 목록")]
[Tooltip("시전 시작 즉시 발동하는 효과 목록. 시전 보호 버프 등에 사용됩니다.")]
[SerializeField] private List<SkillEffect> castStartEffects = new List<SkillEffect>();
[Header("효과 목록")]
[Tooltip("애니메이션 이벤트 OnEffect(index)로 발동. 리스트 순서 = 이벤트 인덱스")]
[SerializeField] private List<SkillEffect> effects = new List<SkillEffect>();
// Properties
public string SkillName => skillName;
public string Description => description;
public Sprite Icon => icon;
public SkillRoleType SkillRole => skillRole;
public SkillActivationType ActivationType => activationType;
public SkillBaseType BaseTypes => baseTypes;
public AnimationClip SkillClip => skillClip;
public AnimationClip EndClip => endClip;
public float AnimationSpeed => animationSpeed;
public float Cooldown => cooldown;
public float ManaCost => manaCost;
public int MaxGemSlotCount => maxGemSlotCount;
public bool UseRootMotion => useRootMotion;
public bool IgnoreRootMotionY => ignoreRootMotionY;
public bool JumpToTarget => jumpToTarget;
public bool BlockMovementWhileCasting => blockMovementWhileCasting;
public bool BlockJumpWhileCasting => blockJumpWhileCasting;
public bool BlockOtherSkillsWhileCasting => blockOtherSkillsWhileCasting;
public IReadOnlyList<SkillEffect> CastStartEffects => castStartEffects;
public IReadOnlyList<SkillEffect> Effects => effects;
/// <summary>
/// 지정한 장착 조건과 현재 스킬 분류가 맞는지 확인합니다.
/// </summary>
public bool MatchesClassification(SkillRoleType allowedRoles, SkillActivationType allowedActivationTypes)
{
bool matchesRole = allowedRoles == SkillRoleType.None ||
allowedRoles == SkillRoleType.All ||
(allowedRoles & skillRole) != 0;
bool matchesActivationType = allowedActivationTypes == SkillActivationType.None ||
allowedActivationTypes == SkillActivationType.All ||
(allowedActivationTypes & activationType) != 0;
return matchesRole && matchesActivationType;
}
}
/// <summary>
/// 스킬/젬 분류를 UI 친화적인 문자열로 변환하는 유틸리티입니다.
/// </summary>
public static class SkillClassificationUtility
{
public static string GetRoleLabel(SkillRoleType role)
{
return role switch
{
SkillRoleType.Attack => "공격",
SkillRoleType.Defense => "방어",
SkillRoleType.Support => "지원",
SkillRoleType.All => "전체",
_ => "미분류",
};
}
public static string GetActivationTypeLabel(SkillActivationType activationType)
{
return activationType switch
{
SkillActivationType.Instant => "즉발",
SkillActivationType.Buff => "버프",
SkillActivationType.All => "전체",
_ => "미분류",
};
}
public static string GetSkillClassificationLabel(SkillData skill)
{
if (skill == null)
return "미분류";
return $"{GetRoleLabel(skill.SkillRole)}/{GetActivationTypeLabel(skill.ActivationType)}";
}
public static string GetGemCategoryLabel(SkillGemCategory category)
{
return category switch
{
SkillGemCategory.Damage => "데미지",
SkillGemCategory.Survival => "생존",
SkillGemCategory.Mana => "마나",
SkillGemCategory.Special => "특수",
SkillGemCategory.BuffPower => "효과 강화",
SkillGemCategory.Duration => "지속시간",
SkillGemCategory.Area => "범위",
SkillGemCategory.Cost => "비용",
_ => "공용",
};
}
public static string GetAllowedRoleSummary(SkillRoleType roles)
{
if (roles == SkillRoleType.None || roles == SkillRoleType.All)
return "전체";
List<string> labels = new List<string>();
if ((roles & SkillRoleType.Attack) != 0)
labels.Add("공격");
if ((roles & SkillRoleType.Defense) != 0)
labels.Add("방어");
if ((roles & SkillRoleType.Support) != 0)
labels.Add("지원");
return labels.Count > 0 ? string.Join(" + ", labels) : "미분류";
}
public static string GetAllowedActivationSummary(SkillActivationType activationTypes)
{
if (activationTypes == SkillActivationType.None || activationTypes == SkillActivationType.All)
return "전체";
List<string> labels = new List<string>();
if ((activationTypes & SkillActivationType.Instant) != 0)
labels.Add("즉발");
if ((activationTypes & SkillActivationType.Buff) != 0)
labels.Add("버프");
return labels.Count > 0 ? string.Join(" + ", labels) : "미분류";
}
}
}