Monobehaviour tree

This commit is contained in:
Pedro Gomes 2024-06-23 19:12:22 +01:00
parent 7613269fa5
commit c76b364ab7
201 changed files with 12277 additions and 2 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 646d077994822874f8e542df122777f7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ba1db740a296d7c47b95f11808bad3a9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 59e9dc397161a6b44991bc327eb108a6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,771 @@
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEditor.IMGUI.Controls;
using MBT;
namespace MBTEditor
{
public class BehaviourTreeWindow : EditorWindow, IHasCustomMenu
{
private MonoBehaviourTree currentMBT;
private Editor currentMBTEditor;
private Node[] currentNodes;
private Node selectedNode;
private bool nodeMoved = false;
private Vector2 workspaceOffset;
private NodeHandle currentHandle;
private NodeHandle dropdownHandleCache;
private bool snapNodesToGrid;
private bool locked = false;
private Rect nodeFinderActivatorRect;
private NodeDropdown nodeDropdown;
private Vector2 nodeDropdownTargetPosition;
private readonly float _handleDetectionDistance = 8f;
private readonly Color _editorBackgroundColor = new Color(0.16f, 0.19f, 0.25f, 1);
private GUIStyle _lockButtonStyle;
private GUIStyle _defaultNodeStyle;
private GUIStyle _selectedNodeStyle;
private GUIStyle _successNodeStyle;
private GUIStyle _failureNodeStyle;
private GUIStyle _runningNodeStyle;
private GUIStyle _nodeContentBoxStyle;
private GUIStyle _nodeLabelStyle;
private GUIStyle _nodeBreakpointLabelStyle;
private void OnEnable()
{
// Read snap option
snapNodesToGrid = EditorPrefs.GetBool("snapNodesToGrid", true);
// Setup events
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
Undo.undoRedoPerformed -= UpdateSelection;
Undo.undoRedoPerformed += UpdateSelection;
// Node finder
nodeDropdown = new NodeDropdown(new AdvancedDropdownState(), AddNode);
// Standard node
_defaultNodeStyle = new GUIStyle();
_defaultNodeStyle.border = new RectOffset(10,10,10,10);
_defaultNodeStyle.normal.background = Resources.Load("mbt_node_default", typeof(Texture2D)) as Texture2D;
// Selected node
_selectedNodeStyle = new GUIStyle();
_selectedNodeStyle.border = new RectOffset(10,10,10,10);
_selectedNodeStyle.normal.background = Resources.Load("mbt_node_selected", typeof(Texture2D)) as Texture2D;
// Success node
_successNodeStyle = new GUIStyle();
_successNodeStyle.border = new RectOffset(10,10,10,10);
_successNodeStyle.normal.background = Resources.Load("mbt_node_success", typeof(Texture2D)) as Texture2D;
// Failure node
_failureNodeStyle = new GUIStyle();
_failureNodeStyle.border = new RectOffset(10,10,10,10);
_failureNodeStyle.normal.background = Resources.Load("mbt_node_failure", typeof(Texture2D)) as Texture2D;
// Running node
_runningNodeStyle = new GUIStyle();
_runningNodeStyle.border = new RectOffset(10,10,10,10);
_runningNodeStyle.normal.background = Resources.Load("mbt_node_running", typeof(Texture2D)) as Texture2D;
// Node content box
_nodeContentBoxStyle = new GUIStyle();
_nodeContentBoxStyle.padding = new RectOffset(0,0,15,15);
// Node label
_nodeLabelStyle = new GUIStyle();
_nodeLabelStyle.normal.textColor = Color.white;
_nodeLabelStyle.alignment = TextAnchor.MiddleCenter;
_nodeLabelStyle.wordWrap = true;
_nodeLabelStyle.margin = new RectOffset(10,10,10,10);
_nodeLabelStyle.font = Resources.Load("mbt_Lato-Regular", typeof(Font)) as Font;
_nodeLabelStyle.fontSize = 12;
// Node label when breakpoint is set to true
_nodeBreakpointLabelStyle = new GUIStyle(_nodeLabelStyle);
_nodeBreakpointLabelStyle.normal.textColor = new Color(1f, 0.35f, 0.18f);
}
private void OnDisable()
{
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
Undo.undoRedoPerformed -= UpdateSelection;
}
[MenuItem("Window/Mono Behaviour Tree")]
public static void OpenEditor()
{
BehaviourTreeWindow window = GetWindow<BehaviourTreeWindow>();
window.titleContent = new GUIContent(
"Behaviour Tree",
Resources.Load("mbt_window_icon", typeof(Texture2D)) as Texture2D
);
}
void OnGUI()
{
// Draw grid in background first
PaintBackground();
// If there is no tree to render just skip rest
if (currentMBT == null) {
// Keep toolbar rendered
PaintWindowToolbar();
return;
}
PaintConnections(Event.current);
// Repaint nodes
PaintNodes();
PaintWindowToolbar();
// Update selection and drag
ProcessEvents(Event.current);
if (GUI.changed) Repaint();
}
private void PaintConnections(Event e)
{
// Paint line when dragging connection
if (currentHandle != null) {
Handles.BeginGUI();
Vector3 p1 = new Vector3(currentHandle.position.x, currentHandle.position.y, 0f);
Vector3 p2 = new Vector3(e.mousePosition.x, e.mousePosition.y, 0f);
Handles.DrawBezier(p1, p2, p1, p2, new Color(0.3f, 0.36f, 0.5f), null, 4f);
Handles.EndGUI();
}
// Paint all current connections
for (int i = 0; i < currentNodes.Length; i++)
{
Node n = currentNodes[i];
Vector3 p1 = GetBottomHandlePosition(n.rect) + workspaceOffset;
for (int j = 0; j < n.children.Count; j++)
{
Handles.BeginGUI();
Vector3 p2 = GetTopHandlePosition(n.children[j].rect) + workspaceOffset;
Handles.DrawBezier(p1, p2, p1, p2, new Color(0.3f, 0.36f, 0.5f), null, 4f);
Handles.EndGUI();
}
}
}
private void PaintWindowToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
EditorGUI.BeginDisabledGroup(currentMBT == null);
if (GUILayout.Toggle(snapNodesToGrid, "Snap Nodes", EditorStyles.toolbarButton) != snapNodesToGrid)
{
snapNodesToGrid = !snapNodesToGrid;
// Store this setting
EditorPrefs.SetBool("snapNodesToGrid", snapNodesToGrid);
}
// TODO: Autolayout
// if (GUILayout.Button("Auto Layout", EditorStyles.toolbarButton)){
// Debug.Log("Auto layout is not implemented.");
// }
EditorGUILayout.Space();
if (GUILayout.Button("Focus Root", EditorStyles.toolbarButton)){
FocusRoot();
}
if (GUILayout.Button("Select In Hierarchy", EditorStyles.toolbarButton)){
if (currentMBT != null)
{
Selection.activeGameObject = currentMBT.gameObject;
EditorGUIUtility.PingObject(currentMBT.gameObject);
}
}
if (GUILayout.Button("Add Node", EditorStyles.toolbarDropDown)){
OpenNodeFinder(nodeFinderActivatorRect, false);
}
if (Event.current.type == EventType.Repaint) nodeFinderActivatorRect = GUILayoutUtility.GetLastRect();
EditorGUI.EndDisabledGroup();
GUILayout.FlexibleSpace();
if (currentMBT != null)
{
GUILayout.Label(string.Format("{0} {1}", currentMBT.name, -workspaceOffset));
}
EditorGUILayout.EndHorizontal();
}
void FocusRoot()
{
Root rootNode = null;
for (int i = 0; i < currentNodes.Length; i++)
{
if (currentNodes[i] is Root) {
rootNode = currentNodes[i] as Root;
break;
}
}
if (rootNode != null) {
workspaceOffset = -rootNode.rect.center + new Vector2(this.position.width/2, this.position.height/2);
}
}
private void OnPlayModeStateChanged(PlayModeStateChange state)
{
// Disable lock when changing state
this.locked = false;
UpdateSelection();
Repaint();
}
void OnInspectorUpdate()
{
// OPTIMIZE: This can be optimized to call repaint once per second
Repaint();
}
void OnSelectionChange()
{
MonoBehaviourTree previous = currentMBT;
UpdateSelection();
// Reset workspace position only when selection changed
if (previous != currentMBT)
{
workspaceOffset = Vector2.zero;
}
Repaint();
}
void OnFocus()
{
UpdateSelection();
Repaint();
}
void IHasCustomMenu.AddItemsToMenu(GenericMenu menu)
{
menu.AddItem(new GUIContent("Lock"), this.locked, () => {
this.locked = !this.locked;
UpdateSelection();
});
}
// http://leahayes.co.uk/2013/04/30/adding-the-little-padlock-button-to-your-editorwindow.html
private void ShowButton(Rect position)
{
// Cache style
if (_lockButtonStyle == null) {
_lockButtonStyle = "IN LockButton";
}
// Generic menu button
GUI.enabled = currentMBT != null;
this.locked = GUI.Toggle(position, this.locked, GUIContent.none, _lockButtonStyle);
GUI.enabled = true;
}
// DeselectNode cannot be called here
// void OnLostFocus()
// {
// // DeselectNode();
// }
private void UpdateSelection()
{
MonoBehaviourTree prevMBT = currentMBT;
if (!this.locked && Selection.activeGameObject != null)
{
currentMBT = Selection.activeGameObject.GetComponent<MonoBehaviourTree>();
// If new selection is null then restore previous one
if (currentMBT == null)
{
currentMBT = prevMBT;
}
}
if (currentMBT != prevMBT)
{
// Get new editor for new MBT
Editor.CreateCachedEditor(currentMBT, null, ref currentMBTEditor);
}
if (currentMBT != null) {
currentNodes = currentMBT.GetComponents<Node>();
// Ensure there is no error when node script is missing
for (int i = 0; i < currentNodes.Length; i++)
{
currentNodes[i].children.RemoveAll(item => item == null);
}
} else {
currentNodes = new Node[0];
// Unlock when there is nothing to display
this.locked = false;
}
}
private void ProcessEvents(Event e)
{
switch (e.type)
{
case EventType.MouseDown:
if (e.button == 0) {
// Reset flag
nodeMoved = false;
// Frist check if any node handle was clicked
NodeHandle handle = FindHandle(e.mousePosition);
if (handle != null)
{
currentHandle = handle;
e.Use();
break;
}
Node node = FindNode(e.mousePosition);
// Select node if contains point
if (node != null) {
DeselectNode();
SelectNode(node);
if (e.clickCount == 2 && node is SubTree) {
SubTree subTree = node as SubTree;
if (subTree.tree != null) {
Selection.activeGameObject = subTree.tree.gameObject;
}
}
} else {
DeselectNode();
}
e.Use();
} else if (e.button == 1) {
Node node = FindNode(e.mousePosition);
// Open proper context menu
if (node != null) {
OpenNodeMenu(e.mousePosition, node);
} else {
DeselectNode();
OpenNodeFinder(new Rect(e.mousePosition.x, e.mousePosition.y, 1, 1));
}
e.Use();
}
break;
case EventType.MouseDrag:
// Drag node, workspace or connection
if (e.button == 0) {
if (currentHandle != null) {
// Let PaintConnections draw lines
} else if (selectedNode != null) {
Undo.RecordObject(selectedNode, "Move Node");
selectedNode.rect.position += Event.current.delta;
// Move whole branch when Ctrl is pressed
if (e.control) {
List<Node> movedNodes = selectedNode.GetAllSuccessors();
for (int i = 0; i < movedNodes.Count; i++)
{
Undo.RecordObject(movedNodes[i], "Move Node");
movedNodes[i].rect.position += Event.current.delta;
}
}
nodeMoved = true;
} else {
workspaceOffset += Event.current.delta;
}
GUI.changed = true;
e.Use();
}
break;
case EventType.MouseUp:
if (currentHandle != null) {
TryConnectNodes(currentHandle, e.mousePosition);
}
// Reorder or snap nodes in case any of them was moved
if (nodeMoved && selectedNode != null) {
// Snap nodes if option is enabled
if (snapNodesToGrid)
{
Undo.RecordObject(selectedNode, "Move Node");
selectedNode.rect.position = SnapPositionToGrid(selectedNode.rect.position);
// When control is pressed snap successors too
if (e.control) {
List<Node> movedNodes = selectedNode.GetAllSuccessors();
for (int i = 0; i < movedNodes.Count; i++)
{
Undo.RecordObject(movedNodes[i], "Move Node");
movedNodes[i].rect.position = SnapPositionToGrid(movedNodes[i].rect.position);
}
}
}
// Reorder siblings if selected node has parent
if (selectedNode.parent != null)
{
Undo.RecordObject(selectedNode.parent, "Move Node");
selectedNode.parent.SortChildren();
}
}
nodeMoved = false;
currentHandle = null;
GUI.changed = true;
break;
}
}
Vector2 SnapPositionToGrid(Vector2 position)
{
return new Vector2(
Mathf.Round(position.x / 20f) * 20f,
Mathf.Round(position.y / 20f) * 20f
);
}
private void TryConnectNodes(NodeHandle handle, Vector2 mousePosition)
{
// Find hovered node and connect or open dropdown
Node targetNode = FindNode(mousePosition);
if (targetNode == null) {
OpenNodeFinder(new Rect(mousePosition.x, mousePosition.y, 1, 1), true, handle);
return;
}
// Check if they are not the same node
if (targetNode == handle.node) {
return;
}
Undo.RecordObject(targetNode, "Connect Nodes");
Undo.RecordObject(handle.node, "Connect Nodes");
// There is node, try to connect if this is possible
if (handle.type == HandleType.Input && targetNode is IParentNode) {
// Do not allow connecting descendants as parents
if (targetNode.IsDescendantOf(handle.node)) {
return;
}
// Then add as child to new parent
targetNode.AddChild(handle.node);
// Update order of nodes
targetNode.SortChildren();
} else if (handle.type == HandleType.Output && targetNode is IChildrenNode) {
// Do not allow connecting descendants as parents
if (handle.node.IsDescendantOf(targetNode)) {
return;
}
// Then add as child to new parent
handle.node.AddChild(targetNode);
// Update order of nodes
handle.node.SortChildren();
}
}
private void SelectNode(Node node)
{
currentMBT.selectedEditorNode = node;
currentMBTEditor.Repaint();
node.selected = true;
selectedNode = node;
GUI.changed = true;
}
private void DeselectNode(Node node)
{
currentMBT.selectedEditorNode = null;
currentMBTEditor.Repaint();
node.selected = false;
selectedNode = null;
GUI.changed = true;
}
private void DeselectNode()
{
currentMBT.selectedEditorNode = null;
currentMBTEditor.Repaint();
for (int i = 0; i < currentNodes.Length; i++)
{
currentNodes[i].selected = false;
}
selectedNode = null;
GUI.changed = true;
}
private Node FindNode(Vector2 mousePosition)
{
for (int i = 0; i < currentNodes.Length; i++)
{
// Get correct position of node with offset
Rect target = currentNodes[i].rect;
target.position += workspaceOffset;
if (target.Contains(mousePosition)) {
return currentNodes[i];
}
}
return null;
}
private NodeHandle FindHandle(Vector2 mousePosition)
{
for (int i = 0; i < currentNodes.Length; i++)
{
Node node = currentNodes[i];
// Get correct position of node with offset
Rect targetRect = node.rect;
targetRect.position += workspaceOffset;
if (node is IChildrenNode) {
Vector2 handlePoint = GetTopHandlePosition(targetRect);
if (Vector2.Distance(handlePoint, mousePosition) < _handleDetectionDistance) {
return new NodeHandle(node, handlePoint, HandleType.Input);
}
}
if (node is IParentNode) {
Vector2 handlePoint = GetBottomHandlePosition(targetRect);
if (Vector2.Distance(handlePoint, mousePosition) < _handleDetectionDistance) {
return new NodeHandle(node, handlePoint, HandleType.Output);
}
}
}
return null;
}
private void PaintNodes()
{
for (int i = currentNodes.Length - 1; i >= 0 ; i--)
{
Node node = currentNodes[i];
Rect targetRect = node.rect;
targetRect.position += workspaceOffset;
// Draw node content
GUILayout.BeginArea(targetRect, GetNodeStyle(node));
GUILayout.BeginVertical(_nodeContentBoxStyle);
if (node.breakpoint)
{
GUILayout.Label(node.title, _nodeBreakpointLabelStyle);
}
else
{
GUILayout.Label(node.title, _nodeLabelStyle);
}
GUILayout.EndVertical();
if (Event.current.type == EventType.Repaint)
{
node.rect.height = GUILayoutUtility.GetLastRect().height;
}
GUILayout.EndArea();
// Paint warning icon
if (!Application.isPlaying && !node.IsValid())
{
GUI.Label(GetWarningIconRect(targetRect), EditorGUIUtility.IconContent("CollabConflict Icon"));
}
// Draw connection handles if needed
if (node is IChildrenNode)
{
Vector2 top = GetTopHandlePosition(targetRect);
GUI.DrawTexture(
new Rect(top.x-8, top.y-5, 16, 16),
Resources.Load("mbt_node_handle", typeof(Texture2D)) as Texture2D
);
}
if (node is IParentNode)
{
Vector2 bottom = GetBottomHandlePosition(targetRect);
GUI.DrawTexture(
new Rect(bottom.x-8, bottom.y-11, 16, 16),
Resources.Load("mbt_node_handle", typeof(Texture2D)) as Texture2D
);
}
}
}
private GUIStyle GetNodeStyle(Node node)
{
if (node.selected) {
return _selectedNodeStyle;
}
switch (node.status)
{
case Status.Success:
return _successNodeStyle;
case Status.Failure:
return _failureNodeStyle;
case Status.Running:
return _runningNodeStyle;
}
return _defaultNodeStyle;
}
private Vector2 GetTopHandlePosition(Rect rect)
{
return new Vector2(rect.x + rect.width/2, rect.y);
}
private Vector2 GetBottomHandlePosition(Rect rect)
{
return new Vector2(rect.x + rect.width/2, rect.y + rect.height);
}
private Rect GetWarningIconRect(Rect rect)
{
// return new Rect(rect.x - 10, rect.y + rect.height/2 - 10 , 20, 20);
return new Rect(rect.x + rect.width - 2, rect.y - 1, 20, 20);
}
private void OpenNodeFinder(Rect rect, bool useRectPosition = true, NodeHandle handle = null)
{
// Store handle to connect later (null by default)
dropdownHandleCache = handle;
// Store real clicked position including workspace offset
if (useRectPosition) {
nodeDropdownTargetPosition = rect.position - workspaceOffset;
} else {
nodeDropdownTargetPosition = new Vector2(this.position.width/2, this.position.height/2) - workspaceOffset;
}
// Open dropdown
nodeDropdown.Show(rect);
}
private void OpenNodeMenu(Vector2 mousePosition, Node node)
{
GenericMenu genericMenu = new GenericMenu();
genericMenu.AddItem(new GUIContent("Breakpoint"), node.breakpoint, () => ToggleNodeBreakpoint(node));
genericMenu.AddItem(new GUIContent("Duplicate"), false, () => DuplicateNode(node));
genericMenu.AddItem(new GUIContent("Disconnect Children"), false, () => DisconnectNodeChildren(node));
genericMenu.AddItem(new GUIContent("Disconnect Parent"), false, () => DisconnectNodeParent(node));
genericMenu.AddItem(new GUIContent("Delete Node"), false, () => DeleteNode(node));
genericMenu.ShowAsContext();
}
void AddNode(ClassTypeDropdownItem item)
{
// In case there is nothing to add
if (currentMBT == null || item.classType == null) {
return;
}
// Allow only one root
if (item.classType.IsAssignableFrom(typeof(Root)) && currentMBT.GetComponent<Root>() != null) {
Debug.LogWarning("You can not add more than one Root node.");
return;
}
Undo.SetCurrentGroupName("Create Node");
Node node = (Node)Undo.AddComponent(currentMBT.gameObject, item.classType);
node.title = item.name;
node.hideFlags = HideFlags.HideInInspector;
node.rect.position = nodeDropdownTargetPosition - new Vector2(node.rect.width/2, 0);
UpdateSelection();
if (dropdownHandleCache != null) {
// Add additonal offset (3,3) to be sure that point is inside rect
TryConnectNodes(dropdownHandleCache, nodeDropdownTargetPosition + workspaceOffset + new Vector2(3,3));
}
}
private void ToggleNodeBreakpoint(Node node)
{
// Toggle breakpoint flag
// Undo.RecordObject(node, "Toggle Breakpoint");
node.breakpoint = !node.breakpoint;
}
private void DeleteNode(Node node)
{
if (currentMBT == null) {
return;
}
DeselectNode();
// Disconnect all children and parent
Undo.SetCurrentGroupName("Delete Node");
DisconnectNodeChildren(node);
DisconnectNodeParent(node);
Undo.DestroyObjectImmediate(node);
// DestroyImmediate(node, true);
UpdateSelection();
}
private void DisconnectNodeParent(Node node)
{
if (node.parent != null) {
Undo.RecordObject(node, "Disconnect Parent");
Undo.RecordObject(node.parent, "Disconnect Parent");
node.parent.RemoveChild(node);
}
}
private void DisconnectNodeChildren(Node node)
{
Undo.RecordObject(node, "Disconnect Children");
for (int i = node.children.Count - 1; i >= 0 ; i--)
{
Undo.RecordObject(node.children[i], "Disconnect Children");
node.RemoveChild(node.children[i]);
}
}
private void DuplicateNode(Node contextNode)
{
// NOTE: This code is mostly copied from AddNode()
// Check if there is MBT
if (currentMBT == null) {
return;
}
System.Type classType = contextNode.GetType();
// Allow only one root
if (classType.IsAssignableFrom(typeof(Root)) && currentMBT.GetComponent<Root>() != null) {
Debug.LogWarning("You can not add more than one Root node.");
return;
}
Undo.SetCurrentGroupName("Duplicate Node");
Node node = (Node)Undo.AddComponent(currentMBT.gameObject, classType);
// Copy values
EditorUtility.CopySerialized(contextNode, node);
// Set flags anyway to ensure it is not visible in inspector
node.hideFlags = HideFlags.HideInInspector;
node.rect.position = contextNode.rect.position + new Vector2(20, 20);
// Remove all connections or graph gonna break
node.parent = null;
node.children.Clear();
UpdateSelection();
}
/// It is quite unique, but https://stackoverflow.com/questions/2920696/how-generate-unique-integers-based-on-guids
private int GenerateId()
{
return System.Guid.NewGuid().GetHashCode();
}
private void PaintBackground()
{
// Background
Handles.BeginGUI();
Handles.DrawSolidRectangleWithOutline(new Rect(0, 0, position.width, position.height), _editorBackgroundColor, Color.gray);
Handles.EndGUI();
// Grid lines
DrawBackgroundGrid(20, 0.1f, new Color(0.3f, 0.36f, 0.5f));
DrawBackgroundGrid(100, 0.2f, new Color(0.3f, 0.36f, 0.5f));
}
/// Method copied from https://gram.gs/gramlog/creating-node-based-editor-unity/
private void DrawBackgroundGrid(float gridSpacing, float gridOpacity, Color gridColor)
{
int widthDivs = Mathf.CeilToInt(position.width / gridSpacing);
int heightDivs = Mathf.CeilToInt(position.height / gridSpacing);
Handles.BeginGUI();
Handles.color = new Color(gridColor.r, gridColor.g, gridColor.b, gridOpacity);
Vector3 newOffset = new Vector3(workspaceOffset.x % gridSpacing, workspaceOffset.y % gridSpacing, 0);
for (int i = 0; i <= widthDivs; i++)
{
Handles.DrawLine(new Vector3(gridSpacing * i, -gridSpacing, 0) + newOffset, new Vector3(gridSpacing * i, position.height+gridSpacing, 0f) + newOffset);
}
for (int j = 0; j <= heightDivs; j++)
{
Handles.DrawLine(new Vector3(-gridSpacing, gridSpacing * j, 0) + newOffset, new Vector3(position.width+gridSpacing, gridSpacing * j, 0f) + newOffset);
}
Handles.color = Color.white;
Handles.EndGUI();
}
private class NodeHandle
{
public Node node;
public Vector2 position;
public HandleType type;
public NodeHandle(Node node, Vector2 position, HandleType type)
{
this.node = node;
this.position = position;
this.type = type;
}
}
private enum HandleType
{
Input, Output
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 238d35a99c7b4b14da156f1c64f9e342
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,210 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.Linq;
using System.Reflection;
using MBT;
namespace MBTEditor
{
[CustomEditor(typeof(Blackboard))]
public class BlackboardEditor : Editor
{
const double CONSTANT_REPAINT_INTERVAL = 0.5d;
readonly string[] varOptions = new string[]{"Delete"};
SerializedProperty variables;
SerializedProperty masterBlackboardProperty;
GUIStyle popupStyle;
string newVariableKey = "";
string[] variableTypesNames = new string[0];
Type[] variableTypes = new Type[0];
int selectedVariableType = 0;
Blackboard blackboard;
GameObject blackboardGameObject;
bool showVariables = true;
private double lastRepaint;
void OnEnable()
{
// Set hide flags in case object was duplicated or turned into prefab
if (target != null)
{
Blackboard bb = (Blackboard) target;
// Sample one variable and check if its hidden. Hide all varialbes if sample is visible.
if (bb.TryGetComponent<BlackboardVariable>(out BlackboardVariable bv) && bv.hideFlags != HideFlags.HideInInspector)
{
BlackboardVariable[] vars = bb.GetComponents<BlackboardVariable>();
for (int i = 0; i < vars.Length; i++)
{
vars[i].hideFlags = HideFlags.HideInInspector;
}
}
}
// Init
variables = serializedObject.FindProperty("variables");
masterBlackboardProperty = serializedObject.FindProperty("masterBlackboard");
blackboard = target as Blackboard;
blackboardGameObject = blackboard.gameObject;
SetupVariableTypes();
}
void OnDestroy()
{
// Remove all variables in case Blackboard was removed
if (Application.isEditor && (Blackboard)target == null && blackboardGameObject != null)
{
// Additional check to avoid errors when exiting playmode
if (Application.IsPlaying(blackboardGameObject) || blackboardGameObject.GetComponent<Blackboard>() != null)
{
return;
}
BlackboardVariable[] blackboardVariables = blackboardGameObject.GetComponents<BlackboardVariable>();
for (int i = 0; i < blackboardVariables.Length; i++)
{
Undo.DestroyObjectImmediate(blackboardVariables[i]);
}
}
}
public override bool RequiresConstantRepaint()
{
return Application.isPlaying && EditorApplication.timeSinceStartup > lastRepaint + CONSTANT_REPAINT_INTERVAL;
}
private void SetupVariableTypes()
{
List<Type> types = new List<Type>();
List<string> names = new List<string>();
// Find all types
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
IEnumerable<Type> enumerable = assembly.GetTypes()
.Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(BlackboardVariable)));
foreach (Type type in enumerable)
{
names.Add(type.Name);
types.Add(type);
}
}
variableTypesNames = names.ToArray();
variableTypes = types.ToArray();
}
public override void OnInspectorGUI()
{
// Update repaint timer
lastRepaint = EditorApplication.timeSinceStartup;
// Init styles
if (popupStyle == null) {
popupStyle = new GUIStyle(GUI.skin.GetStyle("PaneOptions"));
popupStyle.imagePosition = ImagePosition.ImageOnly;
popupStyle.margin.top += 3;
}
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(masterBlackboardProperty);
EditorGUILayout.Space();
if (EditorGUI.EndChangeCheck()) {
serializedObject.ApplyModifiedProperties();
}
// Fields used to add variables
EditorGUILayout.LabelField("Create Variable", EditorStyles.boldLabel);
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Key", GUILayout.MaxWidth(80));
newVariableKey = EditorGUILayout.TextField(newVariableKey);
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Type", GUILayout.MaxWidth(80));
selectedVariableType = EditorGUILayout.Popup(selectedVariableType, variableTypesNames);
GUI.SetNextControlName("AddButton");
if (GUILayout.Button("Add", EditorStyles.miniButton)) {
CreateVariableAndResetInput();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
// DrawDefaultInspector();
// EditorGUILayout.Space();
// serializedObject.Update();
// EditorGUI.BeginChangeCheck();
showVariables = EditorGUILayout.BeginFoldoutHeaderGroup(showVariables, "Variables");
if(showVariables){
SerializedProperty vars = variables.Copy();
if (vars.isArray) {
// xxx: Why this line existed? Why EventType.DragPerform is not allowed here? (maybe BeginChangeCheck)
// if (vars.isArray && Event.current.type != EventType.DragPerform) {
for (int i = 0; i < vars.arraySize; i++)
{
EditorGUI.BeginChangeCheck();
int popupOption = -1;
SerializedProperty serializedV = vars.GetArrayElementAtIndex(i);
SerializedObject serializedVariable = new SerializedObject(serializedV.objectReferenceValue);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(
new GUIContent(serializedVariable.FindProperty("key").stringValue,
serializedVariable.targetObject.GetType().Name)
);
int v = EditorGUILayout.Popup(popupOption, varOptions, popupStyle, GUILayout.MaxWidth(20));
EditorGUILayout.PropertyField(serializedVariable.FindProperty("val"), GUIContent.none);
EditorGUILayout.EndHorizontal();
if (EditorGUI.EndChangeCheck()) {
serializedVariable.ApplyModifiedProperties();
}
// Delete on change
if (v != popupOption) {
DeleteVariabe(serializedV.objectReferenceValue as BlackboardVariable);
}
}
}
}
EditorGUILayout.EndFoldoutHeaderGroup();
EditorGUILayout.Space();
// if (EditorGUI.EndChangeCheck()) {
// serializedObject.ApplyModifiedProperties();
// }
}
private void DeleteVariabe(BlackboardVariable blackboardVariable)
{
Undo.RecordObject(blackboard, "Delete Blackboard Variable");
blackboard.variables.Remove(blackboardVariable);
Undo.DestroyObjectImmediate(blackboardVariable);
}
private void CreateVariableAndResetInput()
{
// Validate field. Key "None" is not allowed.
if (string.IsNullOrEmpty(newVariableKey) || newVariableKey.Equals("None")) {
return;
}
string k = new string( newVariableKey.ToCharArray().Where(c => !Char.IsWhiteSpace(c)).ToArray() );
// Check for key duplicates
for (int i = 0; i < blackboard.variables.Count; i++)
{
if (blackboard.variables[i].key == k) {
Debug.LogWarning("Variable '"+k+"' already exists.");
return;
}
}
// Add variable
Undo.RecordObject(blackboard, "Create Blackboard Variable");
BlackboardVariable var = Undo.AddComponent(blackboard.gameObject, variableTypes[selectedVariableType]) as BlackboardVariable;
var.hideFlags = HideFlags.HideInInspector;
var.key = k;
blackboard.variables.Add(var);
// Reset field
newVariableKey = "";
GUI.FocusControl("Clear");
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b2b8d2392660b6e46bcbb8bd44a1be30
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,126 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using MBT;
using System;
namespace MBTEditor
{
[CustomEditor(typeof(InvokeUnityEvent))]
public class InvokeUnityEventEditor : Editor
{
SerializedProperty titleProp;
SerializedProperty typeProp;
private SerializedProperty transformEventProp;
private SerializedProperty gameObjectEventProp;
private SerializedProperty floatEventProp;
private SerializedProperty intEventProp;
private SerializedProperty boolEventProp;
private SerializedProperty stringEventProp;
private SerializedProperty vector3EventProp;
private SerializedProperty vector2EventProp;
private SerializedProperty transformReferenceProp;
private SerializedProperty gameObjectReferenceProp;
private SerializedProperty floatReferenceProp;
private SerializedProperty intReferenceProp;
private SerializedProperty boolReferenceProp;
private SerializedProperty stringReferenceProp;
private SerializedProperty vector3ReferenceProp;
private SerializedProperty vector2ReferenceProp;
void OnEnable()
{
titleProp = serializedObject.FindProperty("title");
typeProp = serializedObject.FindProperty("type");
transformEventProp = serializedObject.FindProperty("transformEvent");
gameObjectEventProp = serializedObject.FindProperty("gameObjectEvent");
floatEventProp = serializedObject.FindProperty("floatEvent");
intEventProp = serializedObject.FindProperty("intEvent");
boolEventProp = serializedObject.FindProperty("boolEvent");
stringEventProp = serializedObject.FindProperty("stringEvent");
vector3EventProp = serializedObject.FindProperty("vector3Event");
vector2EventProp = serializedObject.FindProperty("vector2Event");
transformReferenceProp = serializedObject.FindProperty("transformReference");
gameObjectReferenceProp = serializedObject.FindProperty("gameObjectReference");
floatReferenceProp = serializedObject.FindProperty("floatReference");
intReferenceProp = serializedObject.FindProperty("intReference");
boolReferenceProp = serializedObject.FindProperty("boolReference");
stringReferenceProp = serializedObject.FindProperty("stringReference");
vector3ReferenceProp = serializedObject.FindProperty("vector3Reference");
vector2ReferenceProp = serializedObject.FindProperty("vector2Reference");
}
private static readonly GUIContent variableLabel = new GUIContent("Parameter");
private static readonly GUIContent eventLabel = new GUIContent("Event");
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(titleProp);
EditorGUILayout.PropertyField(typeProp);
EditorGUILayout.Space();
if (GetSerializedProperties(out SerializedProperty eventProp, out SerializedProperty variableProp))
{
EditorGUILayout.PropertyField(variableProp, variableLabel);
EditorGUILayout.PropertyField(eventProp, eventLabel);
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
private bool GetSerializedProperties(out SerializedProperty eventProp, out SerializedProperty referenceProp)
{
InvokeUnityEvent.EventType eventType = (InvokeUnityEvent.EventType)typeProp.enumValueIndex;
switch (eventType)
{
case InvokeUnityEvent.EventType.Transform:
eventProp = transformEventProp;
referenceProp = transformReferenceProp;
return true;
case InvokeUnityEvent.EventType.Float:
eventProp = floatEventProp;
referenceProp = floatReferenceProp;
return true;
case InvokeUnityEvent.EventType.GameObject:
eventProp = gameObjectEventProp;
referenceProp = gameObjectReferenceProp;
return true;
case InvokeUnityEvent.EventType.Int:
eventProp = intEventProp;
referenceProp = intReferenceProp;
return true;
case InvokeUnityEvent.EventType.String:
eventProp = stringEventProp;
referenceProp = stringReferenceProp;
return true;
case InvokeUnityEvent.EventType.Vector3:
eventProp = vector3EventProp;
referenceProp = vector3ReferenceProp;
return true;
case InvokeUnityEvent.EventType.Vector2:
eventProp = vector2EventProp;
referenceProp = vector2ReferenceProp;
return true;
case InvokeUnityEvent.EventType.Bool:
eventProp = boolEventProp;
referenceProp = boolReferenceProp;
return true;
default:
eventProp = null;
referenceProp = null;
return false;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 007923a936f7a7d4a9e8b8bc59f97592
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,60 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using MBT;
namespace MBTEditor
{
[CustomEditor(typeof(IsSetCondition))]
public class IsSetConditionEditor : Editor
{
SerializedProperty titleProp;
SerializedProperty abortProp;
SerializedProperty boolReferenceProp;
SerializedProperty objectReferenceProp;
SerializedProperty transformReferenceProp;
SerializedProperty typeProp;
SerializedProperty invertProp;
void OnEnable()
{
titleProp = serializedObject.FindProperty("title");
boolReferenceProp = serializedObject.FindProperty("boolReference");
objectReferenceProp = serializedObject.FindProperty("objectReference");
transformReferenceProp = serializedObject.FindProperty("transformReference");
abortProp = serializedObject.FindProperty("abort");
typeProp = serializedObject.FindProperty("type");
invertProp = serializedObject.FindProperty("invert");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(titleProp);
EditorGUILayout.PropertyField(abortProp);
EditorGUILayout.PropertyField(invertProp);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(typeProp);
if (typeProp.enumValueIndex == (int)IsSetCondition.Type.Boolean)
{
EditorGUILayout.PropertyField(boolReferenceProp, new GUIContent("Variable"));
}
else if (typeProp.enumValueIndex == (int)IsSetCondition.Type.GameObject)
{
EditorGUILayout.PropertyField(objectReferenceProp, new GUIContent("Variable"));
}
else
{
EditorGUILayout.PropertyField(transformReferenceProp, new GUIContent("Variable"));
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 45cfc361407c0f4448f74c8395194f6a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,87 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using MBT;
namespace MBTEditor
{
[CustomEditor(typeof(MonoBehaviourTree))]
public class MonoBehaviourTreeEditor : Editor
{
private GUIStyle boxStyle;
private GUIStyle foldStyle;
private Editor nodeEditor;
void InitStyle()
{
if (foldStyle == null)
{
boxStyle = new GUIStyle(EditorStyles.helpBox);
foldStyle = new GUIStyle(EditorStyles.foldoutHeader);
foldStyle.onNormal = foldStyle.onFocused;
}
}
void OnEnable()
{
// Set hide flags in case object was duplicated or turned into prefab
if (target == null)
{
return;
}
MonoBehaviourTree mbt = (MonoBehaviourTree) target;
// Sample one component and check if its hidden. Hide all nodes if sample is visible.
if (mbt.TryGetComponent<Node>(out Node n) && n.hideFlags != HideFlags.HideInInspector)
{
Node[] nodes = mbt.GetComponents<Node>();
for (int i = 0; i < nodes.Length; i++)
{
nodes[i].hideFlags = HideFlags.HideInInspector;
}
}
}
void OnDisable()
{
// Destroy editor if there is any
if (nodeEditor != null)
{
DestroyImmediate(nodeEditor);
}
}
public override void OnInspectorGUI()
{
InitStyle();
DrawDefaultInspector();
GUILayout.Space(5);
if (GUILayout.Button("Open editor")) {
BehaviourTreeWindow.OpenEditor();
}
EditorGUILayout.Space();
MonoBehaviourTree mbt = ((MonoBehaviourTree) target);
bool renderNodeInspector = mbt.selectedEditorNode != null;
EditorGUILayout.Foldout(renderNodeInspector, "Node inspector", foldStyle);
EditorGUILayout.Space(1);
if (renderNodeInspector)
{
EditorGUILayout.BeginHorizontal(boxStyle);
GUILayout.Space(3);
EditorGUILayout.BeginVertical();
GUILayout.Space(5);
Editor.CreateCachedEditor(mbt.selectedEditorNode, null, ref nodeEditor);
nodeEditor.OnInspectorGUI();
GUILayout.Space(5);
EditorGUILayout.EndVertical();
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.Space();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f78abb7d7a304aa41a91ef451cc8cb6e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,143 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.Linq;
using System.Reflection;
using UnityEditor.IMGUI.Controls;
using MBT;
namespace MBTEditor
{
public class ClassTypeDropdownItem : AdvancedDropdownItem
{
public Type classType;
public int order;
public string path;
public ClassTypeDropdownItem(string name, Type type = null, int order = 1000, string path = "") : base(name)
{
this.classType = type;
this.order = order;
this.path = path;
}
}
public class NodeDropdown : AdvancedDropdown
{
protected Action<ClassTypeDropdownItem> Callback;
public NodeDropdown(AdvancedDropdownState state, Action<ClassTypeDropdownItem> callback) : base(state)
{
this.Callback = callback;
minimumSize = new Vector2(230,320);
}
protected override AdvancedDropdownItem BuildRoot()
{
var root = new ClassTypeDropdownItem("Nodes");
// List for all found subclasses
List<Type> results = new List<Type>();
// Search all assemblies
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
// Find all subclasses of Node
IEnumerable<Type> enumerable = assembly.GetTypes()
.Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(Node)));
results.AddRange(enumerable);
}
// Keep track of all paths to correctly build tree later
Dictionary<string, ClassTypeDropdownItem> nodePathsDictionary = new Dictionary<string, ClassTypeDropdownItem>();
nodePathsDictionary.Add("", root);
// Create list of items
List<ClassTypeDropdownItem> items = new List<ClassTypeDropdownItem>();
foreach (Type type in results)
{
if(type.IsDefined(typeof(MBTNode), false))
{
MBTNode nodeMeta = type.GetCustomAttribute<MBTNode>();
string itemName;
string nodePath = "";
if (String.IsNullOrEmpty(nodeMeta.name))
{
itemName = type.Name;
}
else
{
string[] path = nodeMeta.name.Split('/');
itemName = path[path.Length-1];
nodePath = BuildPathIfNotExists(path, ref nodePathsDictionary);
}
ClassTypeDropdownItem classTypeDropdownItem = new ClassTypeDropdownItem(itemName, type, nodeMeta.order, nodePath);
if (nodeMeta.icon != null)
{
classTypeDropdownItem.icon = Resources.Load(nodeMeta.icon, typeof(Texture2D)) as Texture2D;
}
items.Add(classTypeDropdownItem);
}
}
// Sort items
items.Sort((x, y) => {
int result = x.order.CompareTo(y.order);
return result != 0 ? result : x.name.CompareTo(y.name);
});
// Add all nodes to menu
for (int i = 0; i < items.Count; i++)
{
nodePathsDictionary[items[i].path].AddChild(items[i]);
}
// Remove root to avoid infinite root folder loop
nodePathsDictionary.Remove("");
List<ClassTypeDropdownItem> parentNodes = nodePathsDictionary.Values.ToList();
parentNodes.Sort((x, y) => {
return x.name.CompareTo(y.name);
});
// Add folders
for (int i = 0; i < parentNodes.Count(); i++)
{
root.AddChild(parentNodes[i]);
}
return root;
}
protected override void ItemSelected(AdvancedDropdownItem item)
{
Callback(item as ClassTypeDropdownItem);
}
/// <summary>
/// Creates nodes if path does not exists. Supports only signle level folders.
/// </summary>
/// <param name="path">Path to build. Last element should be actual node name.</param>
/// <param name="dictionary">Reference to dictionary to store references to items</param>
/// <returns>Path to provided node in path</returns>
protected string BuildPathIfNotExists(string[] path, ref Dictionary<string, ClassTypeDropdownItem> dictionary)
{
// IMPORTANT: This code supports only single level folders. Nodes can't be nested more than one level.
if (path.Length != 2)
{
return "";
}
AdvancedDropdownItem root = dictionary[""];
// // This code assumes the last element of path is actual name of node
// string nodePath = String.Join("/", path, 0, path.Length-1);
string nodePath = path[0];
// Create path nodes if does not exists
if(!dictionary.ContainsKey(nodePath))
{
ClassTypeDropdownItem node = new ClassTypeDropdownItem(nodePath);
dictionary.Add(nodePath, node);
}
return nodePath;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: adc3d2af9116c5b418b63f5da3dc6feb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,66 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using MBT;
namespace MBTEditor
{
[CustomEditor(typeof(NumberCondition))]
public class NumberConditionEditor : Editor
{
SerializedProperty titleProp;
SerializedProperty abortProp;
SerializedProperty floatReferenceProp;
SerializedProperty intReferenceProp;
SerializedProperty floatReference2Prop;
SerializedProperty intReference2Prop;
SerializedProperty typeProp;
SerializedProperty comparatorProp;
void OnEnable()
{
titleProp = serializedObject.FindProperty("title");
floatReferenceProp = serializedObject.FindProperty("floatReference");
intReferenceProp = serializedObject.FindProperty("intReference");
floatReference2Prop = serializedObject.FindProperty("floatReference2");
intReference2Prop = serializedObject.FindProperty("intReference2");
abortProp = serializedObject.FindProperty("abort");
typeProp = serializedObject.FindProperty("type");
comparatorProp = serializedObject.FindProperty("comparator");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(titleProp);
EditorGUILayout.PropertyField(abortProp);
EditorGUILayout.PropertyField(typeProp);
EditorGUILayout.Space();
// GUILayout.Label("Condition");
if (typeProp.enumValueIndex == (int)NumberCondition.Type.Float)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(floatReferenceProp, GUIContent.none);
EditorGUILayout.PropertyField(comparatorProp, GUIContent.none, GUILayout.MaxWidth(60f));
EditorGUILayout.PropertyField(floatReference2Prop, GUIContent.none);
EditorGUILayout.EndHorizontal();
}
else
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(intReferenceProp, GUIContent.none);
EditorGUILayout.PropertyField(comparatorProp, GUIContent.none, GUILayout.MaxWidth(60f));
EditorGUILayout.PropertyField(intReference2Prop, GUIContent.none);
EditorGUILayout.EndHorizontal();
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b9e3631627d49b4f8349f9efdf7d65a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,17 @@
{
"name": "Qriva.MonoBehaviourTree.Editor",
"references": [
"GUID:b94eb722e14bb88498f76d28e55be3fa"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c2da7737aab07f248add22487334a422
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: af68a1f59256fd94f95d09ca43a95485
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,93 @@
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 217896aaf46e40f4bb3a0d03862bac36
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,23 @@
fileFormatVersion: 2
guid: 4230cef5e36d34240987d46954f779ad
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontName: Lato
fontNames:
- Lato
fallbackFontReferences:
- {fileID: 12800000, guid: 7dbc24fe8e0f7e34ebaaf3637032d72e, type: 3}
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@
fileFormatVersion: 2
guid: 7dbc24fe8e0f7e34ebaaf3637032d72e
TrueTypeFontImporter:
externalObjects: {}
serializedVersion: 4
fontSize: 16
forceTextureCase: -2
characterSpacing: 0
characterPadding: 1
includeFontData: 1
fontName: Lato
fontNames:
- Lato
fallbackFontReferences: []
customCharacters:
fontRenderingMode: 0
ascentCalculationMode: 1
useLegacyBoundsCalculation: 0
shouldRoundAdvanceValue: 1
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: cf1e8cb5def7ab848bce542b344bd1aa
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: befeed4cb9f7e5244a25b4a259043d62
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: f5dae9a45f93952469a039fd15b3734c
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: 0fc36e2d5dcad514c94db17908f0f6ed
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: d7731c28d00532743aa0d1a873443d46
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: a9a060d6ca9704e42914638f91bda04e
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: 7c5d9dfbf21fcf746854f7ccbe787ebb
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@ -0,0 +1,139 @@
fileFormatVersion: 2
guid: 8e319fb1e46299e4bb368bf700097d39
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 10
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: -1
aniso: 1
mipBias: -100
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Windows Store Apps
maxTextureSize: 8192
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spritePackingTag:
pSDRemoveMatte: 0
pSDShowRemoveMatteOption: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,66 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using MBT;
namespace MBTEditor
{
[CustomEditor(typeof(SetNumber))]
public class SetNumberEditor : Editor
{
SerializedProperty titleProp;
SerializedProperty typeProp;
SerializedProperty operationProp;
SerializedProperty sourceIntProp;
SerializedProperty sourceFloatProp;
SerializedProperty destinationFloatProp;
SerializedProperty destinationIntProp;
void OnEnable()
{
titleProp = serializedObject.FindProperty("title");
typeProp = serializedObject.FindProperty("type");
operationProp = serializedObject.FindProperty("operation");
sourceFloatProp = serializedObject.FindProperty("sourceFloat");
sourceIntProp = serializedObject.FindProperty("sourceInt");
destinationFloatProp = serializedObject.FindProperty("destinationFloat");
destinationIntProp = serializedObject.FindProperty("destinationInt");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(titleProp);
EditorGUILayout.PropertyField(typeProp);
EditorGUILayout.Space();
const int floatIndex = 0;
if (typeProp.enumValueIndex == floatIndex)
{
// Float
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(destinationFloatProp, GUIContent.none);
EditorGUILayout.PropertyField(operationProp, GUIContent.none, GUILayout.MaxWidth(60f));
EditorGUILayout.PropertyField(sourceFloatProp, GUIContent.none);
EditorGUILayout.EndHorizontal();
}
else
{
// Int
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(destinationIntProp, GUIContent.none);
EditorGUILayout.PropertyField(operationProp, GUIContent.none, GUILayout.MaxWidth(60f));
EditorGUILayout.PropertyField(sourceIntProp, GUIContent.none);
EditorGUILayout.EndHorizontal();
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2ef8aa66218976b48a2b3fe367e420c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,61 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using MBT;
namespace MBTEditor
{
[CustomEditor(typeof(SetObject))]
public class SetObjectEditor : Editor
{
SerializedProperty titleProp;
SerializedProperty typeProp;
SerializedProperty sourceTransformProp;
SerializedProperty sourceGameObjectProp;
SerializedProperty destinationTransformProp;
SerializedProperty destinationGameObjectProp;
private static readonly GUIContent destinationLabel = new GUIContent("Destination");
private static readonly GUIContent sourceLabel = new GUIContent("Source");
void OnEnable()
{
titleProp = serializedObject.FindProperty("title");
typeProp = serializedObject.FindProperty("type");
sourceGameObjectProp = serializedObject.FindProperty("sourceGameObject");
sourceTransformProp = serializedObject.FindProperty("sourceTransform");
destinationGameObjectProp = serializedObject.FindProperty("destinationGameObject");
destinationTransformProp = serializedObject.FindProperty("destinationTransform");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(titleProp);
EditorGUILayout.PropertyField(typeProp);
EditorGUILayout.Space();
const int transformIndex = 0;
if (typeProp.enumValueIndex == transformIndex)
{
// Transform
EditorGUILayout.PropertyField(destinationTransformProp, destinationLabel);
EditorGUILayout.PropertyField(sourceTransformProp, sourceLabel);
}
else
{
// GameObject
EditorGUILayout.PropertyField(destinationGameObjectProp, destinationLabel);
EditorGUILayout.PropertyField(sourceGameObjectProp, sourceLabel);
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2d4187215e1e7f48b43d3034eebc775
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,65 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using MBT;
namespace MBTEditor
{
[CustomEditor(typeof(SetVector))]
public class SetVectorEditor : Editor
{
SerializedProperty titleProp;
SerializedProperty typeProp;
SerializedProperty sourceVector2Prop;
SerializedProperty sourceVector3Prop;
SerializedProperty destinationVector2Prop;
SerializedProperty destinationVector3Prop;
private static readonly GUIContent destinationLabel = new GUIContent("Destination");
private static readonly GUIContent sourceLabel = new GUIContent("Source");
void OnEnable()
{
titleProp = serializedObject.FindProperty("title");
typeProp = serializedObject.FindProperty("type");
sourceVector3Prop = serializedObject.FindProperty("sourceVector3");
sourceVector2Prop = serializedObject.FindProperty("sourceVector2");
destinationVector3Prop = serializedObject.FindProperty("destinationVector3");
destinationVector2Prop = serializedObject.FindProperty("destinationVector2");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(titleProp);
EditorGUILayout.PropertyField(typeProp);
EditorGUILayout.Space();
const int vector3Index = 1;
if (typeProp.enumValueIndex == vector3Index)
{
// Vector3
// EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(destinationVector3Prop, destinationLabel);
EditorGUILayout.PropertyField(sourceVector3Prop, sourceLabel);
// EditorGUILayout.EndHorizontal();
}
else
{
// Vector2
// EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(destinationVector2Prop, destinationLabel);
EditorGUILayout.PropertyField(sourceVector2Prop, sourceLabel);
// EditorGUILayout.EndHorizontal();
}
if (EditorGUI.EndChangeCheck())
{
serializedObject.ApplyModifiedProperties();
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8a7915226b0ecd4b91fb6fcd415772a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,133 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using MBT;
namespace MBTEditor
{
[CustomPropertyDrawer(typeof(BaseVariableReference), true)]
public class VariableReferenceDrawer : PropertyDrawer
{
private GUIStyle constVarGUIStyle = new GUIStyle("MiniButton");
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
EditorGUI.BeginChangeCheck();
position.height = 18f;
SerializedProperty keyProperty = property.FindPropertyRelative("key");
SerializedProperty blackboardProperty = property.FindPropertyRelative("blackboard");
SerializedProperty useConstProperty = property.FindPropertyRelative("useConstant");
MonoBehaviour inspectedComponent = property.serializedObject.targetObject as MonoBehaviour;
// search only in the same game object
if (inspectedComponent != null)
{
// Blackboard blackboard = inspectedComponent.GetComponent<Blackboard>();
Blackboard blackboard = GetBlackboardInParent(inspectedComponent);
if (blackboard != null)
{
// Draw mode toggle if not disabled
if (property.FindPropertyRelative("mode").enumValueIndex == 0)
{
Rect togglePosition = position;
togglePosition.width = 8;
togglePosition.height = 16;
useConstProperty.boolValue = EditorGUI.Toggle(togglePosition, useConstProperty.boolValue, constVarGUIStyle);
position.xMin += 10;
}
// Draw constant or dropdown
if (useConstProperty.boolValue)
{
// Use constant variable
EditorGUI.PropertyField(position, property.FindPropertyRelative("constantValue"), label);
}
else
{
System.Type desiredVariableType = fieldInfo.FieldType.BaseType.GetGenericArguments()[0];
BlackboardVariable[] variables = blackboard.GetAllVariables();
List<string> keys = new List<string>();
keys.Add("None");
for (int i = 0; i < variables.Length; i++)
{
BlackboardVariable bv = variables[i];
if (bv.GetType() == desiredVariableType) {
keys.Add(bv.key);
}
}
// Setup dropdown
// INFO: "None" can not be used as key
int selected = keys.IndexOf(keyProperty.stringValue);
if (selected < 0) {
selected = 0;
// If key is not empty it means variable was deleted and missing
if (!System.String.IsNullOrEmpty(keyProperty.stringValue))
{
keys[0] = "Missing";
}
}
int result = EditorGUI.Popup(position, label.text, selected, keys.ToArray());
if (result > 0) {
keyProperty.stringValue = keys[result];
blackboardProperty.objectReferenceValue = blackboard;
} else {
keyProperty.stringValue = "";
blackboardProperty.objectReferenceValue = null;
}
}
}
else
{
EditorGUI.LabelField(position, property.displayName);
int indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 1;
position.y += EditorGUI.GetPropertyHeight(keyProperty);// + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(position, keyProperty);
position.y += EditorGUI.GetPropertyHeight(blackboardProperty);// + EditorGUIUtility.standardVerticalSpacing;
EditorGUI.PropertyField(position, blackboardProperty);
EditorGUI.indentLevel = indent;
}
}
if (EditorGUI.EndChangeCheck()) {
property.serializedObject.ApplyModifiedProperties();
}
EditorGUI.EndProperty();
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
MonoBehaviour monoBehaviour = property.serializedObject.targetObject as MonoBehaviour;
if (monoBehaviour != null && GetBlackboardInParent(monoBehaviour) == null) {
return 3 * (EditorGUIUtility.standardVerticalSpacing + 16);
}
return 16 + EditorGUIUtility.standardVerticalSpacing;
}
/// <summary>
/// Find Blackboard in parent including inactive game objects
/// </summary>
/// <param name="component">Component to search</param>
/// <returns>Blackboard if found, otherwise null</returns>
protected Blackboard GetBlackboardInParent(Component component)
{
Transform current = component.transform;
Blackboard result = null;
while (current != null && result == null)
{
if (current.TryGetComponent<Blackboard>(out Blackboard b))
{
result = b;
}
current = current.parent;
}
return result;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ab4047381b703f94a844906084b35480
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Qriva
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 253fb677ccf902d4c83f13ae18604c9f
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,341 @@
# MonoBehaviourTree — Simple behaviour tree for Unity
This project is simple event driven behaviour tree based on Unity engine component system. This asset comes with minimal node library and tree visual editor. [Online version of this readme](https://github.com/Qriva/MonoBehaviourTree/blob/master/README.md)
**Important:** This is not fully fledged visual scripting tool. Package has its own visual editor, however requires you to implement your own nodes.
## Contribute
Contribution in any form is very welcome. Bugs, feature requests or feedback can be reported in form of Issues.
## Getting started
The latest version can be installed via [package manager](https://docs.unity3d.com/Manual/upm-ui-giturl.html) using following git URL:<br> ```https://github.com/Qriva/MonoBehaviourTree.git#upm```
<br>Alternatively you can copy the `Assets/MonoBehaviourTree` folder to your project or download from [Unity Asset Store](https://assetstore.unity.com/packages/slug/213452).
Examples of usage are available in package manager or in folder **Samples** containing demo scenes. If you copy assets manually you might want to delete `Samples` directory to get rid of redundant files.
This documentation assumes you have basic knowledge about behaviour trees. If you don't have it, you should check some online resources like this
[Game Developer article](https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work)
or [Unreal Engine documentation](https://docs.unrealengine.com/en-US/Engine/ArtificialIntelligence/BehaviorTrees/BehaviorTreesOverview/index.html).
## Event Driven Behaviour Tree Overview
Standard behaviour tree design assumes three types of nodes: composites, decorators and leafs. Composites are used to define the flow in the tree, decorators can modify node results and leafs perform tasks or check conditions. This design has one major flaw - tree must be traversed from the beginning every single tick, otherwise it will not be possible to react to changes in state or data. Event driven tree is the fix to that problem. When tree gets update it continues from the last executed running node. Normally it would mean, that executed higher priority nodes will not be reevaluated immediately, but event driven BT introduces **abort system** to give possibility to reset tree to previous state, when certain event occur. Implementation used in this project is very similar to the one used in Unreal engine - leaf nodes are not used as conditions, instead they are in form of decorators. Additionally it is possible to create Service nodes which can perform task periodically.
### Node Abort
When the tree is updated, the first evaluated thing are aborting nodes. If there is any aborting node, the tree will be reset to that position and execution will be continued from this node.
In case there are multiple aborting nodes, the one closest to root will be selected.
There are four abort types:
- **None** - don't do anything when change occurs
- **Self** - abort children running below
- **Lower Priority** - abort running nodes with lower priority (nodes to the right)
- **Both** - abort self and lower priority nodes
>Execution order (priority) of nodes with common ancestor is defined by position on X axis, nodes to the left has higher priority.
Aborts can be performed only by ```Decorator``` nodes. See example abort implementation in [Decorator](#custom-decorator--condition) section.
## Basic Usage
The main core of behaviour tree is **MonoBehaviourTree** component. It contains most of tree state during runtime. It is important to note, that tree does not run automatically and must be updated by other script. This design gives you possibility to tick the tree in Update, FixedUpdate or custom interval. However, most of the time Update event will be used, so you can use component **MBT Executor** to do that.
In most of cases you will need some place to store shared data for nodes. You could implement your own component to do that, but instead it's better to use built in **Blackboard** component. Blackboard allows you to create observable variables of predefined types that are compatible with default nodes.
## Node editor
To open tree editor window click "Open editor" button of MonoBehaviourTree component or click Unity menu: Window / Mono Behaviour Tree. In visual editor you can create, connect, delete and setup nodes.
Every behaviour tree needs an entry point called **Root**. To add it right click on empty canvas to open node popup, then select Root. Execution of BT starts here and goes from top to down, left to right.
> **Implementation note:** All nodes and variables are in fact components, but they are invisible in inspector window.
> It is recommended to use separate empty game object to build the tree - this make it easier to create prefabs and avoid unnecessary unknown problems.
Most of nodes has additional properties that you can change. To do this select the node and list of options will show up in **MonoBehaviourTree component inspector** section (standard component inspector). When node setup is not valid, error icon will be shown next to the node. By default error is displayed if one of variable references is set to "None". You can create custom validation rules in your own nodes, see [Node API section](#node-api).
### Editor Window Features
Right click on empty space to create new node. To connect nodes click on in/out handler (black dot on top and bottom of node), then drag and drop it above another node. In case node cannot have more than one child (decorator) the connection will be overridden by the new one.
To delete, duplicate or disconnect nodes right click on node to open context menu and select the appropriate option.
Use left mouse button to drag workspace or nodes. You can drag whole branch of nodes when CTRL key is pressed.
## Component Reference
### MonoBehaviourTree component
Main component used as hub of behaviour tree.
**Properties**
- **Description** - optional user description.
- **Repeat OnFinish** - whether the tree should be executed again when finished.
- **Max Executions Per Tick** - how many nodes should be executed during single update.
- **Parent** - parent reference if this tree is subtree. Read more in [Subtree node section](#subtree).
**Node Inspector** - Inspector of node selected in Behaviour Tree editor window.
### Blackboard component
Component used to provide and manage observable variables.
To add variable fill the **Key** text field, select it's type and press "Add" button. Key is used as identifier to get or set variable value, this can be done by VariableReference or blackboard method: ```public T GetVariable<T>(string key)```.
Blackboard component displays all available variables in list and allows to set initial value for each of them.
> **Implementation note:** Changing blackboard variables during playmode triggers change listeners, however old and new value will be the same in this event, because it's called from `OnValidate`. Displayed values are refreshed every half second.
**Built In variable types:** Bool, Float, Int, Object, Quaternion, String, Transform, Vector2, Vector3. If you need to add your own custom type read [Custom Variable section](#custom-variable).
**Master Blackboard** option allows to make synchronisation effect between variables. When set, blackboard will replace variables during initialization, but only when their keys match. Replacement is not hierarhical and only variables from given parent blackboard are matched. It is recommended to create one master blackboard on top of the tree and keep all other subtrees blackboards synchronized with the top one.
## Variables and Events
In most of situations nodes need to share some state data between each other, it can be done by Blackboard, Variable and VariableReference system. Variables are observale data containers, that can be accesed via Blackboard. To get variable you need to know its key, but inputting key manually to every node is not handy and very error prone.
To avoid this you can use helper class VariableReference.
This class allows you to automaticaly get and cache reference to blackboard variable.
VariableReference has also constant value mode in case you don't need to retrive values from blackboard. You can toggle VarRef mode in editor by clicking small button to the left. Keys displayed in dropdown will be loaded from blackboard on the same object or if there is none it will look upwards the hierarchy to find one.
```csharp
// Get variable from blackboard by key
FloatVariable floatVar = blackboard.GetVariable<FloatVariable>("myKey");
// Attach listener to variable
floatVar.AddListener(MyVariableChangeListener);
// Create float reference property with default constant value
public FloatReference floatRef = new FloatReference(1f);
// Create int reference, but do not allow constant values
public IntReference intRef = new IntReference(VarRefMode.DisableConstant);
// Check if its in constant or reference mode
bool constant = floatRef.isConstant;
// Attach listener to variable reference
if (!constant)
{
// GetVariable will return null if floatRef is constant
floatRef.GetVariable().AddListener(MyVariableChangeListener);
}
// Get or set value of variable reference
floatRef.Value = 1.5f;
float value = floatRef.Value;
```
> **Important:** TransformVariable change listener will be called only when reference to object changes. Position or rotation changes do not trigger change listener.
### Custom Variable
If built in variables are not enough, you can create your own.
To create new Variable and VariableReference you must extend Variable class and VariableReference class. Variable inheriths MonoBehaviour, so to work properly it must by placed in file of the same name as your custom type. VariableReference is normal serializable class and can be placed in the same file. Add ```[AddComponentMenu("")]``` attribute to disallow adding variable component manually.
Any variable must implement ValueEquals method which is used to detect change of value. This mechanism allows to correctly compare Unity objects in generic class, avoid boxing, plus gives the way to implement your own change detection logic when needed.
```csharp
[AddComponentMenu("")]
public class CustomVariable : Variable<CustomType>
{
protected override bool ValueEquals(CustomType val1, CustomType val2)
{
return val1 == val2;
}
}
[System.Serializable]
public class CustomReference : VariableReference<CustomVariable, CustomType>
{
// You can create additional constructors and Value getter/setter
// See FloatVariable.cs as example
// If your variable is reference type you might want constant validation
// protected override bool isConstantValid
// {
// get { return constantValue != null; }
// }
}
```
## Node Reference
### Root
Entry node of behaviour tree.
### Sequence
Executes children from left to right as long as each subsequent child returns success. Returns success when all children succeeded. Failure if one of them failed. When Random option is enabled, then execution goes in random order.
### Selector
Executes children from left to right until one of them return failure. Returns success if any children succeed. Failure if all of them failed. When Random option is enabled, then execution goes in random order.
### Is Set Condition
Checks if blackboard variable is set. Node supports Bollean, Object and Transform variables. Selecting Invert option will produce "If not set" effect.
### Number Condition
Checks if blackboard number variable meets requirement. Node supports Float and Int variables.
### Distance Condition
Checks distance between two transforms and returns success when given distance condition is met.
### Cooldown
Blocks execition until the specified amount of time has elapsed.
Time starts counting after branch is exited. If abort is enabled, the execution will be moved back to this node after time has elapsed.
### Force Result
Forces success or failure.
### Inverter
Inverts node result. Failure becomes Success and vice versa.
### Invoke Unity Event
Triggers Unity Event with one parameter of selected type
### Random Chance
There is fixed chance that node will be executed. Returns Failure if roll is not favorable.
### Random Float
Generates random float in provided range.
### Random Integer
Generates random integer in provided range.
### Repeat Until Fail
Repeats branch as long as Success is returned from the child. Use Loop node to create a more advanced flow.
### Repeater
Repeats branch specified amount of times or infinitely. Use Loop node to create a more advanced flow.
### Loop
Repeats branch specified amount of times, infinitely or until selected result is returned. Results and flow can be fully customized.
### Set Boolean
Sets blackboard bool variable
### Set Number
Sets blackboard int or float variable
### Set Object
Sets blackboard Transform or GameObject variable
### Set Vector
Sets blackboard Vector3 or Vector2 variable
### Succeeder
Always returns Success.
### Time Limit
Determines how long branch can be executed. After given time elapses branch is aborted and Failure is returned.
### Calculate Distance Service
Calculates distance between two transforms and updates blackboard float variable with the result.
### Update Position Service
Updates blackboard Vector3 variable with position of given source transform.
### Set Number
Sets blackboard Float or Int variable.
### Wait
Waits specific amount of time, then returns Success.
### Subtree
Subtree node allows connection of other behaviour tree as child, this gives you possibility to create reusable blocks of nodes. Such a tree must be created in separate game object and attached as children. Child tree is updated by its parent. **Parent of subtree must be specified in MonoBehaviourTree component to work properly.**
## Creating custom nodes
It is possible to create custom nodes by extending one of base classes provided by library. Each node **must** be in separate file with the name corresponding to class name. MBTNode attribute is required to register node in editor finder, it accepts two parameters: name and order. Name allows use of up to one folder, so "Custom Node" and "Example/Custom Node" is valid, but "Fruits/Bananas/Custom Node" is not.
Order parameter is used to position node higher in finder. Nodes are sorted first by order and then by name. Default order is 1000 and lower values get higher priority. For example Selector, Sequence, Root and SubTree nodes have following values: 100, 150, 200, 250.
### Node API
There are several event methods that can be implemented to control Node execution flow, but the only required one is ```Execute``` used to return state when node is running.
```OnEnter``` and ```OnExit``` primary function is to setup before or cleanup after execution.
```OnAllowInterrupt``` and ```OnDisallowInterrupt``` can be used to detect when its allowed to abort or listen to some events.
Additionally there is ```IsValid``` method used in editor window to determine if Node setup is correct. By default it uses reflection to find variable references with empty keys and in most of cases there is no need to override this method, unless you want to include other fields during validation or custom setup requires it.
### Execution Flow
During runtime node can be in one of following states:
- **Ready** - Default state
- **Running** - Node is currently executed or one of its successors
- **Success** - Node finished execution and returned success
- **Failure** - Node finished execution and returned failure
When node is ready and parent decide to "enter" the node, then ```OnAllowInterrupt``` and ```OnEnter``` is called. After that ```Execute``` method is called which always must return some state.
If running state is returned, then execution will be paused and resumed in next tick, but if running with children node is returned, then execution is "passed down" and continued in that node.
When success or failure is returned, then this result is passed to the parent and ```OnExit``` is called. ```OnDisallowInterrupt``` is not called until the cycle ends or tree is aborted to higher priority node.
### Custom Leaf
Leaf nodes are used to do designated task. It can be something simple as setting variable or very complex like enemy navigation along the path.
```csharp
using UnityEngine;
using MBT;
// Empty Menu attribute prevents Node to show up in "Add Component" menu.
[AddComponentMenu("")]
// Register node in visual editor node finder
[MBTNode(name = "Example/Custom Task")]
public class CustomTask : Leaf
{
public BoolReference somePropertyRef = new BoolReference();
// These two methods are optional, override only when needed
// public override void OnAllowInterrupt() {}
// public override void OnEnter() {}
// This is called every tick as long as node is executed
public override NodeResult Execute()
{
if (somePropertyRef.Value == true)
{
return NodeResult.success;
}
return NodeResult.failure;
}
// These two methods are optional, override only when needed
// public override void OnExit() {}
// public override void OnDisallowInterrupt() {}
// Usually there is no needed to override this method
public override bool IsValid()
{
// You can do some custom validation here
return !somePropertyRef.isInvalid;
}
}
```
### Custom Decorator / Condition
Decorator nodes are mainy used to change flow of the tree. It has single child and must always return either success or failure. You can implement your own subclass by implementing ```Decorator``` class, however in most of cases you might want to create condition - in such a case use ```Condition``` subclass. Implement condition evaluation in ```Check()``` method and node will return success if condition is met.
If you need to implement abort system in your condition node, then below you can find a simple example.
```csharp
[AddComponentMenu("")]
[MBTNode(name = "Example/Custom Condition")]
public class CustomCondition : Condition
{
public Abort abort;
public BoolReference somePropertyRef = new BoolReference(VarRefMode.DisableConstant);
public override bool Check()
{
// Evaluate your custom condition
return somePropertyRef.Value == true;
}
public override void OnAllowInterrupt()
{
// Do not listen any changes if abort is disabled
if (abort != Abort.None)
{
// This method cache current tree state used later by abort system
ObtainTreeSnapshot();
// If somePropertyRef is constant, then null exception will be thrown.
// Use somePropertyRef.isConstant in case you need constant enabled.
// Constant variable is disabled here, so it is safe to do this.
somePropertyRef.GetVariable().AddListener(OnVariableChange);
}
}
public override void OnDisallowInterrupt()
{
if (abort != Abort.None)
{
// Remove listener
somePropertyRef.GetVariable().RemoveListener(OnVariableChange);
}
}
private void OnVariableChange(bool oldValue, bool newValue)
{
// Reevaluate Check() and abort tree when needed
EvaluateConditionAndTryAbort(abort);
}
}
```
### Custom Service
Service is a special decorator that performs the task as long as its branch is executed. This way you can periodically execute some task needed only by the ancestors. Additionally you can fully encapsulate your system into single behaviour tree without need of external scripts running on other game objects.
```csharp
[AddComponentMenu("")]
[MBTNode("Example/Custom Service")]
public class CustomService : Service
{
public Vector3Reference position = new Vector3Reference(VarRefMode.DisableConstant);
public override void Task()
{
// Reset variable to zero every X amount of time
position.Value = Vector3.zero;
}
}
```
## Debugging
During playmode you can preview tree execution flow in editor window. Nodes are marked with the appropriate color corresponding to their state:
- Ready - none (default)
- Success - green
- Failure - orange
- Running - purple
When the node is invalid, an error icon will be displayed in the upper right corner. You should not run the tree when there are any errors in connected nodes.
Except that, you can set breakpoints on multiple nodes. Breakpoint will stop execution and pause play mode after node is entered, but before it get executed. Nodes with breakpoint enabled will have red node names.
## Known Issues
- All nodes should be removed before deleting their script. When the missing script is restored and children of this node were connected to other parent, it will break the tree. Additionaly nodes with missing script remain hidden in the inspector and it is hard to remove them.
- When tree is turned into prefab, all their instances should not change connections between nodes. Sometimes connections are desynchronized and the tree does not work properly.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 77ea71eb8c4c7e644b3dbe712459545b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0265dd17d4c1acd4d899831242a13242
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,94 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[DisallowMultipleComponent]
[DefaultExecutionOrder(-1000)]
public class Blackboard : MonoBehaviour
{
public List<BlackboardVariable> variables = new List<BlackboardVariable>();
private Dictionary<string, BlackboardVariable> dictionary = new Dictionary<string, BlackboardVariable>();
[Tooltip("When set, this blackboard will replace variables with matching names from target parent")]
public Blackboard masterBlackboard;
// IMPROVEMENT: https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html
void Awake()
{
// Initialize variables by keys
dictionary.Clear();
for (int i = 0; i < variables.Count; i++)
{
BlackboardVariable var = variables[i];
dictionary.Add(var.key, var);
}
// Replace variables from master blackboard
if (masterBlackboard != null)
{
List<BlackboardVariable> parentVars = masterBlackboard.variables;
for (int i = 0; i < parentVars.Count; i++)
{
// Find if there is variable with the same key
BlackboardVariable parentVar = parentVars[i];
if (dictionary.TryGetValue(parentVar.key, out BlackboardVariable currentVar))
{
// Ensure that both of them are the same type
if (currentVar.GetType().IsAssignableFrom(parentVar.GetType()))
{
// There are matching variables, replace current one with master blackboard var
dictionary[parentVar.key] = parentVar;
}
else
{
Debug.LogErrorFormat(this,
"Blackboard variable key '{0}' cannot be replaced. " +
"Master blackboard variable of type {1} cannot be assigned to {2}.",
currentVar.key,
parentVar.GetType().Name,
currentVar.GetType().Name
);
}
}
}
}
}
public BlackboardVariable[] GetAllVariables()
{
return variables.ToArray();
}
public T GetVariable<T>(string key) where T : BlackboardVariable
{
return (dictionary.TryGetValue(key, out BlackboardVariable val)) ? (T)val : null;
}
#if UNITY_EDITOR
[ContextMenu("Delete all variables", false)]
protected void DeleteAllVariables()
{
for (int i = 0; i < variables.Count; i++)
{
UnityEditor.Undo.DestroyObjectImmediate(variables[i]);
}
variables.Clear();
}
[ContextMenu("Delete all variables", true)]
protected bool HasVariables()
{
return variables.Count > 0;
}
void OnValidate()
{
if (masterBlackboard == this)
{
Debug.LogWarning("Master blackboard cannot be the same instance.");
masterBlackboard = null;
}
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6b2217e4d35c0c94ea02cf2166c17e4b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: cf1e8cb5def7ab848bce542b344bd1aa, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,11 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
public interface IMonoBehaviourTreeTickListener
{
void OnBehaviourTreeTick();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a72cb8ac73fa6f342b5a07b27abefd66
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,33 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[DisallowMultipleComponent]
[RequireComponent(typeof(MonoBehaviourTree))]
public class MBTExecutor : MonoBehaviour
{
public MonoBehaviourTree monoBehaviourTree;
void Reset()
{
monoBehaviourTree = GetComponent<MonoBehaviourTree>();
OnValidate();
}
void Update()
{
monoBehaviourTree.Tick();
}
void OnValidate()
{
if (monoBehaviourTree != null && monoBehaviourTree.parent != null)
{
monoBehaviourTree = null;
Debug.LogWarning("Subtree should not be target of update. Select parent tree instead.", this.gameObject);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e5f392bbea2fa2499665ed8b604ff86
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
namespace MBT
{
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
public class MBTNode : Attribute
{
public string name;
public int order;
public string icon;
public MBTNode(string name = null, int order = 1000, string icon = null)
{
this.name = name;
this.order = order;
this.icon = icon;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a03e885e14e321347ad1555636efa6f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,329 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Unity.Profiling;
namespace MBT
{
[DisallowMultipleComponent]
// [RequireComponent(typeof(Blackboard))]
public class MonoBehaviourTree : MonoBehaviour
{
private static readonly ProfilerMarker _TickMarker = new ProfilerMarker("MonoBehaviourTree.Tick");
[HideInInspector]
public Node selectedEditorNode;
public bool repeatOnFinish = false;
public int maxExecutionsPerTick = 1000;
public MonoBehaviourTree parent;
/// <summary>
/// Event triggered when tree is about to be updated
/// </summary>
public event UnityAction onTick;
private List<IMonoBehaviourTreeTickListener> tickListeners = new List<IMonoBehaviourTreeTickListener>();
private Root rootNode;
private List<Node> executionStack;
private List<Node> executionLog;
private List<Decorator> interruptingNodes = new List<Decorator>();
public float LastTick { get; internal set; }
void Awake()
{
rootNode = GetComponent<Root>();
if (rootNode == null) {
Debug.LogWarning("Missing Root node in behaviour tree.", this);
}
// Find master parent tree and all nodes
MonoBehaviourTree masterTree = this.GetMasterTree();
Node[] nodes = GetComponents<Node>();
if(masterTree == this)
{
// Create lists with capicity
executionStack = new List<Node>(8);
executionLog = new List<Node>(nodes.Length);
// Set start node when tree is created first time
executionStack.Add(rootNode);
executionLog.Add(rootNode);
}
// Initialize nodes of tree/subtree
for (int i = 0; i < nodes.Length; i++)
{
Node n = nodes[i];
n.behaviourTree = masterTree;
n.runningNodeResult = new NodeResult(Status.Running, n);
}
}
private void EvaluateInterruptions()
{
if (interruptingNodes.Count == 0) {
return;
}
// Find node with highest priority - closest to the root (the smallest number)
Decorator abortingNode = interruptingNodes[0];
for (int i = 1; i < interruptingNodes.Count; i++)
{
Decorator d = interruptingNodes[i];
if (d.runtimePriority < abortingNode.runtimePriority) {
abortingNode = d;
}
}
// Revert stack
executionStack.Clear();
executionStack.AddRange(abortingNode.GetStoredTreeSnapshot());
// Restore flow of events in nodes after abort
for (int i = 0; i < executionStack.Count; i++)
{
Node node = executionStack[i];
if (node.status == Status.Running)
{
// This node is still running and might need to restore the state
node.OnBehaviourTreeAbort();
}
else if (node.status == Status.Success || node.status == Status.Failure)
{
// This node returned failure or success, so reenter it and call OnEnter
node.OnEnter();
}
// All nodes in execution stack should be in running state
node.status = Status.Running;
}
int nodeIndex = abortingNode.runtimePriority - 1;
// Sanity check
if (abortingNode != executionLog[nodeIndex]) {
Debug.LogWarning("Priority of node does not match with exectuion log");
}
// Abort nodes in log
ResetNodesTo(abortingNode, true);
// Reset aborting node
abortingNode.status = Status.Ready;
// Reset list and wait for new interruptions
interruptingNodes.Clear();
}
/// <summary>
/// Update tree state.
/// </summary>
public void Tick()
{
_TickMarker.Begin();
// Fire Tick event and notify listeners
onTick?.Invoke();
for (int i = 0; i < tickListeners.Count; i++)
{
tickListeners[i].OnBehaviourTreeTick();
}
// Check if there are any interrupting nodes
EvaluateInterruptions();
// Max number of traversed nodes
int executionLimit = maxExecutionsPerTick;
// Traverse tree
while (executionStack.Count > 0)
{
if (executionLimit == 0) {
LastTick = Time.time;
_TickMarker.End();
return;
}
executionLimit -= 1;
// Execute last element in stack
Node currentNode = executionStack[executionStack.Count - 1];
NodeResult nodeResult = currentNode.Execute();
// Set new status
currentNode.status = nodeResult.status;
if (nodeResult.status == Status.Running) {
// If node is running, then stop execution or continue children
Node child = nodeResult.child;
if (child == null) {
// Stop execution and continue next tick
LastTick = Time.time;
_TickMarker.End();
return;
} else {
// Add child to execution stack and execute it in next loop
executionStack.Add(child);
executionLog.Add(child);
// IMPORTANT: Priority must be > 0 and assigned in this order
child.runtimePriority = executionLog.Count;
child.OnAllowInterrupt();
child.OnEnter();
#if UNITY_EDITOR
// Stop execution if breakpoint is set on this node
if (child.breakpoint)
{
Debug.Break();
UnityEditor.Selection.activeGameObject = this.gameObject;
Debug.Log("MBT Breakpoint: " + child.title, this);
LastTick = Time.time;
_TickMarker.End();
return;
}
#endif
continue;
}
} else {
// Remove last node from stack and move up (closer to root)
currentNode.OnExit();
executionStack.RemoveAt(executionStack.Count - 1);
}
}
// Run this when execution stack is empty and BT should repeat
if (repeatOnFinish) {
Restart();
}
LastTick = Time.time;
_TickMarker.End();
}
public void AddTickListener(IMonoBehaviourTreeTickListener listener)
{
#if UNITY_EDITOR
if (tickListeners.Contains(listener))
{
Debug.LogErrorFormat(this, "Tick listener {0} has been already added.", listener);
}
#endif
tickListeners.Add(listener);
}
public void RemoveTickListener(IMonoBehaviourTreeTickListener listener)
{
tickListeners.Remove(listener);
}
/// <summary>
/// This method should be called to abort tree to given node
/// </summary>
/// <param name="node">Abort and revert tree to this node</param>
internal void Interrupt(Decorator node)
{
if (!interruptingNodes.Contains(node)) {
interruptingNodes.Add(node);
}
}
internal void ResetNodesTo(Node node, bool aborted = false)
{
int i = executionLog.Count - 1;
// Reset status and get index of node
while (i >= 0)
{
Node n = executionLog[i];
if (n == node) {
break;
}
// If node is running (on exec stack) then call exit
if (n.status == Status.Running) {
n.OnExit();
// IMPROVEMENT: Abort event can be added or abort param onExit
}
n.status = Status.Ready;
n.OnDisallowInterrupt();
i -= 1;
}
// Reset log
i += 1;
if (i >= executionLog.Count) {
return;
}
executionLog.RemoveRange(i, executionLog.Count - i);
}
private void ResetNodes()
{
for (int i = 0; i < executionLog.Count; i++)
{
Node node = executionLog[i];
if (node.status == Status.Running)
{
node.OnExit();
}
node.OnDisallowInterrupt();
node.status = Status.Ready;
}
executionLog.Clear();
executionStack.Clear();
}
/// <summary>
/// Resets state to root node
/// </summary>
public void Restart()
{
ResetNodes();
executionStack.Add(rootNode);
executionLog.Add(rootNode);
}
internal void GetStack(ref Node[] stack)
{
// Resize array when size is too small
if (executionStack.Count > stack.Length)
{
// Node should not change priority and position during runtime
// It means the array will be resized once during first call of this method
Array.Resize<Node>(ref stack, executionStack.Count);
}
#if UNITY_EDITOR
// Additional sanity check in case nodes are reordered or changed in editor
if (stack.Length > executionStack.Count)
{
Debug.LogError("Changing order of MBT nodes during runtime might cause errors or unpredictable results.");
}
#endif
// Copy elements to provided array
executionStack.CopyTo(stack);
}
public Node GetRoot()
{
return rootNode;
}
public MonoBehaviourTree GetMasterTree()
{
if (parent == null)
{
return this;
}
return parent.GetMasterTree();
}
#if UNITY_EDITOR
void OnValidate()
{
if (maxExecutionsPerTick <= 0)
{
maxExecutionsPerTick = 1;
}
if (parent != null)
{
if (parent == this)
{
parent = null;
Debug.LogWarning("This tree cannot be its own parent.");
return;
}
if (transform.parent == null || parent.gameObject != transform.parent.gameObject)
{
// parent = null;
Debug.LogWarning("Parent tree should be also parent of this gameobject.", this.gameObject);
}
}
}
#endif
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b25af8627659c6949b60949c3aa8d91d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8e319fb1e46299e4bb368bf700097d39, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ab968361104bced4eb5870774bffc1d4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,27 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode("Services/Calculate Distance Service")]
public class CalculateDistanceService : Service
{
[Space]
public TransformReference transform1;
public TransformReference transform2;
public FloatReference variable = new FloatReference(VarRefMode.DisableConstant);
public override void Task()
{
Transform t1 = transform1.Value;
Transform t2 = transform2.Value;
if (t1 == null || t2 == null)
{
return;
}
variable.Value = Vector3.Distance(t1.position, t2.position);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc2659d9740e09744b583d40f78bde8a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,47 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
public abstract class Composite : Node, IParentNode, IChildrenNode
{
private static readonly System.Random rng = new System.Random();
public bool random = false;
public override void AddChild(Node node)
{
if (!children.Contains(node))
{
// Remove parent in case there is one already
if (node.parent != null) {
node.parent.RemoveChild(node);
}
children.Add(node);
node.parent = this;
}
}
public override void RemoveChild(Node node)
{
if (children.Contains(node))
{
children.Remove(node);
node.parent = null;
}
}
protected static void ShuffleList<T>(List<T> list)
{
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e91b1b2e9c2fc234e83cb440a812b4fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,47 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
public abstract class Condition : Decorator
{
protected bool lastConditionCheckResult = false;
public override NodeResult Execute()
{
if (!TryGetChild(out Node node))
{
return NodeResult.failure;
}
if (node.status == Status.Success || node.status == Status.Failure) {
return NodeResult.From(node.status);
}
lastConditionCheckResult = Check();
if (lastConditionCheckResult == false) {
return NodeResult.failure;
}
return node.runningNodeResult;
}
/// <summary>
/// Reevaluate condition and try to abort the tree if required
/// </summary>
/// <param name="abort">Abort type</param>
protected void EvaluateConditionAndTryAbort(Abort abortType)
{
bool c = Check();
if (c != lastConditionCheckResult)
{
lastConditionCheckResult = c;
TryAbort(abortType);
}
}
/// <summary>
/// Method called to check condition
/// </summary>
/// <returns>Condition result</returns>
public abstract bool Check();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: caf17e0e40d8198449b5a547beb02515
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode(name = "Decorators/Cooldown")]
public class Cooldown : Decorator, IMonoBehaviourTreeTickListener
{
public AbortTypes abort = AbortTypes.None;
[Space]
public FloatReference time = new FloatReference(1f);
[Tooltip("When set to true, there will be no cooldown when child node returns failure")]
public bool resetOnChildFailure = false;
private float cooldownTime = 0f;
private bool entered = false;
private bool childFailed = false;
public enum AbortTypes
{
None, LowerPriority
}
public override void OnAllowInterrupt()
{
if (abort == AbortTypes.LowerPriority)
{
ObtainTreeSnapshot();
}
}
public override NodeResult Execute()
{
if (!TryGetChild(out Node node))
{
return NodeResult.failure;
}
if (node.status == Status.Success) {
return NodeResult.success;
}
if (node.status == Status.Failure) {
// If reset option is enabled flag will be raised and set true
childFailed = resetOnChildFailure;
return NodeResult.failure;
}
if (cooldownTime <= Time.time) {
entered = true;
return node.runningNodeResult;
} else {
return NodeResult.failure;
}
}
public override void OnExit()
{
// Setup cooldown and event when child was entered
// Check reset option too
if (entered && !childFailed)
{
cooldownTime = Time.time + time.Value;
// For LowerPriority try to abort after given time
if (abort == AbortTypes.LowerPriority)
{
behaviourTree.AddTickListener(this);
}
}
// Reset flags
entered = false;
childFailed = false;
}
public override void OnDisallowInterrupt()
{
behaviourTree.RemoveTickListener(this);
}
void IMonoBehaviourTreeTickListener.OnBehaviourTreeTick()
{
if (cooldownTime <= Time.time)
{
// Task should be aborted, so there is no need to listen anymore
behaviourTree.RemoveTickListener(this);
TryAbort(Abort.LowerPriority);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2370820ab6f42c8449d09456ac02d66d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,68 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
public abstract class CoroutineService : Decorator
{
public float interval = 1f;
public bool callOnEnter = true;
protected Coroutine coroutine;
private WaitForSeconds waitForSeconds;
public override void OnEnter()
{
// IMPROVEMENT: WaitForSeconds could be initialized in some special node init callback
if (waitForSeconds == null)
{
// Create new WaitForSeconds
OnValidate();
}
coroutine = StartCoroutine(ScheduleTask());
if (callOnEnter)
{
Task();
}
}
public override NodeResult Execute()
{
Node node = GetChild();
if (node == null) {
return NodeResult.failure;
}
if (node.status == Status.Success || node.status == Status.Failure) {
return NodeResult.From(node.status);
}
return node.runningNodeResult;
}
public abstract void Task();
public override void OnExit()
{
if (coroutine == null)
{
return;
}
StopCoroutine(coroutine);
coroutine = null;
}
private IEnumerator ScheduleTask()
{
while(true)
{
yield return waitForSeconds;
Task();
}
}
protected virtual void OnValidate()
{
interval = Mathf.Max(0f, interval);
waitForSeconds = new WaitForSeconds(interval);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9cf48bbd74f869449a0045ef1e4de316
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,111 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
public abstract class Decorator : Node, IParentNode, IChildrenNode
{
private Node[] stackState = new Node[0];
public override void AddChild(Node node)
{
// Allow only one children
if (this.children.Count > 0)
{
Node child = this.children[0];
if (child == node) {
return;
}
child.parent.RemoveChild(child);
this.children.Clear();
}
// Remove parent in case there is one already
if (node.parent != null) {
node.parent.RemoveChild(node);
}
this.children.Add(node);
node.parent = this;
}
protected Node GetChild()
{
if (children.Count > 0) {
return children[0];
}
return null;
}
protected bool TryGetChild(out Node node)
{
if (children.Count > 0)
{
node = children[0];
return true;
}
node = null;
return false;
}
protected bool HasChild()
{
return children.Count > 0;
}
public override void RemoveChild(Node node)
{
if (children.Contains(node))
{
children.Remove(node);
node.parent = null;
}
}
/// <summary>
/// Copy and store current state of execution stack if it was not saved before.
/// </summary>
protected void ObtainTreeSnapshot()
{
// Copy stack only when this method is called for the first time
if (stackState.Length == 0)
{
behaviourTree.GetStack(ref stackState);
}
}
[System.Obsolete]
protected void DisposeBTState()
{
stackState = new Node[0];
}
internal Node[] GetStoredTreeSnapshot()
{
return stackState;
}
/// <summary>
/// Helper method used to abort nodes in valid case
/// </summary>
/// <param name="abort">Abort type</param>
protected void TryAbort(Abort abort)
{
switch (abort)
{
case Abort.Self:
if (status == Status.Running) {
behaviourTree.Interrupt(this);
}
break;
case Abort.LowerPriority:
if (status == Status.Success || status == Status.Failure) {
behaviourTree.Interrupt(this);
}
break;
case Abort.Both:
behaviourTree.Interrupt(this);
break;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 76bba4b4f778fa9409a634bedf9d1aa1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,37 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode(name = "Conditions/Distance Condition")]
public class DistanceCondition : Condition
{
public Comparator comparator = Comparator.GreaterThan;
public FloatReference distance = new FloatReference(10f);
[Space]
public TransformReference transform1;
public TransformReference transform2;
public override bool Check()
{
// Squared magnitude is enough to compare distances
float sqrMagnitude = (transform1.Value.position - transform2.Value.position).sqrMagnitude;
float dist = distance.Value;
if (comparator == Comparator.GreaterThan)
{
return sqrMagnitude > dist * dist;
}
else
{
return sqrMagnitude < dist * dist;
}
}
public enum Comparator
{
GreaterThan, LessThan
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eb0b7cfe3e9cfba47a378c725cf5d0b4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,33 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode("Decorators/Force Result")]
public class ForceResult : Decorator
{
[SerializeField] private ForcedResult result = ForcedResult.Success;
public override NodeResult Execute()
{
if (!TryGetChild(out Node node))
{
return NodeResult.failure;
}
if (node.status == Status.Success || node.status == Status.Failure)
{
return result == ForcedResult.Success ? NodeResult.success : NodeResult.failure;
}
return node.runningNodeResult;
}
private enum ForcedResult
{
Success, Failure
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 163d3a55bffbacb42b57e10e4d9f3d13
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,25 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode("Decorators/Inverter")]
public class Inverter : Decorator
{
public override NodeResult Execute()
{
if (!TryGetChild(out Node node))
{
return NodeResult.failure;
}
if (node.status == Status.Success) {
return NodeResult.failure;
} else if (node.status == Status.Failure) {
return NodeResult.success;
}
return node.runningNodeResult;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df7dcd3c2da37eb4ba49162d0ca654c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,86 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode(name = "Tasks/Invoke Unity Event")]
public class InvokeUnityEvent : Leaf
{
public EventType type;
public TransformReference transformReference = new TransformReference();
public GameObjectReference gameObjectReference = new GameObjectReference();
public FloatReference floatReference = new FloatReference();
public IntReference intReference = new IntReference();
public BoolReference boolReference = new BoolReference();
public StringReference stringReference = new StringReference();
public Vector3Reference vector3Reference = new Vector3Reference();
public Vector2Reference vector2Reference = new Vector2Reference();
public TransformEvent transformEvent;
public GameObjectEvent gameObjectEvent;
public FloatEvent floatEvent;
public IntEvent intEvent;
public BoolEvent boolEvent;
public StringEvent stringEvent;
public Vector3Event vector3Event;
public Vector2Event vector2Event;
public override NodeResult Execute()
{
switch (type)
{
case EventType.Transform: transformEvent.Invoke(transformReference.Value);
break;
case EventType.Float: floatEvent.Invoke(floatReference.Value);
break;
case EventType.Bool: boolEvent.Invoke(boolReference.Value);
break;
case EventType.String: stringEvent.Invoke(stringReference.Value);
break;
case EventType.Vector3: vector3Event.Invoke(vector3Reference.Value);
break;
case EventType.Vector2: vector2Event.Invoke(vector2Reference.Value);
break;
case EventType.Int: intEvent.Invoke(intReference.Value);
break;
case EventType.GameObject: gameObjectEvent.Invoke(gameObjectReference.Value);
break;
}
return NodeResult.success;
}
public override bool IsValid()
{
switch (type)
{
case EventType.Transform: return !transformReference.isInvalid;
case EventType.Float: return !floatReference.isInvalid;
case EventType.Bool: return !boolReference.isInvalid;
case EventType.String: return !stringReference.isInvalid;
case EventType.Vector3: return !vector3Reference.isInvalid;
case EventType.Vector2: return !vector2Reference.isInvalid;
case EventType.Int: return !intReference.isInvalid;
case EventType.GameObject: return !gameObjectReference.isInvalid;
default: return true;
}
}
public enum EventType
{
Transform, GameObject, Float, Int, Bool, String, Vector3, Vector2
}
[System.Serializable] public class TransformEvent : UnityEvent<Transform>{}
[System.Serializable] public class GameObjectEvent : UnityEvent<GameObject>{}
[System.Serializable] public class FloatEvent : UnityEvent<float>{}
[System.Serializable] public class IntEvent : UnityEvent<int>{}
[System.Serializable] public class BoolEvent : UnityEvent<bool>{}
[System.Serializable] public class StringEvent : UnityEvent<string>{}
[System.Serializable] public class Vector3Event : UnityEvent<Vector3>{}
[System.Serializable] public class Vector2Event : UnityEvent<Vector2>{}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d47807df5b5587e44bfd9aface30c12f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,102 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode(name = "Conditions/Is Set Condition")]
public class IsSetCondition : Condition
{
public Abort abort;
public bool invert = false;
public Type type = Type.Boolean;
public BoolReference boolReference = new BoolReference(VarRefMode.DisableConstant);
public GameObjectReference objectReference = new GameObjectReference(VarRefMode.DisableConstant);
public TransformReference transformReference = new TransformReference(VarRefMode.DisableConstant);
public override bool Check()
{
switch (type)
{
case Type.Boolean:
return (boolReference.Value == true) ^ invert;
case Type.GameObject:
return (objectReference.Value != null) ^ invert;
case Type.Transform:
return (transformReference.Value != null) ^ invert;
}
return invert;
}
public override void OnAllowInterrupt()
{
if (abort != Abort.None)
{
ObtainTreeSnapshot();
switch (type)
{
case Type.Boolean:
boolReference.GetVariable().AddListener(OnVariableChange);
break;
case Type.GameObject:
objectReference.GetVariable().AddListener(OnVariableChange);
break;
case Type.Transform:
transformReference.GetVariable().AddListener(OnVariableChange);
break;
}
}
}
public override void OnDisallowInterrupt()
{
if (abort != Abort.None)
{
switch (type)
{
case Type.Boolean:
boolReference.GetVariable().RemoveListener(OnVariableChange);
break;
case Type.GameObject:
objectReference.GetVariable().RemoveListener(OnVariableChange);
break;
case Type.Transform:
transformReference.GetVariable().RemoveListener(OnVariableChange);
break;
}
}
}
private void OnVariableChange(bool oldValue, bool newValue)
{
EvaluateConditionAndTryAbort(abort);
}
private void OnVariableChange(GameObject oldValue, GameObject newValue)
{
EvaluateConditionAndTryAbort(abort);
}
private void OnVariableChange(Transform oldValue, Transform newValue)
{
EvaluateConditionAndTryAbort(abort);
}
public override bool IsValid()
{
switch (type)
{
case Type.Boolean: return !boolReference.isInvalid;
case Type.GameObject: return !objectReference.isInvalid;
case Type.Transform: return !transformReference.isInvalid;
default: return true;
}
}
public enum Type
{
Boolean, GameObject, Transform
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 534e3ec7de6cd444e984c2e7f68e42e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,19 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
public abstract class Leaf : Node, IChildrenNode
{
public sealed override void AddChild(Node node)
{
return;
}
public sealed override void RemoveChild(Node node)
{
return;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2cb4093606af8044fbf4aa9fac6dca97
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,80 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode(name = "Decorators/Loop")]
public class Loop : Decorator
{
public IntReference loops = new IntReference(3);
public BoolReference infinite = new BoolReference(false);
[Tooltip("Break loop when selected result is returned by child.")]
public BreakMode breakOnStatus = BreakMode.Disabled;
[Space]
[Tooltip("Result returned by this node after the loop ends.")]
public ResultRemapMode resultOnFinish = ResultRemapMode.Success;
[Tooltip("The result returned by this node when loop is broken.")]
public ResultRemapMode resultOnBreak = ResultRemapMode.Failure;
private int count;
public enum ResultRemapMode
{
Success = 0,
Failure = 1,
Inherit = 2,
InheritInverted = 3,
}
/// <summary>
/// Enum mapped to Status enum. Disabled is casted to 'running' as this state is never returned by child.
/// </summary>
public enum BreakMode
{
Disabled = 2,
Success = 0,
Failure = 1,
}
public override void OnEnter()
{
count = loops.Value;
}
public override NodeResult Execute()
{
if (!TryGetChild(out Node node))
{
return NodeResult.failure;
}
if (node.status == (Status)breakOnStatus)
{
return RemapResult(resultOnBreak, node.status);
}
if (count > 0 || infinite.Value)
{
// Repeat children
behaviourTree.ResetNodesTo(this);
count -= 1;
return node.runningNodeResult;
}
return RemapResult(resultOnFinish, node.status);
}
private NodeResult RemapResult(ResultRemapMode mode, Status childStatus)
{
switch (mode)
{
case ResultRemapMode.Success: return NodeResult.success;
case ResultRemapMode.Failure: return NodeResult.failure;
case ResultRemapMode.Inherit: return childStatus == Status.Success ? NodeResult.success : NodeResult.failure;
case ResultRemapMode.InheritInverted: return childStatus == Status.Success ? NodeResult.failure : NodeResult.success;
default: Debug.LogError("Unexpected behaviour", this); return NodeResult.failure;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8595d37ac5b0f340b7f64ca227f8860
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,162 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[RequireComponent(typeof(MonoBehaviourTree))]
public abstract class Node : MonoBehaviour
{
public const float NODE_DEFAULT_WIDTH = 160f;
public string title;
[HideInInspector]
public Rect rect = new Rect(0, 0, NODE_DEFAULT_WIDTH, 50);
[HideInInspector]
public Node parent;
[HideInInspector]
public List<Node> children = new List<Node>();
[System.NonSerialized]
public Status status = Status.Ready;
[HideInInspector]
public MonoBehaviourTree behaviourTree;
// [HideInInspector]
public NodeResult runningNodeResult { get; internal set;}
[HideInInspector]
public int runtimePriority = 0;
[HideInInspector]
public bool breakpoint = false;
private bool _selected = false;
public bool selected
{
get { return _selected; }
set { _selected = value; }
}
/// <summary>
/// Time of last tick retrieved from Time.time
/// </summary>
public float LastTick => behaviourTree.LastTick;
/// <summary>
/// The interval in seconds from the last tick of behaviour tree.
/// </summary>
public float DeltaTime => Time.time - behaviourTree.LastTick;
public virtual void OnAllowInterrupt() {}
public virtual void OnEnter() {}
public abstract NodeResult Execute();
public virtual void OnExit() {}
public virtual void OnDisallowInterrupt() {}
public virtual void OnBehaviourTreeAbort() {}
public abstract void AddChild(Node node);
public abstract void RemoveChild(Node node);
public virtual Node GetParent()
{
return parent;
}
public virtual List<Node> GetChildren()
{
return children;
}
public bool IsDescendantOf(Node node)
{
if (this.parent == null) {
return false;
} else if (this.parent == node) {
return true;
}
return this.parent.IsDescendantOf(node);
}
public List<Node> GetAllSuccessors()
{
List<Node> result = new List<Node>();
for (int i = 0; i < children.Count; i++)
{
result.Add(children[i]);
result.AddRange(children[i].GetAllSuccessors());
}
return result;
}
public void SortChildren()
{
this.children.Sort((c, d) => c.rect.x.CompareTo(d.rect.x));
}
/// <summary>
/// Check if node setup is valid
/// </summary>
/// <returns>Returns true if node is configured correctly</returns>
public virtual bool IsValid()
{
#if UNITY_EDITOR
System.Reflection.FieldInfo[] propertyInfos = this.GetType().GetFields();
for (int i = 0; i < propertyInfos.Length; i++)
{
if (propertyInfos[i].FieldType.IsSubclassOf(typeof(BaseVariableReference)))
{
BaseVariableReference varReference = propertyInfos[i].GetValue(this) as BaseVariableReference;
if (varReference != null && varReference.isInvalid)
{
return false;
}
}
}
#endif
return true;
}
}
public enum Status
{
Success = 0,
Failure = 1,
Running = 2,
Ready = 3
}
public enum Abort
{
None, Self, LowerPriority, Both
}
public class NodeResult
{
public Status status {get; private set;}
public Node child {get; private set;}
public NodeResult(Status status, Node child = null)
{
this.status = status;
this.child = child;
}
public static NodeResult From(Status s)
{
switch (s)
{
case Status.Success: return success;
case Status.Failure: return failure;
default: return running;
}
}
public static readonly NodeResult success = new NodeResult(Status.Success);
public static readonly NodeResult failure = new NodeResult(Status.Failure);
public static readonly NodeResult running = new NodeResult(Status.Running);
}
public interface IChildrenNode{
// void SetParent(Node node);
}
public interface IParentNode{
// void AddChild(Node node);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d96d8d07ac425543b9f24ad5120b51d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,125 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode(name = "Conditions/Number Condition")]
public class NumberCondition : Condition
{
public Abort abort;
public Type type = Type.Float;
public FloatReference floatReference = new FloatReference(VarRefMode.DisableConstant);
public IntReference intReference = new IntReference(VarRefMode.DisableConstant);
public Comparator comparator = Comparator.Equal;
public FloatReference floatReference2 = new FloatReference(0f);
public IntReference intReference2 = new IntReference(0);
// IMPROVEMENT: This class could be split into to different nodes
public override bool Check()
{
if (type == Type.Float)
{
switch (comparator)
{
case Comparator.Equal:
return floatReference.Value == floatReference2.Value;
case Comparator.GreaterThan:
return floatReference.Value > floatReference2.Value;
case Comparator.LessThan:
return floatReference.Value < floatReference2.Value;
}
}
else
{
switch (comparator)
{
case Comparator.Equal:
return intReference.Value == intReference2.Value;
case Comparator.GreaterThan:
return intReference.Value > intReference2.Value;
case Comparator.LessThan:
return intReference.Value < intReference2.Value;
}
}
return false;
}
public override void OnAllowInterrupt()
{
if (abort != Abort.None)
{
ObtainTreeSnapshot();
if (type == Type.Float) {
floatReference.GetVariable().AddListener(OnVariableChange);
if (!floatReference2.isConstant)
{
floatReference2.GetVariable().AddListener(OnVariableChange);
}
} else {
intReference.GetVariable().AddListener(OnVariableChange);
if (!intReference2.isConstant)
{
intReference2.GetVariable().AddListener(OnVariableChange);
}
}
}
}
public override void OnDisallowInterrupt()
{
if (abort != Abort.None)
{
if (type == Type.Float) {
floatReference.GetVariable().RemoveListener(OnVariableChange);
if (!floatReference2.isConstant)
{
floatReference2.GetVariable().RemoveListener(OnVariableChange);
}
} else {
intReference.GetVariable().RemoveListener(OnVariableChange);
if (!intReference2.isConstant)
{
intReference2.GetVariable().RemoveListener(OnVariableChange);
}
}
}
}
private void OnVariableChange(float newVal, float oldVal)
{
EvaluateConditionAndTryAbort(abort);
}
private void OnVariableChange(int newVal, int oldVal)
{
EvaluateConditionAndTryAbort(abort);
}
public override bool IsValid()
{
switch (type)
{
case Type.Float: return !(floatReference.isInvalid || floatReference2.isInvalid);
case Type.Int: return !(intReference.isInvalid || intReference2.isInvalid);
default: return true;
}
}
public enum Type
{
Float, Int
}
public enum Comparator
{
[InspectorName("==")]
Equal,
[InspectorName(">")]
GreaterThan,
[InspectorName("<")]
LessThan
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d0b95a918be8d9a4f9085201010bdec1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,43 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode("Decorators/Random Chance")]
public class RandomChance : Decorator
{
[Tooltip("Probability should be between 0 and 1")]
public FloatReference probability = new FloatReference(0.5f);
private float roll;
public override void OnAllowInterrupt()
{
roll = Random.Range(0f, 1f);
}
public override NodeResult Execute()
{
Node node = GetChild();
if (node == null) {
return NodeResult.failure;
}
if (node.status == Status.Success || node.status == Status.Failure) {
return NodeResult.From(node.status);
}
if (roll > probability.Value) {
return NodeResult.failure;
}
return node.runningNodeResult;
}
void OnValidate()
{
if (probability.isConstant)
{
probability.Value = Mathf.Clamp(probability.GetConstant(), 0f, 1f);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f9f8fc43d8d667e46a433232d46363ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace MBT
{
[AddComponentMenu("")]
[MBTNode(name = "Tasks/Random Float")]
public class RandomFloat : Leaf
{
public FloatReference min = new FloatReference(0f);
public FloatReference max = new FloatReference(1f);
public FloatReference output = new FloatReference(VarRefMode.DisableConstant);
public override NodeResult Execute()
{
output.Value = Random.Range(min.Value, max.Value);
return NodeResult.success;
}
void OnValidate()
{
if (min.isConstant && max.isConstant)
{
min.Value = Mathf.Min(min.GetConstant(), max.GetConstant());
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c4b91b5cfd9ae042ba2496eb83eeb0c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More