Files
Colosseum/Assets/_Game/Scripts/Skills/SkillGemData.cs
dal4segno 40e3252901 refactor: 스킬 트리거 효과를 그룹 구조로 변경 — 하나의 애니메이션 이벤트로 여러 effect 발동 가능
- SkillGemTriggeredEffectEntry를 공용 타입 SkillTriggeredEffectEntry로 승격

- SkillData.effects를 List<SkillTriggeredEffectEntry>로 변경 (기존 flat list는 OnValidate에서 자동 마이그레이션)

- SkillLoadoutEntry, PlayerSkillInput 등 소비자 코드 그룹 구조 대응

- 기존 스킬 에셋 마이그레이션 완료

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-03 08:26:08 +09:00

201 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
using Colosseum.Abnormalities;
namespace Colosseum.Skills
{
/// <summary>
/// 젬의 주 역할 분류입니다.
/// </summary>
public enum SkillGemCategory
{
Common,
Damage,
Survival,
Mana,
Special,
BuffPower,
Duration,
Area,
Cost,
}
/// <summary>
/// 애니메이션 이벤트 인덱스와 해당 타이밍에 발동할 효과 목록입니다.
/// SkillData와 SkillGemData 모두에서 사용합니다.
/// </summary>
[Serializable]
public class SkillTriggeredEffectEntry
{
[Tooltip("OnEffect(index)와 매칭되는 애니메이션 이벤트 인덱스")]
[Min(0)] [SerializeField] private int triggerIndex = 0;
[Tooltip("해당 인덱스에서 함께 실행할 효과 목록")]
[SerializeField] private List<SkillEffect> effects = new();
public int TriggerIndex => triggerIndex;
public IReadOnlyList<SkillEffect> Effects => effects;
public SkillTriggeredEffectEntry() { }
public SkillTriggeredEffectEntry(int triggerIndex, List<SkillEffect> effects)
{
this.triggerIndex = Mathf.Max(0, triggerIndex);
this.effects = effects ?? new List<SkillEffect>();
}
}
/// <summary>
/// 젬이 적중 대상에게 부여할 이상상태 목록입니다.
/// </summary>
[Serializable]
public class SkillGemTriggeredAbnormalityEntry
{
[Tooltip("OnEffect(index)와 매칭되는 애니메이션 이벤트 인덱스")]
[Min(0)] [SerializeField] private int triggerIndex = 0;
[Tooltip("해당 인덱스에서 적중 대상에게 부여할 이상상태")]
[SerializeField] private List<AbnormalityData> abnormalities = new();
public int TriggerIndex => triggerIndex;
public IReadOnlyList<AbnormalityData> Abnormalities => abnormalities;
}
/// <summary>
/// 스킬의 기반 효과 위에 추가 동작을 덧붙이는 젬 데이터입니다.
/// </summary>
[CreateAssetMenu(fileName = "NewSkillGem", menuName = "Colosseum/Skill Gem")]
public class SkillGemData : ScriptableObject
{
[Header("기본 정보")]
[SerializeField] private string gemName;
[TextArea(2, 4)]
[SerializeField] private string description;
[SerializeField] private Sprite icon;
[Tooltip("젬의 주 역할 분류")]
[SerializeField] private SkillGemCategory category = SkillGemCategory.Common;
[Header("장착 제약")]
[Tooltip("장착 가능한 스킬 역할 조합입니다. None 또는 All이면 역할 제한을 두지 않습니다.")]
[SerializeField] private SkillRoleType allowedSkillRoles = SkillRoleType.All;
[Tooltip("장착 가능한 스킬 발동 타입 조합입니다. None 또는 All이면 타입 제한을 두지 않습니다.")]
[SerializeField] private SkillActivationType allowedSkillActivationTypes = SkillActivationType.All;
[Tooltip("기존 테스트 자산과의 호환을 위한 기반 스킬 분류입니다. 새 사양에서는 역할/발동 타입을 우선 사용합니다.")]
[SerializeField] private SkillBaseType allowedSkillTypes = SkillBaseType.All;
[Tooltip("함께 장착할 수 없는 젬 효과 분류")]
[SerializeField] private SkillGemCategory[] incompatibleCategories = Array.Empty<SkillGemCategory>();
[Tooltip("함께 장착할 수 없는 특정 젬")]
[SerializeField] private List<SkillGemData> incompatibleGems = new();
[Header("기본 수치 보정")]
[Tooltip("장착 시 마나 비용 배율")]
[Min(0f)] [SerializeField] private float manaCostMultiplier = 1f;
[Tooltip("장착 시 쿨타임 배율")]
[Min(0f)] [SerializeField] private float cooldownMultiplier = 1f;
[Tooltip("장착 시 스킬 애니메이션 재생 속도 배율")]
[Min(0.1f)] [SerializeField] private float castSpeedMultiplier = 1f;
[Tooltip("장착 시 스킬이 만드는 피해량 배율")]
[Min(0f)] [SerializeField] private float damageMultiplier = 1f;
[Tooltip("장착 시 스킬이 만드는 회복량 배율")]
[Min(0f)] [SerializeField] private float healMultiplier = 1f;
[Tooltip("장착 시 스킬이 만드는 보호막량 배율")]
[Min(0f)] [SerializeField] private float shieldMultiplier = 1f;
[Tooltip("장착 시 스킬이 만드는 위협량 배율")]
[Min(0f)] [SerializeField] private float threatMultiplier = 1f;
[Tooltip("기반 스킬 시전을 몇 회 더 반복할지 정의합니다. 현재는 계산/표시용으로만 사용됩니다.")]
[Min(0)] [SerializeField] private int additionalRepeatCount = 0;
[Header("추가 효과")]
[Tooltip("시전 시작 시 즉시 발동하는 추가 효과")]
[SerializeField] private List<SkillEffect> castStartEffects = new();
[Tooltip("애니메이션 이벤트 인덱스별로 발동하는 추가 효과")]
[SerializeField] private List<SkillTriggeredEffectEntry> triggeredEffects = new();
[Header("이상상태 부여")]
[Tooltip("스킬 사용 시 자신에게 즉시 부여할 이상상태")]
[SerializeField] private List<AbnormalityData> selfAbnormalities = new();
[Tooltip("애니메이션 이벤트 인덱스별로 적중 대상에게 부여할 이상상태")]
[SerializeField] private List<SkillGemTriggeredAbnormalityEntry> onHitAbnormalities = new();
public string GemName => gemName;
public string Description => description;
public Sprite Icon => icon;
public SkillGemCategory Category => category;
public SkillRoleType AllowedSkillRoles => allowedSkillRoles;
public SkillActivationType AllowedSkillActivationTypes => allowedSkillActivationTypes;
public SkillBaseType AllowedSkillTypes => allowedSkillTypes;
public float ManaCostMultiplier => manaCostMultiplier;
public float CooldownMultiplier => cooldownMultiplier;
public float CastSpeedMultiplier => castSpeedMultiplier;
public float DamageMultiplier => damageMultiplier;
public float HealMultiplier => healMultiplier;
public float ShieldMultiplier => shieldMultiplier;
public float ThreatMultiplier => threatMultiplier;
public int AdditionalRepeatCount => additionalRepeatCount;
public IReadOnlyList<SkillGemCategory> IncompatibleCategories => incompatibleCategories;
public IReadOnlyList<SkillGemData> IncompatibleGems => incompatibleGems;
public IReadOnlyList<SkillEffect> CastStartEffects => castStartEffects;
public IReadOnlyList<SkillTriggeredEffectEntry> TriggeredEffects => triggeredEffects;
public IReadOnlyList<AbnormalityData> SelfAbnormalities => selfAbnormalities;
public IReadOnlyList<SkillGemTriggeredAbnormalityEntry> OnHitAbnormalities => onHitAbnormalities;
/// <summary>
/// 지정한 기반 스킬에 이 젬을 장착할 수 있는지 확인합니다.
/// </summary>
public bool CanAttachToSkill(SkillData skill)
{
if (skill == null)
return false;
bool usesRoleRestriction = allowedSkillRoles != SkillRoleType.None && allowedSkillRoles != SkillRoleType.All;
bool usesActivationRestriction = allowedSkillActivationTypes != SkillActivationType.None &&
allowedSkillActivationTypes != SkillActivationType.All;
if (usesRoleRestriction || usesActivationRestriction)
{
return skill.MatchesClassification(allowedSkillRoles, allowedSkillActivationTypes);
}
if (allowedSkillTypes == SkillBaseType.None || allowedSkillTypes == SkillBaseType.All)
return true;
return (allowedSkillTypes & skill.BaseTypes) != 0;
}
/// <summary>
/// 지정한 젬 분류와 상호 배타적인지 확인합니다.
/// </summary>
public bool IsCategoryIncompatible(SkillGemCategory otherCategory)
{
if (incompatibleCategories == null || incompatibleCategories.Length == 0)
return false;
for (int i = 0; i < incompatibleCategories.Length; i++)
{
if (incompatibleCategories[i] == otherCategory)
return true;
}
return false;
}
/// <summary>
/// 지정한 젬과 상호 배타적인지 확인합니다.
/// </summary>
public bool IsGemIncompatible(SkillGemData other)
{
if (other == null || incompatibleGems == null || incompatibleGems.Count == 0)
return false;
for (int i = 0; i < incompatibleGems.Count; i++)
{
if (incompatibleGems[i] == other)
return true;
}
return false;
}
}
}