using System;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine.SceneManagement;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
// ReSharper disable SuggestVarOrType_SimpleTypes
// ReSharper disable ForCanBeConvertedToForeach
// ReSharper disable SuggestVarOrType_BuiltInTypes
// ReSharper disable SuggestVarOrType_Elsewhere
// ReSharper disable InconsistentNaming
// ReSharper disable LoopCanBeConvertedToQuery
// ReSharper disable CommentTypo
// ReSharper disable StringLiteralTypo
namespace DestroyIt
{
///
/// Manages destructible terrain trees.
/// Attach this script to a gameobject (ie, _TreeManager) in your scene.
/// On your Terrain, turn off Enable Tree Colliders. Tree colliders will be attached by this script at runtime.
///
[RequireComponent(typeof(TerrainPreserver))]
[DisallowMultipleComponent]
public class TreeManager : MonoBehaviour
{
[Tooltip("The terrain managed by this script. Leave empty to manage the current active terrain.")]
public Terrain terrain;
[Tooltip("Backs up the active terrain in the editor when you play the scene. This way if a crash occurs, you'll be able to restore from the backup and won't lose your placed trees, since the TreeManager replaces terrain trees with destructible stand-ins at runtime.")]
public bool backupTerrain = true;
// NOTE: This folder and all its contents will be deleted each time Destructible Trees are updated!
[Tooltip("The folder where the stripped-down destructible terrain tree prototype prefabs are stored.\n\nYou can change this if you want to store your tree stand-in resources somewhere else.")]
public string pathToStandIns = "Assets/DestroyIt/Resources/TreeStandIns/";
[Tooltip("These are stripped-down tree prototype objects, containing only colliders and other essential components to make them destructible.\n\nYou don't need to change these - they are automatically generated when the Update Destructible Trees button is clicked.")]
public List destructibleTrees;
[HideInInspector]
public List treesToReset;
// Hide the default constructor (use TreeManager.Instance to access this class)
private TreeManager() { }
private static TreeManager _instance;
private List currentTreeInstances; // NOTE: It's important to keep this a List, don't convert to Array
private TreeInstance[] originalTreeInstances;
private bool isTerrainDataDirty; // Determines if the terrainData has a backup that hasn't been resolved (possibly from a Unity crash). Don't try to make changes to a dirty TerrainData - give the user an option to fix it first.
// Public Instance reference other classes will use
public static TreeManager Instance
{
get
{
// If _instance hasn't been set yet, we grab it from the scene.
// This will only happen the first time this reference is used.
if (_instance == null)
_instance = FindObjectOfType();
return _instance;
}
}
private void OnDisable()
{
SceneManager.activeSceneChanged -= OnActiveSceneChanged;
}
private void OnEnable()
{
// Exit immediately if there are no Destructible trees. In that case, there is nothing to manage.
if (destructibleTrees == null || destructibleTrees.Count == 0) return;
// Get terrain to manage
if (terrain == null)
terrain = Terrain.activeTerrain;
if (terrain == null || terrain.terrainData == null)
{
Debug.LogWarning("No terrain to manage destructible trees on.");
return;
}
TreePrototype[] treePrototypes = terrain.terrainData.treePrototypes;
TreeInstance[] treeInstances = terrain.terrainData.treeInstances;
#if UNITY_EDITOR
// Check if there is already a backup of the terrainData and exit if so.
string terrainDataPath = AssetDatabase.GetAssetPath(terrain.terrainData);
string terrainDataBkpPath = terrainDataPath.Replace(".asset", "") + "_bkp.asset";
TerrainData terrainDataBkp = AssetDatabase.LoadAssetAtPath(terrainDataBkpPath);
if (terrainDataBkp != null)
{
// A terrainData backup already exists. Log an error and exit.
isTerrainDataDirty = true;
Debug.LogError("Cannot backup terrainData for [" + terrain.terrainData.name + "]. A backup already exists. Please exit Play mode to fix.");
return;
}
#endif
if (treeInstances == null || treeInstances.Length == 0 || treePrototypes == null || treePrototypes.Length == 0)
{
Debug.LogWarning("No trees found on terrain. Nothing to manage.");
return;
}
if (treePrototypes.Length != destructibleTrees.Count)
{
Debug.LogWarning("Tree prototypes do not match DestroyIt's tree stand-in prefabs. Please click the \"Update Trees\" button on the TreeManager script.");
return;
}
for (int i = 0; i < treePrototypes.Length; i++)
{
if (destructibleTrees[i].Prefab == null || treePrototypes[i].prefab == null || treePrototypes[i].prefab.name != destructibleTrees[i].Prefab.name)
{
Debug.LogWarning("Tree prototype names do not match Destructible tree stand-in prefab names. You may need to click the \"Update Trees\" button on the TreeManager script.");
return;
}
}
// Capture original tree instances so we can reset them on application quit
originalTreeInstances = treeInstances;
currentTreeInstances = new List(treeInstances);
treesToReset = new List();
#if UNITY_EDITOR
// Save the original terrainData object to a Resources folder, just in case there is a crash.
// This way, the TerrainPreserver can check for any Resources data to load and ask the user if he/she wants to restore terrainData.
if (backupTerrain)
{
AssetDatabase.CopyAsset(terrainDataPath, terrainDataBkpPath);
AssetDatabase.Refresh();
}
#endif
// For each terrain tree, place a stripped-down tree prototype object at its location
for (int i = 0; i < treeInstances.Length; i++)
{
TreeInstance tree = treeInstances[i];
DestructibleTree destructibleTree = destructibleTrees.Find(x => x.prototypeIndex == tree.prototypeIndex);
if (destructibleTree == null) continue;
GameObject treeObj = Instantiate(destructibleTree.Prefab, terrain.transform.parent, true);
treeObj.transform.position = terrain.WorldPositionOfTree(i);
treeObj.transform.localScale = new Vector3(tree.widthScale, tree.heightScale, tree.widthScale);
treeObj.transform.rotation = Quaternion.AngleAxis(tree.rotation * Mathf.Rad2Deg, Vector3.up);
}
SceneManager.activeSceneChanged += OnActiveSceneChanged;
}
private void FixedUpdate()
{
if (treesToReset == null || treesToReset.Count == 0) return;
List treesReset = new List();
foreach (TreeReset tree in treesToReset)
{
if (DateTime.Now >= tree.resetTime)
{
TreeInstance treeInstance = new TreeInstance
{
position = tree.position,
color = Color.white,
heightScale = 1,
widthScale = 1,
prototypeIndex = tree.prototypeIndex
};
terrain.AddTreeInstance(treeInstance);
treesReset.Add(tree);
}
}
foreach(TreeReset tree in treesReset)
treesToReset.Remove(tree);
}
#if UNITY_EDITOR
public void UpdateTrees()
{
// Get the current scene asset so we can find the InstanceId assigned to it.
Object sceneAsset = AssetDatabase.LoadAssetAtPath