chore: initial commit
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bfee368f88aa6e4a81281d10c71072d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c5777d50e5756a4b85d335f81c9d301
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be274f359e2db764998eb36f2adeaed9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d76db679b119fe4c9359f9b9b0c2869
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user