This commit is contained in:
2026-05-27 16:48:22 +08:00
parent 0082c0b727
commit e81e4547e7
535 changed files with 35957 additions and 10 deletions

View File

@@ -0,0 +1,162 @@
#if GRAPH_DESIGNER
/// ---------------------------------------------
/// Behavior Designer
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.Runtime.Utility
{
using Opsive.BehaviorDesigner.Runtime.Components;
using Unity.Collections;
using Unity.Entities;
/// <summary>
/// Utility functions that are used throughout the behavior tree execution.
/// </summary>
public static class ComponentUtility
{
/// <summary>
/// The size of a ulong in bits.
/// </summary>
public static int ulongBitSize => sizeof(ulong) * 8;
/// <summary>
/// Adds an evaluation component to the specified entity based on the task count and evaluation type.
/// </summary>
/// <param name="world">The ECS world to add the component to.</param>
/// <param name="entity">The entity to add the evaluation component to.</param>
/// <param name="taskCount">The total number of tasks in the behavior tree.</param>
/// <param name="evaluationType">The type of evaluation to perform.</param>
/// <param name="maxEvaluationCount">The maximum number of evaluations allowed.</param>
public static void AddEvaluationComponent(World world, Entity entity, int taskCount, EvaluationType evaluationType, int maxEvaluationCount)
{
// Both EvaluationType.EntireTree and EvaluationType.Count use a bitmask to track evaluated tasks.
// For Count mode, an additional element is reserved at the end to store the execution count.
var bitmaskElementCount = taskCount / ulongBitSize + 1;
var countModeExtraElement = evaluationType == EvaluationType.Count ? 1 : 0;
if (taskCount < 192) {
world.EntityManager.AddComponent<EvaluationComponent32>(entity);
var evaluatedTasks = new FixedList32Bytes<ulong>();
for (int i = 0; i < bitmaskElementCount + countModeExtraElement; ++i) {
evaluatedTasks.Add(0);
}
world.EntityManager.AddComponentData(entity, new EvaluationComponent32() { EvaluationType = evaluationType, MaxEvaluationCount = (ushort)UnityEngine.Mathf.Max(1, maxEvaluationCount), EvaluatedTasks = evaluatedTasks });
} else if (taskCount < 448) {
world.EntityManager.AddComponent<EvaluationComponent64>(entity);
var evaluatedTasks = new FixedList64Bytes<ulong>();
for (int i = 0; i < bitmaskElementCount + countModeExtraElement; ++i) {
evaluatedTasks.Add(0);
}
world.EntityManager.AddComponentData(entity, new EvaluationComponent64() { EvaluationType = evaluationType, MaxEvaluationCount = (ushort)UnityEngine.Mathf.Max(1, maxEvaluationCount), EvaluatedTasks = evaluatedTasks });
} else if (taskCount < 960) {
world.EntityManager.AddComponent<EvaluationComponent128>(entity);
var evaluatedTasks = new FixedList128Bytes<ulong>();
for (int i = 0; i < bitmaskElementCount + countModeExtraElement; ++i) {
evaluatedTasks.Add(0);
}
world.EntityManager.AddComponentData(entity, new EvaluationComponent128() { EvaluationType = evaluationType, MaxEvaluationCount = (ushort)UnityEngine.Mathf.Max(1, maxEvaluationCount), EvaluatedTasks = evaluatedTasks });
} else if (taskCount < 4032) {
world.EntityManager.AddComponent<EvaluationComponent512>(entity);
var evaluatedTasks = new FixedList512Bytes<ulong>();
for (int i = 0; i < bitmaskElementCount + countModeExtraElement; ++i) {
evaluatedTasks.Add(0);
}
world.EntityManager.AddComponentData(entity, new EvaluationComponent512() { EvaluationType = evaluationType, MaxEvaluationCount = (ushort)UnityEngine.Mathf.Max(1, maxEvaluationCount), EvaluatedTasks = evaluatedTasks });
} else if (taskCount < 32704) {
world.EntityManager.AddComponent<EvaluationComponent4096>(entity);
var evaluatedTasks = new FixedList4096Bytes<ulong>();
for (int i = 0; i < bitmaskElementCount + countModeExtraElement; ++i) {
evaluatedTasks.Add(0);
}
world.EntityManager.AddComponentData(entity, new EvaluationComponent4096() { EvaluationType = evaluationType, MaxEvaluationCount = (ushort)UnityEngine.Mathf.Max(1, maxEvaluationCount), EvaluatedTasks = evaluatedTasks });
} else {
UnityEngine.Debug.LogError("Error: Trees with more than 32,703 tasks are not supported. Please email support@opsive.com.");
}
}
/// <summary>
/// Resets the evaluation component data for the specified entity by clearing all evaluated task flags.
/// </summary>
/// <param name="world">The ECS world containing the entity.</param>
/// <param name="entity">The entity whose evaluation component should be reset.</param>
public static void ResetEvaluationComponent(World world, Entity entity)
{
if (world.EntityManager.HasComponent<EvaluationComponent32>(entity)) {
var evaluateComponent = world.EntityManager.GetComponentData<EvaluationComponent32>(entity);
var evaluatedTasks = evaluateComponent.EvaluatedTasks;
for (int i = 0; i < evaluatedTasks.Length; ++i) {
evaluatedTasks[i] = 0;
}
evaluateComponent.EvaluatedTasks = evaluatedTasks;
world.EntityManager.SetComponentData(entity, evaluateComponent);
} else if (world.EntityManager.HasComponent<EvaluationComponent64>(entity)) {
var evaluateComponent = world.EntityManager.GetComponentData<EvaluationComponent64>(entity);
var evaluatedTasks = evaluateComponent.EvaluatedTasks;
for (int i = 0; i < evaluatedTasks.Length; ++i) {
evaluatedTasks[i] = 0;
}
evaluateComponent.EvaluatedTasks = evaluatedTasks;
world.EntityManager.SetComponentData(entity, evaluateComponent);
} else if (world.EntityManager.HasComponent<EvaluationComponent128>(entity)) {
var evaluateComponent = world.EntityManager.GetComponentData<EvaluationComponent128>(entity);
var evaluatedTasks = evaluateComponent.EvaluatedTasks;
for (int i = 0; i < evaluatedTasks.Length; ++i) {
evaluatedTasks[i] = 0;
}
evaluateComponent.EvaluatedTasks = evaluatedTasks;
world.EntityManager.SetComponentData(entity, evaluateComponent);
} else if (world.EntityManager.HasComponent<EvaluationComponent512>(entity)) {
var evaluateComponent = world.EntityManager.GetComponentData<EvaluationComponent512>(entity);
var evaluatedTasks = evaluateComponent.EvaluatedTasks;
for (int i = 0; i < evaluatedTasks.Length; ++i) {
evaluatedTasks[i] = 0;
}
evaluateComponent.EvaluatedTasks = evaluatedTasks;
world.EntityManager.SetComponentData(entity, evaluateComponent);
} else if (world.EntityManager.HasComponent<EvaluationComponent4096>(entity)) {
var evaluateComponent = world.EntityManager.GetComponentData<EvaluationComponent4096>(entity);
var evaluatedTasks = evaluateComponent.EvaluatedTasks;
for (int i = 0; i < evaluatedTasks.Length; ++i) {
evaluatedTasks[i] = 0;
}
evaluateComponent.EvaluatedTasks = evaluatedTasks;
world.EntityManager.SetComponentData(entity, evaluateComponent);
}
}
/// <summary>
/// Removes the evaluation component from the specified entity.
/// </summary>
/// <param name="world">The ECS world containing the entity.</param>
/// <param name="entity">The entity whose evaluation component should be removed.</param>
public static void RemoveEvaluationComponent(World world, Entity entity)
{
if (world.EntityManager.HasComponent<EvaluationComponent32>(entity)) {
world.EntityManager.RemoveComponent<EvaluationComponent32>(entity);
} else if (world.EntityManager.HasComponent<EvaluationComponent64>(entity)) {
world.EntityManager.RemoveComponent<EvaluationComponent64>(entity);
} else if (world.EntityManager.HasComponent<EvaluationComponent128>(entity)) {
world.EntityManager.RemoveComponent<EvaluationComponent128>(entity);
} else if (world.EntityManager.HasComponent<EvaluationComponent512>(entity)) {
world.EntityManager.RemoveComponent<EvaluationComponent512>(entity);
} else if (world.EntityManager.HasComponent<EvaluationComponent4096>(entity)) {
world.EntityManager.RemoveComponent<EvaluationComponent4096>(entity);
}
}
/// <summary>
/// Adds the components necessary in order to trigger an interrupt.
/// </summary>
/// <param name="entityManager">The EntityManager that the entity belongs to.</param>
/// <param name="entity">The entity that should have the components added.</param>
public static void AddInterruptComponents(EntityManager entityManager, Entity entity)
{
entityManager.AddComponent<InterruptFlag>(entity);
entityManager.SetComponentEnabled<InterruptFlag>(entity, false);
entityManager.AddComponent<InterruptedFlag>(entity);
entityManager.SetComponentEnabled<InterruptedFlag>(entity, false);
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,651 @@
#if GRAPH_DESIGNER
/// ---------------------------------------------
/// Behavior Designer
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.Runtime.Utility
{
using Opsive.BehaviorDesigner.Runtime;
using Opsive.BehaviorDesigner.Runtime.Components;
using Opsive.BehaviorDesigner.Runtime.Tasks;
using Opsive.GraphDesigner.Runtime;
using Opsive.GraphDesigner.Runtime.Variables;
using Opsive.Shared.Utility;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Unity.Entities;
using UnityEngine;
using static Opsive.BehaviorDesigner.Runtime.BehaviorTreeData;
/// <summary>
/// Helper class which will save and load behavior tree tasks.
/// </summary>
public static class SaveManager
{
/// <summary>
/// Specifies which objects should be saved.
/// </summary>
public enum VariableSaveScope : byte
{
GameObjectVariables = 1, // Saves the GameObjectVariables.
SceneVariables = 2, // Saves the SceneVariables.
ProjectVariables = 4 // Saves the ProjectVariables.
}
/// <summary>
/// The save data associated with the behavior tree and shared variables.
/// </summary>
[System.Serializable]
public struct SaveData
{
[Tooltip("The behavior tree save data.")]
public BehaviorTreeSaveData[] BehaviorTreeSaveData;
[Tooltip("The external shared variable save data.")]
public VariableSaveData[] VariableSaveData;
}
/// <summary>
/// The save data associated with the behavior tree.
/// </summary>
[System.Serializable]
public struct BehaviorTreeSaveData
{
[Tooltip("The unique ID of the save data. This will change each time the data is serialized")]
public int UniqueID;
[Tooltip("The loaded task components.")]
public Serialization[] TaskComponents;
[Tooltip("The loaded branch components.")]
public Serialization[] BranchComponents;
[Tooltip("The loaded reevaluate task components.")]
public Serialization[] ReevaluateTaskComponents;
[Tooltip("The user task data.")]
public TaskSaveData[] TaskData;
[Tooltip("The values of the graph SharedVariables.")]
public VariableSaveData GraphSharedVariables;
}
/// <summary>
/// The save data associated with the shared variables.
/// </summary>
[System.Serializable]
public struct VariableSaveData
{
[Tooltip("The unique ID of the variable data. This will change each time the data is serialized")]
public int UniqueID;
[Tooltip("The values of the SharedVariables.")]
public Serialization[] Values;
[Tooltip("The scope of the variables.")]
public SharedVariable.SharingScope Scope;
}
/// <summary>
/// Container for the task save data. Allows for nested tasks.
/// </summary>
[System.Serializable]
public struct TaskSaveData
{
[Tooltip("The save data associated with each task.")]
public Serialization[] Value;
}
/// <summary>
/// Gets the save data from the specified behavior trees.
/// </summary>
/// <param name="behaviorTrees">The behavior trees that should be saved.</param>
/// <param name="variableSaveScope">Specifies which variables should be saved. Graph variables will automatically be saved.</param>
/// <returns>The resulting save data. Can be null.</returns>
public static SaveData? Save(BehaviorTree[] behaviorTrees, VariableSaveScope variableSaveScope = 0)
{
if (!Application.isPlaying) {
Debug.LogWarning($"Warning: Behavior trees can only be saved at runtime.");
return null;
}
if (behaviorTrees.Length == 0) {
return null;
}
// Assume all behavior trees can be saved.
var saveData = new SaveData();
saveData.BehaviorTreeSaveData = new BehaviorTreeSaveData[behaviorTrees.Length];
var variableSaveDataList = new List<VariableSaveData>();
HashSet<GameObject> savedGameObjectVariables = null;
var behaviorTreeSaveCount = 0;
for (int i = 0; i < behaviorTrees.Length; ++i) {
if (behaviorTrees[i] == null) {
continue;
}
// Add the save data to the array if the save is valid.
var behaviorTreeSaveData = Save(behaviorTrees[i]);
if (behaviorTreeSaveData.HasValue) {
saveData.BehaviorTreeSaveData[behaviorTreeSaveCount] = behaviorTreeSaveData.Value;
behaviorTreeSaveCount++;
// The associated GameObject variables may also need to be saved.
if ((variableSaveScope & VariableSaveScope.GameObjectVariables) != 0) {
// Only save the GameObject variables once.
if (savedGameObjectVariables == null) {
savedGameObjectVariables = new HashSet<GameObject>();
}
if (savedGameObjectVariables.Contains(behaviorTrees[i].gameObject)) {
continue;
}
savedGameObjectVariables.Add(behaviorTrees[i].gameObject);
// The GameObject variables can be saved.
var gameObjectSharedVariables = behaviorTrees[i].gameObject.GetComponent<GameObjectSharedVariables>();
if (gameObjectSharedVariables != null) {
var variableSaveData = Save(gameObjectSharedVariables);
if (variableSaveData.HasValue) {
variableSaveDataList.Add(variableSaveData.Value);
}
}
}
}
}
// Not all behavior trees can be saved.
if (behaviorTrees.Length != behaviorTreeSaveCount) {
var behaviorTreesSaveData = saveData.BehaviorTreeSaveData;
System.Array.Resize(ref behaviorTreesSaveData, behaviorTreeSaveCount);
saveData.BehaviorTreeSaveData = behaviorTreesSaveData;
}
// The scene variables can be saved.
if ((variableSaveScope & VariableSaveScope.SceneVariables) != 0) {
if (SceneSharedVariables.Instance != null) {
var variableSaveData = Save(SceneSharedVariables.Instance);
if (variableSaveData.HasValue) {
variableSaveDataList.Add(variableSaveData.Value);
}
}
}
// The project variables can be saved.
if ((variableSaveScope & VariableSaveScope.ProjectVariables) != 0) {
if (ProjectSharedVariables.Instance != null) {
var variableSaveData = Save(ProjectSharedVariables.Instance);
if (variableSaveData.HasValue) {
variableSaveDataList.Add(variableSaveData.Value);
}
}
}
// There has to be something to be saved.
if (behaviorTreeSaveCount == 0 && variableSaveDataList.Count == 0) {
return null;
}
// Persist the variable save data.
if (variableSaveDataList != null && variableSaveDataList.Count > 0) {
saveData.VariableSaveData = variableSaveDataList.ToArray();
}
return saveData;
}
/// <summary>
/// Saves the save data from the specified behavior trees to the specified file.
/// </summary>
/// <param name="behaviorTrees">The behavior trees that should be saved.</param>
/// <param name="filePath">The save data path.</param>
/// <param name="variableSaveScope">Specifies which variables should be saved. Graph variables will automatically be saved.</param>
/// <returns>True if at least one behavior tree's save data was saved.</returns>
public static bool Save(BehaviorTree[] behaviorTrees, string filePath, VariableSaveScope variableSaveScope = 0)
{
var saveData = Save(behaviorTrees, variableSaveScope);
if (!saveData.HasValue) {
return false;
}
// Save the save data.
if (File.Exists(filePath)) {
File.Delete(filePath);
}
try {
if (!Directory.Exists(Path.GetDirectoryName(filePath))) {
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
}
var fileStream = File.Create(filePath);
using (var streamWriter = new StreamWriter(fileStream)) {
streamWriter.Write(JsonUtility.ToJson(saveData));
}
fileStream.Close();
} catch (System.Exception e) {
Debug.LogException(e);
return false;
}
return true;
}
/// <summary>
/// Returns the save data associated with the behavior tree.
/// </summary>
/// <param name="behaviorTree">The behavior tree that should be saved.</param>
/// <returns>The save data associated with the behavior tree.</returns>
private static BehaviorTreeSaveData? Save(BehaviorTree behaviorTree)
{
if (behaviorTree.Entity.Index == 0) {
Debug.LogWarning($"Warning: The behavior tree {behaviorTree.name} must be active in order to be saved.", behaviorTree);
return null;
}
if (behaviorTree.LogicNodes == null || behaviorTree.LogicNodes.Length == 0) {
Debug.LogWarning($"Warning: The behavior tree {behaviorTree.name} must have tasks in order to be saved.", behaviorTree);
return null;
}
// The current task status must be serialized.
var taskComponents = behaviorTree.World.EntityManager.GetBuffer<TaskComponent>(behaviorTree.Entity);
var serializedTaskComponents = new Serialization[taskComponents.Length];
for (int i = 0; i < taskComponents.Length; ++i) {
serializedTaskComponents[i] = Serialization.Serialize(taskComponents[i]);
}
// The branch info must be serialized.
var branchComponents = behaviorTree.World.EntityManager.GetBuffer<BranchComponent>(behaviorTree.Entity);
var serializedBranchComponents = new Serialization[branchComponents.Length];
for (int i = 0; i < branchComponents.Length; ++i) {
serializedBranchComponents[i] = Serialization.Serialize(branchComponents[i]);
}
// The reevaluation status must be serialized.
Serialization[] serializedReevaluateTaskComponents = null;
if (behaviorTree.World.EntityManager.HasBuffer<ReevaluateTaskComponent>(behaviorTree.Entity)) {
var reevaluateTaskComponents = behaviorTree.World.EntityManager.GetBuffer<ReevaluateTaskComponent>(behaviorTree.Entity);
serializedReevaluateTaskComponents = new Serialization[reevaluateTaskComponents.Length];
for (int i = 0; i < reevaluateTaskComponents.Length; ++i) {
serializedReevaluateTaskComponents[i] = Serialization.Serialize(reevaluateTaskComponents[i]);
}
}
// Each task can serialize their own data.
var tasks = behaviorTree.LogicNodes;
var serializedTaskData = new List<TaskSaveData>();
for (int i = 0; i < tasks.Length; ++i) {
if (!(tasks[i] is ISavableTask)) {
continue;
}
var saveableTask = tasks[i] as ISavableTask;
var reflectionType = saveableTask.GetSaveReflectionType(-1);
if (reflectionType != MemberVisibility.None) {
var serializedData = Serialization.Serialize(saveableTask, reflectionType, BehaviorTreeData.ValidateSerializedObject);
serializedTaskData.Add(new TaskSaveData() { Value = new Serialization[] { serializedData } });
} else {
var taskData = saveableTask.Save(behaviorTree.World, behaviorTree.Entity);
if (taskData == null) {
continue;
}
// Tasks can contain more tasks. Serialize each task element separately.
if (typeof(IList).IsAssignableFrom(taskData.GetType())) {
var taskDataList = taskData as IList;
var taskSaveData = new Serialization[taskDataList.Count];
for (int j = 0; j < taskDataList.Count; ++j) {
if (taskDataList[j] is Serialization) {
taskSaveData[j] = taskDataList[j] as Serialization;
} else {
taskSaveData[j] = Serialization.Serialize(taskDataList[j]);
}
}
serializedTaskData.Add(new TaskSaveData() { Value = taskSaveData });
} else {
var serializedValue = new Serialization[] { (taskData is Serialization) ? (taskData as Serialization) : Serialization.Serialize(taskData) };
serializedTaskData.Add(new TaskSaveData() { Value = serializedValue });
}
}
}
var behaviorTreeSaveData = new BehaviorTreeSaveData() {
UniqueID = behaviorTree.UniqueID,
TaskComponents = serializedTaskComponents,
BranchComponents = serializedBranchComponents,
ReevaluateTaskComponents = serializedReevaluateTaskComponents,
TaskData = serializedTaskData.ToArray(),
};
var variableSaveData = Save(behaviorTree as ISharedVariableContainer);
if (variableSaveData.HasValue) {
behaviorTreeSaveData.GraphSharedVariables = variableSaveData.Value;
}
return behaviorTreeSaveData;
}
/// <summary>
/// Returns the save data associated with the variable container.
/// </summary>
/// <param name="variableContainer">The variable container that should be saved.</param>
/// <returns>The save data associated with the variable container.</returns>
private static VariableSaveData? Save(ISharedVariableContainer variableContainer)
{
if (variableContainer == null) {
return null;
}
var sharedVariables = variableContainer.SharedVariables;
if (sharedVariables == null || sharedVariables.Length == 0) {
return null;
}
Serialization[] serializedSharedVariablesData = null;
if (sharedVariables != null) {
serializedSharedVariablesData = new Serialization[sharedVariables.Length];
for (int i = 0; i < sharedVariables.Length; ++i) {
serializedSharedVariablesData[i] = Serialization.Serialize(sharedVariables[i].GetValue(), BehaviorTreeData.ValidateSerializedObject);
}
}
return new VariableSaveData() {
UniqueID = variableContainer.UniqueID,
Values = serializedSharedVariablesData,
Scope = variableContainer.VariableScope
};
}
/// <summary>
/// Loads the save data contained within the specified file.
/// </summary>
/// <param name="behaviorTrees">The behavior trees that should be loaded.</param>
/// <param name="filePath">The save data path.</param>
/// <param name="afterVariablesRestored">Optional callback after the graph variables have been restored.</param>
/// <returns>True if at least one behavior tree's save data was loaded.</returns>
public static bool Load(BehaviorTree[] behaviorTrees, string filePath, Action<BehaviorTree> afterVariablesRestored = null)
{
if (!Application.isPlaying) {
Debug.LogWarning($"Warning: Behavior trees can only be loaded at runtime.");
return false;
}
if (!File.Exists(filePath)) {
Debug.LogWarning($"Warning: The file at path {filePath} does not exist.");
return false;
}
var fileStream = File.Open(filePath, FileMode.Open);
var saveData = new SaveData();
using (var streamReader = new StreamReader(fileStream)) {
var fileData = streamReader.ReadToEnd();
saveData = JsonUtility.FromJson<SaveData>(fileData);
}
fileStream.Close();
return Load(behaviorTrees, saveData, afterVariablesRestored);
}
/// <summary>
/// Loads the save data.
/// </summary>
/// <param name="behaviorTrees">The behavior trees that should be loaded.</param>
/// <param name="saveData">The loaded save data.</param>
/// <param name="afterVariablesRestored">Optional callback after the graph variables have been restored.</param>
/// <returns>True if at least one behavior tree's save data was loaded.</returns>
public static bool Load(BehaviorTree[] behaviorTrees, SaveData saveData, Action<BehaviorTree> afterVariablesRestored = null)
{
// Load the shared variables before the behavior trees so the trees can reference the variables.
var loadedSceneVariables = false;
var loadedProjectVariables = false;
var loadCount = 0;
Dictionary<int, VariableSaveData> variableSaveDataByID = null;
if (saveData.VariableSaveData != null && saveData.VariableSaveData.Length > 0) {
// The save data is unique to the variable container specified by the unique ID.
for (int i = 0; i < saveData.VariableSaveData.Length; ++i) {
// Remember the scope to determine if the variable scope needs to be checked again when loading.
if (saveData.VariableSaveData[i].Scope == SharedVariable.SharingScope.GameObject) {
// The GameObject SharedVariables will be loaded with the behavior tree.
if (variableSaveDataByID == null) {
variableSaveDataByID = new Dictionary<int, VariableSaveData>();
}
variableSaveDataByID.Add(saveData.VariableSaveData[i].UniqueID, saveData.VariableSaveData[i]);
} else if (saveData.VariableSaveData[i].Scope == SharedVariable.SharingScope.Scene) {
// Restore the SceneSharedVariables if it hasn't already been loaded.
if (!loadedSceneVariables) {
if (SceneSharedVariables.Instance != null) {
if (Load(SceneSharedVariables.Instance, saveData.VariableSaveData[i])) {
loadCount++;
}
}
loadedSceneVariables = true;
}
} else if (saveData.VariableSaveData[i].Scope == SharedVariable.SharingScope.Project) {
// Restore the ProjectSharedVariables if it hasn't already been loaded.
if (!loadedProjectVariables) {
if (ProjectSharedVariables.Instance != null) {
if (Load(ProjectSharedVariables.Instance, saveData.VariableSaveData[i])) {
loadCount++;
}
}
}
}
}
}
if (saveData.BehaviorTreeSaveData != null && saveData.BehaviorTreeSaveData.Length > 0) {
// The save data is unique to the behavior tree specified by the unique ID.
var behaviorTreeSaveDataByID = new Dictionary<int, BehaviorTreeSaveData>();
for (int i = 0; i < saveData.BehaviorTreeSaveData.Length; ++i) {
behaviorTreeSaveDataByID.Add(saveData.BehaviorTreeSaveData[i].UniqueID, saveData.BehaviorTreeSaveData[i]);
}
// Load the save data for each behavior tree.
for (int i = 0; i < behaviorTrees.Length; ++i) {
var behaviorTree = behaviorTrees[i];
if (behaviorTreeSaveDataByID.TryGetValue(behaviorTree.UniqueID, out var behaviorTreeSaveData)) {
// Restore the GameObjectSharedVariables if they haven't already been restored.
if (variableSaveDataByID != null) {
var gameObjectSharedVariables = behaviorTree.GetComponent<GameObjectSharedVariables>();
if (gameObjectSharedVariables != null) {
if (variableSaveDataByID.TryGetValue(gameObjectSharedVariables.UniqueID, out var variableSaveData)) {
if (Load(gameObjectSharedVariables, variableSaveData)) {
loadCount++;
}
// Remove the ID after it has been loaded so the variables aren't loaded again. This can happen if multiple
// trees on the same GameObject reference the same GameObject variable.
variableSaveDataByID.Remove(gameObjectSharedVariables.UniqueID);
}
}
}
// Restore the Graph SharedVariables.
if (Load(behaviorTree, behaviorTreeSaveData.GraphSharedVariables)) {
loadCount++;
}
// Callback after the variables have been restored.
afterVariablesRestored?.Invoke(behaviorTree);
// Populate the variables in an internal mapping for quick lookup.
var variableByNameMap = BehaviorTreeData.PopulateSharedVariablesMapping(behaviorTree, true);
// Restore the tree after the variables have been restored.
if (Load(behaviorTree, behaviorTreeSaveData, afterVariablesRestored, variableByNameMap)) {
loadCount++;
}
}
}
}
return loadCount > 0;
}
/// <summary>
/// Loads the behavior tree from the specified save data.
/// </summary>
/// <param name="behaviorTree">The behavior tree that should be restored.</param>
/// <param name="saveData">The save data associated with the behavior tree.</param>
/// <param name="afterVariablesRestored">Optional callback after the graph variables have been restored.</param>
/// <param name="variableByNameMap">A mapping between the variable name and the variable reference.</param>
/// <returns>True if the behavior tree was successfully loaded.</returns>
private static bool Load(BehaviorTree behaviorTree, BehaviorTreeSaveData saveData, Action<BehaviorTree> afterVariablesRestored, Dictionary<VariableAssignment, SharedVariable> variableByNameMap)
{
// The ID must match.
if (behaviorTree.UniqueID != saveData.UniqueID) {
Debug.LogError($"Error: The behavior tree {behaviorTree.name} cannot be loaded due to being saved in a different version of the behavior tree.");
return false;
}
// The behavior tree must be initialized in order to be loaded.
if (!behaviorTree.InitializeTree()) {
return false;
}
// Stop the behavior tree so all tasks issue their end callback.
var enableEntity = behaviorTree.World.EntityManager.HasComponent<EnabledFlag>(behaviorTree.Entity) && behaviorTree.World.EntityManager.IsComponentEnabled<EnabledFlag>(behaviorTree.Entity);
var evaluateEntity = behaviorTree.World.EntityManager.HasComponent<EvaluateFlag>(behaviorTree.Entity) && behaviorTree.World.EntityManager.IsComponentEnabled<EvaluateFlag>(behaviorTree.Entity);
var active = behaviorTree.IsActive();
if (active) {
behaviorTree.StopBehavior();
}
// Restore the task component status.
var taskComponents = behaviorTree.World.EntityManager.GetBuffer<TaskComponent>(behaviorTree.Entity);
for (int i = 0; i < saveData.TaskComponents.Length; ++i) {
taskComponents[i] = (TaskComponent)saveData.TaskComponents[i].DeserializeFields(MemberVisibility.Public);
}
var branchComponents = behaviorTree.World.EntityManager.GetBuffer<BranchComponent>(behaviorTree.Entity);
// Restore the branch info components.
for (int i = 0; i < saveData.BranchComponents.Length; ++i) {
branchComponents[i] = (BranchComponent)saveData.BranchComponents[i].DeserializeFields(MemberVisibility.Public);
if (branchComponents[i].ActiveFlagComponentType.TypeIndex == TypeIndex.Null) {
continue;
}
behaviorTree.World.EntityManager.SetComponentEnabled(behaviorTree.Entity, branchComponents[i].ActiveFlagComponentType, true);
}
if (behaviorTree.World.EntityManager.HasBuffer<ReevaluateTaskComponent>(behaviorTree.Entity)) {
var reevaluatedTaskComponents = behaviorTree.World.EntityManager.GetBuffer<ReevaluateTaskComponent>(behaviorTree.Entity);
// Restore the reevaluated components.
for (int i = 0; i < saveData.ReevaluateTaskComponents.Length; ++i) {
reevaluatedTaskComponents[i] = (ReevaluateTaskComponent)saveData.ReevaluateTaskComponents[i].DeserializeFields(MemberVisibility.Public);
if (reevaluatedTaskComponents[i].ReevaluateFlagComponentType.TypeIndex == TypeIndex.Null) {
continue;
}
behaviorTree.World.EntityManager.SetComponentEnabled(behaviorTree.Entity, reevaluatedTaskComponents[i].ReevaluateFlagComponentType, true);
}
}
// Each task can serialize their own data.
var tasks = behaviorTree.LogicNodes;
var saveDataIndex = 0;
ResizableArray<TaskAssignment> taskReferences = null;
for (int i = 0; i < tasks.Length; ++i) {
if (!(tasks[i] is ISavableTask)) {
if (tasks[i] is Task) {
(tasks[i] as Task).Initialize(behaviorTree, (ushort)i);
}
continue;
}
var taskSaveData = saveData.TaskData[saveDataIndex];
saveDataIndex++;
if (taskSaveData.Value == null || taskSaveData.Value.Length == 0) {
if (tasks[i] is Task) {
(tasks[i] as Task).Initialize(behaviorTree, (ushort)i);
}
continue;
}
var saveableTask = tasks[i] as ISavableTask;
var reflectionType = saveableTask.GetSaveReflectionType(-1);
if (reflectionType != MemberVisibility.None) {
taskSaveData.Value[0].DeserializeFields(saveableTask, saveableTask.GetSaveReflectionType(0), BehaviorTreeData.ValidateDeserializedTypeObject,
(object fieldInfoObj, object task, object value) =>
{
return BehaviorTreeData.ValidateDeserializedObject(fieldInfoObj, task, value, ref variableByNameMap, ref taskReferences);
});
} else {
if (taskSaveData.Value.Length == 1) {
if (saveableTask.GetSaveReflectionType(0) != MemberVisibility.None) {
saveableTask.Load(taskSaveData.Value[0], behaviorTree.World, behaviorTree.Entity, variableByNameMap, ref taskReferences);
} else {
saveableTask.Load(taskSaveData.Value[0].DeserializeFields(MemberVisibility.Public, BehaviorTreeData.ValidateDeserializedTypeObject,
(object fieldInfoObj, object task, object value) =>
{
return BehaviorTreeData.ValidateDeserializedObject(fieldInfoObj, task, value, ref variableByNameMap, ref taskReferences);
}), behaviorTree.World, behaviorTree.Entity, variableByNameMap, ref taskReferences);
}
} else {
var taskData = new object[taskSaveData.Value.Length];
for (int j = 0; j < taskSaveData.Value.Length; ++j) {
if (taskSaveData.Value[j] == null) {
continue;
}
if (saveableTask.GetSaveReflectionType(j) != MemberVisibility.None) {
// The task is responsible for deserializing the fields.
taskData[j] = taskSaveData.Value[j];
} else {
taskData[j] = taskSaveData.Value[j].DeserializeFields(MemberVisibility.Public, BehaviorTreeData.ValidateDeserializedTypeObject,
(object fieldInfoObj, object task, object value) =>
{
return BehaviorTreeData.ValidateDeserializedObject(fieldInfoObj, task, value, ref variableByNameMap, ref taskReferences);
});
}
}
saveableTask.Load(taskData, behaviorTree.World, behaviorTree.Entity, variableByNameMap, ref taskReferences);
}
}
if (tasks[i] is Task) {
(tasks[i] as Task).Initialize(behaviorTree, (ushort)i);
}
}
// After the tree has been loaded the task references need to be assigned.
BehaviorTreeData.AssignTaskReferences(behaviorTree.LogicNodes, taskReferences);
if (active) {
behaviorTree.StartBehavior();
}
if (enableEntity) {
behaviorTree.World.EntityManager.SetComponentEnabled<EnabledFlag>(behaviorTree.Entity, true);
}
if (evaluateEntity) {
behaviorTree.World.EntityManager.SetComponentEnabled<EvaluateFlag>(behaviorTree.Entity, true);
}
return true;
}
/// <summary>
/// Loads the variable from the specified file path.
/// </summary>
/// <param name="variableContainer">The variable container that should be restored.</param>
/// <param name="saveData">The save data associated with the variable container.</param>
/// <returns>True if the variable container was successfully loaded.</returns>
private static bool Load(ISharedVariableContainer variableContainer, VariableSaveData saveData)
{
// There may not be any variables saved.
if (saveData.UniqueID == 0) {
return false;
}
// The ID must match.
if (variableContainer.UniqueID != saveData.UniqueID) {
Debug.LogError($"Error: The variables {variableContainer} cannot be loaded due to being saved in a different version of the variable container.");
return false;
}
var sharedVariables = variableContainer.SharedVariables;
if (sharedVariables != null) {
for (int i = 0; i < saveData.Values.Length; ++i) {
if (saveData.Values[i] == null) {
continue;
}
var sharedVariableValue = saveData.Values[i].DeserializeFields(MemberVisibility.Public);
sharedVariables[i].SetValue(sharedVariableValue);
}
}
return true;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,108 @@
#if GRAPH_DESIGNER
/// ---------------------------------------------
/// Behavior Designer
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.Runtime.Utility
{
using Opsive.BehaviorDesigner.Runtime.Components;
using Unity.Burst;
using Unity.Entities;
/// <summary>
/// Utility functions that are used throughout the behavior tree execution.
/// </summary>
[BurstCompile]
public static class TraversalUtility
{
/// <summary>
/// Returns true if the specified index is a child of the parent index.
/// </summary>
/// <param name="index">The index to determine if it is a child.</param>
/// <param name="parentIndex">The index of the parent.</param>
/// <param name="taskComponents">The array of nodes.</param>
/// <returns>True if the specified index is a child of the parent index.</returns>
[BurstCompile]
public static bool IsParent(ushort index, ushort parentIndex, ref DynamicBuffer<TaskComponent> taskComponents)
{
if (parentIndex == ushort.MaxValue || index == ushort.MaxValue) {
return false;
}
// The child can be considered a parent of itself.
if (parentIndex == index) {
return true;
}
// Return true as soon as there is a parent.
while (index != ushort.MaxValue) {
if (index == parentIndex) {
return true;
}
index = taskComponents[index].ParentIndex;
}
return false;
}
/// <summary>
/// Returns the total number of children belonging to the specified node.
/// </summary>
/// <param name="index">The index of the task to retrieve the child count of.</param>
/// <param name="taskComponents">The array of nodes.</param>
/// <returns>The total number of children belonging to the specified node.</returns>
public static int GetChildCount(int index, ref DynamicBuffer<TaskComponent> taskComponents)
{
if (index == ushort.MaxValue) {
return 0;
}
var taskComponent = taskComponents[index];
if (taskComponent.SiblingIndex != ushort.MaxValue) {
return taskComponent.SiblingIndex - taskComponent.Index - 1;
}
if (taskComponent.Index + 1 == taskComponents.Length) {
return 0;
}
var childTaskComponent = taskComponents[taskComponent.Index + 1];
if (childTaskComponent.ParentIndex != taskComponent.Index) {
return 0;
}
// Determine the child count based off of the sibling index.
var lastChildTaskComponent = childTaskComponent;
while (childTaskComponent.ParentIndex == taskComponent.Index) {
lastChildTaskComponent = childTaskComponent;
if (childTaskComponent.SiblingIndex == ushort.MaxValue) {
break;
}
childTaskComponent = taskComponents[childTaskComponent.SiblingIndex];
}
return lastChildTaskComponent.Index - taskComponent.Index + GetChildCount(lastChildTaskComponent.Index, ref taskComponents);
}
/// <summary>
/// Returns the immediate number of children belonging to the specified task.
/// </summary>
/// <param name="task">The task to retrieve the children of.</param>
/// <param name="taskComponents">The list of tasks.</param>
/// <returns>The number of immediate children belonging to the specified task.</returns>
[BurstCompile]
public static int GetImmediateChildCount(ref TaskComponent task, ref DynamicBuffer<TaskComponent> taskComponents)
{
var count = 0;
var siblingIndex = task.Index + 1;
while (siblingIndex < taskComponents.Length && taskComponents[siblingIndex].ParentIndex == task.Index) {
count++;
siblingIndex = taskComponents[siblingIndex].SiblingIndex;
}
return count;
}
}
}
#endif

View File

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

View File

@@ -0,0 +1,42 @@
#if GRAPH_DESIGNER
/// ---------------------------------------------
/// Behavior Designer
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.Runtime.Utility
{
using UnityEngine;
using System;
/// <summary>
/// Specifies a range between two floats.
/// </summary>
public struct RangeFloat
{
[Tooltip("The minimal float value (inclusive).")]
[SerializeField] public float Min;
[Tooltip("The maximum float value (inclusive).")]
[SerializeField] public float Max;
public float RandomValue { get => UnityEngine.Random.Range(Min, Max); }
/// <summary>
/// RangeFloat constructor.
/// </summary>
/// <param name="min">The minimal float value.</param>
/// <param name="max">The maximal float value.</param>
public RangeFloat(float min, float max)
{
Min = min;
Max = max;
}
}
/// <summary>
/// Specifies that the task name should be hidden in the node view.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public class HideNameInTaskControlAttribute : Attribute { }
}
#endif

View File

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