You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
14 KiB
C#
388 lines
14 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
#if UNITY_EDITOR
|
|
using UnityEditorInternal;
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
|
|
namespace MalbersAnimations.Controller.AI
|
|
{
|
|
[CreateAssetMenu(menuName = "Malbers Animations/Pluggable AI/Decision/AND Decision", order = 100)]
|
|
public class ANDDecision : MAIDecision
|
|
{
|
|
public override string DisplayName => "General/AND";
|
|
|
|
[HideInInspector,Tooltip("Selected Index of the list in the inspector")] public int list_index;
|
|
|
|
public List<MAIDecision> decisions = new List<MAIDecision>();
|
|
public List<bool> invert = new List<bool>();
|
|
|
|
public bool debug;
|
|
|
|
public override void PrepareDecision(MAnimalBrain brain, int Index)
|
|
{
|
|
if (invert.Count != decisions.Count) invert.Resize(decisions.Count);
|
|
|
|
foreach (var d in decisions) d.PrepareDecision(brain, Index);
|
|
}
|
|
|
|
public override bool Decide(MAnimalBrain brain, int Index)
|
|
{
|
|
for (int i = 0; i < decisions.Count; i++)
|
|
{
|
|
bool Decision = decisions[i].Decide(brain, Index);
|
|
|
|
if (invert[i]) Decision = !Decision;
|
|
if (debug) Debug.Log($"[{brain.Animal.name}] -> [{(invert[i] ? "NOT " : " " )}{decisions[i].name}] -> [{Decision}]",this);
|
|
if (!Decision) return false;
|
|
}
|
|
return true;
|
|
}
|
|
public override void FinishDecision(MAnimalBrain brain, int Index)
|
|
{
|
|
foreach (var d in decisions) d?.FinishDecision(brain, Index);
|
|
}
|
|
|
|
public override void DrawGizmos(MAnimalBrain brain)
|
|
{
|
|
if (decisions != null)
|
|
foreach (var d in decisions) d?.DrawGizmos(brain);
|
|
}
|
|
|
|
void Reset() { Description = "All Decisions on the list must be TRUE in order to sent a True Decision"; }
|
|
}
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
[CustomEditor(typeof(ANDDecision))]
|
|
public class ANDDecisionEd : Editor
|
|
{
|
|
private SerializedProperty Description, MessageID, send, interval, decisions, invert, list_index, debug;
|
|
private ReorderableList list;
|
|
private List<Type> DecisionType;
|
|
|
|
ANDDecision ANDD;
|
|
|
|
private GUIContent plus;
|
|
|
|
private void OnEnable()
|
|
{
|
|
FindTarget();
|
|
if (plus == null) plus = UnityEditor.EditorGUIUtility.IconContent("d_Toolbar Plus");
|
|
|
|
Description = serializedObject.FindProperty("Description");
|
|
debug = serializedObject.FindProperty("debug");
|
|
MessageID = serializedObject.FindProperty("DecisionID");
|
|
send = serializedObject.FindProperty("send");
|
|
interval = serializedObject.FindProperty("interval");
|
|
decisions = serializedObject.FindProperty("decisions");
|
|
invert = serializedObject.FindProperty("invert");
|
|
list_index = serializedObject.FindProperty("list_index");
|
|
DecisionType = MTools.GetAllTypes<MAIDecision>();
|
|
|
|
AndDecisions();
|
|
|
|
ResizeInvert();
|
|
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
|
|
protected virtual void ResizeInvert() => ANDD.invert.Resize(ANDD.decisions.Count);
|
|
//protected virtual void ResizeDecisionList() => ANDD.decisions.Resize(ANDD.decisions.Count + 1);
|
|
|
|
protected virtual void FindTarget() => ANDD = (ANDDecision)target;
|
|
protected virtual string ListLabel => "AND";
|
|
|
|
private void AndDecisions()
|
|
{
|
|
list = new ReorderableList(serializedObject, decisions)
|
|
{
|
|
drawHeaderCallback = rect =>
|
|
EditorGUI.LabelField(rect, new GUIContent($" Invert Decisions [{ListLabel}] ")),
|
|
|
|
drawElementCallback = (rect, index, isActive, isFocused) =>
|
|
{
|
|
var element = decisions.GetArrayElementAtIndex(index);
|
|
var inv = invert.GetArrayElementAtIndex(index);
|
|
|
|
|
|
var NotW = 39f;
|
|
var r = new Rect(rect) { y = rect.y + 2, height = EditorGUIUtility.singleLineHeight, width = rect.width - NotW - 4 };
|
|
|
|
var space = element.objectReferenceValue == null;
|
|
|
|
if (space)
|
|
r.width -= 22;
|
|
|
|
var InRect = new Rect(r);
|
|
InRect.width = NotW;
|
|
|
|
r.x += NotW + 4;
|
|
|
|
var defaultColor = GUI.backgroundColor;
|
|
|
|
if (list_index.intValue == index)GUI.backgroundColor = Color.yellow;
|
|
EditorGUI.PropertyField(r, element, GUIContent.none);
|
|
GUI.backgroundColor = defaultColor;
|
|
|
|
|
|
var S = new GUIStyle(EditorStyles.miniButton);
|
|
|
|
S.fontStyle = inv.boolValue ? FontStyle.Bold : S.fontStyle;
|
|
|
|
var currentColor = GUI.color;
|
|
GUI.color = inv.boolValue ? ((GUI.color * 0.6f) + (Color.red * 0.4f)) : currentColor;
|
|
inv.boolValue = GUI.Toggle(InRect, inv.boolValue, new GUIContent("NOT", "Invert decision value"), S);
|
|
GUI.color = currentColor;
|
|
if (space)
|
|
{
|
|
var AddButtonRect = new Rect(rect)
|
|
{
|
|
x = rect.width + 22,
|
|
width = 22,
|
|
y = rect.y + 2,
|
|
height = EditorGUIUtility.singleLineHeight
|
|
};
|
|
|
|
// if (!internalData.boolValue)
|
|
if (GUI.Button(AddButtonRect, plus, EditorStyles.helpBox))
|
|
{
|
|
MTools.AddScriptableAssetContextMenu(element, typeof(MAIDecision),
|
|
MTools.GetSelectedPathOrFallback());
|
|
}
|
|
}
|
|
},
|
|
onAddCallback = AddDecision,
|
|
onSelectCallback = SelectedItem,
|
|
onRemoveCallback = list =>
|
|
{
|
|
var decision = decisions.GetArrayElementAtIndex(list.index).objectReferenceValue;
|
|
|
|
if (decision != null)
|
|
{
|
|
if (EditorUtility.DisplayDialog("Remove Decision", "Deleting a Decision cannot be undone. Are you sure you want to delete it?", "Yes", "No"))
|
|
{
|
|
string Path = AssetDatabase.GetAssetPath(decision);
|
|
|
|
if (Path == AssetDatabase.GetAssetPath(target)) //mean it was created inside the AI STATE
|
|
{
|
|
decisions.GetArrayElementAtIndex(list.index).objectReferenceValue = null;
|
|
DestroyImmediate(decision, true); //Delete the internal asset!
|
|
decisions.DeleteArrayElementAtIndex(list.index);
|
|
decisions.serializedObject.ApplyModifiedProperties();
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
else
|
|
{
|
|
decisions.DeleteArrayElementAtIndex(list.index);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
decisions.DeleteArrayElementAtIndex(list.index);
|
|
decisions.serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
MTools.CheckListIndex(list);
|
|
ResizeInvert();
|
|
GUIUtility.ExitGUI();
|
|
EditorUtility.SetDirty(target);
|
|
}
|
|
};
|
|
}
|
|
|
|
private void SelectedItem(ReorderableList list)
|
|
{
|
|
list_index.intValue = list.index;
|
|
}
|
|
|
|
private void AddDecision(ReorderableList list)
|
|
{
|
|
var addMenu = new GenericMenu();
|
|
|
|
decisions.InsertArrayElementAtIndex(decisions.arraySize);
|
|
invert.InsertArrayElementAtIndex(invert.arraySize);
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
EditorUtility.SetDirty(target);
|
|
|
|
|
|
for (int i = 0; i < DecisionType.Count; i++)
|
|
{
|
|
Type st = DecisionType[i];
|
|
|
|
|
|
//Fast Ugly get the name of the Asset thing
|
|
MAIDecision t = (MAIDecision)CreateInstance(st);
|
|
var Rname = t.DisplayName;
|
|
DestroyImmediate(t);
|
|
|
|
//var Rname = Regex.Replace(st.Name, @"([a-z])([A-Z])", "$1 $2");
|
|
|
|
addMenu.AddItem(new GUIContent(Rname), false, () => AddTDecision(st));
|
|
}
|
|
|
|
|
|
addMenu.AddSeparator("");
|
|
addMenu.AddItem(new GUIContent("Empty"), false, () =>
|
|
{
|
|
var index = decisions.arraySize - 1;
|
|
decisions.GetArrayElementAtIndex(index).objectReferenceValue = null;
|
|
}
|
|
);
|
|
addMenu.ShowAsContext();
|
|
|
|
}
|
|
|
|
|
|
private void AddTDecision(Type desicion)
|
|
{
|
|
MAIDecision des = (MAIDecision)CreateInstance(desicion);
|
|
des.hideFlags = HideFlags.None;
|
|
des.name = "D_" + desicion.Name;
|
|
AssetDatabase.AddObjectToAsset(des, AssetDatabase.GetAssetPath(target));
|
|
AssetDatabase.SaveAssets();
|
|
|
|
//decisions.InsertArrayElementAtIndex(decisions.arraySize);
|
|
//invert.InsertArrayElementAtIndex(invert.arraySize);
|
|
|
|
var index = decisions.arraySize - 1;
|
|
decisions.GetArrayElementAtIndex(index).objectReferenceValue = des;
|
|
|
|
EditorUtility.SetDirty(des);
|
|
EditorUtility.SetDirty(target);
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
ResizeInvert();
|
|
|
|
list.index = index;
|
|
}
|
|
|
|
|
|
//private void CheckListIndex(ReorderableList list)
|
|
//{
|
|
// list.index -= 1;
|
|
// if (list.index == -1 && list.serializedProperty.arraySize > 0) //In Case you remove the first one
|
|
// list.index = 0;
|
|
//}
|
|
|
|
public override void OnInspectorGUI()
|
|
{
|
|
serializedObject.Update();
|
|
|
|
EditorGUILayout.PropertyField(Description);
|
|
EditorGUILayout.PropertyField(MessageID);
|
|
EditorGUILayout.PropertyField(send);
|
|
EditorGUILayout.BeginHorizontal();
|
|
EditorGUILayout.PropertyField(interval);
|
|
MalbersEditor.DrawDebugIcon(debug);
|
|
EditorGUILayout.EndHorizontal();
|
|
EditorGUILayout.Space();
|
|
|
|
|
|
for (int i = 0; i < decisions.arraySize; i++)
|
|
{
|
|
if (decisions.GetArrayElementAtIndex(i).objectReferenceValue == null)
|
|
{
|
|
EditorGUILayout.HelpBox("The Brain cannot contain empty Tasks. Set the missing tasks", MessageType.Error);
|
|
break;
|
|
}
|
|
}
|
|
|
|
list.DoLayoutList();
|
|
list.index = list_index.intValue;
|
|
|
|
if (list.index != -1 && list.count > list.index)
|
|
{
|
|
var element = decisions.GetArrayElementAtIndex(list.index);
|
|
|
|
if (element != null && element.objectReferenceValue != null)
|
|
{
|
|
var asset = element.objectReferenceValue;
|
|
|
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
|
|
|
EditorGUILayout.LabelField("Decision: " + asset.name, EditorStyles.boldLabel);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
{
|
|
asset.name = EditorGUILayout.TextField("Name", asset.name);
|
|
element.serializedObject.ApplyModifiedProperties();
|
|
EditorUtility.SetDirty(asset);
|
|
|
|
if (GUILayout.Button(new GUIContent("R", "Update the Asset name"), GUILayout.Width(20)))
|
|
{
|
|
string taskPath = AssetDatabase.GetAssetPath(asset);
|
|
string targetPath = AssetDatabase.GetAssetPath(target);
|
|
|
|
// Check if the asset itself is external or internal to the target
|
|
if (taskPath != targetPath)
|
|
AssetDatabase.RenameAsset(taskPath, asset.name);
|
|
|
|
AssetDatabase.SaveAssets();
|
|
EditorGUIUtility
|
|
.PingObject(
|
|
asset); //Final way of changing the name of the asset... dirty but it works
|
|
}
|
|
|
|
|
|
if (GUILayout.Button(new GUIContent("E", "Extract the task into its own file"), GUILayout.Width(20)))
|
|
{
|
|
ExtractDecisionFromList(asset, element, list.index);
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
MTools.DrawObjectReferenceInspector(element);
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
}
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
}
|
|
|
|
|
|
private void ExtractDecisionFromList(UnityEngine.Object asset, SerializedProperty element, int index)
|
|
{
|
|
string taskPath = AssetDatabase.GetAssetPath(asset);
|
|
string targetPath = AssetDatabase.GetAssetPath(target);
|
|
if (taskPath == targetPath)
|
|
{
|
|
UnityEngine.Object clone = MTools.ExtractObject(asset, index);
|
|
if (!clone)
|
|
return;
|
|
|
|
// Remove from list
|
|
DestroyImmediate(asset, true);
|
|
|
|
// Add as external decision
|
|
SerializedProperty decision = element.FindPropertyRelative("decision");
|
|
decision.objectReferenceValue = clone;
|
|
list.index = -1;
|
|
EditorUtility.SetDirty(target);
|
|
serializedObject.ApplyModifiedProperties();
|
|
AssetDatabase.SaveAssets();
|
|
|
|
EditorGUIUtility.PingObject(clone);
|
|
}
|
|
else
|
|
{
|
|
// Checking this beforehand is quite in-efficient as the AssetDatabase.GetAssetPath() is slow
|
|
// If there is a better way to check whether it's an internal or external asset then that could be used
|
|
Debug.LogWarning("Cannot extract already extracted decision");
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|