using System; using System.Collections; using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; namespace Colosseum.Editor { /// /// 드로그 Behavior Graph authoring 자산을 현재 BT 우선순위 구조로 재생성합니다. /// public static class RebuildDrogBehaviorAuthoringGraph { private const string GraphAssetPath = "Assets/_Game/AI/BT_Drog.asset"; [MenuItem("Tools/Colosseum/Rebuild Drog Behavior Authoring Graph")] private static void Rebuild() { UnityEngine.Object graphAsset = AssetDatabase.LoadMainAssetAtPath(GraphAssetPath); if (graphAsset == null) { Debug.LogError($"[DrogBTRebuild] 그래프 자산을 찾을 수 없습니다: {GraphAssetPath}"); return; } try { Type authoringGraphType = graphAsset.GetType(); Assembly authoringAssembly = authoringGraphType.Assembly; Assembly runtimeAssembly = typeof(Unity.Behavior.BehaviorGraph).Assembly; MethodInfo createNodeMethod = authoringGraphType.BaseType?.GetMethod("CreateNode", BindingFlags.Instance | BindingFlags.Public); MethodInfo connectEdgeMethod = authoringGraphType.BaseType?.GetMethod("ConnectEdge", BindingFlags.Instance | BindingFlags.Public); MethodInfo buildRuntimeGraphMethod = authoringGraphType.GetMethod("BuildRuntimeGraph", BindingFlags.Instance | BindingFlags.Public); MethodInfo saveAssetMethod = authoringGraphType.BaseType?.GetMethod("SaveAsset", BindingFlags.Instance | BindingFlags.Public); MethodInfo setAssetDirtyMethod = authoringGraphType.BaseType?.GetMethod("SetAssetDirty", BindingFlags.Instance | BindingFlags.Public); MethodInfo getNodeInfoMethod = authoringAssembly.GetType("Unity.Behavior.NodeRegistry", true) ?.GetMethod("GetInfo", BindingFlags.Static | BindingFlags.NonPublic); if (createNodeMethod == null || connectEdgeMethod == null || buildRuntimeGraphMethod == null || saveAssetMethod == null || setAssetDirtyMethod == null || getNodeInfoMethod == null) { Debug.LogError("[DrogBTRebuild] Behavior Authoring 리플렉션 메서드를 찾지 못했습니다."); return; } SerializedObject serializedObject = new SerializedObject(graphAsset); SerializedProperty nodesProperty = serializedObject.FindProperty("m_Nodes"); if (nodesProperty == null) { Debug.LogError("[DrogBTRebuild] m_Nodes 프로퍼티를 찾지 못했습니다."); return; } nodesProperty.ClearArray(); serializedObject.ApplyModifiedPropertiesWithoutUndo(); object targetVariable = FindBlackboardVariableModel("Target"); if (targetVariable == null) { Debug.LogError("[DrogBTRebuild] Target 블랙보드 변수를 찾지 못했습니다."); return; } object startNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.Start", true), new Vector2(420f, -620f)); object repeatNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.RepeaterModifier", true), new Vector2(420f, -470f)); object selectorNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.SelectorComposite", true), new Vector2(420f, -280f)); object downSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true), new Vector2(-620f, -40f)); object leapSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true), new Vector2(-220f, -40f)); object slamSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true), new Vector2(180f, -40f)); object mainSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true), new Vector2(580f, -40f)); object slamFallbackSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true), new Vector2(980f, -40f)); object chaseSequence = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, runtimeAssembly.GetType("Unity.Behavior.SequenceComposite", true), new Vector2(1380f, -40f)); object downSelectNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SelectNearestDownedTargetAction), new Vector2(-740f, 240f)); object downReadyNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckPunishPatternReadyAction), new Vector2(-620f, 240f)); object downUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePunishPatternAction), new Vector2(-500f, 240f)); object leapSelectNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(SelectTargetByDistanceAction), new Vector2(-340f, 240f)); object leapReadyNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckMobilityPatternReadyAction), new Vector2(-220f, 240f)); object leapUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UseMobilityPatternAction), new Vector2(-100f, 240f)); object slamRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(0f, 240f)); object slamHasTargetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(120f, 240f)); object slamRangeNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckTargetInAttackRangeAction), new Vector2(240f, 240f)); object slamTurnNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckSecondaryPatternTurnAction), new Vector2(360f, 240f)); object slamReadyNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckSecondaryPatternReadyAction), new Vector2(480f, 240f)); object slamUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UseSecondaryPatternAction), new Vector2(600f, 240f)); object mainRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(520f, 240f)); object mainHasTargetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(640f, 240f)); object mainRangeNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckTargetInAttackRangeAction), new Vector2(760f, 240f)); object mainReadyNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckPrimaryPatternReadyAction), new Vector2(880f, 240f)); object mainUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UsePrimaryPatternAction), new Vector2(1000f, 240f)); object fallbackRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(920f, 240f)); object fallbackHasTargetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(1040f, 240f)); object fallbackRangeNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckTargetInAttackRangeAction), new Vector2(1160f, 240f)); object fallbackReadyNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(CheckSecondaryPatternReadyAction), new Vector2(1280f, 240f)); object fallbackUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(UseSecondaryPatternAction), new Vector2(1400f, 240f)); object chaseRefreshNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(RefreshPrimaryTargetAction), new Vector2(1320f, 240f)); object chaseHasTargetNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ValidateTargetAction), new Vector2(1440f, 240f)); object chaseUseNode = CreateNode(graphAsset, createNodeMethod, getNodeInfoMethod, typeof(ChaseTargetAction), new Vector2(1560f, 240f)); Connect(graphAsset, connectEdgeMethod, GetDefaultOutputPort(startNode), GetDefaultInputPort(repeatNode)); Connect(graphAsset, connectEdgeMethod, GetDefaultOutputPort(repeatNode), GetDefaultInputPort(selectorNode)); ConnectChildren(graphAsset, connectEdgeMethod, selectorNode, downSequence, leapSequence, slamSequence, mainSequence, slamFallbackSequence, chaseSequence); ConnectChildren(graphAsset, connectEdgeMethod, downSequence, downSelectNode, downReadyNode, downUseNode); ConnectChildren(graphAsset, connectEdgeMethod, leapSequence, leapSelectNode, leapReadyNode, leapUseNode); ConnectChildren(graphAsset, connectEdgeMethod, slamSequence, slamRefreshNode, slamHasTargetNode, slamRangeNode, slamTurnNode, slamReadyNode, slamUseNode); ConnectChildren(graphAsset, connectEdgeMethod, mainSequence, mainRefreshNode, mainHasTargetNode, mainRangeNode, mainReadyNode, mainUseNode); ConnectChildren(graphAsset, connectEdgeMethod, slamFallbackSequence, fallbackRefreshNode, fallbackHasTargetNode, fallbackRangeNode, fallbackReadyNode, fallbackUseNode); ConnectChildren(graphAsset, connectEdgeMethod, chaseSequence, chaseRefreshNode, chaseHasTargetNode, chaseUseNode); LinkTarget(downSelectNode, targetVariable); LinkTarget(downUseNode, targetVariable); LinkTarget(leapSelectNode, targetVariable); LinkTarget(leapUseNode, targetVariable); LinkTarget(slamRefreshNode, targetVariable); LinkTarget(slamHasTargetNode, targetVariable); LinkTarget(slamRangeNode, targetVariable); LinkTarget(slamUseNode, targetVariable); LinkTarget(mainRefreshNode, targetVariable); LinkTarget(mainHasTargetNode, targetVariable); LinkTarget(mainRangeNode, targetVariable); LinkTarget(mainUseNode, targetVariable); LinkTarget(fallbackRefreshNode, targetVariable); LinkTarget(fallbackHasTargetNode, targetVariable); LinkTarget(fallbackRangeNode, targetVariable); LinkTarget(fallbackUseNode, targetVariable); LinkTarget(chaseRefreshNode, targetVariable); LinkTarget(chaseHasTargetNode, targetVariable); LinkTarget(chaseUseNode, targetVariable); SetStartRepeatFlags(startNode, repeat: true, allowMultipleRepeatsPerTick: false); setAssetDirtyMethod.Invoke(graphAsset, new object[] { true }); buildRuntimeGraphMethod.Invoke(graphAsset, new object[] { true }); saveAssetMethod.Invoke(graphAsset, null); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("[DrogBTRebuild] 드로그 Behavior Graph authoring 자산 재구성이 완료되었습니다."); } catch (Exception exception) { Debug.LogException(exception); } } private static object CreateNode(UnityEngine.Object graphAsset, MethodInfo createNodeMethod, MethodInfo getNodeInfoMethod, Type runtimeType, Vector2 position) { if (runtimeType == null) throw new InvalidOperationException("[DrogBTRebuild] 런타임 타입이 null입니다."); object nodeInfo = getNodeInfoMethod.Invoke(null, new object[] { runtimeType }); if (nodeInfo == null) throw new InvalidOperationException($"[DrogBTRebuild] NodeInfo를 찾지 못했습니다: {runtimeType.FullName}"); Type nodeInfoType = nodeInfo.GetType(); FieldInfo modelTypeField = nodeInfoType.GetField("ModelType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object serializableType = modelTypeField?.GetValue(nodeInfo); PropertyInfo serializableTypeValueProperty = serializableType?.GetType().GetProperty("Type", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Type modelType = serializableTypeValueProperty?.GetValue(serializableType) as Type; if (modelType == null) throw new InvalidOperationException($"[DrogBTRebuild] ModelType을 찾지 못했습니다: {runtimeType.FullName}"); return createNodeMethod.Invoke(graphAsset, new object[] { modelType, position, null, new object[] { nodeInfo } }); } private static void ConnectChildren(UnityEngine.Object graphAsset, MethodInfo connectEdgeMethod, object parentNode, params object[] children) { object outputPort = GetDefaultOutputPort(parentNode); for (int i = 0; i < children.Length; i++) { Connect(graphAsset, connectEdgeMethod, outputPort, GetDefaultInputPort(children[i])); } } private static void Connect(UnityEngine.Object graphAsset, MethodInfo connectEdgeMethod, object outputPort, object inputPort) { if (outputPort == null || inputPort == null) throw new InvalidOperationException("[DrogBTRebuild] 포트 연결 대상이 null입니다."); connectEdgeMethod.Invoke(graphAsset, new[] { outputPort, inputPort }); } private static object GetDefaultInputPort(object node) { return GetDefaultPort(node, "TryDefaultInputPortModel"); } private static object GetDefaultOutputPort(object node) { return GetDefaultPort(node, "TryDefaultOutputPortModel"); } private static object GetDefaultPort(object node, string methodName) { MethodInfo method = node.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public); object[] parameters = { null }; bool success = method != null && (bool)method.Invoke(node, parameters); return success ? parameters[0] : null; } private static object FindBlackboardVariableModel(string variableName) { UnityEngine.Object blackboardAsset = AssetDatabase.LoadAllAssetsAtPath(GraphAssetPath) .FirstOrDefault(asset => asset != null && asset.GetType().Name.Contains("BehaviorBlackboardAuthoringAsset", StringComparison.Ordinal)); if (blackboardAsset == null) return null; PropertyInfo variablesProperty = blackboardAsset.GetType().GetProperty("Variables", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo variablesField = blackboardAsset.GetType().GetField("m_Variables", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); IEnumerable variables = variablesProperty?.GetValue(blackboardAsset) as IEnumerable ?? variablesField?.GetValue(blackboardAsset) as IEnumerable; if (variables == null) return null; foreach (object variable in variables) { if (variable == null) continue; PropertyInfo nameProperty = variable.GetType().GetProperty("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo nameField = variable.GetType().GetField("Name", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); string name = nameProperty?.GetValue(variable) as string ?? nameField?.GetValue(variable) as string; if (name == variableName) return variable; } return null; } private static void LinkTarget(object node, object targetVariable) { LinkFieldToVariable(node, "Target", typeof(GameObject), targetVariable); } private static void LinkFieldToVariable(object node, string fieldName, Type fieldType, object variableModel) { MethodInfo getVariableLinkMethod = node.GetType().GetMethod("GetVariableLink", BindingFlags.Instance | BindingFlags.Public); object variableLink = getVariableLinkMethod?.Invoke(node, new object[] { fieldName, fieldType }); if (variableLink != null) { PropertyInfo blackboardVariableProperty = variableLink.GetType().GetProperty("BlackboardVariable", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); blackboardVariableProperty?.SetValue(variableLink, variableModel); } object fieldModel = FindFieldModel(node, fieldName); if (fieldModel == null) return; PropertyInfo linkedVariableProperty = fieldModel.GetType().GetProperty("LinkedVariable", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo linkedVariableField = fieldModel.GetType().GetField("LinkedVariable", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (linkedVariableProperty != null && linkedVariableProperty.CanWrite) { linkedVariableProperty.SetValue(fieldModel, variableModel); } else { linkedVariableField?.SetValue(fieldModel, variableModel); } } private static object FindFieldModel(object node, string fieldName) { FieldInfo fieldValuesField = node.GetType().GetField("m_FieldValues", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); PropertyInfo fieldValuesProperty = node.GetType().GetProperty("FieldValues", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); IEnumerable fieldValues = fieldValuesField?.GetValue(node) as IEnumerable ?? fieldValuesProperty?.GetValue(node) as IEnumerable; if (fieldValues == null) return null; foreach (object fieldModel in fieldValues) { if (fieldModel == null) continue; PropertyInfo fieldNameProperty = fieldModel.GetType().GetProperty("FieldName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo fieldNameField = fieldModel.GetType().GetField("FieldName", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); string currentFieldName = fieldNameProperty?.GetValue(fieldModel) as string ?? fieldNameField?.GetValue(fieldModel) as string; if (currentFieldName == fieldName) return fieldModel; } return null; } private static void SetStartRepeatFlags(object startNode, bool repeat, bool allowMultipleRepeatsPerTick) { Type startNodeType = startNode.GetType(); FieldInfo repeatField = startNodeType.GetField("Repeat", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); FieldInfo allowField = startNodeType.GetField("AllowMultipleRepeatsPerTick", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); repeatField?.SetValue(startNode, repeat); allowField?.SetValue(startNode, allowMultipleRepeatsPerTick); } } }