481 lines
17 KiB
C#
481 lines
17 KiB
C#
//============= Copyright (c) Ludic GmbH, All rights reserved. ==============
|
|
//
|
|
// Purpose: Part of the My Behaviour Tree Code
|
|
//
|
|
//=============================================================================
|
|
|
|
using UnityEngine;
|
|
using System;
|
|
using System.Linq;
|
|
//using System.Runtime.Serialization.Configuration;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.Serialization.Formatters.Binary;
|
|
using System.Runtime.Serialization;
|
|
using System.Xml.Serialization;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace MyBT {
|
|
[System.Serializable] public class DictionaryOfStringAndMethodImplementation : SerializableDictionary<string, MethodImplementation> {}
|
|
[System.Serializable] public class DictionaryOfStringAndDictionaryOfStringAndMethodImplementation : SerializableDictionary<string, DictionaryOfStringAndMethodImplementation> {}
|
|
|
|
public enum TickUpdateMode {
|
|
// run in update / lateUpdate
|
|
Update,
|
|
LateUpdate,
|
|
// run in an interval for 1 step
|
|
DelayedUpdate,
|
|
DelayedLateUpdate,
|
|
// run for a maximum time
|
|
MaximumTimeUpdate,
|
|
MaximumTimeLateUpdate,
|
|
// run a full cycle
|
|
FullrunUpdate,
|
|
FullrunLateUpdate,
|
|
// run manually
|
|
Manual
|
|
}
|
|
|
|
[ExecuteInEditMode, System.Serializable]
|
|
public partial class TaskController : MonoBehaviour {
|
|
#region unity inspector updater (only in editor)
|
|
// handling changes of the bt -> requires redrawing of the editor ui
|
|
[NonSerialized]
|
|
public Action<bool> InspectorRedraw;
|
|
[NonSerialized]
|
|
public Action<bool> InspectorRegenerate;
|
|
|
|
// called from lateUpdate
|
|
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
|
void CallInspectorRedraw() {
|
|
// makes unity editor very slow!!!
|
|
//UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
|
|
lastTickTime = currentUiTime;
|
|
if (InspectorRedraw != null) InspectorRedraw.Invoke(false);
|
|
}
|
|
|
|
// called from lateUpdate
|
|
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
|
void CallInspectorRegenerate() {
|
|
if (InspectorRegenerate != null) InspectorRegenerate.Invoke(false);
|
|
}
|
|
|
|
// when was the tick executed last time
|
|
public float lastTickTime = 0;
|
|
// when was the behaviour tree changed last time
|
|
private float lastUiChangedTime = 0;
|
|
|
|
// has the behaviour tree changed in this frame?
|
|
// it is called from TaskControllerInspector
|
|
public bool UiChangedThisFrame () {
|
|
return (lastTickTime == lastUiChangedTime);
|
|
}
|
|
|
|
public bool UiChangedAt (float time) {
|
|
return lastTickTime == time;
|
|
}
|
|
|
|
// called to set behaviour tree changed
|
|
public void SetUiUpdate() {
|
|
// time at beginning of frame
|
|
lastUiChangedTime = TaskController.currentUiTime;
|
|
}
|
|
|
|
public static float currentUiTime {
|
|
get { return Time.time; }
|
|
}
|
|
#endregion
|
|
|
|
#region settings
|
|
// tick settings
|
|
public float tickDelay = 0f;
|
|
public float maximumRuntime = 0.1f;
|
|
public TickUpdateMode tickMode = TickUpdateMode.Update;
|
|
// generation settings
|
|
public bool runtimeAutomaticRegenerate = true;
|
|
public bool resetDataOnCodeChange = false;
|
|
// logging
|
|
public bool runtimeLogging = false;
|
|
public bool overrideLogStringDisplay = false;
|
|
public bool overrideDebugInternalActive = false;
|
|
public bool overrideDebugChangesActive = false;
|
|
// timer for tick delay calculation
|
|
private float tickDelayTimer = 0f;
|
|
|
|
NodeResult rootResult = NodeResult.Succeeded;
|
|
|
|
#if false
|
|
public IEnumerator<NodeResult> _tickEnumerator;
|
|
public IEnumerator<NodeResult> tickEnumerator {
|
|
get {
|
|
return _tickEnumerator;
|
|
}
|
|
set {
|
|
if (runtimeLogging)
|
|
Debug.LogWarning($"setting tickEnumerator to '{value}'");
|
|
_tickEnumerator = value;
|
|
}
|
|
}
|
|
#else
|
|
public IEnumerator<NodeResult> tickEnumerator;
|
|
#endif
|
|
|
|
private bool tickSuccess = false;
|
|
#endregion
|
|
|
|
#region duplicate watcher
|
|
// #if UNITY_EDITOR
|
|
// Catch duplication of this GameObject
|
|
[SerializeField]
|
|
private int instanceID = 0;
|
|
/// <summary>
|
|
/// Detect when the GameObject has been duplicated
|
|
/// if so make sure then dont share the same instance of the behaviour tree generator
|
|
/// </summary>
|
|
void AwakeDuplicateCatcher() {
|
|
if (instanceID != GetInstanceID()) {
|
|
if (generatorLogging) Debug.Log($"Detected Duplicate! ( {instanceID} != {GetInstanceID()} )");
|
|
// Do whatever you need to set up the duplicate
|
|
|
|
// copy old contents
|
|
// List<UnityEngine.TextAsset> oldTaskScripts = new List<UnityEngine.TextAsset>();
|
|
// for (int i=0; i<oldTaskScripts.Count; i++) { //(TextAsset txtAss in oldTaskScripts) {
|
|
// taskScripts.Add(oldTaskScripts[i]);
|
|
// }
|
|
|
|
// clear current data
|
|
__behaviourTreeGenerator = null;
|
|
// assign scripts
|
|
// read scripts
|
|
// for (int i=0; i<oldTaskScripts.Count; i++) { //(TextAsset txtAss in oldTaskScripts) {
|
|
// taskScripts.Add(oldTaskScripts[i]);
|
|
// }
|
|
|
|
// works, but regenerates everything, breaks when unity builds the project
|
|
// BtScriptChangeDetected(true);
|
|
// FullReset();
|
|
|
|
instanceID = GetInstanceID();
|
|
|
|
#if UNITY_EDITOR
|
|
EditorUtility.SetDirty(this);
|
|
#endif
|
|
}
|
|
}
|
|
// #endif
|
|
|
|
private void Awake() {
|
|
AwakeDuplicateCatcher();
|
|
if (Application.isPlaying) {
|
|
VerifyBindings();
|
|
RegenerateBehaviourTreeIfRequired(false);
|
|
}
|
|
}
|
|
|
|
public void OnDestroy () {
|
|
#if !UNITY_EDITOR
|
|
// calling this when building causes it to fail
|
|
ClearBinding();
|
|
#endif
|
|
}
|
|
#endregion
|
|
|
|
// public bool destroyNextFrame = false;
|
|
// public bool breakNextFrame = false;
|
|
|
|
#region tick functions
|
|
public void Update() {
|
|
// if (breakNextFrame) {
|
|
// Debug.Break();
|
|
// }
|
|
// if (destroyNextFrame) {
|
|
// OnDestroy();
|
|
// breakNextFrame = true;
|
|
// }
|
|
|
|
// Debug.Log($"Update {Time.frameCount} {Time.time}");
|
|
if (Application.isPlaying) {
|
|
if (tickMode == TickUpdateMode.Update) {
|
|
TickUpdateStep();
|
|
}
|
|
else if (tickMode == TickUpdateMode.DelayedUpdate) {
|
|
tickDelayTimer -= Time.deltaTime;
|
|
if (tickDelayTimer < 0) {
|
|
tickDelayTimer += tickDelay;
|
|
TickUpdateStep();
|
|
}
|
|
}
|
|
else if (tickMode == TickUpdateMode.MaximumTimeUpdate) {
|
|
float startTime = Time.realtimeSinceStartup;
|
|
while ((Time.realtimeSinceStartup - startTime) < maximumRuntime) {
|
|
TickUpdateStep();
|
|
}
|
|
}
|
|
else if (tickMode == TickUpdateMode.FullrunUpdate) {
|
|
TickUpdateFull();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void LateUpdate () {
|
|
if (Application.isPlaying) {
|
|
if (tickMode == TickUpdateMode.LateUpdate) {
|
|
TickUpdateStep();
|
|
}
|
|
else if (tickMode == TickUpdateMode.DelayedLateUpdate) {
|
|
tickDelayTimer -= Time.deltaTime;
|
|
if (tickDelayTimer < 0) {
|
|
tickDelayTimer += tickDelay;
|
|
TickUpdateStep();
|
|
}
|
|
}
|
|
else if (tickMode == TickUpdateMode.MaximumTimeLateUpdate) {
|
|
float startTime = Time.realtimeSinceStartup;
|
|
while ((Time.realtimeSinceStartup - startTime) < maximumRuntime) {
|
|
TickUpdateStep();
|
|
}
|
|
}
|
|
else if (tickMode == TickUpdateMode.FullrunLateUpdate) {
|
|
TickUpdateFull();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void TickUpdate () {
|
|
TickUpdateStep();
|
|
}
|
|
|
|
public void TickUpdateStep () {
|
|
if (restartBehaviourTreeNextFrame) {
|
|
RestartNow();
|
|
}
|
|
|
|
if (hasRootNode) {
|
|
// if null or looped trough all items
|
|
if ((!tickSuccess) || (tickEnumerator == null)) {
|
|
// restart tick
|
|
tickEnumerator = rootNode.Tick(null, 0).GetEnumerator();
|
|
if (runtimeLogging)
|
|
Debug.Log($"Assigned new Iterator {(tickEnumerator != null)}");
|
|
}
|
|
|
|
// run as usual
|
|
tickSuccess = tickEnumerator.MoveNext();
|
|
rootResult = tickEnumerator.Current;
|
|
if (runtimeLogging)
|
|
Debug.Log($"--- Ticked --- s: {tickSuccess} r:{rootResult} --- t:{Time.frameCount} ----------------------------------------------------------------------------------------------------------------------");
|
|
}
|
|
else {
|
|
Debug.LogError("cannot tick, missing root node! disabling this gameObject !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
|
|
gameObject.SetActive(false);
|
|
}
|
|
|
|
CallInspectorRedraw();
|
|
}
|
|
|
|
public void TickUpdateFull() {
|
|
if (restartBehaviourTreeNextFrame) {
|
|
RestartNow();
|
|
}
|
|
|
|
tickDelayTimer = tickDelay;
|
|
//behaviourTreeGenerator.SetTaskControllerAndGenerateCache(this);
|
|
|
|
if (hasRootNode) {
|
|
// reset the bt
|
|
foreach (var result in rootNode.Tick(null, 0)) {
|
|
rootResult = result;
|
|
}
|
|
rootResult = NodeResult.Continue;
|
|
}
|
|
if (runtimeLogging)
|
|
Debug.Log($"--- Restart --- r:{rootResult} --- t:{Time.frameCount} ----------------------------------------------------------------------------------------------------------------------");
|
|
|
|
CallInspectorRedraw();
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public partial class TaskController {
|
|
#region behaviourtree generator & exposing of methods and variables
|
|
[SerializeField]
|
|
private BehaviourTreeGenerator __behaviourTreeGenerator;
|
|
[HideInInspector, SerializeField]
|
|
private BehaviourTreeGenerator behaviourTreeGenerator {
|
|
get {
|
|
if (__behaviourTreeGenerator == null) { __behaviourTreeGenerator = BehaviourTreeGenerator.CreateInstance<BehaviourTreeGenerator>(); }
|
|
if (__behaviourTreeGenerator == null) { Debug.LogError($"behaviourTreeGenerator is null... how? {behaviourTreeGenerator}"); }
|
|
return __behaviourTreeGenerator;
|
|
}
|
|
}
|
|
// expose parts of the behaviourTreeGenerator
|
|
#endregion
|
|
|
|
#region functions and variables from behaviourTreeGenerator
|
|
// configuration
|
|
[SerializeField]
|
|
public List<TextAsset> taskScripts;
|
|
|
|
public bool generatorLogging {
|
|
get { return behaviourTreeGenerator.generatorLogging; }
|
|
set { behaviourTreeGenerator.generatorLogging = value; }
|
|
}
|
|
public bool internalAutomaticRegenerate {
|
|
get { return behaviourTreeGenerator.internalAutomaticRegenerate; }
|
|
set { behaviourTreeGenerator.internalAutomaticRegenerate = value; }
|
|
}
|
|
|
|
public TreeNode rootNode {
|
|
get { return behaviourTreeGenerator.rootNode; }
|
|
}
|
|
public bool hasRootNode {
|
|
get { return (behaviourTreeGenerator.rootNode != null); }
|
|
}
|
|
|
|
public void FullGenerate (bool force) {
|
|
behaviourTreeGenerator.FullGenerate(this, force);
|
|
}
|
|
|
|
public void FullReset () {
|
|
behaviourTreeGenerator.FullReset();
|
|
}
|
|
|
|
private bool restartBehaviourTreeNextFrame = false;
|
|
public void Restart(bool forceImmediate=false) {
|
|
restartBehaviourTreeNextFrame = true;
|
|
if (forceImmediate) {
|
|
RestartNow();
|
|
}
|
|
}
|
|
private void RestartNow () {
|
|
behaviourTreeGenerator.Restart();
|
|
restartBehaviourTreeNextFrame = false;
|
|
}
|
|
|
|
public void VerifyBindings() {
|
|
behaviourTreeGenerator.VerifyBinding(this);
|
|
}
|
|
|
|
public void RegenerateBehaviourTreeIfRequired (bool forceUpdate) {
|
|
behaviourTreeGenerator.RegenerateBehaviourTreeIfRequired(this, forceUpdate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// the methods called have changed (or might have...)
|
|
/// we dont yet filter which scripts are changed
|
|
/// </summary>
|
|
public void ClearBindingsAndScanAndBindMethods(bool forceUpdate = false) {
|
|
// forcefully clear and regenerate
|
|
behaviourTreeGenerator.ClearBindingsAndScanAndBindMethods(this, forceUpdate);
|
|
}
|
|
|
|
/// <summary>
|
|
/// is called by DetectBtChanges
|
|
/// </summary>
|
|
/// <param name="forceUpdate"></param>
|
|
public void BtScriptChangeDetected(bool forceUpdate = false) {
|
|
behaviourTreeGenerator.BtScriptChangeDetected(this, forceUpdate);
|
|
}
|
|
public void ResetNodesAndTokens() {
|
|
behaviourTreeGenerator.ResetNodesAndTokens();
|
|
}
|
|
public void GenerateTokensAndNodes(bool forceUpdate = false) {
|
|
Debug.Log("GenerateTokensAndNodes");
|
|
behaviourTreeGenerator.GenerateTokensAndNodes(this, forceUpdate);
|
|
}
|
|
public void ClearBinding() {
|
|
behaviourTreeGenerator.ClearBinding();
|
|
}
|
|
public void ScanAndBindMethods(bool forceUpdate = false) {
|
|
behaviourTreeGenerator.ScanAndBindMethods(this, forceUpdate);
|
|
}
|
|
public void SetTaskController() {
|
|
behaviourTreeGenerator.SetTaskControllerAndGenerateCache(this);
|
|
}
|
|
public void SetBehaviourTreeDirty() {
|
|
#if UNITY_EDITOR
|
|
EditorUtility.SetDirty(behaviourTreeGenerator);
|
|
#endif
|
|
}
|
|
public bool CheckBindingOutdated(bool allowNull = true) {
|
|
return behaviourTreeGenerator.CheckBindingOutdated(this, allowNull);
|
|
}
|
|
public bool CheckGenerationOutdated(bool allowNull = true) {
|
|
return behaviourTreeGenerator.CheckGenerationOutdated(allowNull);
|
|
}
|
|
|
|
// status
|
|
// hasScript && tokensGenerated && nodesGenerated
|
|
// public bool isGenerated {
|
|
// get { return behaviourTreeGenerator.isGenerated; }
|
|
// }
|
|
// public string generationState {
|
|
// get { return behaviourTreeGenerator.generationState; }
|
|
// }
|
|
public bool hasScript {
|
|
get { return behaviourTreeGenerator.HasScripts(this); }
|
|
}
|
|
// public bool tokensGenerated {
|
|
// get { return behaviourTreeGenerator.tokensGenerated; }
|
|
// }
|
|
// public bool nodesGenerated {
|
|
// get { return behaviourTreeGenerator.nodesGenerated; }
|
|
// }
|
|
|
|
|
|
public FileHash textAssetHashes {
|
|
get { return behaviourTreeGenerator.textAssetHashes; }
|
|
}
|
|
public FileHash generatedCodeBtFileHashes {
|
|
get { return behaviourTreeGenerator.generatedCodeBtFileHashes; }
|
|
}
|
|
public FileHash boundCodeBtFileHashes {
|
|
get { return behaviourTreeGenerator.boundCodeBtFileHashes; }
|
|
}
|
|
|
|
public List<TokenList> tokens {
|
|
get { return behaviourTreeGenerator.tokens; }
|
|
}
|
|
|
|
public List<NodeList> treeRootNodes {
|
|
get { return behaviourTreeGenerator.treeRootNodes; }
|
|
}
|
|
#if UNITY_EDITOR
|
|
public SerializedProperty treeRootNodesAsSerializedProperty {
|
|
get {
|
|
SerializedObject sObj = new SerializedObject(behaviourTreeGenerator);
|
|
return sObj.FindProperty("treeRootNodes");
|
|
}
|
|
}
|
|
#endif
|
|
public int nodeCount {
|
|
get { return behaviourTreeGenerator.nodeCount; }
|
|
}
|
|
|
|
public bool generationError {
|
|
get { return behaviourTreeGenerator.generationError; }
|
|
}
|
|
|
|
public bool bindingError {
|
|
get { return behaviourTreeGenerator.bindingError; }
|
|
}
|
|
|
|
// public bool methodsScanned {
|
|
// get { return behaviourTreeGenerator.methodsScanned; }
|
|
// }
|
|
// public bool methodsBound {
|
|
// get { return behaviourTreeGenerator.methodsBound; }
|
|
// }
|
|
public DictionaryOfStringAndDictionaryOfStringAndMethodImplementation methods {
|
|
get { return behaviourTreeGenerator.methods; }
|
|
}
|
|
#endregion
|
|
}
|
|
} |