feat: 드로그 BT 및 전투 패턴 재구성
- 드로그 BT를 페이즈 전환, 부활 트리거, 가중치 근접 패턴 중심으로 재구성 - 땅 울리기 및 콤보-기본기1_3 패턴/스킬/이펙트를 추가하고 기존 평타 파생 자산을 정리 - 드로그 행동 검증용 PlayMode/Editor 테스트와 관련 런타임 상태 추적을 추가
This commit is contained in:
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
using Colosseum.AI;
|
||||
using Colosseum.AI.BehaviorActions.Actions;
|
||||
using Colosseum.AI.BehaviorActions.Conditions;
|
||||
using Colosseum.Enemy;
|
||||
using Colosseum.Skills;
|
||||
@@ -23,10 +24,12 @@ namespace Colosseum.Editor
|
||||
private const string GraphAssetPath = "Assets/_Game/AI/BT_Drog.asset";
|
||||
private const string DefaultPunishPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_밟기.asset";
|
||||
private const string DefaultSignaturePatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_집행.asset";
|
||||
private const string DefaultGroundShakePatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_땅 울리기.asset";
|
||||
private const string DefaultMobilityPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_도약.asset";
|
||||
private const string DefaultSecondaryPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_콤보-기본기2.asset";
|
||||
private const string DefaultComboPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_콤보-강타.asset";
|
||||
private const string DefaultPrimaryPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_콤보-기본기1.asset";
|
||||
private const string DefaultSecondaryPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_콤보-기본기2.asset";
|
||||
private const string DefaultTertiaryPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_콤보-기본기3.asset";
|
||||
private const string DefaultComboPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_콤보-강타.asset";
|
||||
private const string DefaultPressurePatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_콤보-발구르기.asset";
|
||||
private const string DefaultUtilityPatternPath = "Assets/_Game/Data/Patterns/Data_Pattern_Drog_투척.asset";
|
||||
private const string DefaultPhase3TransitionSkillPath = "Assets/_Game/Data/Skills/Data_Skill_Drog_포효.asset";
|
||||
@@ -38,9 +41,15 @@ namespace Colosseum.Editor
|
||||
private const float DefaultTargetSearchRange = 20f;
|
||||
private const float DefaultThrowAvailabilityDelay = 4f;
|
||||
private const float DefaultPhaseTransitionLockDuration = 1.25f;
|
||||
private const float DefaultPhase3SignatureDelay = 0.25f;
|
||||
private const float DefaultSignatureRepeatInterval = 90f;
|
||||
private const float DefaultGroundShakeInterval = 12f;
|
||||
private const float DefaultPhase2EnterHealthPercent = 75f;
|
||||
private const float DefaultPhase3EnterHealthPercent = 40f;
|
||||
private const float DefaultComboPatternWeight = 26f;
|
||||
private const float DefaultPressurePatternWeight = 24f;
|
||||
private const float DefaultPrimaryPatternWeight = 22f;
|
||||
private const float DefaultSecondaryPatternWeight = 16f;
|
||||
private const float DefaultTertiaryPatternWeight = 12f;
|
||||
|
||||
[MenuItem("Tools/Colosseum/Rebuild Drog Behavior Authoring Graph")]
|
||||
private static void Rebuild()
|
||||
@@ -216,6 +225,7 @@ namespace Colosseum.Editor
|
||||
"MobilityTriggerDistance",
|
||||
"UtilityTriggerDistance",
|
||||
"PrimaryAttackRange",
|
||||
"SelectedMeleePattern",
|
||||
"Phase2HealthPercent",
|
||||
"Phase3HealthPercent",
|
||||
"SightRange",
|
||||
@@ -223,130 +233,352 @@ namespace Colosseum.Editor
|
||||
"MoveSpeed");
|
||||
|
||||
BossPatternData punishPattern = LoadRequiredAsset<BossPatternData>(DefaultPunishPatternPath, "밟기 패턴");
|
||||
BossPatternData signaturePattern = LoadRequiredAsset<BossPatternData>(DefaultSignaturePatternPath, "집행 패턴");
|
||||
BossPatternData groundShakePattern = LoadRequiredAsset<BossPatternData>(DefaultGroundShakePatternPath, "땅 울리기 패턴");
|
||||
BossPatternData mobilityPattern = LoadRequiredAsset<BossPatternData>(DefaultMobilityPatternPath, "도약 패턴");
|
||||
BossPatternData primaryPattern = LoadRequiredAsset<BossPatternData>(DefaultPrimaryPatternPath, "콤보-기본기1 패턴");
|
||||
BossPatternData secondaryPattern = LoadRequiredAsset<BossPatternData>(DefaultSecondaryPatternPath, "콤보-기본기2 패턴");
|
||||
BossPatternData tertiaryPattern = LoadRequiredAsset<BossPatternData>(DefaultTertiaryPatternPath, "콤보-기본기3 패턴");
|
||||
BossPatternData comboPattern = LoadRequiredAsset<BossPatternData>(DefaultComboPatternPath, "콤보-강타 패턴");
|
||||
BossPatternData pressurePattern = LoadRequiredAsset<BossPatternData>(DefaultPressurePatternPath, "콤보-발구르기 패턴");
|
||||
BossPatternData utilityPattern = LoadRequiredAsset<BossPatternData>(DefaultUtilityPatternPath, "투척 패턴");
|
||||
SkillData phase3TransitionSkill = LoadRequiredAsset<SkillData>(DefaultPhase3TransitionSkillPath, "포효 스킬");
|
||||
|
||||
if (punishPattern == null || comboPattern == null)
|
||||
if (punishPattern == null
|
||||
|| signaturePattern == null
|
||||
|| groundShakePattern == null
|
||||
|| mobilityPattern == null
|
||||
|| primaryPattern == null
|
||||
|| secondaryPattern == null
|
||||
|| tertiaryPattern == null
|
||||
|| comboPattern == null
|
||||
|| pressurePattern == null
|
||||
|| utilityPattern == null
|
||||
|| phase3TransitionSkill == null)
|
||||
{
|
||||
Debug.LogError("[DrogBTRebuild] 프리팹에서 필수 패턴 에셋을 읽지 못했습니다.");
|
||||
Debug.LogError("[DrogBTRebuild] 드로그 BT 재구성에 필요한 패턴/스킬 에셋을 읽지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// ── 단순 우선순위 체인 ──
|
||||
// 요구사항: 밟기 > 강타 계열 > 추적
|
||||
// 다운 대상이 근처에 있으면 밟기를 우선 사용하고, 그렇지 않으면 강타 계열 패턴만 반복합니다.
|
||||
// 사거리 밖에서는 추적으로 재진입합니다.
|
||||
|
||||
const float branchX = -800f;
|
||||
const float rootRefreshX = branchX - 540f;
|
||||
const float mainSequenceX = branchX + 340f;
|
||||
const float mainValidateX = branchX + 700f;
|
||||
const float mainUseX = branchX + 1100f;
|
||||
const float branchX = -900f;
|
||||
const float rootRefreshX = branchX - 500f;
|
||||
const float sequenceX = branchX + 360f;
|
||||
const float actionX1 = sequenceX + 360f;
|
||||
const float actionX2 = actionX1 + 320f;
|
||||
const float actionX3 = actionX2 + 320f;
|
||||
const float actionX4 = actionX3 + 320f;
|
||||
const float actionX5 = actionX4 + 320f;
|
||||
const float actionX6 = actionX5 + 320f;
|
||||
const float truePortOffsetX = 203f;
|
||||
const float truePortOffsetY = 120f;
|
||||
const float falsePortOffsetX = -211f;
|
||||
const float falsePortOffsetY = 124f;
|
||||
const float startY = -700f;
|
||||
const float rootRefreshY = startY - 120f;
|
||||
const float stepY = 620f;
|
||||
const float startY = -1320f;
|
||||
const float stepY = 360f;
|
||||
|
||||
// 루프 시작마다 주 대상을 블랙보드에 동기화한 뒤 패턴 우선순위 체인으로 들어갑니다.
|
||||
object rootRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(rootRefreshX, rootRefreshY));
|
||||
object phase2TransitionBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY));
|
||||
AttachConditionWithValue(phase2TransitionBranch, typeof(IsCurrentPhaseCondition), "Phase", 1, authoringAssembly);
|
||||
AttachConditionWithValue(phase2TransitionBranch, typeof(IsHealthBelowCondition), "HealthPercent", DefaultPhase2EnterHealthPercent, authoringAssembly);
|
||||
SetBranchRequiresAll(phase2TransitionBranch, true);
|
||||
|
||||
object phase2TransitionSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(sequenceX, startY));
|
||||
object phase2TransitionWaitNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(WaitAction), new Vector2(actionX1, startY));
|
||||
SetNodeFieldValue(phase2TransitionWaitNode, "Duration", DefaultPhaseTransitionLockDuration, setFieldValueMethod);
|
||||
object phase2SetPhaseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SetBossPhaseAction), new Vector2(actionX2, startY));
|
||||
SetNodeFieldValue(phase2SetPhaseNode, "TargetPhase", 2, setFieldValueMethod);
|
||||
SetNodeFieldValue(phase2SetPhaseNode, "ResetTimer", true, setFieldValueMethod);
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, phase2TransitionSequence, phase2TransitionWaitNode, phase2SetPhaseNode);
|
||||
|
||||
object phase3TransitionBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY + stepY));
|
||||
AttachConditionWithValue(phase3TransitionBranch, typeof(IsCurrentPhaseCondition), "Phase", 2, authoringAssembly);
|
||||
AttachConditionWithValue(phase3TransitionBranch, typeof(IsHealthBelowCondition), "HealthPercent", DefaultPhase3EnterHealthPercent, authoringAssembly);
|
||||
SetBranchRequiresAll(phase3TransitionBranch, true);
|
||||
|
||||
object phase3TransitionSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(sequenceX, startY + stepY));
|
||||
object phase3RoarNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UseSkillAction), new Vector2(actionX1, startY + stepY));
|
||||
SetNodeFieldValue(phase3RoarNode, "스킬", phase3TransitionSkill, setFieldValueMethod);
|
||||
object phase3TransitionWaitNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(WaitAction), new Vector2(actionX2, startY + stepY));
|
||||
SetNodeFieldValue(phase3TransitionWaitNode, "Duration", DefaultPhaseTransitionLockDuration, setFieldValueMethod);
|
||||
object phase3SetPhaseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SetBossPhaseAction), new Vector2(actionX3, startY + stepY));
|
||||
SetNodeFieldValue(phase3SetPhaseNode, "TargetPhase", 3, setFieldValueMethod);
|
||||
SetNodeFieldValue(phase3SetPhaseNode, "ResetTimer", true, setFieldValueMethod);
|
||||
object phase3RefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(actionX4, startY + stepY));
|
||||
SetNodeFieldValue(phase3RefreshNode, "SearchRange", DefaultTargetSearchRange, setFieldValueMethod);
|
||||
object phase3ValidateNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(actionX5, startY + stepY));
|
||||
object phase3UseSignatureNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePatternByRoleAction), new Vector2(actionX6, startY + stepY));
|
||||
SetNodeFieldValue(phase3UseSignatureNode, "Pattern", signaturePattern, setFieldValueMethod);
|
||||
SetNodeFieldValue(phase3UseSignatureNode, "ContinueOnResolvedFailure", true, setFieldValueMethod);
|
||||
object phase3SignatureResultBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(actionX6 + 420f, startY + stepY));
|
||||
AttachConditionWithValue(phase3SignatureResultBranch, typeof(IsPatternExecutionResultCondition), "Result", BossPatternExecutionResult.Failed, authoringAssembly);
|
||||
SetBranchRequiresAll(phase3SignatureResultBranch, true);
|
||||
object phase3SignatureStaggerNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(BossStaggerAction), new Vector2(actionX6 + 820f, startY + stepY - 120f));
|
||||
object phase3SignatureFailureNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SignatureFailureEffectsAction), new Vector2(actionX6 + 820f, startY + stepY + 120f));
|
||||
object phase3SignatureTimerResetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SetBossPhaseAction), new Vector2(actionX6 + 1220f, startY + stepY));
|
||||
SetNodeFieldValue(phase3SignatureTimerResetNode, "TargetPhase", 3, setFieldValueMethod);
|
||||
SetNodeFieldValue(phase3SignatureTimerResetNode, "ResetTimer", true, setFieldValueMethod);
|
||||
ConnectChildren(
|
||||
graphAsset,
|
||||
connectEdgeMethod,
|
||||
phase3TransitionSequence,
|
||||
phase3RoarNode,
|
||||
phase3TransitionWaitNode,
|
||||
phase3SetPhaseNode,
|
||||
phase3RefreshNode,
|
||||
phase3ValidateNode,
|
||||
phase3UseSignatureNode,
|
||||
phase3SignatureResultBranch,
|
||||
phase3SignatureTimerResetNode);
|
||||
|
||||
object rootRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(rootRefreshX, startY + stepY * 2f - 120f));
|
||||
SetNodeFieldValue(rootRefreshNode, "SearchRange", DefaultTargetSearchRange, setFieldValueMethod);
|
||||
|
||||
// #1 Punish — 밟기 (전제 조건: 다운된 대상이 반경 이내에 있어야 함)
|
||||
object downBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY));
|
||||
AttachPatternReadyCondition(downBranch, punishPattern, authoringAssembly);
|
||||
AttachConditionWithValue(downBranch, typeof(IsDownedTargetInRangeCondition), "SearchRadius", DefaultDownedTargetSearchRadius, authoringAssembly);
|
||||
SetBranchRequiresAll(downBranch, true);
|
||||
object punishBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY + stepY * 2f));
|
||||
AttachPatternReadyCondition(punishBranch, punishPattern, authoringAssembly);
|
||||
AttachConditionWithValue(punishBranch, typeof(IsDownedTargetInRangeCondition), "SearchRadius", DefaultDownedTargetSearchRadius, authoringAssembly);
|
||||
SetBranchRequiresAll(punishBranch, true);
|
||||
|
||||
object downSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod,
|
||||
object punishSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(mainSequenceX, startY));
|
||||
object downSelectNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SelectNearestDownedTargetAction), new Vector2(mainValidateX, startY));
|
||||
SetNodeFieldValue(downSelectNode, "SearchRadius", DefaultDownedTargetSearchRadius, setFieldValueMethod);
|
||||
LinkTarget(downSelectNode, targetVariable);
|
||||
object downUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePatternByRoleAction), new Vector2(mainUseX, startY));
|
||||
SetNodeFieldValue(downUseNode, "Pattern", punishPattern, setFieldValueMethod);
|
||||
LinkTarget(downUseNode, targetVariable);
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, downSequence, downSelectNode, downUseNode);
|
||||
new Vector2(sequenceX, startY + stepY * 2f));
|
||||
object punishSelectNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SelectNearestDownedTargetAction), new Vector2(actionX1, startY + stepY * 2f));
|
||||
SetNodeFieldValue(punishSelectNode, "SearchRadius", DefaultDownedTargetSearchRadius, setFieldValueMethod);
|
||||
object punishUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePatternByRoleAction), new Vector2(actionX2, startY + stepY * 2f));
|
||||
SetNodeFieldValue(punishUseNode, "Pattern", punishPattern, setFieldValueMethod);
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, punishSequence, punishSelectNode, punishUseNode);
|
||||
|
||||
// #2 Combo — 강타 계열 기본 루프
|
||||
object comboBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY + stepY));
|
||||
object comboRangeCondModel = AttachCondition(comboBranch, typeof(IsTargetInAttackRangeCondition), authoringAssembly);
|
||||
if (comboRangeCondModel != null)
|
||||
setFieldMethod.Invoke(comboRangeCondModel, new object[] { "Target", targetVariable, typeof(GameObject) });
|
||||
if (comboRangeCondModel != null)
|
||||
SetConditionFieldValue(comboRangeCondModel, "AttackRange", DefaultPrimaryBranchAttackRange);
|
||||
AttachPatternReadyCondition(comboBranch, comboPattern, authoringAssembly);
|
||||
SetBranchRequiresAll(comboBranch, true);
|
||||
object signatureBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY + stepY * 3f));
|
||||
AttachConditionWithValue(signatureBranch, typeof(IsCurrentPhaseCondition), "Phase", 3, authoringAssembly);
|
||||
AttachConditionWithValue(signatureBranch, typeof(IsPhaseElapsedTimeAboveCondition), "Seconds", DefaultSignatureRepeatInterval, authoringAssembly);
|
||||
AttachPatternReadyCondition(signatureBranch, signaturePattern, authoringAssembly);
|
||||
SetBranchRequiresAll(signatureBranch, true);
|
||||
|
||||
object comboSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod,
|
||||
object signatureSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(mainSequenceX, startY + stepY));
|
||||
object comboValidateNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(mainValidateX, startY + stepY));
|
||||
LinkTarget(comboValidateNode, targetVariable);
|
||||
object comboUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePatternByRoleAction), new Vector2(mainUseX, startY + stepY));
|
||||
SetNodeFieldValue(comboUseNode, "Pattern", comboPattern, setFieldValueMethod);
|
||||
LinkTarget(comboUseNode, targetVariable);
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, comboSequence, comboValidateNode, comboUseNode);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, comboBranch, "True", comboSequence);
|
||||
new Vector2(sequenceX, startY + stepY * 3f));
|
||||
object signatureRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(actionX1, startY + stepY * 3f));
|
||||
SetNodeFieldValue(signatureRefreshNode, "SearchRange", DefaultTargetSearchRange, setFieldValueMethod);
|
||||
object signatureValidateNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(actionX2, startY + stepY * 3f));
|
||||
object signatureUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePatternByRoleAction), new Vector2(actionX3, startY + stepY * 3f));
|
||||
SetNodeFieldValue(signatureUseNode, "Pattern", signaturePattern, setFieldValueMethod);
|
||||
SetNodeFieldValue(signatureUseNode, "ContinueOnResolvedFailure", true, setFieldValueMethod);
|
||||
object signatureResultBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(actionX4, startY + stepY * 3f));
|
||||
AttachConditionWithValue(signatureResultBranch, typeof(IsPatternExecutionResultCondition), "Result", BossPatternExecutionResult.Failed, authoringAssembly);
|
||||
SetBranchRequiresAll(signatureResultBranch, true);
|
||||
object signatureStaggerNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(BossStaggerAction), new Vector2(actionX4 + 420f, startY + stepY * 3f - 120f));
|
||||
object signatureFailureNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SignatureFailureEffectsAction), new Vector2(actionX4 + 420f, startY + stepY * 3f + 120f));
|
||||
object signatureTimerResetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SetBossPhaseAction), new Vector2(actionX5, startY + stepY * 3f));
|
||||
SetNodeFieldValue(signatureTimerResetNode, "TargetPhase", 3, setFieldValueMethod);
|
||||
SetNodeFieldValue(signatureTimerResetNode, "ResetTimer", true, setFieldValueMethod);
|
||||
ConnectChildren(
|
||||
graphAsset,
|
||||
connectEdgeMethod,
|
||||
signatureSequence,
|
||||
signatureRefreshNode,
|
||||
signatureValidateNode,
|
||||
signatureUseNode,
|
||||
signatureResultBranch,
|
||||
signatureTimerResetNode);
|
||||
|
||||
// #3 Chase — fallback
|
||||
object chaseSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true), new Vector2(branchX, startY + stepY * 2));
|
||||
object chaseRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(branchX + 160f, startY + stepY * 2 + 80f));
|
||||
object throwBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY + stepY * 4f));
|
||||
AttachPatternReadyCondition(throwBranch, utilityPattern, authoringAssembly);
|
||||
AttachConditionWithValue(throwBranch, typeof(IsRecentReviveTriggerCondition), "MaxAge", DefaultThrowAvailabilityDelay, authoringAssembly);
|
||||
SetBranchRequiresAll(throwBranch, true);
|
||||
|
||||
object throwSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(sequenceX, startY + stepY * 4f));
|
||||
object throwSelectNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SelectRecentReviveTargetAction), new Vector2(actionX1, startY + stepY * 4f));
|
||||
SetNodeFieldValue(throwSelectNode, "MaxAge", DefaultThrowAvailabilityDelay, setFieldValueMethod);
|
||||
SetNodeFieldValue(throwSelectNode, "PreferCaster", true, setFieldValueMethod);
|
||||
SetNodeFieldValue(throwSelectNode, "FallbackToRevivedTarget", true, setFieldValueMethod);
|
||||
object throwUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePatternByRoleAction), new Vector2(actionX2, startY + stepY * 4f));
|
||||
SetNodeFieldValue(throwUseNode, "Pattern", utilityPattern, setFieldValueMethod);
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, throwSequence, throwSelectNode, throwUseNode);
|
||||
|
||||
object leapBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY + stepY * 5f));
|
||||
AttachPatternReadyCondition(leapBranch, mobilityPattern, authoringAssembly);
|
||||
AttachConditionWithValue(leapBranch, typeof(IsTargetBeyondDistanceCondition), "MinDistance", DefaultLeapTargetMinDistance, authoringAssembly);
|
||||
SetBranchRequiresAll(leapBranch, true);
|
||||
|
||||
object leapSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(sequenceX, startY + stepY * 5f));
|
||||
object leapSelectNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SelectTargetByDistanceAction), new Vector2(actionX1, startY + stepY * 5f));
|
||||
SetNodeFieldValue(leapSelectNode, "MinRange", DefaultLeapTargetMinDistance, setFieldValueMethod);
|
||||
SetNodeFieldValue(leapSelectNode, "MaxRange", DefaultTargetSearchRange, setFieldValueMethod);
|
||||
SetNodeFieldValue(leapSelectNode, "SelectionMode", DistanceTargetSelectionMode.Farthest, setFieldValueMethod);
|
||||
object leapValidateNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(actionX2, startY + stepY * 5f));
|
||||
object leapUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePatternByRoleAction), new Vector2(actionX3, startY + stepY * 5f));
|
||||
SetNodeFieldValue(leapUseNode, "Pattern", mobilityPattern, setFieldValueMethod);
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, leapSequence, leapSelectNode, leapValidateNode, leapUseNode);
|
||||
|
||||
object groundShakeBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY + stepY * 6f));
|
||||
AttachConditionWithValue(groundShakeBranch, typeof(IsCurrentPhaseCondition), "Phase", 2, authoringAssembly);
|
||||
AttachConditionWithValue(groundShakeBranch, typeof(IsPhaseElapsedTimeAboveCondition), "Seconds", DefaultGroundShakeInterval, authoringAssembly);
|
||||
AttachPatternReadyCondition(groundShakeBranch, groundShakePattern, authoringAssembly);
|
||||
SetBranchRequiresAll(groundShakeBranch, true);
|
||||
|
||||
object groundShakeSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(sequenceX, startY + stepY * 6f));
|
||||
object groundShakeRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(actionX1, startY + stepY * 6f));
|
||||
SetNodeFieldValue(groundShakeRefreshNode, "SearchRange", DefaultTargetSearchRange, setFieldValueMethod);
|
||||
object groundShakeValidateNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(actionX2, startY + stepY * 6f));
|
||||
object groundShakeUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePatternByRoleAction), new Vector2(actionX3, startY + stepY * 6f));
|
||||
SetNodeFieldValue(groundShakeUseNode, "Pattern", groundShakePattern, setFieldValueMethod);
|
||||
object groundShakeTimerResetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SetBossPhaseAction), new Vector2(actionX4, startY + stepY * 6f));
|
||||
SetNodeFieldValue(groundShakeTimerResetNode, "TargetPhase", 2, setFieldValueMethod);
|
||||
SetNodeFieldValue(groundShakeTimerResetNode, "ResetTimer", true, setFieldValueMethod);
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, groundShakeSequence, groundShakeRefreshNode, groundShakeValidateNode, groundShakeUseNode, groundShakeTimerResetNode);
|
||||
|
||||
object meleeBranch = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, branchCompositeType, new Vector2(branchX, startY + stepY * 7f));
|
||||
object meleeRangeCondModel = AttachCondition(meleeBranch, typeof(IsTargetInAttackRangeCondition), authoringAssembly);
|
||||
if (meleeRangeCondModel != null)
|
||||
setFieldMethod.Invoke(meleeRangeCondModel, new object[] { "Target", targetVariable, typeof(GameObject) });
|
||||
if (meleeRangeCondModel != null)
|
||||
SetConditionFieldValue(meleeRangeCondModel, "AttackRange", DefaultPrimaryBranchAttackRange);
|
||||
object meleeReadyCondModel = AttachCondition(meleeBranch, typeof(HasAnyReadyPatternCondition), authoringAssembly);
|
||||
SetWeightedPatternFields(meleeReadyCondModel, setFieldMethod, comboPattern, DefaultComboPatternWeight, pressurePattern, DefaultPressurePatternWeight, primaryPattern, DefaultPrimaryPatternWeight, secondaryPattern, DefaultSecondaryPatternWeight, tertiaryPattern, DefaultTertiaryPatternWeight);
|
||||
SetBranchRequiresAll(meleeBranch, true);
|
||||
|
||||
object meleeSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(sequenceX, startY + stepY * 7f));
|
||||
object meleeValidateNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(actionX1, startY + stepY * 7f));
|
||||
object meleeUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UseWeightedReadyPatternAction), new Vector2(actionX2, startY + stepY * 7f));
|
||||
SetWeightedPatternFields(meleeUseNode, setFieldValueMethod, comboPattern, DefaultComboPatternWeight, pressurePattern, DefaultPressurePatternWeight, primaryPattern, DefaultPrimaryPatternWeight, secondaryPattern, DefaultSecondaryPatternWeight, tertiaryPattern, DefaultTertiaryPatternWeight);
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, meleeSequence, meleeValidateNode, meleeUseNode);
|
||||
|
||||
object chaseSequence = CreateNode(
|
||||
graphAsset,
|
||||
createNodeMethod,
|
||||
getNodeInfoMethod,
|
||||
runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true),
|
||||
new Vector2(branchX, startY + stepY * 11.5f));
|
||||
object chaseRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(branchX + 160f, startY + stepY * 11.5f + 80f));
|
||||
SetNodeFieldValue(chaseRefreshNode, "SearchRange", DefaultTargetSearchRange, setFieldValueMethod);
|
||||
object chaseHasTargetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(branchX + 320f, startY + stepY * 2 + 80f));
|
||||
object chaseUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ChaseTargetAction), new Vector2(branchX + 480f, startY + stepY * 2 + 80f));
|
||||
object chaseHasTargetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(branchX + 480f, startY + stepY * 11.5f + 80f));
|
||||
object chaseUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ChaseTargetAction), new Vector2(branchX + 800f, startY + stepY * 11.5f + 80f));
|
||||
SetNodeFieldValue(chaseUseNode, "StopDistance", DefaultPrimaryBranchAttackRange, setFieldValueMethod);
|
||||
|
||||
Connect(graphAsset, connectEdgeMethod, GetDefaultOutputPort(rootRefreshNode), GetDefaultInputPort(downBranch));
|
||||
LinkTarget(rootRefreshNode, targetVariable);
|
||||
LinkTarget(phase3RefreshNode, targetVariable);
|
||||
LinkTarget(phase3ValidateNode, targetVariable);
|
||||
LinkTarget(phase3UseSignatureNode, targetVariable);
|
||||
LinkTarget(punishSelectNode, targetVariable);
|
||||
LinkTarget(punishUseNode, targetVariable);
|
||||
LinkTarget(signatureRefreshNode, targetVariable);
|
||||
LinkTarget(signatureValidateNode, targetVariable);
|
||||
LinkTarget(signatureUseNode, targetVariable);
|
||||
LinkTarget(throwSelectNode, targetVariable);
|
||||
LinkTarget(throwUseNode, targetVariable);
|
||||
LinkTarget(leapSelectNode, targetVariable);
|
||||
LinkTarget(leapValidateNode, targetVariable);
|
||||
LinkTarget(leapUseNode, targetVariable);
|
||||
LinkTarget(groundShakeRefreshNode, targetVariable);
|
||||
LinkTarget(groundShakeValidateNode, targetVariable);
|
||||
LinkTarget(groundShakeUseNode, targetVariable);
|
||||
LinkTarget(meleeValidateNode, targetVariable);
|
||||
LinkTarget(meleeUseNode, targetVariable);
|
||||
LinkTarget(chaseRefreshNode, targetVariable);
|
||||
LinkTarget(chaseHasTargetNode, targetVariable);
|
||||
LinkTarget(chaseUseNode, targetVariable);
|
||||
|
||||
Connect(graphAsset, connectEdgeMethod, GetDefaultOutputPort(rootRefreshNode), GetDefaultInputPort(punishBranch));
|
||||
|
||||
var allBranches = new List<object>
|
||||
{
|
||||
phase2TransitionBranch,
|
||||
phase3TransitionBranch,
|
||||
punishBranch,
|
||||
signatureBranch,
|
||||
throwBranch,
|
||||
leapBranch,
|
||||
groundShakeBranch,
|
||||
meleeBranch,
|
||||
phase3SignatureResultBranch,
|
||||
signatureResultBranch,
|
||||
};
|
||||
|
||||
// ── FloatingPortNodeModel 생성 + 위치 보정 ──
|
||||
// Branch 노드의 NamedPort(True/False)에 대해 FloatingPortNodeModel을 생성합니다.
|
||||
// CreateNodePortsForNode는 기본 위치(Branch + 200px Y)를 사용하므로, 생성 후 사용자 조정 기준 위치로 이동합니다.
|
||||
var allBranches = new List<object>();
|
||||
allBranches.AddRange(new[] { downBranch, comboBranch });
|
||||
foreach (object branch in allBranches)
|
||||
{
|
||||
createNodePortsMethod?.Invoke(graphAsset, new object[] { branch });
|
||||
}
|
||||
|
||||
// FloatingPortNodeModel 위치를 사용자 조정 기준으로 보정
|
||||
foreach (object branch in allBranches)
|
||||
{
|
||||
// Branch의 현재 위치 읽기 (Position은 public 필드)
|
||||
FieldInfo posField = branch.GetType().GetField("Position", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (posField == null) continue;
|
||||
Vector2 branchPos = (Vector2)posField.GetValue(branch);
|
||||
if (posField == null)
|
||||
continue;
|
||||
|
||||
// FloatingPortNodeModel에서 PortName이 "True"/"False"인 것을 찾아 위치 수정
|
||||
Vector2 branchPos = (Vector2)posField.GetValue(branch);
|
||||
SetFloatingPortPosition(graphAsset, branch, "True", branchPos.x + truePortOffsetX, branchPos.y + truePortOffsetY);
|
||||
SetFloatingPortPosition(graphAsset, branch, "False", branchPos.x + falsePortOffsetX, branchPos.y + falsePortOffsetY);
|
||||
}
|
||||
|
||||
// ── 연결 ──
|
||||
|
||||
// Start → Repeater → 주 대상 갱신
|
||||
Connect(graphAsset, connectEdgeMethod, GetDefaultOutputPort(startNode), GetDefaultInputPort(repeatNode));
|
||||
Connect(graphAsset, connectEdgeMethod, GetDefaultOutputPort(repeatNode), GetDefaultInputPort(rootRefreshNode));
|
||||
Connect(graphAsset, connectEdgeMethod, GetDefaultOutputPort(repeatNode), GetDefaultInputPort(phase2TransitionBranch));
|
||||
|
||||
// 각 Branch의 True FloatingPort → Action
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, downBranch, "True", downSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, comboBranch, "True", comboSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, phase2TransitionBranch, "True", phase2TransitionSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, phase2TransitionBranch, "False", phase3TransitionBranch);
|
||||
|
||||
// 각 Branch의 False FloatingPort → 다음 우선순위
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, downBranch, "False", comboBranch);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, comboBranch, "False", chaseSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, phase3TransitionBranch, "True", phase3TransitionSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, phase3TransitionBranch, "False", rootRefreshNode);
|
||||
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, phase3SignatureResultBranch, "True", phase3SignatureStaggerNode);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, phase3SignatureResultBranch, "False", phase3SignatureFailureNode);
|
||||
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, signatureResultBranch, "True", signatureStaggerNode);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, signatureResultBranch, "False", signatureFailureNode);
|
||||
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, punishBranch, "True", punishSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, punishBranch, "False", signatureBranch);
|
||||
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, signatureBranch, "True", signatureSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, signatureBranch, "False", throwBranch);
|
||||
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, throwBranch, "True", throwSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, throwBranch, "False", leapBranch);
|
||||
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, leapBranch, "True", leapSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, leapBranch, "False", groundShakeBranch);
|
||||
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, groundShakeBranch, "True", groundShakeSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, groundShakeBranch, "False", meleeBranch);
|
||||
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, meleeBranch, "True", meleeSequence);
|
||||
ConnectBranch(graphAsset, connectEdgeMethod, meleeBranch, "False", chaseSequence);
|
||||
|
||||
// Chase Sequence 자식 연결
|
||||
ConnectChildren(graphAsset, connectEdgeMethod, chaseSequence, chaseRefreshNode, chaseHasTargetNode, chaseUseNode);
|
||||
|
||||
// Chase/루트 노드 블랙보드 변수 연결
|
||||
LinkTarget(rootRefreshNode, targetVariable);
|
||||
LinkTarget(chaseRefreshNode, targetVariable);
|
||||
LinkTarget(chaseHasTargetNode, targetVariable);
|
||||
LinkTarget(chaseUseNode, targetVariable);
|
||||
|
||||
// 저장
|
||||
SetStartRepeatFlags(startNode, repeat: true, allowMultipleRepeatsPerTick: false);
|
||||
setAssetDirtyMethod.Invoke(graphAsset, new object[] { true });
|
||||
@@ -1101,6 +1333,57 @@ namespace Colosseum.Editor
|
||||
closedMethod.Invoke(condModel, new object[] { "Pattern", pattern });
|
||||
}
|
||||
|
||||
private static void SetWeightedPatternFields(
|
||||
object nodeOrCondition,
|
||||
MethodInfo setFieldMethod,
|
||||
BossPatternData pattern1,
|
||||
float weight1,
|
||||
BossPatternData pattern2,
|
||||
float weight2,
|
||||
BossPatternData pattern3,
|
||||
float weight3,
|
||||
BossPatternData pattern4,
|
||||
float weight4,
|
||||
BossPatternData pattern5,
|
||||
float weight5)
|
||||
{
|
||||
if (nodeOrCondition == null)
|
||||
return;
|
||||
|
||||
MethodInfo genericSetFieldMethod = setFieldMethod;
|
||||
if (genericSetFieldMethod == null || !genericSetFieldMethod.IsGenericMethod)
|
||||
{
|
||||
genericSetFieldMethod = nodeOrCondition.GetType().GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
.FirstOrDefault(method => method.Name == "SetField" && method.IsGenericMethod && method.GetParameters().Length == 2);
|
||||
}
|
||||
|
||||
if (genericSetFieldMethod == null)
|
||||
{
|
||||
Debug.LogError("[DrogBTRebuild] 가중치 패턴 필드 설정용 SetField<T>를 찾지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Pattern1", pattern1);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Weight1", weight1);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Pattern2", pattern2);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Weight2", weight2);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Pattern3", pattern3);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Weight3", weight3);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Pattern4", pattern4);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Weight4", weight4);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Pattern5", pattern5);
|
||||
SetFieldValue(nodeOrCondition, genericSetFieldMethod, "Weight5", weight5);
|
||||
}
|
||||
|
||||
private static void SetFieldValue(object target, MethodInfo genericSetFieldMethod, string fieldName, object value)
|
||||
{
|
||||
if (target == null || genericSetFieldMethod == null || value == null)
|
||||
return;
|
||||
|
||||
MethodInfo closedMethod = genericSetFieldMethod.MakeGenericMethod(value.GetType());
|
||||
closedMethod.Invoke(target, new[] { fieldName, value });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패턴의 MinPhase가 1보다 큰 경우, Branch에 IsMinPhaseSatisfiedCondition을 부착합니다.
|
||||
/// Phase 진입 조건을 BT에서 시각적으로 확인할 수 있습니다.
|
||||
|
||||
@@ -77,10 +77,13 @@ namespace Colosseum.Editor
|
||||
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 groundShakeClip = EnsureClipFromSource($"{AnimationsFolder}/Anim_Drog_땅 울리기_0.anim", $"{AnimationsFolder}/Anim_Drog_강타R_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(stompClip, 0.80f);
|
||||
SetSingleOnEffectEvent(groundShakeClip, 0.95f);
|
||||
SetSingleOnEffectEvent(stepClip, 0.80f);
|
||||
AnimationClip throwClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_투척_0.anim");
|
||||
AnimationClip roarClip = EnsurePlaceholderClip($"{AnimationsFolder}/Anim_Drog_포효_0.anim");
|
||||
@@ -267,6 +270,28 @@ namespace Colosseum.Editor
|
||||
180f,
|
||||
AreaCenterType.Caster);
|
||||
|
||||
DamageEffect groundShakeDamage = CreateDamageEffect(
|
||||
$"{EffectsFolder}/Data_SkillEffect_Drog_땅 울리기_0_데미지.asset",
|
||||
36f,
|
||||
DamageType.Physical,
|
||||
0.95f,
|
||||
AreaShapeType.Sphere,
|
||||
6.5f,
|
||||
1f,
|
||||
6.5f,
|
||||
180f,
|
||||
AreaCenterType.Caster);
|
||||
|
||||
DownEffect groundShakeDown = CreateDownEffect(
|
||||
$"{EffectsFolder}/Data_SkillEffect_Drog_땅 울리기_1_다운.asset",
|
||||
2.2f,
|
||||
AreaShapeType.Sphere,
|
||||
3.4f,
|
||||
1f,
|
||||
3.4f,
|
||||
180f,
|
||||
AreaCenterType.Caster);
|
||||
|
||||
DamageEffect leapLandingDamage = CreateDamageEffect(
|
||||
$"{EffectsFolder}/Data_SkillEffect_Drog_도약_착지_0_데미지.asset",
|
||||
34f,
|
||||
@@ -526,6 +551,19 @@ namespace Colosseum.Editor
|
||||
stompDamage,
|
||||
stompStagger);
|
||||
|
||||
SkillData groundShakeSkill = CreateSkill(
|
||||
$"{SkillsFolder}/Data_Skill_Drog_땅 울리기.asset",
|
||||
"땅 울리기",
|
||||
"Phase 2 중반 압박 전환을 선언하는 광역 내려찍기입니다.",
|
||||
new[] { groundShakeClip },
|
||||
1f,
|
||||
SkillCastTargetTrackingMode.FaceTarget,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
groundShakeDamage,
|
||||
groundShakeDown);
|
||||
|
||||
SkillData leapPrepareSkill = CreateSkill(
|
||||
$"{SkillsFolder}/Data_Skill_Drog_도약_준비.asset",
|
||||
"도약 준비",
|
||||
@@ -722,10 +760,22 @@ namespace Colosseum.Editor
|
||||
false,
|
||||
TargetResolveMode.HighestThreat,
|
||||
2.5f,
|
||||
1,
|
||||
2,
|
||||
false,
|
||||
PatternStepDefinition.CreateSkillStep(stepSkill));
|
||||
|
||||
CreatePattern(
|
||||
$"{PatternsFolder}/Data_Pattern_Drog_땅 울리기.asset",
|
||||
"땅 울리기",
|
||||
PatternCategory.Big,
|
||||
false,
|
||||
false,
|
||||
TargetResolveMode.HighestThreat,
|
||||
30f,
|
||||
2,
|
||||
false,
|
||||
PatternStepDefinition.CreateSkillStep(groundShakeSkill));
|
||||
|
||||
CreatePattern(
|
||||
$"{PatternsFolder}/Data_Pattern_Drog_도약.asset",
|
||||
"도약",
|
||||
@@ -761,7 +811,7 @@ namespace Colosseum.Editor
|
||||
true,
|
||||
false,
|
||||
TargetResolveMode.HighestThreat,
|
||||
45f,
|
||||
90f,
|
||||
3,
|
||||
false,
|
||||
PatternStepDefinition.CreateSkillStep(executionReadySkill),
|
||||
|
||||
Reference in New Issue
Block a user