直接上代码了
using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;[System.Serializable]
public class TerrainSaveData
{public int heightmapResolution;public float terrainWidth;public float terrainLength;public float terrainHeight;// 改为使用一维数组和尺寸信息来存储高度图public float[] heightmapDataFlat;public int heightmapSizeX;public int heightmapSizeY;
}[System.Serializable]
public class DynamicObjectData
{public string prefabName;public Vector3 position;public Quaternion rotation;public Vector3 scale;
}[System.Serializable]
public class SceneSaveData
{public TerrainSaveData terrainData;public List<DynamicObjectData> dynamicObjects = new List<DynamicObjectData>();
}public class SceneSaveLoadSystem : MonoBehaviour
{public Terrain terrain;public TerrainEditor terrainEditor; // 引用您的地形编辑器public string saveFileName = "terrainSave";[Header("UI References")]public InputField saveNameInput;public Button saveButton;public Button loadButton;// public Text statusText;private void Start(){if (saveButton != null) saveButton.onClick.AddListener(SaveTerrain);if (loadButton != null) loadButton.onClick.AddListener(LoadTerrain);}public void SaveTerrain(){try{if (terrain == null){throw new NullReferenceException("terrain引用未设置");}SceneSaveData saveData = new SceneSaveData();// 保存地形数据saveData.terrainData = SaveTerrainData();// 添加这行:保存动态物体SaveDynamicObjects(saveData); // <-- 这是关键修复// 检查数据有效性if (saveData.terrainData.heightmapDataFlat == null){throw new Exception("高度图数据为空");}if (saveData.dynamicObjects == null){throw new Exception("动态物体列表为空");}string jsonData = JsonUtility.ToJson(saveData, true);string savePath = Path.Combine(Application.persistentDataPath, GetSaveFileName() + ".json");Directory.CreateDirectory(Path.GetDirectoryName(savePath));File.WriteAllText(savePath, jsonData);Debug.Log($"保存成功,动态物体数量: {saveData.dynamicObjects.Count}");UpdateStatus($"场景已保存,包含 {saveData.dynamicObjects.Count} 个动态物体");}catch (Exception e){Debug.LogError($"保存失败: {e.Message}\n{e.StackTrace}");UpdateStatus($"保存失败: {e.Message}");}}public void LoadTerrain(){string fileName = GetSaveFileName();string savePath = Path.Combine(Application.persistentDataPath, fileName + ".json");Debug.Log($"正在从路径加载: {savePath}");// 检查文件是否存在且非空if (!File.Exists(savePath)){Debug.LogError($"文件不存在: {savePath}");UpdateStatus("错误: 存档文件不存在");return;}if (new FileInfo(savePath).Length == 0){Debug.LogError($"文件为空: {savePath}");UpdateStatus("错误: 存档文件为空");return;}try{string jsonData = File.ReadAllText(savePath);Debug.Log($"读取的JSON数据(前100字符): {jsonData.Substring(0, Math.Min(100, jsonData.Length))}");SceneSaveData saveData = JsonUtility.FromJson<SceneSaveData>(jsonData);// 详细检查反序列化结果if (saveData == null){throw new Exception("反序列化失败,saveData为null");}if (saveData.terrainData == null){throw new Exception("地形数据为null");}if (saveData.terrainData.heightmapDataFlat == null){throw new Exception("高度图数据为null");}Debug.Log($"即将加载地形数据,分辨率: {saveData.terrainData.heightmapResolution}");// 确保地形引用有效if (terrain == null || terrain.terrainData == null){throw new Exception("地形引用无效");}LoadTerrainData(saveData.terrainData);LoadDynamicObjects(saveData);UpdateStatus($"场景已从 {fileName} 加载成功");}catch (Exception e){Debug.LogError($"加载失败: {e.GetType()} - {e.Message}\n堆栈跟踪:\n{e.StackTrace}");UpdateStatus($"加载失败: {e.Message}");}}private TerrainSaveData SaveTerrainData(){TerrainData tData = terrain.terrainData;int resolution = tData.heightmapResolution;float[,] heights = tData.GetHeights(0, 0, resolution, resolution);// 将二维数组转换为一维数组float[] flatHeights = new float[resolution * resolution];for (int y = 0; y < resolution; y++){for (int x = 0; x < resolution; x++){flatHeights[y * resolution + x] = heights[x, y];}}return new TerrainSaveData{heightmapResolution = resolution,heightmapDataFlat = flatHeights,heightmapSizeX = resolution,heightmapSizeY = resolution,terrainWidth = tData.size.x,terrainLength = tData.size.z,terrainHeight = tData.size.y};}private void LoadTerrainData(TerrainSaveData terrainData){TerrainData tData = terrain.terrainData;// 验证数据if (terrainData.heightmapDataFlat == null || terrainData.heightmapDataFlat.Length == 0){throw new Exception("高度图数据为空");}int targetResolution = tData.heightmapResolution;float[,] heights;// 如果分辨率匹配if (targetResolution == terrainData.heightmapResolution){heights = new float[targetResolution, targetResolution];for (int y = 0; y < targetResolution; y++){for (int x = 0; x < targetResolution; x++){heights[x, y] = terrainData.heightmapDataFlat[y * targetResolution + x];}}}else{// 分辨率不匹配时需要重新采样heights = ResampleHeightmap(terrainData);}tData.SetHeights(0, 0, heights);}private float[,] ResampleHeightmap(TerrainSaveData terrainData){int sourceRes = terrainData.heightmapResolution;int targetRes = terrain.terrainData.heightmapResolution;float[,] result = new float[targetRes, targetRes];float ratio = (float)sourceRes / targetRes;for (int y = 0; y < targetRes; y++){for (int x = 0; x < targetRes; x++){float srcX = x * ratio;float srcY = y * ratio;int x1 = Mathf.FloorToInt(srcX);int y1 = Mathf.FloorToInt(srcY);int x2 = Mathf.Min(x1 + 1, sourceRes - 1);int y2 = Mathf.Min(y1 + 1, sourceRes - 1);// 双线性插值float fx = srcX - x1;float fy = srcY - y1;float h11 = terrainData.heightmapDataFlat[y1 * sourceRes + x1];float h21 = terrainData.heightmapDataFlat[y1 * sourceRes + x2];float h12 = terrainData.heightmapDataFlat[y2 * sourceRes + x1];float h22 = terrainData.heightmapDataFlat[y2 * sourceRes + x2];result[x, y] = Mathf.Lerp(Mathf.Lerp(h11, h21, fx),Mathf.Lerp(h12, h22, fx),fy);}}return result;}private float[,] ResampleHeightmap(float[,] source, int sourceSize, int targetSize){float[,] result = new float[targetSize, targetSize];float ratio = (float)sourceSize / targetSize;for (int y = 0; y < targetSize; y++){for (int x = 0; x < targetSize; x++){float srcX = x * ratio;float srcY = y * ratio;int x1 = Mathf.FloorToInt(srcX);int y1 = Mathf.FloorToInt(srcY);int x2 = Mathf.Min(x1 + 1, sourceSize - 1);int y2 = Mathf.Min(y1 + 1, sourceSize - 1);// 双线性插值float fx = srcX - x1;float fy = srcY - y1;result[x, y] = Mathf.Lerp(Mathf.Lerp(source[x1, y1], source[x2, y1], fx),Mathf.Lerp(source[x1, y2], source[x2, y2], fx),fy);}}return result;}private void SaveDynamicObjects(SceneSaveData saveData){GameObject[] dynamicObjects = GameObject.FindGameObjectsWithTag("DynamicObject");foreach (var obj in dynamicObjects){saveData.dynamicObjects.Add(new DynamicObjectData{prefabName = GetPrefabName(obj),position = obj.transform.position,rotation = obj.transform.rotation,scale = obj.transform.localScale});}}private void LoadDynamicObjects(SceneSaveData saveData){// 先清除现有动态物体GameObject[] existingObjects = GameObject.FindGameObjectsWithTag("DynamicObject");foreach (var obj in existingObjects){Destroy(obj);}// 重新生成保存的动态物体foreach (var objData in saveData.dynamicObjects){// 实际项目中应该从资源管理系统加载预制体var prefab = Resources.Load<GameObject>("ModePrefab/" + objData.prefabName);if (prefab != null){var newObj = Instantiate(prefab, objData.position, objData.rotation);newObj.transform.localScale = objData.scale;newObj.tag = "DynamicObject";}}}private string GetPrefabName(GameObject obj){// 简化版本 - 实际项目中可能需要更复杂的逻辑return obj.name.Replace("(Clone)", "").Trim();}private string GetSaveFileName(){return string.IsNullOrEmpty(saveNameInput?.text) ? saveFileName : saveNameInput.text;}private void UpdateStatus(string message){// if (statusText != null) statusText.text = message;Debug.Log(message);}// 编辑器快捷方式
#if UNITY_EDITOR[ContextMenu("Quick Save")]private void QuickSave(){SaveTerrain();}[ContextMenu("Quick Load")]private void QuickLoad(){LoadTerrain();}
#endif// 修改后的动态物体生成方法public GameObject SpawnDynamicObject(GameObject prefab, Vector3 position){if (prefab == null) return null;GameObject newObj = Instantiate(prefab, position, Quaternion.identity);newObj.tag = "DynamicObject"; // 确保设置标签newObj.name = prefab.name; // 保持名称一致// 添加唯一标识组件(可选)if (!newObj.GetComponent<DynamicObjectID>()){newObj.AddComponent<DynamicObjectID>();}return newObj;}// 唯一标识组件public class DynamicObjectID : MonoBehaviour{public string originalPrefabName;void Awake(){originalPrefabName = gameObject.name.Replace("(Clone)", "").Trim();}}
}
```还有地形编辑脚本```csharp```csharp
using UnityEngine;
using UnityEngine.EventSystems; // 添加这个命名空间public class TerrainEditor : MonoBehaviour
{public Terrain terrain; // 地形对象public float strength = 0.01f; // 拉伸强度public float brushSize = 10f; // 笔刷大小public bool useCircleBrush = true; // 是否使用圆形笔刷void Update(){// 检查鼠标是否在UI上if (EventSystem.current.IsPointerOverGameObject()){return; // 如果在UI上,则不执行地形编辑}// 鼠标左键提升地形,右键降低地形if (Input.GetMouseButton(0)){EditTerrain(true); // 提升地形}else if (Input.GetMouseButton(1)){EditTerrain(false); // 降低地形}// 使用鼠标滚轮调整笔刷大小float scroll = Input.GetAxis("Mouse ScrollWheel");if (scroll != 0){brushSize += scroll * 5f;brushSize = Mathf.Clamp(brushSize, 1f, 50f);Debug.Log("Brush Size: " + brushSize);}// 按 B 键切换笔刷形状if (Input.GetKeyDown(KeyCode.B)){useCircleBrush = !useCircleBrush;Debug.Log("Brush Shape: " + (useCircleBrush ? "Circle" : "Square"));}}void EditTerrain(bool raise){Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);RaycastHit hit;if (Physics.Raycast(ray, out hit)){if (hit.collider.GetComponent<Terrain>()){// 将鼠标点击的世界坐标转换为地形局部坐标Vector3 terrainPos = hit.point - terrain.transform.position;int x = (int)(terrainPos.x / terrain.terrainData.size.x * terrain.terrainData.heightmapResolution);int y = (int)(terrainPos.z / terrain.terrainData.size.z * terrain.terrainData.heightmapResolution);// 计算笔刷范围int size = (int)brushSize;int offsetX = x - size / 2;int offsetY = y - size / 2;// 检查边界,确保不会超出地形范围offsetX = Mathf.Clamp(offsetX, 0, terrain.terrainData.heightmapResolution - size);offsetY = Mathf.Clamp(offsetY, 0, terrain.terrainData.heightmapResolution - size);// 获取当前高度图数据float[,] heights = terrain.terrainData.GetHeights(offsetX, offsetY, size, size);// 修改高度图数据for (int i = 0; i < size; i++){for (int j = 0; j < size; j++){// 计算当前点到笔刷中心的距离float distance = Mathf.Sqrt((i - size / 2) * (i - size / 2) + (j - size / 2) * (j - size / 2));// 如果是圆形笔刷且距离大于半径,则跳过if (useCircleBrush && distance > size / 2){continue;}// 根据 raise 参数提升或降低地形if (raise){heights[i, j] += strength * Time.deltaTime;}else{heights[i, j] -= strength * Time.deltaTime;}heights[i, j] = Mathf.Clamp01(heights[i, j]);}}// 应用修改后的高度图数据terrain.terrainData.SetHeights(offsetX, offsetY, heights);}}}
}