650 lines
33 KiB
C#
650 lines
33 KiB
C#
#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.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<EnabledTag>(behaviorTree.Entity) && behaviorTree.World.EntityManager.IsComponentEnabled<EnabledTag>(behaviorTree.Entity);
|
|
var evaluateEntity = behaviorTree.World.EntityManager.HasComponent<EvaluationComponent>(behaviorTree.Entity) && behaviorTree.World.EntityManager.IsComponentEnabled<EvaluationComponent>(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].ActiveTagComponentType.TypeIndex == TypeIndex.Null) {
|
|
continue;
|
|
}
|
|
behaviorTree.World.EntityManager.SetComponentEnabled(behaviorTree.Entity, branchComponents[i].ActiveTagComponentType, 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].ReevaluateTagComponentType.TypeIndex == TypeIndex.Null) {
|
|
continue;
|
|
}
|
|
behaviorTree.World.EntityManager.SetComponentEnabled(behaviorTree.Entity, reevaluatedTaskComponents[i].ReevaluateTagComponentType, 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<EnabledTag>(behaviorTree.Entity, true);
|
|
}
|
|
if (evaluateEntity) {
|
|
behaviorTree.World.EntityManager.SetComponentEnabled<EvaluationComponent>(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 |