305 lines
20 KiB
C#
305 lines
20 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace Colosseum.Editor
|
|
{
|
|
/// <summary>
|
|
/// 드로그 Behavior Graph authoring 자산을 현재 BT 우선순위 구조로 재생성합니다.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|