using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; using Colosseum.Abnormalities; using Colosseum.AI; using Colosseum.Skills; using Colosseum.Skills.Effects; namespace Colosseum.Editor { /// /// 드로그 기획/패턴 문서를 기준으로 스킬/이펙트/패턴 플레이스홀더 자산을 재구성합니다. /// 애니메이션이 아직 확정되지 않은 단계에서도 BT와 데이터 연결을 먼저 맞추는 용도입니다. /// public static class RebuildDrogCombatAssets { private const string AnimationsFolder = "Assets/_Game/Animations"; private const string SkillsFolder = "Assets/_Game/Data/Skills"; private const string PatternsFolder = "Assets/_Game/Data/Patterns"; private const string EffectsFolder = "Assets/_Game/Data/Skills/Effects"; private const string ExecutionTelegraphAbnormalityPath = "Assets/_Game/Data/Abnormalities/Data_Abnormality_Drog_집행준비.asset"; [MenuItem("Tools/Colosseum/Rebuild Drog Combat Assets")] private static void Rebuild() { try { EnsureFolder("Assets/_Game"); EnsureFolder("Assets/_Game/Animations"); EnsureFolder("Assets/_Game/Data"); EnsureFolder("Assets/_Game/Data/Skills"); EnsureFolder("Assets/_Game/Data/Patterns"); EnsureFolder("Assets/_Game/Data/Skills/Effects"); AbnormalityData executionTelegraph = AssetDatabase.LoadAssetAtPath(ExecutionTelegraphAbnormalityPath); if (executionTelegraph == null) { Debug.LogError($"[DrogCombatAssets] 집행 전조 이상상태를 찾을 수 없습니다: {ExecutionTelegraphAbnormalityPath}"); return; } AnimationClip combo1Clip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_연타1_0.anim"); AnimationClip combo2Clip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_연타2_0.anim"); AnimationClip slamClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_강타_0.anim"); AnimationClip combo3Clip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_연타3_0.anim"); AnimationClip combo4Clip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_연타4_0.anim"); AnimationClip stompClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_발구르기_0.anim"); AnimationClip leapPrepareClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_도약_준비_0.anim"); AnimationClip leapAirClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_도약_공중_0.anim"); AnimationClip leapLandingClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_도약_착지_0.anim"); AnimationClip stepClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_밟기_0.anim"); AnimationClip throwClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_투척_0.anim"); AnimationClip roarClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_포효_0.anim"); AnimationClip executionReadyClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_집행_준비_0.anim"); AnimationClip executionHit1Clip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_집행_연타1_0.anim"); AnimationClip executionHit2Clip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_집행_연타2_0.anim"); AnimationClip executionHit3Clip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_집행_연타3_0.anim"); DamageEffect combo1Damage = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_연타1_0_데미지.asset", 24f, DamageType.Physical, 0.75f, AreaShapeType.Fan, 3.25f, 1.25f, 3.25f, 42f, AreaCenterType.Caster); DamageEffect combo2Damage = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_연타2_0_데미지.asset", 30f, DamageType.Physical, 0.9f, AreaShapeType.Fan, 3.5f, 1.35f, 3.5f, 46f, AreaCenterType.Caster); DamageEffect slamDamage = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_강타_0_데미지.asset", 48f, DamageType.Physical, 1.15f, AreaShapeType.Fan, 3.4f, 1.2f, 3.4f, 32f, AreaCenterType.Caster); DownEffect slamDown = CreateDownEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_강타_1_다운.asset", 1.8f, AreaShapeType.Fan, 3.4f, 1.2f, 3.4f, 32f, AreaCenterType.Caster); DamageEffect combo3Damage = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_연타3_0_데미지.asset", 26f, DamageType.Physical, 0.8f, AreaShapeType.Fan, 3.6f, 1.3f, 3.6f, 55f, AreaCenterType.Caster); DamageEffect combo4Damage = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_연타4_0_데미지.asset", 28f, DamageType.Physical, 0.85f, AreaShapeType.Fan, 3.8f, 1.35f, 3.8f, 58f, AreaCenterType.Caster); DamageEffect stompDamage = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_발구르기_0_데미지.asset", 22f, DamageType.Physical, 0.65f, AreaShapeType.Sphere, 4.75f, 1f, 4.75f, 180f, AreaCenterType.Caster); KnockbackEffect stompKnockback = CreateKnockbackEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_발구르기_1_넉백.asset", 6f, 1.5f, 0.2f, AreaShapeType.Sphere, 4.75f, 1f, 4.75f, 180f, AreaCenterType.Caster); DamageEffect leapLandingDamage = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_도약_착지_0_데미지.asset", 34f, DamageType.Physical, 0.95f, AreaShapeType.Sphere, 4.2f, 1f, 4.2f, 180f, AreaCenterType.Caster); KnockbackEffect leapLandingKnockback = CreateKnockbackEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_도약_착지_1_넉백.asset", 8f, 2f, 0.25f, AreaShapeType.Sphere, 4.2f, 1f, 4.2f, 180f, AreaCenterType.Caster); HitReactionDamageEffect stepDamage = CreateHitReactionDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_밟기_0_피격가중데미지.asset", 52f, DamageType.Physical, 1.1f, 1.6f, AreaShapeType.Sphere, 2.8f, 1f, 2.8f, 180f, AreaCenterType.Caster); KnockbackEffect stepKnockback = CreateKnockbackEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_밟기_1_넉백.asset", 5f, 1f, 0.18f, AreaShapeType.Sphere, 2.8f, 1f, 2.8f, 180f, AreaCenterType.Caster); DamageEffect throwDamage = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_투척_0_데미지.asset", 28f, DamageType.Physical, 0.7f, AreaShapeType.Beam, 12f, 1.2f, 0.75f, 0f, AreaCenterType.Caster); DamageEffect executionHit1 = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_집행_연타1_0_데미지.asset", 14f, DamageType.Physical, 0.35f, AreaShapeType.Sphere, 8.5f, 1f, 8.5f, 180f, AreaCenterType.Caster); DamageEffect executionHit2 = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_집행_연타2_0_데미지.asset", 17f, DamageType.Physical, 0.4f, AreaShapeType.Sphere, 8.5f, 1f, 8.5f, 180f, AreaCenterType.Caster); DamageEffect executionHit3 = CreateDamageEffect( $"{EffectsFolder}/Data_SkillEffect_Drog_집행_연타3_0_데미지.asset", 20f, DamageType.Physical, 0.45f, AreaShapeType.Sphere, 8.5f, 1f, 8.5f, 180f, AreaCenterType.Caster); SkillData combo1Skill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_연타1.asset", "연타1", "기본 루프의 첫 타격입니다.", combo1Clip, 1f, SkillCastTargetTrackingMode.FaceTarget, false, true, false, combo1Damage); SkillData combo2Skill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_연타2.asset", "연타2", "기본 루프의 두 번째 타격입니다.", combo2Clip, 1f, SkillCastTargetTrackingMode.FaceTarget, false, true, false, combo2Damage); SkillData slamSkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_강타.asset", "강타", "정면 관리 실패를 응징하는 강한 일격입니다.", slamClip, 1f, SkillCastTargetTrackingMode.FaceTarget, false, true, false, slamDamage, slamDown); SkillData combo3Skill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_연타3.asset", "연타3", "강타로 이어지는 선행 타격입니다.", combo3Clip, 1f, SkillCastTargetTrackingMode.FaceTarget, false, true, false, combo3Damage); SkillData combo4Skill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_연타4.asset", "연타4", "발구르기로 이어지는 압박용 선행 타격입니다.", combo4Clip, 1f, SkillCastTargetTrackingMode.FaceTarget, false, true, false, combo4Damage); SkillData stompSkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_발구르기.asset", "발구르기", "근접 측후방 전체를 흔드는 광역 압박입니다.", stompClip, 1f, SkillCastTargetTrackingMode.None, false, true, false, stompDamage, stompKnockback); SkillData leapPrepareSkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_도약_준비.asset", "도약 준비", "원거리 이탈 대상에게 시선을 고정합니다.", leapPrepareClip, 1f, SkillCastTargetTrackingMode.FaceTarget, false, true, false); SkillData leapAirSkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_도약_공중.asset", "도약 공중", "대상 위치로 도약하는 이동 스텝입니다.", leapAirClip, 1f, SkillCastTargetTrackingMode.MoveTowardTarget, true, false, true); SkillData leapLandingSkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_도약_착지.asset", "도약 착지", "도약 종료 시 주변에 피해와 넉백을 줍니다.", leapLandingClip, 1f, SkillCastTargetTrackingMode.None, false, true, false, leapLandingDamage, leapLandingKnockback); SkillData stepSkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_밟기.asset", "밟기", "다운된 대상을 후속 압박으로 처벌합니다.", stepClip, 1f, SkillCastTargetTrackingMode.FaceTarget, false, true, false, stepDamage, stepKnockback); SkillData throwSkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_투척.asset", "투척", "부활 시전자나 원거리 대상을 견제하는 유틸리티 공격입니다.", throwClip, 1f, SkillCastTargetTrackingMode.FaceTarget, false, true, false, throwDamage); SkillData roarSkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_포효.asset", "포효", "Phase 3 진입을 알리는 전환 신호입니다.", roarClip, 0.9f, SkillCastTargetTrackingMode.None, false, true, false); SkillData executionReadySkill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_집행_준비.asset", "집행 준비", "집행 돌입 전 자세를 고정합니다.", executionReadyClip, 0.85f, SkillCastTargetTrackingMode.None, false, true, false); SkillData executionHit1Skill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_집행_연타1.asset", "집행 연타1", "집행의 첫 압박 타격입니다.", executionHit1Clip, 1f, SkillCastTargetTrackingMode.None, false, true, false, executionHit1); SkillData executionHit2Skill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_집행_연타2.asset", "집행 연타2", "집행의 두 번째 압박 타격입니다.", executionHit2Clip, 1.1f, SkillCastTargetTrackingMode.None, false, true, false, executionHit2); SkillData executionHit3Skill = CreateSkill( $"{SkillsFolder}/Data_Skill_Drog_집행_연타3.asset", "집행 연타3", "집행의 세 번째 압박 타격입니다.", executionHit3Clip, 1.2f, SkillCastTargetTrackingMode.None, false, true, false, executionHit3); CreatePattern( $"{PatternsFolder}/Data_Pattern_Drog_연타1.asset", "연타1", PatternCategory.Basic, false, true, TargetResolveMode.HighestThreat, 2.5f, 1, false, PatternStepDefinition.CreateSkillStep(combo1Skill)); CreatePattern( $"{PatternsFolder}/Data_Pattern_Drog_연타2.asset", "연타2", PatternCategory.Basic, false, true, TargetResolveMode.HighestThreat, 2.75f, 1, false, PatternStepDefinition.CreateSkillStep(combo2Skill)); CreatePattern( $"{PatternsFolder}/Data_Pattern_Drog_연타3-강타.asset", "연타3-강타", PatternCategory.Basic, false, true, TargetResolveMode.HighestThreat, 4.5f, 1, false, PatternStepDefinition.CreateSkillStep(combo3Skill), PatternStepDefinition.CreateWaitStep(0.15f), PatternStepDefinition.CreateSkillStep(slamSkill)); CreatePattern( $"{PatternsFolder}/Data_Pattern_Drog_연타4-발구르기.asset", "연타4-발구르기", PatternCategory.Basic, false, true, TargetResolveMode.HighestThreat, 5f, 1, false, PatternStepDefinition.CreateSkillStep(combo4Skill), PatternStepDefinition.CreateWaitStep(0.15f), PatternStepDefinition.CreateSkillStep(stompSkill)); CreatePattern( $"{PatternsFolder}/Data_Pattern_Drog_밟기.asset", "밟기", PatternCategory.Punish, false, false, TargetResolveMode.HighestThreat, 2.5f, 2, false, PatternStepDefinition.CreateSkillStep(stepSkill)); CreatePattern( $"{PatternsFolder}/Data_Pattern_Drog_도약.asset", "도약", PatternCategory.Big, false, false, TargetResolveMode.Mobility, 8f, 2, false, PatternStepDefinition.CreateSkillStep(leapPrepareSkill), PatternStepDefinition.CreateWaitStep(0.1f), PatternStepDefinition.CreateSkillStep(leapAirSkill), PatternStepDefinition.CreateWaitStep(0.1f), PatternStepDefinition.CreateSkillStep(leapLandingSkill)); CreatePattern( $"{PatternsFolder}/Data_Pattern_Drog_투척.asset", "투척", PatternCategory.Basic, false, false, TargetResolveMode.Utility, 10f, 2, false, PatternStepDefinition.CreateSkillStep(throwSkill)); CreatePattern( $"{PatternsFolder}/Data_Pattern_Drog_집행.asset", "집행", PatternCategory.Big, true, false, TargetResolveMode.HighestThreat, 45f, 3, false, PatternStepDefinition.CreateSkillStep(executionReadySkill), PatternStepDefinition.CreateChargeWaitStep(2.25f, executionTelegraph, 0.1f, 2f), PatternStepDefinition.CreateSkillStep(executionHit1Skill), PatternStepDefinition.CreateWaitStep(0.65f), PatternStepDefinition.CreateSkillStep(executionHit2Skill), PatternStepDefinition.CreateWaitStep(0.45f), PatternStepDefinition.CreateSkillStep(executionHit3Skill)); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("[DrogCombatAssets] 드로그 스킬/이펙트/패턴 플레이스홀더 자산 재구성이 완료되었습니다."); } catch (Exception exception) { Debug.LogException(exception); } } /// /// 폴더가 없으면 생성합니다. /// private static void EnsureFolder(string path) { if (AssetDatabase.IsValidFolder(path)) return; string parent = Path.GetDirectoryName(path)?.Replace('\\', '/'); if (string.IsNullOrEmpty(parent)) return; EnsureFolder(parent); AssetDatabase.CreateFolder(parent, Path.GetFileName(path)); } /// /// 지정 경로의 ScriptableObject를 읽거나 새로 생성합니다. /// private static T LoadOrCreateAsset(string path) where T : ScriptableObject { T asset = AssetDatabase.LoadAssetAtPath(path); if (asset != null) return asset; asset = ScriptableObject.CreateInstance(); asset.name = Path.GetFileNameWithoutExtension(path); AssetDatabase.CreateAsset(asset, path); return asset; } /// /// 드로그 스킬 플레이스홀더용 빈 애니메이션 클립을 보장합니다. /// private static AnimationClip EnsurePlaceholderClip(string path) { AnimationClip clip = AssetDatabase.LoadAssetAtPath(path); if (clip != null) return clip; clip = new AnimationClip { name = Path.GetFileNameWithoutExtension(path), frameRate = 60f, }; AssetDatabase.CreateAsset(clip, path); return clip; } /// /// Effect/Skill/Pattern의 공통 Object 리스트를 설정합니다. /// private static void SetObjectList(SerializedObject serializedObject, string propertyName, IReadOnlyList values) { SerializedProperty listProperty = serializedObject.FindProperty(propertyName); listProperty.arraySize = values != null ? values.Count : 0; for (int i = 0; i < listProperty.arraySize; i++) { listProperty.GetArrayElementAtIndex(i).objectReferenceValue = values[i]; } } /// /// 범위형 효과의 공통 판정 설정을 적용합니다. /// private static void ConfigureAreaEffect( SerializedObject serializedObject, AreaShapeType areaShape, float areaRadius, float fanOriginDistance, float fanRadius, float fanHalfAngle, AreaCenterType areaCenter) { serializedObject.FindProperty("targetType").enumValueIndex = (int)TargetType.Area; serializedObject.FindProperty("targetTeam").enumValueIndex = (int)TargetTeam.Enemy; serializedObject.FindProperty("areaCenter").enumValueIndex = (int)areaCenter; serializedObject.FindProperty("areaShape").enumValueIndex = (int)areaShape; serializedObject.FindProperty("includeCasterInArea").boolValue = false; serializedObject.FindProperty("areaRadius").floatValue = areaRadius; serializedObject.FindProperty("fanOriginDistance").floatValue = fanOriginDistance; serializedObject.FindProperty("fanRadius").floatValue = fanRadius; serializedObject.FindProperty("fanHalfAngle").floatValue = fanHalfAngle; } /// /// 범위 피해 효과를 생성하거나 갱신합니다. /// private static DamageEffect CreateDamageEffect( string path, float baseDamage, DamageType damageType, float statScaling, AreaShapeType areaShape, float areaRadius, float fanOriginDistance, float fanRadius, float fanHalfAngle, AreaCenterType areaCenter) { DamageEffect effect = LoadOrCreateAsset(path); SerializedObject serializedObject = new SerializedObject(effect); ConfigureAreaEffect(serializedObject, areaShape, areaRadius, fanOriginDistance, fanRadius, fanHalfAngle, areaCenter); serializedObject.FindProperty("baseDamage").floatValue = baseDamage; serializedObject.FindProperty("damageType").enumValueIndex = (int)damageType; serializedObject.FindProperty("statScaling").floatValue = statScaling; serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(effect); return effect; } /// /// 범위 다운 효과를 생성하거나 갱신합니다. /// private static DownEffect CreateDownEffect( string path, float duration, AreaShapeType areaShape, float areaRadius, float fanOriginDistance, float fanRadius, float fanHalfAngle, AreaCenterType areaCenter) { DownEffect effect = LoadOrCreateAsset(path); SerializedObject serializedObject = new SerializedObject(effect); ConfigureAreaEffect(serializedObject, areaShape, areaRadius, fanOriginDistance, fanRadius, fanHalfAngle, areaCenter); serializedObject.FindProperty("duration").floatValue = duration; serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(effect); return effect; } /// /// 범위 넉백 효과를 생성하거나 갱신합니다. /// private static KnockbackEffect CreateKnockbackEffect( string path, float force, float upwardForce, float duration, AreaShapeType areaShape, float areaRadius, float fanOriginDistance, float fanRadius, float fanHalfAngle, AreaCenterType areaCenter) { KnockbackEffect effect = LoadOrCreateAsset(path); SerializedObject serializedObject = new SerializedObject(effect); ConfigureAreaEffect(serializedObject, areaShape, areaRadius, fanOriginDistance, fanRadius, fanHalfAngle, areaCenter); serializedObject.FindProperty("force").floatValue = force; serializedObject.FindProperty("upwardForce").floatValue = upwardForce; serializedObject.FindProperty("duration").floatValue = duration; serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(effect); return effect; } /// /// 다운 대상 추가 피해 효과를 생성하거나 갱신합니다. /// private static HitReactionDamageEffect CreateHitReactionDamageEffect( string path, float baseDamage, DamageType damageType, float statScaling, float downedDamageMultiplier, AreaShapeType areaShape, float areaRadius, float fanOriginDistance, float fanRadius, float fanHalfAngle, AreaCenterType areaCenter) { HitReactionDamageEffect effect = LoadOrCreateAsset(path); SerializedObject serializedObject = new SerializedObject(effect); ConfigureAreaEffect(serializedObject, areaShape, areaRadius, fanOriginDistance, fanRadius, fanHalfAngle, areaCenter); serializedObject.FindProperty("baseDamage").floatValue = baseDamage; serializedObject.FindProperty("damageType").enumValueIndex = (int)damageType; serializedObject.FindProperty("statScaling").floatValue = statScaling; serializedObject.FindProperty("bonusAgainstDownedTarget").boolValue = true; serializedObject.FindProperty("downedDamageMultiplier").floatValue = downedDamageMultiplier; serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(effect); return effect; } /// /// 빈 애니메이션 상태에서도 즉시 발동 가능한 드로그 스킬 플레이스홀더를 생성하거나 갱신합니다. /// private static SkillData CreateSkill( string path, string skillName, string description, AnimationClip clip, float animationSpeed, SkillCastTargetTrackingMode trackingMode, bool useRootMotion, bool ignoreRootMotionY, bool jumpToTarget, params SkillEffect[] castStartEffects) { SkillData skill = LoadOrCreateAsset(path); SerializedObject serializedObject = new SerializedObject(skill); serializedObject.FindProperty("skillName").stringValue = skillName; serializedObject.FindProperty("description").stringValue = description; serializedObject.FindProperty("skillRole").enumValueIndex = (int)SkillRoleType.Attack; serializedObject.FindProperty("activationType").enumValueIndex = (int)SkillActivationType.Instant; serializedObject.FindProperty("baseTypes").intValue = (int)SkillBaseType.Attack; serializedObject.FindProperty("animationSpeed").floatValue = animationSpeed; serializedObject.FindProperty("useRootMotion").boolValue = useRootMotion; serializedObject.FindProperty("ignoreRootMotionY").boolValue = ignoreRootMotionY; serializedObject.FindProperty("jumpToTarget").boolValue = jumpToTarget; serializedObject.FindProperty("blockMovementWhileCasting").boolValue = true; serializedObject.FindProperty("blockJumpWhileCasting").boolValue = true; serializedObject.FindProperty("blockOtherSkillsWhileCasting").boolValue = true; serializedObject.FindProperty("castTargetTrackingMode").enumValueIndex = (int)trackingMode; serializedObject.FindProperty("castTargetRotationSpeed").floatValue = 12f; serializedObject.FindProperty("castTargetStopDistance").floatValue = 2.5f; serializedObject.FindProperty("cooldown").floatValue = 0f; serializedObject.FindProperty("manaCost").floatValue = 0f; serializedObject.FindProperty("maxGemSlotCount").intValue = 0; serializedObject.FindProperty("triggeredEffects").arraySize = 0; SetObjectList(serializedObject, "animationClips", new UnityEngine.Object[] { clip }); var effectObjects = new List(); if (castStartEffects != null) { for (int i = 0; i < castStartEffects.Length; i++) { if (castStartEffects[i] != null) effectObjects.Add(castStartEffects[i]); } } SetObjectList(serializedObject, "castStartEffects", effectObjects); serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(skill); return skill; } /// /// 드로그 보스 패턴 자산을 생성하거나 갱신합니다. /// private static BossPatternData CreatePattern( string path, string patternName, PatternCategory category, bool isSignature, bool isMelee, TargetResolveMode targetMode, float cooldown, int minPhase, bool skipJumpStepOnNoTarget, params PatternStepDefinition[] stepDefinitions) { BossPatternData pattern = LoadOrCreateAsset(path); SerializedObject serializedObject = new SerializedObject(pattern); serializedObject.FindProperty("patternName").stringValue = patternName; serializedObject.FindProperty("category").enumValueIndex = (int)category; serializedObject.FindProperty("isSignature").boolValue = isSignature; serializedObject.FindProperty("isMelee").boolValue = isMelee; serializedObject.FindProperty("targetMode").enumValueIndex = (int)targetMode; serializedObject.FindProperty("cooldown").floatValue = cooldown; serializedObject.FindProperty("minPhase").intValue = minPhase; serializedObject.FindProperty("skipJumpStepOnNoTarget").boolValue = skipJumpStepOnNoTarget; SerializedProperty stepsProperty = serializedObject.FindProperty("steps"); stepsProperty.arraySize = stepDefinitions != null ? stepDefinitions.Length : 0; for (int i = 0; i < stepsProperty.arraySize; i++) { ConfigurePatternStep(stepsProperty.GetArrayElementAtIndex(i), stepDefinitions[i]); } serializedObject.ApplyModifiedPropertiesWithoutUndo(); EditorUtility.SetDirty(pattern); return pattern; } /// /// 단일 패턴 스텝 데이터를 SerializedProperty에 기록합니다. /// private static void ConfigurePatternStep(SerializedProperty stepProperty, PatternStepDefinition definition) { stepProperty.FindPropertyRelative("Type").enumValueIndex = (int)definition.StepType; stepProperty.FindPropertyRelative("Skill").objectReferenceValue = definition.Skill; stepProperty.FindPropertyRelative("Duration").floatValue = definition.Duration; SerializedProperty chargeDataProperty = stepProperty.FindPropertyRelative("ChargeData"); if (chargeDataProperty == null) return; chargeDataProperty.FindPropertyRelative("requiredDamageRatio").floatValue = definition.RequiredDamageRatio; chargeDataProperty.FindPropertyRelative("telegraphAbnormality").objectReferenceValue = definition.TelegraphAbnormality; chargeDataProperty.FindPropertyRelative("staggerDuration").floatValue = definition.StaggerDuration; } /// /// 패턴 스텝 정의를 간단히 구성하기 위한 헬퍼입니다. /// private sealed class PatternStepDefinition { public PatternStepType StepType { get; private set; } public SkillData Skill { get; private set; } public float Duration { get; private set; } public AbnormalityData TelegraphAbnormality { get; private set; } public float RequiredDamageRatio { get; private set; } public float StaggerDuration { get; private set; } public static PatternStepDefinition CreateSkillStep(SkillData skill) { return new PatternStepDefinition { StepType = PatternStepType.Skill, Skill = skill, Duration = 0f, }; } public static PatternStepDefinition CreateWaitStep(float duration) { return new PatternStepDefinition { StepType = PatternStepType.Wait, Duration = duration, }; } public static PatternStepDefinition CreateChargeWaitStep(float duration, AbnormalityData telegraphAbnormality, float requiredDamageRatio, float staggerDuration) { return new PatternStepDefinition { StepType = PatternStepType.ChargeWait, Duration = duration, TelegraphAbnormality = telegraphAbnormality, RequiredDamageRatio = requiredDamageRatio, StaggerDuration = staggerDuration, }; } } } }