• Unity_建造系统及保存加载


    主要是尝试类似RTS的建造系统, 以及通过序列化/反序列化保存和加载游戏数据, 测试版本Unity2020.3


    建造物体

    移动

    生成使用透明材质的的模型到场景, 通过射线检测获取鼠标位置并使模型位置跟随移动

    1. private Camera mCamera;//场景相机
    2. public GameObject PrefabModel;//预制体模型
    3. private GameObject createModel;//用于创建的场景模型
    4. void Start()
    5. {
    6. //获取场景相机
    7. GameObject gameObject=GameObject.Find("Main Camera");
    8. mCamera=gameObject.GetComponent();
    9. createModel = Instantiate(PrefabModel);//生成模型到场景
    10. }
    11. void Update()
    12. {
    13. /*从摄像机向鼠标方向发射射线,获取鼠标位置*/
    14. Ray ray = mCamera.ScreenPointToRay(Input.mousePosition);
    15. RaycastHit hit;
    16. LayerMask mask = 1 << (LayerMask.NameToLayer("Floor"));//遮罩图层,使射线只检测Floor图层
    17. if (Physics.Raycast(ray, out hit,Mathf.Infinity,mask.value))
    18. {
    19. Debug.DrawLine(ray.origin, hit.point,Color.green);//在编辑器视图中绘制射线
    20. createModel.transform.position = hit.point;//使模型位置跟随鼠标坐标
    21. }
    22. }

    预制体模型的一些处理

    测试时发现默认情况下的预制体模型实例化到鼠标位置后会弹起来, 检查发现模型轴点位置是在碰撞体积内部的, 怀疑是产生了和地板模型的碰撞导致了,所以调整抬高了一点碰撞体积, 让模型轴点在碰撞体积以外, 再进行生成测试就正常了, 如果生成有问题可以参考:

    旋转

    用鼠标滚轮控制模型旋转角度

    1. void Update()
    2. {
    3. if (Input.GetAxis("Mouse ScrollWheel")!=0)//鼠标滚轮
    4. {
    5. //旋转模型角度
    6. newcreateModel.transform.Rotate(0,Input.GetAxis("Mouse ScrollWheel") *Time.deltaTime*20000,0,Space.World);//Space.World为使用全局坐标系
    7. }
    8. }

    设置材质

    固定模型到场景中后把模型材质从透明恢复为正常材质

    1. public Material AlbedoMat;//正常材质
    2. ...
    3. createModel.GetComponent().sharedMaterial = AlbedoMat;//设置模型为正常材质

    碰撞/重叠检测

    给预制体模型添加Collider碰撞体和Rigidbody刚体组件并使用触发器来检测是否与其他已经固定的模型有重叠导致穿模, 如果有重叠则不能固定到场景并把模型材质变红提示

    [参考的]Unity碰撞和触发

    1. //触发开始时执行一次
    2. public void OnTriggerEnter(Collider collider){
    3. Debug.Log("与其他模型有重叠,不可建造");
    4. }
    5. //触发过程中一直重复执行
    6. public void OnTriggerStay(Collider collider){
    7. Debug.Log("与其他模型有重叠,不可建造");
    8. }
    9. //触发结束时执行一次
    10. public void OnTriggerExit(Collider collider){
    11. Debug.Log("无重叠,可以建造");
    12. }

    数据保存/加载

    查资料时都管这个叫序列化和反序列化, 听起来好高大上的样子, Unity自带了JsonUtility用于处理 json数据(JsonUtility文档)

    测试用到的主要是获取场景上所有已固定的建筑模型的类型、位置和角度并保存为json文件, 在重新加载场景后可以加载保存的数据并根据数据恢复保存时的状态

    保存数据

    1. /*用于保存数据的类*/
    2. [Serializable]//应用可序列化属性
    3. public class BuildStateSave
    4. {
    5. public List<string> modelType = new List<string>();//模型类型
    6. public List<double> posX = new List<double>();//X位置
    7. public List<double> posY = new List<double>();//Y位置
    8. public List<double> posZ = new List<double>();//Z位置
    9. public List<double> rotX = new List<double>();//X方向
    10. public List<double> rotY = new List<double>();//Y方向
    11. public List<double> rotZ = new List<double>();//Z方向
    12. /*把数据转换为json字符串*/
    13. public string SaveToString()
    14. {
    15. return JsonUtility.ToJson(this,true);//[prettyPrint]为true时返回的字符串保持可读格式,为false时大小最小
    16. }
    17. }
    18. /*保存按钮*/
    19. public void SaveSence()
    20. {
    21. GameObject[] buildCubes =GameObject.FindGameObjectsWithTag("Build");//获取所有已建造对象
    22. BuildStateSave buildState = new BuildStateSave();
    23. for (int i = 0; i < buildCubes.Length; i++)
    24. {
    25. buildState.modelType.Add(buildCubes[i].name);
    26. buildState.posX.Add(buildCubes[i].transform.position.x);
    27. buildState.posY.Add(buildCubes[i].transform.position.y);
    28. buildState.posZ.Add(buildCubes[i].transform.position.z);
    29. buildState.rotX.Add(buildCubes[i].transform.rotation.eulerAngles.x);
    30. buildState.rotY.Add(buildCubes[i].transform.rotation.eulerAngles.y);
    31. buildState.rotZ.Add(buildCubes[i].transform.rotation.eulerAngles.z);
    32. }
    33. string filePath = Application.streamingAssetsPath + "/BuildSave.json";
    34. if (File.Exists(filePath))//判断文件是否存在
    35. {
    36. File.Delete(filePath);//删除这个文件
    37. }
    38. //找到当前路径
    39. FileInfo file = new FileInfo(filePath);
    40. //判断有没有文件,有则打开文件,没有则创建后打开
    41. StreamWriter sw = file.CreateText();
    42. //数据转换为字符串并写入文件
    43. sw.WriteLine(buildState.SaveToString());
    44. sw.Close();//关闭文件
    45. sw.Dispose();
    46. }

    执行保存后路径下的json文件中可以看到保存的信息数据

    加载数据

    1. /*用于加载数据的类*/
    2. [System.Serializable]//应用可序列化属性
    3. public class BuildStateLoad
    4. {
    5. public List<string> modelType = new List<string>();//模型类型
    6. public List<double> posX = new List<double>();//X位置
    7. public List<double> posY = new List<double>();//Y位置
    8. public List<double> posZ = new List<double>();//Z位置
    9. public List<double> rotX = new List<double>();//X方向
    10. public List<double> rotY = new List<double>();//Y方向
    11. public List<double> rotZ = new List<double>();//Z方向
    12. /*把json字符串转换为变量数据*/
    13. public BuildStateLoad CreateFromJSON(string jsonString)
    14. {
    15. return JsonUtility.FromJson(jsonString);
    16. }
    17. }
    18. /*加载按钮*/
    19. public void LoadSence()
    20. {
    21. /*从json文件读取数据*/
    22. FileStream fs = new FileStream(Application.streamingAssetsPath + "/BuildSave.json", FileMode.Open, FileAccess.Read, FileShare.None);
    23. StreamReader sr = new StreamReader(fs, System.Text.Encoding.Default);
    24. if (null == sr) return;
    25. string str = sr.ReadToEnd();
    26. sr.Close();
    27. /*字符串数据反序列化到变量中*/
    28. BuildStateLoad buildInfo = new BuildStateLoad();
    29. buildInfo=buildInfo.CreateFromJSON(str);
    30. for (int i = 0; i < buildInfo.modelType.Count; i++)
    31. {
    32. /*生成模型到场景*/
    33. switch (buildInfo.modelType[i])
    34. {
    35. case "Model1":
    36. createModel = Instantiate(PrefabModel1);
    37. break;
    38. case "Model2":
    39. createModel = Instantiate(PrefabModel2);
    40. break;
    41. }
    42. /*重命名*/
    43. createModel.name = buildInfo.modelType[i];
    44. /*位置*/
    45. createModel.transform.position = new Vector3((float)buildInfo.posX[i],(float)buildInfo.posY[i],(float)buildInfo.posZ[i]);
    46. /*旋转*/
    47. createModel.transform.rotation = Quaternion.Euler(new Vector3((float)buildInfo.rotX[i],(float)buildInfo.rotY[i],(float)buildInfo.rotZ[i]));
    48. /*固定到场景*/
    49. createModel.GetComponent().isTrigger=false;//关闭触发器恢复碰撞
    50. createModel.GetComponent().useGravity=true;//恢复刚体组件的重力
    51. }
    52. }

    执行加载后获取保存的数据


    完整代码

    主要逻辑脚本, 挂载在一个场景空对象上

    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.IO;
    5. using UnityEngine;
    6. public class BuildTest : MonoBehaviour
    7. {
    8. private Camera mCamera;//场景相机
    9. public GameObject boxPrefabModel;//预制体模型
    10. public GameObject flak88PrefabModel;//预制体模型
    11. private GameObject newcreateModel;//用于创建的场景模型
    12. private GameObject loadcreateModel;//用于加载的场景模型
    13. public Material transparentMat;//透明材质
    14. public Material unInitMat;//阻挡材质
    15. public Material boxAlbedoMat;//Box正常材质
    16. public Material flak88AlbedoMat;//88炮正常材质
    17. private bool iSBuilding = false;//是否为建造模式
    18. public bool unInitPrefab = false;//是否重叠穿模
    19. private string selectBuildType;//记录当前选择建造的物体类型
    20. void Start()
    21. {
    22. /*获取相机*/
    23. GameObject gameObject=GameObject.Find("Main Camera");
    24. mCamera=gameObject.GetComponent();
    25. }
    26. void Update()
    27. {
    28. if (iSBuilding)
    29. {
    30. /*从摄像机向鼠标方向发射射线,获取鼠标位置*/
    31. Ray ray = mCamera.ScreenPointToRay(Input.mousePosition);
    32. RaycastHit hit;
    33. LayerMask mask = 1 << (LayerMask.NameToLayer("Floor"));
    34. /*Raycast:
    35. [1]射线方向
    36. [2]碰撞到的对象信息
    37. [3]射线长度,Mathf.Infinity为无限远
    38. [4]遮罩图层,不检测其他图层的物体
    39. */
    40. if (Physics.Raycast(ray, out hit,Mathf.Infinity,mask.value))
    41. {
    42. Debug.DrawLine(ray.origin, hit.point,Color.green);//在编辑器视图中绘制射线
    43. /*模型位置跟随鼠标坐标*/
    44. newcreateModel.transform.position = hit.point;
    45. if (unInitPrefab)//若位置与已生成物体重叠,修改显示材质
    46. {
    47. /*设置模型材质为不可生成状态*/
    48. MeshRenderer[] Mat = newcreateModel.GetComponentsInChildren();//获取当前对象及所有子对象上的材质组件并更换材质
    49. for (int i = 0; i < Mat.Length; i++)
    50. {
    51. Mat[i].sharedMaterial = unInitMat;
    52. }
    53. }
    54. else
    55. {
    56. /*设置模型材质为透明*/
    57. MeshRenderer[] Mat = newcreateModel.GetComponentsInChildren();//获取当前对象及所有子对象上的材质组件并更换材质
    58. for (int i = 0; i < Mat.Length; i++)
    59. {
    60. Mat[i].sharedMaterial = transparentMat;
    61. }
    62. if (Input.GetMouseButtonDown(0))//点击鼠标左键
    63. {
    64. /*固定对象到场景中*/
    65. newcreateModel.GetComponent().isTrigger=false;//关闭触发器恢复碰撞
    66. newcreateModel.GetComponent().useGravity=true;//恢复刚体组件的重力
    67. //newcreateModel.layer = 20;//设置图层用于接收爆炸力影响
    68. switch (selectBuildType)
    69. {
    70. case "Box":
    71. newcreateModel.GetComponent().sharedMaterial = boxAlbedoMat;//设置旧模型为正常材质
    72. newcreateModel = Instantiate(boxPrefabModel);//生成新模型到场景
    73. break;
    74. case "Flak88":
    75. /*设置旧模型为正常材质*/
    76. for (int i = 0; i < Mat.Length; i++)
    77. {
    78. Mat[i].sharedMaterial = flak88AlbedoMat;
    79. }
    80. newcreateModel = Instantiate(flak88PrefabModel);//生成新模型到场景
    81. break;
    82. }
    83. newcreateModel.name = selectBuildType;//重命名对象
    84. }
    85. }
    86. if (Input.GetAxis("Mouse ScrollWheel")!=0)//滑动鼠标滚轮
    87. {
    88. //旋转模型角度
    89. newcreateModel.transform.Rotate(0,Input.GetAxis("Mouse ScrollWheel") *Time.deltaTime*20000,0,Space.World);//加上Space.World为基于全局坐标系变换
    90. }
    91. }
    92. }
    93. //按下鼠标右键退出建造模式
    94. if (Input.GetMouseButtonDown(1))
    95. {
    96. iSBuilding = false;
    97. SetCursor(true);
    98. Destroy(newcreateModel);
    99. /*退出建造模式时如果是重叠状态会导致再次进入建造模式时也是重叠状态,这里在退出时强制取消重叠状态*/
    100. if (unInitPrefab)
    101. {
    102. unInitPrefab = false;
    103. }
    104. }
    105. }
    106. ///
    107. /// 设置鼠标状态
    108. ///
    109. /// true显示,false隐藏
    110. public void SetCursor(bool _cursorState)
    111. {
    112. //隐藏鼠标
    113. Cursor.visible = _cursorState;
    114. }
    115. /*进入Box建造模式按钮*/
    116. public void StartBoxBuild()
    117. {
    118. selectBuildType = "Box";
    119. iSBuilding = true;
    120. SetCursor(false);
    121. /*生成模型到场景*/
    122. newcreateModel = Instantiate(boxPrefabModel);
    123. /*设置模型材质为透明*/
    124. newcreateModel.GetComponent().sharedMaterial = transparentMat;
    125. /*重命名对象便于区分*/
    126. newcreateModel.name = selectBuildType;
    127. }
    128. /*进入Flak88建造模式按钮*/
    129. public void StartFlak88Build()
    130. {
    131. selectBuildType = "Flak88";
    132. iSBuilding = true;
    133. SetCursor(false);
    134. /*生成模型到场景*/
    135. newcreateModel = Instantiate(flak88PrefabModel);
    136. /*设置模型材质为透明*/
    137. MeshRenderer[] Mat = newcreateModel.GetComponentsInChildren();
    138. for (int i = 0; i < Mat.Length; i++)
    139. {
    140. Mat[i].sharedMaterial = transparentMat;
    141. }
    142. /*重命名*/
    143. newcreateModel.name = selectBuildType;
    144. }
    145. /*保存按钮*/
    146. public void ClickSaveGame()
    147. {
    148. GameObject[] buildCubes =GameObject.FindGameObjectsWithTag("Build");//寻找对象
    149. BuildStateSave buildState = new BuildStateSave();
    150. for (int i = 0; i < buildCubes.Length; i++)
    151. {
    152. buildState.modelType.Add(buildCubes[i].name);
    153. buildState.posX.Add(buildCubes[i].transform.position.x);
    154. buildState.posY.Add(buildCubes[i].transform.position.y);
    155. buildState.posZ.Add(buildCubes[i].transform.position.z);
    156. buildState.rotX.Add(buildCubes[i].transform.rotation.eulerAngles.x);
    157. buildState.rotY.Add(buildCubes[i].transform.rotation.eulerAngles.y);
    158. buildState.rotZ.Add(buildCubes[i].transform.rotation.eulerAngles.z);
    159. }
    160. string filePath = Application.streamingAssetsPath + "/BuildSave.json";
    161. if (File.Exists(filePath))//判断文件是否存在
    162. {
    163. File.Delete(filePath);//删除这个文件
    164. }
    165. //找到当前路径
    166. FileInfo file = new FileInfo(filePath);
    167. //判断有没有文件,有则打开文件,没有则创建后打开
    168. StreamWriter sw = file.CreateText();
    169. //数据转换为字符串并写入文件
    170. sw.WriteLine(buildState.SaveToString());
    171. sw.Close();//关闭文件
    172. sw.Dispose();
    173. }
    174. /*重新加载场景按钮*/
    175. public void ReLoadGame()
    176. {
    177. Application.LoadLevel(Application.loadedLevel);//重置场景
    178. }
    179. /*加载按钮*/
    180. public void ClickLoadGame()
    181. {
    182. /*从json文件读取数据*/
    183. FileStream fs = new FileStream(Application.streamingAssetsPath + "/BuildSave.json", FileMode.Open, FileAccess.Read, FileShare.None);
    184. StreamReader sr = new StreamReader(fs, System.Text.Encoding.Default);
    185. if (null == sr) return;
    186. string str = sr.ReadToEnd();
    187. sr.Close();
    188. /*字符串数据反序列化到变量中*/
    189. BuildStateLoad buildInfo = new BuildStateLoad();
    190. buildInfo=buildInfo.CreateFromJSON(str);
    191. for (int i = 0; i < buildInfo.modelType.Count; i++)
    192. {
    193. /*生成模型到场景*/
    194. switch (buildInfo.modelType[i])
    195. {
    196. case "Box":
    197. loadcreateModel = Instantiate(boxPrefabModel);
    198. break;
    199. case "Flak88":
    200. loadcreateModel = Instantiate(flak88PrefabModel);
    201. break;
    202. }
    203. /*重命名*/
    204. loadcreateModel.name = buildInfo.modelType[i];
    205. /*位置*/
    206. loadcreateModel.transform.position = new Vector3((float)buildInfo.posX[i],(float)buildInfo.posY[i],(float)buildInfo.posZ[i]);
    207. /*旋转*/
    208. loadcreateModel.transform.rotation = Quaternion.Euler(new Vector3((float)buildInfo.rotX[i],(float)buildInfo.rotY[i],(float)buildInfo.rotZ[i]));
    209. /*固定到场景*/
    210. loadcreateModel.GetComponent().isTrigger=false;//关闭触发器恢复碰撞
    211. loadcreateModel.GetComponent().useGravity=true;//恢复刚体组件的重力
    212. }
    213. }
    214. /*用于保存数据的类*/
    215. [Serializable]//应用可序列化属性,不受支持的字段以及私有字段、静态字段和应用了NonSerialized属性的字段会被忽略
    216. public class BuildStateSave
    217. {
    218. /*
    219. 泛型集合List
    220. 大小可按需动态增加,不会强行对值类型进行装箱和拆箱,或对引用类型进行向下强制类型转换,性能提高
    221. */
    222. public List<string> modelType = new List<string>();//模型类型
    223. public List<double> posX = new List<double>();//X位置
    224. public List<double> posY = new List<double>();//Y位置
    225. public List<double> posZ = new List<double>();//Z位置
    226. public List<double> rotX = new List<double>();//X方向
    227. public List<double> rotY = new List<double>();//Y方向
    228. public List<double> rotZ = new List<double>();//Z方向
    229. /*把列表数据转换为json字符串*/
    230. public string SaveToString()
    231. {
    232. return JsonUtility.ToJson(this,true);//[prettyPrint]为true时保持可读格式,为false时最小大小
    233. }
    234. }
    235. /*用于加载数据的类*/
    236. [System.Serializable]//应用可序列化属性,不受支持的字段以及私有字段、静态字段和应用了NonSerialized属性的字段会被忽略
    237. public class BuildStateLoad
    238. {
    239. public List<string> modelType = new List<string>();//模型类型
    240. public List<double> posX = new List<double>();//X位置
    241. public List<double> posY = new List<double>();//Y位置
    242. public List<double> posZ = new List<double>();//Z位置
    243. public List<double> rotX = new List<double>();//X方向
    244. public List<double> rotY = new List<double>();//Y方向
    245. public List<double> rotZ = new List<double>();//Z方向
    246. /*把json字符串转换为变量数据*/
    247. public BuildStateLoad CreateFromJSON(string jsonString)
    248. {
    249. return JsonUtility.FromJson(jsonString);
    250. }
    251. }
    252. }

    用于碰撞触发检测的脚本, 挂载在预制体模型上

    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. public class 碰撞检测 : MonoBehaviour
    6. {
    7. private BuildTest testScript;
    8. private void Start()
    9. {
    10. /*获取脚本对象*/
    11. GameObject gameObject=GameObject.Find("脚本挂载");
    12. testScript = gameObject.GetComponent();
    13. }
    14. //触发开始时执行一次
    15. public void OnTriggerEnter(Collider collider){
    16. testScript.unInitPrefab = true;
    17. }
    18. //触发过程中一直重复执行
    19. public void OnTriggerStay(Collider collider){
    20. testScript.unInitPrefab = true;
    21. }
    22. //触发结束时执行一次
    23. public void OnTriggerExit(Collider collider){
    24. testScript.unInitPrefab = false;
    25. }
    26. }
  • 相关阅读:
    Java NIO 通信基础
    计算机毕业设计SSM钓鱼爱好者交流平台【附源码数据库】
    基于Http Basic Authentication的接口
    【JAVA】快速排序
    【数据结构2】算法的基本概念
    .NET Core WebAPI中使用Swagger(完整教程)
    嵌入式C语言设计模式 --- 前言
    S-2K2001 pmon调试
    用pymysql封装项目通用的连接和查询
    力扣(LeetCode)1333. 餐厅过滤器(C++)
  • 原文地址:https://blog.csdn.net/qq_36917144/article/details/127111997