- 드로그 BT를 밟기 우선, 콤보-강타, 추적 순서로 유지하도록 관련 자산을 정리 - 강타/밟기 클립에 효과 이벤트를 추가하고 밟기 패턴의 Phase 1 진입을 복구 - 플레이어 다운 시간을 DownBegin 이후 루프 구간 기준으로 계산하도록 조정
1372 lines
60 KiB
C#
1372 lines
60 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// 드로그 기획/패턴 문서를 기준으로 스킬/이펙트/패턴 플레이스홀더 자산을 재구성합니다.
|
|
/// 애니메이션이 아직 확정되지 않은 단계에서도 BT와 데이터 연결을 먼저 맞추는 용도입니다.
|
|
/// </summary>
|
|
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";
|
|
private const string LightCombo01BSourcePath = "Assets/External/Animations/AnimationSwordCombat/Animations/Sidekick/Attack/LightCombo01/A_MOD_SWD_Attack_LightCombo01B_RM_Neut.fbx";
|
|
private const string HeavyCombo01BSourcePath = "Assets/External/Animations/AnimationSwordCombat/Animations/Sidekick/Attack/HeavyCombo01/A_MOD_SWD_Attack_HeavyCombo01B_RM_Neut.fbx";
|
|
private const string HeavyCombo01CSourcePath = "Assets/External/Animations/AnimationSwordCombat/Animations/Sidekick/Attack/HeavyCombo01/A_MOD_SWD_Attack_HeavyCombo01C_RM_Neut.fbx";
|
|
private const string ZweihanderAttack013SourcePath = "Assets/External/Animations/Knight_Zweihander_Animset/Animation/Attack/Root/Zweihander_Attack01_3_Root.FBX";
|
|
private const string PunchLSourcePath = "Assets/External/Animations/Mixamo/펀치L.fbx";
|
|
private const string PunchRSourcePath = "Assets/External/Animations/Mixamo/펀치R.fbx";
|
|
private const string KickRSourcePath = "Assets/External/Animations/Mixamo/킥R.fbx";
|
|
|
|
[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");
|
|
DeleteLegacyComboAssets();
|
|
|
|
AbnormalityData executionTelegraph = AssetDatabase.LoadAssetAtPath<AbnormalityData>(ExecutionTelegraphAbnormalityPath);
|
|
if (executionTelegraph == null)
|
|
{
|
|
Debug.LogError($"[DrogCombatAssets] 집행 전조 이상상태를 찾을 수 없습니다: {ExecutionTelegraphAbnormalityPath}");
|
|
return;
|
|
}
|
|
|
|
AnimationClip comboBasic1Hit1Clip0 = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기1_1_0.anim", $"{AnimationsFolder}/Anim_Drog_평타1R_0.anim");
|
|
AnimationClip comboBasic1Hit1Clip1 = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기1_1_1.anim", $"{AnimationsFolder}/Anim_Drog_평타1R_1.anim");
|
|
AnimationClip comboBasic1Hit2Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기1_2_0.anim", LightCombo01BSourcePath, "A_MOD_SWD_Attack_LightCombo01B_RM_Neut");
|
|
SetSingleOnEffectEvent(comboBasic1Hit1Clip0, -1f);
|
|
SetSingleOnEffectEvent(comboBasic1Hit1Clip1, 0.30f);
|
|
SetSingleOnEffectEvent(comboBasic1Hit2Clip, 0.28f);
|
|
|
|
AnimationClip comboBasic2Hit1Clip0 = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기2_1_0.anim", $"{AnimationsFolder}/Anim_Drog_평타2R_0.anim");
|
|
AnimationClip comboBasic2Hit1Clip1 = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기2_1_1.anim", $"{AnimationsFolder}/Anim_Drog_평타2R_1.anim");
|
|
AnimationClip comboBasic2Hit1Clip2 = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기2_1_2.anim", $"{AnimationsFolder}/Anim_Drog_평타2R_2.anim");
|
|
AnimationClip comboBasic2Hit2Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기2_2_0.anim", HeavyCombo01CSourcePath, "A_MOD_SWD_Attack_HeavyCombo01C_RM_Neut");
|
|
|
|
AnimationClip comboBasic3Hit1Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기3_1_0.anim", PunchLSourcePath, "mixamo.com");
|
|
AnimationClip comboBasic3Hit2Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기3_2_0.anim", PunchRSourcePath, "mixamo.com");
|
|
AnimationClip comboBasic3Hit3Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-기본기3_3_0.anim", KickRSourcePath, "mixamo.com");
|
|
|
|
AnimationClip comboSlamHit1Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-강타_1_0.anim", HeavyCombo01BSourcePath, "A_MOD_SWD_Attack_HeavyCombo01B_RM_Neut");
|
|
AnimationClip comboSlamHit2Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-강타_2_0.anim", LightCombo01BSourcePath, "A_MOD_SWD_Attack_LightCombo01B_RM_Neut");
|
|
AnimationClip slamClip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_강타_0.anim", $"{AnimationsFolder}/Anim_Drog_강타R_0.anim");
|
|
SetSingleOnEffectEvent(comboSlamHit1Clip, 0.30f);
|
|
SetSingleOnEffectEvent(comboSlamHit2Clip, 0.28f);
|
|
SetSingleOnEffectEvent(slamClip, 0.95f);
|
|
|
|
AnimationClip comboStompHit1Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-발구르기_1_0.anim", ZweihanderAttack013SourcePath, "Zweihander_Attack01_3_Root");
|
|
AnimationClip comboStompHit2Clip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_콤보-발구르기_2_0.anim", HeavyCombo01CSourcePath, "A_MOD_SWD_Attack_HeavyCombo01C_RM_Neut");
|
|
AnimationClip stompClip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_발구르기_0.anim", $"{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");
|
|
SetSingleOnEffectEvent(stepClip, 0.80f);
|
|
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 comboBasic1Hit1Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기1_1_0_데미지.asset",
|
|
22f,
|
|
DamageType.Physical,
|
|
0.65f,
|
|
AreaShapeType.Fan,
|
|
3.25f,
|
|
1.25f,
|
|
3.25f,
|
|
42f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboBasic1Hit2Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기1_2_0_데미지.asset",
|
|
16f,
|
|
DamageType.Physical,
|
|
0.5f,
|
|
AreaShapeType.Fan,
|
|
3.3f,
|
|
1.25f,
|
|
3.3f,
|
|
40f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboBasic2Hit1Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기2_1_0_데미지.asset",
|
|
26f,
|
|
DamageType.Physical,
|
|
0.8f,
|
|
AreaShapeType.Fan,
|
|
3.5f,
|
|
1.35f,
|
|
3.5f,
|
|
46f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboBasic2Hit2Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기2_2_0_데미지.asset",
|
|
20f,
|
|
DamageType.Physical,
|
|
0.6f,
|
|
AreaShapeType.Fan,
|
|
3.6f,
|
|
1.35f,
|
|
3.6f,
|
|
42f,
|
|
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 comboBasic3Hit1Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기3_1_0_데미지.asset",
|
|
12f,
|
|
DamageType.Physical,
|
|
0.35f,
|
|
AreaShapeType.Fan,
|
|
2.6f,
|
|
1.1f,
|
|
2.6f,
|
|
55f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboBasic3Hit2Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기3_2_0_데미지.asset",
|
|
12f,
|
|
DamageType.Physical,
|
|
0.35f,
|
|
AreaShapeType.Fan,
|
|
2.6f,
|
|
1.1f,
|
|
2.6f,
|
|
55f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboBasic3Hit3Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기3_3_0_데미지.asset",
|
|
18f,
|
|
DamageType.Physical,
|
|
0.55f,
|
|
AreaShapeType.Fan,
|
|
3.1f,
|
|
1.15f,
|
|
3.1f,
|
|
68f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboSlamHit1Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-강타_1_0_데미지.asset",
|
|
20f,
|
|
DamageType.Physical,
|
|
0.6f,
|
|
AreaShapeType.Fan,
|
|
3.6f,
|
|
1.3f,
|
|
3.6f,
|
|
52f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboSlamHit2Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-강타_2_0_데미지.asset",
|
|
16f,
|
|
DamageType.Physical,
|
|
0.45f,
|
|
AreaShapeType.Fan,
|
|
3.2f,
|
|
1.15f,
|
|
3.2f,
|
|
42f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboStompHit1Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-발구르기_1_0_데미지.asset",
|
|
22f,
|
|
DamageType.Physical,
|
|
0.65f,
|
|
AreaShapeType.Fan,
|
|
3.9f,
|
|
1.35f,
|
|
3.9f,
|
|
60f,
|
|
AreaCenterType.Caster);
|
|
|
|
DamageEffect comboStompHit2Damage = CreateDamageEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-발구르기_2_0_데미지.asset",
|
|
18f,
|
|
DamageType.Physical,
|
|
0.5f,
|
|
AreaShapeType.Fan,
|
|
3.6f,
|
|
1.25f,
|
|
3.6f,
|
|
44f,
|
|
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);
|
|
|
|
StaggerEffect stompStagger = CreateStaggerEffect(
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_발구르기_1_경직.asset",
|
|
0.35f,
|
|
true,
|
|
1f,
|
|
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,
|
|
true,
|
|
1f,
|
|
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,
|
|
true,
|
|
1f,
|
|
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 comboBasic1Hit1Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기1_1.asset",
|
|
"콤보-기본기1 1타",
|
|
"기본기 콤보1의 첫 타격입니다.",
|
|
new[] { comboBasic1Hit1Clip0, comboBasic1Hit1Clip1 },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboBasic1Hit1Damage);
|
|
|
|
SkillData comboBasic1Hit2Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기1_2.asset",
|
|
"콤보-기본기1 2타",
|
|
"기본기 콤보1의 후속 타격입니다.",
|
|
new[] { comboBasic1Hit2Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboBasic1Hit2Damage);
|
|
|
|
SkillData comboBasic2Hit1Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기2_1.asset",
|
|
"콤보-기본기2 1타",
|
|
"기본기 콤보2의 시작 타격입니다.",
|
|
new[] { comboBasic2Hit1Clip0, comboBasic2Hit1Clip1, comboBasic2Hit1Clip2 },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboBasic2Hit1Damage);
|
|
|
|
SkillData comboBasic2Hit2Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기2_2.asset",
|
|
"콤보-기본기2 2타",
|
|
"기본기 콤보2의 마무리 타격입니다.",
|
|
new[] { comboBasic2Hit2Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboBasic2Hit2Damage);
|
|
|
|
SkillData slamSkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_강타.asset",
|
|
"강타",
|
|
"정면 관리 실패를 응징하는 강한 일격입니다.",
|
|
new[] { slamClip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
slamDamage,
|
|
slamDown);
|
|
|
|
SkillData comboBasic3Hit1Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기3_1.asset",
|
|
"콤보-기본기3 1타",
|
|
"기본기 콤보3의 첫 번째 타격입니다.",
|
|
new[] { comboBasic3Hit1Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboBasic3Hit1Damage);
|
|
|
|
SkillData comboBasic3Hit2Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기3_2.asset",
|
|
"콤보-기본기3 2타",
|
|
"기본기 콤보3의 두 번째 타격입니다.",
|
|
new[] { comboBasic3Hit2Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboBasic3Hit2Damage);
|
|
|
|
SkillData comboBasic3Hit3Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기3_3.asset",
|
|
"콤보-기본기3 3타",
|
|
"기본기 콤보3의 발차기 마무리입니다.",
|
|
new[] { comboBasic3Hit3Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboBasic3Hit3Damage);
|
|
|
|
SkillData comboSlamHit1Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-강타_1.asset",
|
|
"콤보-강타 1타",
|
|
"강타 콤보의 첫 선행 타격입니다.",
|
|
new[] { comboSlamHit1Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboSlamHit1Damage);
|
|
|
|
SkillData comboSlamHit2Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-강타_2.asset",
|
|
"콤보-강타 2타",
|
|
"강타로 이어지는 두 번째 선행 타격입니다.",
|
|
new[] { comboSlamHit2Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboSlamHit2Damage);
|
|
|
|
SkillData comboStompHit1Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-발구르기_1.asset",
|
|
"콤보-발구르기 1타",
|
|
"발구르기 콤보의 첫 선행 타격입니다.",
|
|
new[] { comboStompHit1Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboStompHit1Damage);
|
|
|
|
SkillData comboStompHit2Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-발구르기_2.asset",
|
|
"콤보-발구르기 2타",
|
|
"발구르기로 연결되는 두 번째 선행 타격입니다.",
|
|
new[] { comboStompHit2Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
true,
|
|
true,
|
|
false,
|
|
comboStompHit2Damage);
|
|
|
|
SkillData stompSkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_발구르기.asset",
|
|
"발구르기",
|
|
"근접 측후방 전체를 흔드는 광역 압박입니다.",
|
|
new[] { stompClip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.None,
|
|
true,
|
|
true,
|
|
false,
|
|
stompDamage,
|
|
stompStagger);
|
|
|
|
SkillData leapPrepareSkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_도약_준비.asset",
|
|
"도약 준비",
|
|
"원거리 이탈 대상에게 시선을 고정합니다.",
|
|
new[] { leapPrepareClip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
false,
|
|
true,
|
|
false);
|
|
|
|
SkillData leapAirSkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_도약_공중.asset",
|
|
"도약 공중",
|
|
"대상 위치로 도약하는 이동 스텝입니다.",
|
|
new[] { leapAirClip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.MoveTowardTarget,
|
|
true,
|
|
false,
|
|
true);
|
|
|
|
SkillData leapLandingSkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_도약_착지.asset",
|
|
"도약 착지",
|
|
"도약 종료 시 주변에 피해와 넉백을 줍니다.",
|
|
new[] { leapLandingClip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.None,
|
|
false,
|
|
true,
|
|
false,
|
|
leapLandingDamage,
|
|
leapLandingKnockback);
|
|
|
|
SkillData stepSkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_밟기.asset",
|
|
"밟기",
|
|
"다운된 대상을 후속 압박으로 처벌합니다.",
|
|
new[] { stepClip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
false,
|
|
true,
|
|
false,
|
|
stepDamage,
|
|
stepKnockback);
|
|
|
|
SkillData throwSkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_투척.asset",
|
|
"투척",
|
|
"부활 시전자나 원거리 대상을 견제하는 유틸리티 공격입니다.",
|
|
new[] { throwClip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.FaceTarget,
|
|
false,
|
|
true,
|
|
false,
|
|
throwDamage);
|
|
|
|
SkillData roarSkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_포효.asset",
|
|
"포효",
|
|
"Phase 3 진입을 알리는 전환 신호입니다.",
|
|
new[] { roarClip },
|
|
0.9f,
|
|
SkillCastTargetTrackingMode.None,
|
|
false,
|
|
true,
|
|
false);
|
|
|
|
SkillData executionReadySkill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_집행_준비.asset",
|
|
"집행 준비",
|
|
"집행 돌입 전 자세를 고정합니다.",
|
|
new[] { executionReadyClip },
|
|
0.85f,
|
|
SkillCastTargetTrackingMode.None,
|
|
false,
|
|
true,
|
|
false);
|
|
|
|
SkillData executionHit1Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_집행_연타1.asset",
|
|
"집행 연타1",
|
|
"집행의 첫 압박 타격입니다.",
|
|
new[] { executionHit1Clip },
|
|
1f,
|
|
SkillCastTargetTrackingMode.None,
|
|
false,
|
|
true,
|
|
false,
|
|
executionHit1);
|
|
|
|
SkillData executionHit2Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_집행_연타2.asset",
|
|
"집행 연타2",
|
|
"집행의 두 번째 압박 타격입니다.",
|
|
new[] { executionHit2Clip },
|
|
1.1f,
|
|
SkillCastTargetTrackingMode.None,
|
|
false,
|
|
true,
|
|
false,
|
|
executionHit2);
|
|
|
|
SkillData executionHit3Skill = CreateSkill(
|
|
$"{SkillsFolder}/Data_Skill_Drog_집행_연타3.asset",
|
|
"집행 연타3",
|
|
"집행의 세 번째 압박 타격입니다.",
|
|
new[] { 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(comboBasic1Hit1Skill),
|
|
PatternStepDefinition.CreateSkillStep(comboBasic1Hit2Skill));
|
|
|
|
CreatePattern(
|
|
$"{PatternsFolder}/Data_Pattern_Drog_콤보-기본기2.asset",
|
|
"콤보-기본기2",
|
|
PatternCategory.Basic,
|
|
false,
|
|
true,
|
|
TargetResolveMode.HighestThreat,
|
|
3f,
|
|
1,
|
|
false,
|
|
PatternStepDefinition.CreateSkillStep(comboBasic2Hit1Skill),
|
|
PatternStepDefinition.CreateSkillStep(comboBasic2Hit2Skill));
|
|
|
|
CreatePattern(
|
|
$"{PatternsFolder}/Data_Pattern_Drog_콤보-기본기3.asset",
|
|
"콤보-기본기3",
|
|
PatternCategory.Basic,
|
|
false,
|
|
true,
|
|
TargetResolveMode.HighestThreat,
|
|
3.25f,
|
|
1,
|
|
false,
|
|
PatternStepDefinition.CreateSkillStep(comboBasic3Hit1Skill),
|
|
PatternStepDefinition.CreateSkillStep(comboBasic3Hit2Skill),
|
|
PatternStepDefinition.CreateSkillStep(comboBasic3Hit3Skill));
|
|
|
|
CreatePattern(
|
|
$"{PatternsFolder}/Data_Pattern_Drog_콤보-강타.asset",
|
|
"콤보-강타",
|
|
PatternCategory.Basic,
|
|
false,
|
|
true,
|
|
TargetResolveMode.HighestThreat,
|
|
4.5f,
|
|
1,
|
|
false,
|
|
PatternStepDefinition.CreateSkillStep(comboSlamHit1Skill),
|
|
PatternStepDefinition.CreateSkillStep(comboSlamHit2Skill),
|
|
PatternStepDefinition.CreateWaitStep(0.1f),
|
|
PatternStepDefinition.CreateSkillStep(slamSkill));
|
|
|
|
CreatePattern(
|
|
$"{PatternsFolder}/Data_Pattern_Drog_콤보-발구르기.asset",
|
|
"콤보-발구르기",
|
|
PatternCategory.Basic,
|
|
false,
|
|
true,
|
|
TargetResolveMode.HighestThreat,
|
|
5f,
|
|
1,
|
|
false,
|
|
PatternStepDefinition.CreateSkillStep(comboStompHit1Skill),
|
|
PatternStepDefinition.CreateSkillStep(comboStompHit2Skill),
|
|
PatternStepDefinition.CreateWaitStep(0.1f),
|
|
PatternStepDefinition.CreateSkillStep(stompSkill));
|
|
|
|
CreatePattern(
|
|
$"{PatternsFolder}/Data_Pattern_Drog_밟기.asset",
|
|
"밟기",
|
|
PatternCategory.Punish,
|
|
false,
|
|
false,
|
|
TargetResolveMode.HighestThreat,
|
|
2.5f,
|
|
1,
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 폴더가 없으면 생성합니다.
|
|
/// </summary>
|
|
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));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 이전 단일 스킬 기반 콤보 자산을 제거합니다.
|
|
/// </summary>
|
|
private static void DeleteLegacyComboAssets()
|
|
{
|
|
string[] legacyPaths =
|
|
{
|
|
$"{AnimationsFolder}/Anim_Drog_콤보-기본기1_0.anim",
|
|
$"{AnimationsFolder}/Anim_Drog_콤보-기본기2_0.anim",
|
|
$"{AnimationsFolder}/Anim_Drog_콤보-기본기3_0.anim",
|
|
$"{AnimationsFolder}/Anim_Drog_콤보-발구르기_선행_0.anim",
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기1.asset",
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기2.asset",
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-기본기3.asset",
|
|
$"{SkillsFolder}/Data_Skill_Drog_콤보-발구르기_선행.asset",
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기1_0_데미지.asset",
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기2_0_데미지.asset",
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-기본기3_0_데미지.asset",
|
|
$"{EffectsFolder}/Data_SkillEffect_Drog_콤보-발구르기_선행_0_데미지.asset",
|
|
};
|
|
|
|
for (int i = 0; i < legacyPaths.Length; i++)
|
|
{
|
|
if (AssetDatabase.LoadMainAssetAtPath(legacyPaths[i]) == null)
|
|
continue;
|
|
|
|
AssetDatabase.DeleteAsset(legacyPaths[i]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정 경로의 ScriptableObject를 읽거나 새로 생성합니다.
|
|
/// </summary>
|
|
private static T LoadOrCreateAsset<T>(string path) where T : ScriptableObject
|
|
{
|
|
T asset = AssetDatabase.LoadAssetAtPath<T>(path);
|
|
if (asset != null)
|
|
return asset;
|
|
|
|
asset = ScriptableObject.CreateInstance<T>();
|
|
asset.name = Path.GetFileNameWithoutExtension(path);
|
|
AssetDatabase.CreateAsset(asset, path);
|
|
return asset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드로그 스킬 플레이스홀더용 빈 애니메이션 클립을 보장합니다.
|
|
/// </summary>
|
|
private static AnimationClip EnsurePlaceholderClip(string path)
|
|
{
|
|
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
|
|
if (clip != null)
|
|
return clip;
|
|
|
|
clip = new AnimationClip
|
|
{
|
|
name = Path.GetFileNameWithoutExtension(path),
|
|
frameRate = 60f,
|
|
};
|
|
|
|
AssetDatabase.CreateAsset(clip, path);
|
|
return clip;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 소스 클립을 독립 .anim 자산으로 복제하거나 기존 자산을 갱신합니다.
|
|
/// </summary>
|
|
private static AnimationClip EnsureClipFromSource(string targetPath, string sourcePath, string sourceClipName = null)
|
|
{
|
|
AnimationClip sourceClip = LoadClipFromPath(sourcePath, sourceClipName);
|
|
if (sourceClip == null)
|
|
{
|
|
Debug.LogWarning($"[DrogCombatAssets] 소스 클립을 찾지 못해 플레이스홀더를 유지합니다: {sourcePath} ({sourceClipName})");
|
|
return EnsurePlaceholderClip(targetPath);
|
|
}
|
|
|
|
AnimationClip clonedClip = CloneClip(sourceClip, Path.GetFileNameWithoutExtension(targetPath));
|
|
AnimationClip existingClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(targetPath);
|
|
if (existingClip == null)
|
|
{
|
|
AssetDatabase.CreateAsset(clonedClip, targetPath);
|
|
return clonedClip;
|
|
}
|
|
|
|
EditorUtility.CopySerialized(clonedClip, existingClip);
|
|
existingClip.name = Path.GetFileNameWithoutExtension(targetPath);
|
|
EditorUtility.SetDirty(existingClip);
|
|
UnityEngine.Object.DestroyImmediate(clonedClip);
|
|
return existingClip;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 경로에서 AnimationClip을 읽습니다. FBX인 경우 지정 이름의 서브 클립을 우선 사용합니다.
|
|
/// </summary>
|
|
private static AnimationClip LoadClipFromPath(string path, string preferredClipName = null)
|
|
{
|
|
AnimationClip directClip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
|
|
if (directClip != null && (string.IsNullOrEmpty(preferredClipName) || directClip.name == preferredClipName))
|
|
return directClip;
|
|
|
|
UnityEngine.Object[] subAssets = AssetDatabase.LoadAllAssetsAtPath(path);
|
|
AnimationClip fallbackClip = null;
|
|
|
|
for (int i = 0; i < subAssets.Length; i++)
|
|
{
|
|
if (subAssets[i] is not AnimationClip clip)
|
|
continue;
|
|
|
|
if (!string.IsNullOrEmpty(preferredClipName) && clip.name == preferredClipName)
|
|
return clip;
|
|
|
|
if (fallbackClip == null && !clip.name.StartsWith("__preview__", StringComparison.Ordinal))
|
|
fallbackClip = clip;
|
|
}
|
|
|
|
return fallbackClip;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 소스 클립의 커브와 이벤트를 복사한 독립 AnimationClip을 생성합니다.
|
|
/// </summary>
|
|
private static AnimationClip CloneClip(AnimationClip sourceClip, string targetName)
|
|
{
|
|
AnimationClip clonedClip = new AnimationClip
|
|
{
|
|
name = targetName,
|
|
frameRate = sourceClip.frameRate,
|
|
legacy = sourceClip.legacy,
|
|
wrapMode = sourceClip.wrapMode,
|
|
localBounds = sourceClip.localBounds,
|
|
};
|
|
|
|
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(sourceClip);
|
|
for (int i = 0; i < bindings.Length; i++)
|
|
{
|
|
AnimationCurve curve = AnimationUtility.GetEditorCurve(sourceClip, bindings[i]);
|
|
if (curve == null || curve.keys.Length == 0)
|
|
continue;
|
|
|
|
clonedClip.SetCurve(bindings[i].path, bindings[i].type, bindings[i].propertyName, curve);
|
|
}
|
|
|
|
AnimationEvent[] sourceEvents = AnimationUtility.GetAnimationEvents(sourceClip);
|
|
if (sourceEvents.Length > 0)
|
|
{
|
|
var clonedEvents = new AnimationEvent[sourceEvents.Length];
|
|
for (int i = 0; i < sourceEvents.Length; i++)
|
|
{
|
|
clonedEvents[i] = new AnimationEvent
|
|
{
|
|
time = sourceEvents[i].time,
|
|
functionName = sourceEvents[i].functionName,
|
|
floatParameter = sourceEvents[i].floatParameter,
|
|
intParameter = sourceEvents[i].intParameter,
|
|
stringParameter = sourceEvents[i].stringParameter,
|
|
objectReferenceParameter = sourceEvents[i].objectReferenceParameter,
|
|
messageOptions = sourceEvents[i].messageOptions,
|
|
};
|
|
}
|
|
|
|
AnimationUtility.SetAnimationEvents(clonedClip, clonedEvents);
|
|
}
|
|
|
|
return clonedClip;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Effect/Skill/Pattern의 공통 Object 리스트를 설정합니다.
|
|
/// </summary>
|
|
private static void SetObjectList(SerializedObject serializedObject, string propertyName, IReadOnlyList<UnityEngine.Object> 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];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 지정한 클립에 단일 OnEffect(0) 이벤트를 설정합니다. 음수 시간이면 이벤트를 비웁니다.
|
|
/// </summary>
|
|
private static void SetSingleOnEffectEvent(AnimationClip clip, float time)
|
|
{
|
|
if (clip == null)
|
|
return;
|
|
|
|
if (time < 0f)
|
|
{
|
|
AnimationUtility.SetAnimationEvents(clip, Array.Empty<AnimationEvent>());
|
|
EditorUtility.SetDirty(clip);
|
|
return;
|
|
}
|
|
|
|
float clampedTime = Mathf.Clamp(time, 0f, Mathf.Max(0f, clip.length - 0.01f));
|
|
AnimationUtility.SetAnimationEvents(clip, new[]
|
|
{
|
|
new AnimationEvent
|
|
{
|
|
time = clampedTime,
|
|
functionName = "OnEffect",
|
|
intParameter = 0,
|
|
},
|
|
});
|
|
|
|
EditorUtility.SetDirty(clip);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 범위형 효과의 공통 판정 설정을 적용합니다.
|
|
/// </summary>
|
|
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("targetLayers").intValue = Physics.AllLayers;
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 범위 피해 효과를 생성하거나 갱신합니다.
|
|
/// </summary>
|
|
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<DamageEffect>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 범위 다운 효과를 생성하거나 갱신합니다.
|
|
/// </summary>
|
|
private static DownEffect CreateDownEffect(
|
|
string path,
|
|
float duration,
|
|
AreaShapeType areaShape,
|
|
float areaRadius,
|
|
float fanOriginDistance,
|
|
float fanRadius,
|
|
float fanHalfAngle,
|
|
AreaCenterType areaCenter)
|
|
{
|
|
DownEffect effect = LoadOrCreateAsset<DownEffect>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 범위 넉백 효과를 생성하거나 갱신합니다.
|
|
/// </summary>
|
|
private static KnockbackEffect CreateKnockbackEffect(
|
|
string path,
|
|
float force,
|
|
float upwardForce,
|
|
float duration,
|
|
bool playHitAnimation,
|
|
float hitAnimationSpeedMultiplier,
|
|
AreaShapeType areaShape,
|
|
float areaRadius,
|
|
float fanOriginDistance,
|
|
float fanRadius,
|
|
float fanHalfAngle,
|
|
AreaCenterType areaCenter)
|
|
{
|
|
KnockbackEffect effect = LoadOrCreateAsset<KnockbackEffect>(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.FindProperty("playHitAnimation").boolValue = playHitAnimation;
|
|
serializedObject.FindProperty("hitAnimationSpeedMultiplier").floatValue = hitAnimationSpeedMultiplier;
|
|
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
|
|
|
EditorUtility.SetDirty(effect);
|
|
return effect;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 범위 경직 효과를 생성하거나 갱신합니다.
|
|
/// </summary>
|
|
private static StaggerEffect CreateStaggerEffect(
|
|
string path,
|
|
float duration,
|
|
bool playHitAnimation,
|
|
float hitAnimationSpeedMultiplier,
|
|
AreaShapeType areaShape,
|
|
float areaRadius,
|
|
float fanOriginDistance,
|
|
float fanRadius,
|
|
float fanHalfAngle,
|
|
AreaCenterType areaCenter)
|
|
{
|
|
StaggerEffect effect = LoadOrCreateAsset<StaggerEffect>(path);
|
|
SerializedObject serializedObject = new SerializedObject(effect);
|
|
|
|
ConfigureAreaEffect(serializedObject, areaShape, areaRadius, fanOriginDistance, fanRadius, fanHalfAngle, areaCenter);
|
|
serializedObject.FindProperty("duration").floatValue = duration;
|
|
serializedObject.FindProperty("playHitAnimation").boolValue = playHitAnimation;
|
|
serializedObject.FindProperty("hitAnimationSpeedMultiplier").floatValue = hitAnimationSpeedMultiplier;
|
|
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
|
|
|
EditorUtility.SetDirty(effect);
|
|
return effect;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 다운 대상 추가 피해 효과를 생성하거나 갱신합니다.
|
|
/// </summary>
|
|
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<HitReactionDamageEffect>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 빈 애니메이션 상태에서도 즉시 발동 가능한 드로그 스킬 플레이스홀더를 생성하거나 갱신합니다.
|
|
/// </summary>
|
|
private static SkillData CreateSkill(
|
|
string path,
|
|
string skillName,
|
|
string description,
|
|
IReadOnlyList<AnimationClip> clips,
|
|
float animationSpeed,
|
|
SkillCastTargetTrackingMode trackingMode,
|
|
bool useRootMotion,
|
|
bool ignoreRootMotionY,
|
|
bool jumpToTarget,
|
|
params SkillEffect[] castStartEffects)
|
|
{
|
|
SkillData skill = LoadOrCreateAsset<SkillData>(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("castStartEffects").arraySize = 0;
|
|
ConfigureTriggeredEffects(serializedObject, castStartEffects);
|
|
|
|
var clipObjects = new List<UnityEngine.Object>();
|
|
if (clips != null)
|
|
{
|
|
for (int i = 0; i < clips.Count; i++)
|
|
{
|
|
if (clips[i] != null)
|
|
clipObjects.Add(clips[i]);
|
|
}
|
|
}
|
|
|
|
SetObjectList(serializedObject, "animationClips", clipObjects);
|
|
|
|
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
|
skill.RefreshAnimationClips();
|
|
|
|
EditorUtility.SetDirty(skill);
|
|
return skill;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 전달된 효과를 모두 Trigger Index 0의 애니메이션 이벤트 효과로 기록합니다.
|
|
/// </summary>
|
|
private static void ConfigureTriggeredEffects(SerializedObject serializedObject, IReadOnlyList<SkillEffect> effects)
|
|
{
|
|
SerializedProperty triggeredEffectsProperty = serializedObject.FindProperty("triggeredEffects");
|
|
if (triggeredEffectsProperty == null)
|
|
return;
|
|
|
|
var validEffects = new List<SkillEffect>();
|
|
if (effects != null)
|
|
{
|
|
for (int i = 0; i < effects.Count; i++)
|
|
{
|
|
if (effects[i] != null)
|
|
validEffects.Add(effects[i]);
|
|
}
|
|
}
|
|
|
|
triggeredEffectsProperty.arraySize = validEffects.Count > 0 ? 1 : 0;
|
|
if (triggeredEffectsProperty.arraySize == 0)
|
|
return;
|
|
|
|
SerializedProperty entryProperty = triggeredEffectsProperty.GetArrayElementAtIndex(0);
|
|
entryProperty.FindPropertyRelative("triggerIndex").intValue = 0;
|
|
SerializedProperty effectsProperty = entryProperty.FindPropertyRelative("effects");
|
|
effectsProperty.arraySize = validEffects.Count;
|
|
|
|
for (int i = 0; i < validEffects.Count; i++)
|
|
{
|
|
effectsProperty.GetArrayElementAtIndex(i).objectReferenceValue = validEffects[i];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드로그 보스 패턴 자산을 생성하거나 갱신합니다.
|
|
/// </summary>
|
|
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<BossPatternData>(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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 단일 패턴 스텝 데이터를 SerializedProperty에 기록합니다.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 패턴 스텝 정의를 간단히 구성하기 위한 헬퍼입니다.
|
|
/// </summary>
|
|
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,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|