unity运行状态下保存物体至资源文件夹内

3 篇文章 0 订阅

参考链接:https://blog.csdn.net/tom_221x/article/details/61920213
 

Unity运行时,动态创建的Mesh挂载到MeshFilter组件上,并不能保存到本地Prefab文件里。在运行的场景里,拖拽正确配置的MeshFilter对象到Unity资源管理器。生成的Prefab文件,里面的Mesh对象会missing。所以,我们需要在运行状态,导出Mesh到本地生成一个obj模型文件。

原理,就是根据obj文件的属性,把运行时Mesh的顶点,索引,贴图数据转化为固定格式流写入文件,生成obj模型文件。

     

   private string MeshToString(MeshFilter mf, Vector3 scale)
        {
            Mesh          mesh            = mf.mesh;
            Material[]    sharedMaterials = mf.GetComponent<Renderer>().sharedMaterials;
            Vector2       textureOffset   = mf.GetComponent<Renderer>().material.GetTextureOffset("_MainTex");
            Vector2       textureScale    = mf.GetComponent<Renderer>().material.GetTextureScale ("_MainTex");
 
            StringBuilder stringBuilder   = new StringBuilder().Append("mtllib design.mtl")
                .Append("\n")
                .Append("g ")
                .Append(mf.name)
                .Append("\n");
 
            Vector3[] vertices = mesh.vertices;
            for (int i = 0; i < vertices.Length; i++)
            {
                Vector3 vector = vertices[i];
                stringBuilder.Append(string.Format("v {0} {1} {2}\n", vector.x * scale.x, vector.y * scale.y, vector.z * scale.z));
            }
 
            stringBuilder.Append("\n");
 
            Dictionary<int, int> dictionary = new Dictionary<int, int>();
 
            if (mesh.subMeshCount > 1)
            {
                int[] triangles = mesh.GetTriangles(1);
 
                for (int j = 0; j < triangles.Length; j += 3)
                {
                    if (!dictionary.ContainsKey(triangles[j]))
                    {
                        dictionary.Add(triangles[j], 1);
                    }
 
                    if (!dictionary.ContainsKey(triangles[j + 1]))
                    {
                        dictionary.Add(triangles[j + 1], 1);
                    }
 
                    if (!dictionary.ContainsKey(triangles[j + 2]))
                    {
                        dictionary.Add(triangles[j + 2], 1);
                    }
                }
            }
 
            for (int num = 0; num != mesh.uv.Length; num++)
            {
                Vector2 vector2 = Vector2.Scale(mesh.uv[num], textureScale) + textureOffset;
 
                if (dictionary.ContainsKey(num))
                {
                    stringBuilder.Append(string.Format("vt {0} {1}\n", mesh.uv[num].x, mesh.uv[num].y));
                }
                else
                {
                    stringBuilder.Append(string.Format("vt {0} {1}\n", vector2.x, vector2.y));
                }
            }
 
            for (int k = 0; k < mesh.subMeshCount; k++)
            {
                stringBuilder.Append("\n");
 
                if (k == 0)
                {
                    stringBuilder.Append("usemtl ").Append("Material_design").Append("\n");
                }
 
                if (k == 1)
                {
                    stringBuilder.Append("usemtl ").Append("Material_logo").Append("\n");
                }
 
                int[] triangles2 = mesh.GetTriangles(k);
 
                for (int l = 0; l < triangles2.Length; l += 3)
                {
                    stringBuilder.Append(string.Format("f {0}/{0} {1}/{1} {2}/{2}\n", triangles2[l] + 1, triangles2[l + 2] + 1, triangles2[l + 1] + 1));
                }
            }
 
            return stringBuilder.ToString();
        }


这段代码可以直接使用,把MeshFilter组件里Mesh数据变成一个固定格式的字符串流。写入到本地文件就是一个obj模型。这里有一个需要注意的地方,就是Unity加载obj文件的时候,顶点的X轴是翻转的。
          

 using (StreamWriter streamWriter = new StreamWriter(string.Format("{0}{1}.obj", datPath, this.meshGO.name)))
            {
                streamWriter.Write(MeshToString(mf, new Vector3(-1f, 1f, 1f)));
                streamWriter.Close();
            }
            AssetDatabase.Refresh();


所以,在写入数据的时候,我们把scale.x设置为-1, 这样就翻转了X轴。并且正常情况下Mesh的顶点索引就是Triangles,需要逆时针才不会被摄像机剔除。当这里我们翻转了X顶点,同步我们需要在生成Mesh Triangles的时候,使用顺时针排列。这样,翻转X轴以后,对摄像机来说,顶点索引又是逆时针排列的了,就可以看见了。


第二部分,生成了obj模型文件以后,我们可以通过这个文件加载一个Mesh对象。动态生成一个Prefab到本地,把obj模型文件中的Mesh对象赋值给它,成为一个正确加载Mesh的Prefab。

          

 // create prefab
            Mesh mesh     = AssetDatabase.LoadAssetAtPath<Mesh>(string.Format("{0}{1}.obj", projectPath, this.meshGO.name));
            mf.mesh     = mesh;
 
            PrefabUtility.CreatePrefab(string.Format("{0}{1}.prefab", projectPath, this.meshGO.name), this.meshGO);
            AssetDatabase.Refresh();


主要通过,AssetDatabase.LoadAssetAtPath的泛型方法来记载obj模型文件里的Mesh对象。然后动态创建一个Prefab这里需要注意Refresh一下,才能正确保存Mesh对象到Prefab上。
————————————————
版权声明:本文为CSDN博主「scottcgi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/tom_221x/article/details/61920213

 

番外:Unity开发Mesh合并网格

参考链接:https://www.cnblogs.com/chinarbolg/p/9601379.html
 

using UnityEngine;


/// <summary>
/// 合并网格
/// </summary>
public class ChinarMergeMesh : MonoBehaviour
{
    void Start()
    {
        MergeMesh();
    }


    /// <summary>
    /// 合并网格
    /// </summary>
    private void MergeMesh()
    {
        MeshFilter[]      meshFilters      = GetComponentsInChildren<MeshFilter>();   //获取 所有子物体的网格
        CombineInstance[] combineInstances = new CombineInstance[meshFilters.Length]; //新建一个合并组,长度与 meshfilters一致
        for (int i = 0; i < meshFilters.Length; i++)                                  //遍历
        {
            combineInstances[i].mesh      = meshFilters[i].sharedMesh;                   //将共享mesh,赋值
            combineInstances[i].transform = meshFilters[i].transform.localToWorldMatrix; //本地坐标转矩阵,赋值
        }
        Mesh newMesh = new Mesh();                                  //声明一个新网格对象
        newMesh.CombineMeshes(combineInstances);                    //将combineInstances数组传入函数
        gameObject.AddComponent<MeshFilter>().sharedMesh = newMesh; //给当前空物体,添加网格组件;将合并后的网格,给到自身网格
        //到这里,新模型的网格就已经生成了。运行模式下,可以点击物体的 MeshFilter 进行查看网格

        #region 以下是对新模型做的一些处理:添加材质,关闭所有子物体,添加自转脚本和控制相机的脚本

        //gameObject.AddComponent<MeshRenderer>().material = Resources.Load<Material>("Materials/Koala"); //给当前空物体添加渲染组件,给新模型网格上色;
        //foreach (Transform t in transform)                                                              //禁用掉所有子物体
        //{
        //    t.gameObject.SetActive(false);
        //}
        //gameObject.AddComponent<BallRotate>();
        //Camera.main.gameObject.AddComponent<ChinarCamera>().pivot = transform;

        #endregion
    }
}

 

 

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
将JSON保存到StreamingAssets文件夹需要的步骤与保存到本地文件夹类似。以下是一个示例: ``` using UnityEngine; using System.IO; [System.Serializable] public class MyData { public int id; public string name; public float value; } public class SaveJsonToStreamingAssets : MonoBehaviour { void Start() { MyData myData = new MyData(); myData.id = 1; myData.name = "test"; myData.value = 0.5f; string json = JsonUtility.ToJson(myData); string filePath = Application.streamingAssetsPath + "/mydata.json"; // 如果是Android平台,需要使用WWW类读写StreamingAssets文件夹中的文件 #if UNITY_ANDROID && !UNITY_EDITOR StartCoroutine(CopyFileAndroid(filePath, json)); #else // 如果是其他平台,可以直接使用File类读写文件 File.WriteAllText(filePath, json); #endif } IEnumerator CopyFileAndroid(string filePath, string json) { // 如果文件已经存在,先删除文件 if (File.Exists(filePath)) { File.Delete(filePath); } // 将文件从apk中复制到Application.persistentDataPath中 UnityWebRequest www = UnityWebRequest.Get(Application.streamingAssetsPath + "/mydata.json"); yield return www.SendWebRequest(); File.WriteAllBytes(filePath, www.downloadHandler.data); // 将JSON字符串写入文件 StreamWriter streamWriter = new StreamWriter(filePath, true); streamWriter.Write(json); streamWriter.Close(); } } ``` 在此示例中,我们将文件保存到StreamingAssets文件夹中的`mydata.json`文件。因为在Android平台上,无法直接使用`File`类读写StreamingAssets文件夹中的文件,所以我们需要使用`UnityWebRequest`类将文件从apk中复制到`Application.persistentDataPath`中,然后再使用`StreamWriter`将JSON字符串写入文件。对于其他平台,可以直接使用`File`类读写文件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山竹炒大蒜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值