/// ---------------------------------------------
/// Behavior Designer
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.BehaviorDesigner.Samples.SceneManagers
{
using Opsive.BehaviorDesigner.Runtime;
using System.IO;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UI;
using static Opsive.BehaviorDesigner.Runtime.Utility.SaveManager;
///
/// Saves and loads the behavior tree state.
///
public class SaveLoadSceneManager : MonoBehaviour
{
[Tooltip("A reference to the behavior tree agent.")]
[SerializeField] protected GameObject m_Agent;
[Tooltip("The location that the behavior tree data should be saved.")]
[SerializeField] protected string m_SaveLocation = "Assets/Opsive/BehaviorDesigner/Samples/SaveLoad.save";
[Tooltip("A reference to the load button.")]
[SerializeField] protected Button m_LoadButton;
[Tooltip("A reference to the enemy agents.")]
[SerializeField] protected GameObject[] m_EnemyAgents;
[Tooltip("The location the enemies should move towards.")]
[SerializeField] protected GameObject[] m_EnemyDestinations;
private Transform m_Transform;
private BehaviorTree m_BehaviorTree;
private Animator m_Animator;
///
/// Cache the variables and start moving the enemies.
///
private void Awake()
{
m_Transform = m_Agent.transform;
m_BehaviorTree = m_Agent.GetComponent();
m_Animator = m_Agent.GetComponent();
m_BehaviorTree.SetVariableValue("Enemies", m_EnemyAgents);
if (!File.Exists(m_SaveLocation)) {
m_LoadButton.interactable = false;
}
for (int i = 0; i < m_EnemyAgents.Length; ++i) {
m_EnemyAgents[i].GetComponent().SetDestination(m_EnemyDestinations[Random.Range(0, m_EnemyDestinations.Length)].transform.position);
}
}
///
/// Stores all of the agent save data in one structure.
///
private struct AgentSaveData
{
public SaveData BehaviorTreeSaveData; // The behavior tree save data.
public int TargetIndex; // The index of the Target SharedVariable.
public int AnimationStateHash; // The current Animator state.
public int AnimationParameter; // The Animator "State" parameter value.
public Vector3[] EnemyPositions; // The enemy's position.
public Quaternion[] EnemyRotations; // The enemy's rotation.
public int[] EnemyAnimationStateHashes; // The enemy's Animator state.
public float[] EnemyAnimationNormalizedTimes; // The enemy's Animator playback time.
public float[] EnemyAnimationForwardParameters; // The enemy's Animator "Forward" parameter value.
public int[] EnemyAnimationStateParameters; // The enemy's Animator "State" parameter value.
public bool[] EnemyIsStopped; // True if the enemy's NavmeshAgent is stopped.
public Vector3[] EnemyDestinations; // The destination of the enemy.
public float CameraAnimationTime; // The camera's current animation time.
}
///
/// Save the agent state.
///
public void Save()
{
var saveData = m_BehaviorTree.Save(); // Don't use BehaviorTree.Save(FilePath) because it will only save the BehaviorTree data to the file path.
if (!saveData.HasValue) {
return;
}
var target = m_BehaviorTree.GetVariable("Target");
var targetIndex = -1;
for (int i = 0; i < m_EnemyAgents.Length; ++i) {
if (target.Value == m_EnemyAgents[i]) {
targetIndex = i;
break;
}
}
var enemyPositions = new Vector3[m_EnemyAgents.Length];
var enemyRotations = new Quaternion[m_EnemyAgents.Length];
var enemyAnimationStateHashes = new int[m_EnemyAgents.Length];
var enemyAnimationNormalizedTimes = new float[m_EnemyAgents.Length];
var enemyAnimationForwardParameters = new float[m_EnemyAgents.Length];
var enemyAnimationStateParameters = new int[m_EnemyAgents.Length];
var enemyIsStopped = new bool[m_EnemyAgents.Length];
var enemyDestinations = new Vector3[m_EnemyAgents.Length];
for (int i = 0; i < m_EnemyAgents.Length; ++i) {
enemyPositions[i] = m_EnemyAgents[i].transform.position;
enemyRotations[i] = m_EnemyAgents[i].transform.rotation;
enemyAnimationStateHashes[i] = m_EnemyAgents[i].GetComponent().GetCurrentAnimatorStateInfo(0).fullPathHash;
enemyAnimationNormalizedTimes[i] = m_EnemyAgents[i].GetComponent().GetCurrentAnimatorStateInfo(0).normalizedTime;
enemyAnimationForwardParameters[i] = m_EnemyAgents[i].GetComponent().GetFloat("Forward");
enemyAnimationStateParameters[i] = m_EnemyAgents[i].GetComponent().GetInteger("State");
enemyDestinations[i] = m_EnemyAgents[i].GetComponent().destination;
enemyIsStopped[i] = m_EnemyAgents[i].GetComponent().isStopped;
}
// Create the data structure which contains all of the values that should be saved.
var agentSaveData = new AgentSaveData() { BehaviorTreeSaveData = saveData.Value, TargetIndex = targetIndex,
AnimationStateHash = m_Animator.GetCurrentAnimatorStateInfo(0).fullPathHash, AnimationParameter = m_Animator.GetInteger("State"),
EnemyPositions = enemyPositions, EnemyRotations = enemyRotations, EnemyAnimationStateHashes = enemyAnimationStateHashes,
EnemyAnimationNormalizedTimes = enemyAnimationNormalizedTimes, EnemyAnimationForwardParameters = enemyAnimationForwardParameters,
EnemyAnimationStateParameters = enemyAnimationStateParameters, EnemyIsStopped = enemyIsStopped,
EnemyDestinations = enemyDestinations, CameraAnimationTime = Camera.main.GetComponent().GetCurrentAnimatorStateInfo(0).normalizedTime };
if (File.Exists(m_SaveLocation)) {
File.Delete(m_SaveLocation);
}
try {
if (!Directory.Exists(Path.GetDirectoryName(m_SaveLocation))) {
Directory.CreateDirectory(Path.GetDirectoryName(m_SaveLocation));
}
var fileStream = File.Create(m_SaveLocation);
using (var streamWriter = new StreamWriter(fileStream)) {
streamWriter.Write(JsonUtility.ToJson(agentSaveData));
}
fileStream.Close();
} catch (System.Exception e) {
Debug.LogException(e);
return;
}
m_LoadButton.interactable = true;
}
///
/// Load the agent state.
///
public void Load()
{
if (!File.Exists(m_SaveLocation)) {
return;
}
AgentSaveData agentSaveData;
var fileStream = File.Open(m_SaveLocation, FileMode.Open);
using (var streamReader = new StreamReader(fileStream)) {
var fileData = streamReader.ReadToEnd();
agentSaveData = JsonUtility.FromJson(fileData);
}
fileStream.Close();
// Restore the values.
m_BehaviorTree.Load(agentSaveData.BehaviorTreeSaveData, (BehaviorTree tree) =>
{
// Scene objects cannot be persisted to a file. Restore the scene values before the tasks are restored.
m_BehaviorTree.SetVariableValue("Enemies", m_EnemyAgents);
if (agentSaveData.TargetIndex != -1) {
m_BehaviorTree.SetVariableValue("Target", m_EnemyAgents[agentSaveData.TargetIndex]);
}
});
m_Animator.Play(agentSaveData.AnimationStateHash, 0);
m_Animator.SetInteger("State", agentSaveData.AnimationParameter);
// Restore the enemy agent values.
for (int i = 0; i < m_EnemyAgents.Length; ++i) {
m_EnemyAgents[i].transform.SetPositionAndRotation(agentSaveData.EnemyPositions[i], agentSaveData.EnemyRotations[i]);
m_EnemyAgents[i].GetComponent().Play(agentSaveData.EnemyAnimationStateHashes[i], 0, agentSaveData.EnemyAnimationNormalizedTimes[i]);
m_EnemyAgents[i].GetComponent().SetFloat("Forward", agentSaveData.EnemyAnimationForwardParameters[i]);
m_EnemyAgents[i].GetComponent().SetInteger("State", agentSaveData.EnemyAnimationStateParameters[i]);
m_EnemyAgents[i].GetComponent().isStopped = agentSaveData.EnemyIsStopped[i];
if (!agentSaveData.EnemyIsStopped[i]) {
m_EnemyAgents[i].GetComponent().SetDestination(agentSaveData.EnemyDestinations[i]);
}
m_EnemyAgents[i].GetComponent().Value = agentSaveData.EnemyIsStopped[i] ? 0 : 100;
}
Camera.main.GetComponent().Play(Camera.main.GetComponent().GetCurrentAnimatorStateInfo(0).fullPathHash, 0, agentSaveData.CameraAnimationTime);
}
}
}