Unity 自動製作LowPoly隨機形態的樹預製體工具

通過一個樹幹模型和樹葉模型 隨機制作不同的預製體

Editor腳本:

//=====================================================
// - FileName:      AutoCreateTreeModel.cs
// - Author:       #AuthorName#
// - CreateTime:    #CreateTime#
// - Email:         #AuthorEmail#
// - Description:   
// -  (C) Copyright 2019, webeye,Inc.
// -  All Rights Reserved.
//======================================================
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Qarth;
using Random = UnityEngine.Random;
using Object = UnityEngine.Object;

namespace GameWish.Game
{
    public class AutoCreateTreeModel
    {
        static int UpLevel = 1;
        static int MidLevel = 3;
        static int LowLevel = 2;

        static float midR=1.5f;
        static float topR = 0.1f;
        static float botR = 1f;

        static float lowHighIntevel = 0.4f;

        [MenuItem("Tools/CreateTreePrefab %#J")]
        private static void CreateTreePrefab()
        {
            GameObject go = PrefabUtility.InstantiatePrefab(Resources.Load("TreeCreateModel/scene_tree_model"))as GameObject;
            Transform root = go.transform.GetChild(0).Find("LeaveRoot");
            GameObject model = null;
            if (!go)
            {
                Log.e("Select is null!Please select a treeModel");
                return;
            }
            go.IterateGameObject((child)=> 
            {
                if (child.name.Contains("leave_"))
                {
                    GameObject.DestroyImmediate(child);
                }
            });

            if (root)
            {
                model = root.Find("leave").gameObject;
            }
            if (model)
            {
                //樹的中段(空心圓生成)
                for (int i = 0; i < MidLevel; i++)
                {
                    GameObject clone1 = GameObject.Instantiate(model);
                    clone1.SetActive(true);
                    clone1.name = string.Format("leave_mid_{0}",i);
                    clone1.transform.SetParent(root);
                    Vector2 p = Random.insideUnitCircle * midR;
                    clone1.transform.localPosition = new Vector3(p.x, 0, p.y);

                }
                //樹的下段
                for (int i = 0; i < LowLevel; i++)
                {
                    GameObject clone2 = GameObject.Instantiate(model);
                    clone2.SetActive(true);
                    clone2.name = string.Format("leave_bot_{0}", i);
                    clone2.transform.SetParent(root);
                    Vector2 p = Random.insideUnitCircle * botR;
                    clone2.transform.localPosition = new Vector3(p.x, -lowHighIntevel, p.y);

                }
                //樹的上段
                for (int i = 0; i < UpLevel; i++)
                {
                    GameObject clone3 = GameObject.Instantiate(model);
                    clone3.SetActive(true);
                    clone3.name = string.Format("leave_top_{0}", i);
                    clone3.transform.SetParent(root);
                    Vector2 p = Random.insideUnitCircle * topR;
                    clone3.transform.localPosition = new Vector3(p.x, lowHighIntevel, p.y);
                }
                model.SetActive(false);
            }
            else
            {
                Log.e("選中的物體{0}無法制作樹木模型!", go.name);
                return;
            }

            foreach (Transform child in root)
            {
                child.transform.localScale *= Random.Range(0.9f, 1.2f);
                //child.transform.Rotate(new Vector3(RandomHelper.Range(0, 360), RandomHelper.Range(0, 360), RandomHelper.Range(0, 360)));
            }
            go.IterateGameObject((child) =>
            {
                child.isStatic = true;
            });

            go.name = string.Format("auto_tree_{0}",System.DateTime.Now.ToString("mmddss"));
            //製作預製體到對應路徑
            bool success = false;
            Object tempPre = PrefabUtility.SaveAsPrefabAsset(go,string.Format("Assets/Res/FolderMode/Prefabs/AutoTree/{0}.prefab", go.name),out success);
            if (success)
            {
                Log.i("Create {0} Prefab Successful!!", go.name);
            }
            else
            {
                Log.e("Create {0} Prefab Failed!!", go.name);
            }
        }
    }
}

PS:由於生產的樹雖然勾選了static 但是在場景中的性能依舊很差,暫時不知道原因,所以爲了優化性能,採取了合併mesh的方法,將樹的mesh合併成一個整體。合併之後發現不能保存預製體,於是將mesh保存了下來,所以路徑到時候需要自己修改:

新的Editor

//=====================================================
// - FileName:      AutoCreateTreeModel.cs
// - Author:       #AuthorName#
// - CreateTime:    #CreateTime#
// - Email:         #AuthorEmail#
// - Description:   
// -  (C) Copyright 2019, webeye,Inc.
// -  All Rights Reserved.
//======================================================
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Qarth;
using Random = UnityEngine.Random;
using Object = UnityEngine.Object;
using UnityEditor.SceneManagement;
using System.IO;

namespace GameWish.Game
{
    public class AutoCreateTreeModel
    {
        static int UpLevel = 1;
        static int MidLevel = 3;
        static int LowLevel = 2;

        static float midR = 1.5f;
        static float topR = 0.1f;
        static float botR = 1f;

        static float lowHighIntevel = 0.4f;

        [MenuItem("Tools/CreateTreePrefab %#J")]
        private static void CreateTreePrefab()
        {
            GameObject go = PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath("Assets/Res/FileMode/TreeCreateModel/scene_tree_model.prefab",typeof(GameObject))) as GameObject;
            Transform root = go.transform.GetChild(0).Find("LeaveRoot");
            GameObject model = null;
            if (!go)
            {
                Log.e("Select is null!Please select a treeModel");
                return;
            }
            go.IterateGameObject((child) =>
            {
                if (child.name.Contains("leave_"))
                {
                    GameObject.DestroyImmediate(child);
                }
            });

            if (root)
            {
                model = root.Find("leave").gameObject;
            }
            if (model)
            {
                //樹的中段(空心圓生成)
                for (int i = 0; i < MidLevel; i++)
                {
                    GameObject clone1 = GameObject.Instantiate(model);
                    clone1.SetActive(true);
                    clone1.name = string.Format("leave_mid_{0}", i);
                    clone1.transform.SetParent(root);
                    Vector2 p = Random.insideUnitCircle * midR;
                    clone1.transform.localPosition = new Vector3(p.x, 0, p.y);

                }
                //樹的下段
                for (int i = 0; i < LowLevel; i++)
                {
                    GameObject clone2 = GameObject.Instantiate(model);
                    clone2.SetActive(true);
                    clone2.name = string.Format("leave_bot_{0}", i);
                    clone2.transform.SetParent(root);
                    Vector2 p = Random.insideUnitCircle * botR;
                    clone2.transform.localPosition = new Vector3(p.x, -lowHighIntevel, p.y);

                }
                //樹的上段
                for (int i = 0; i < UpLevel; i++)
                {
                    GameObject clone3 = GameObject.Instantiate(model);
                    clone3.SetActive(true);
                    clone3.name = string.Format("leave_top_{0}", i);
                    clone3.transform.SetParent(root);
                    Vector2 p = Random.insideUnitCircle * topR;
                    clone3.transform.localPosition = new Vector3(p.x, lowHighIntevel, p.y);
                }
                model.SetActive(false);
            }
            else
            {
                Log.e("選中的物體{0}無法制作樹木模型!", go.name);
                return;
            }

            foreach (Transform child in root)
            {
                child.transform.localScale *= Random.Range(0.9f, 1.2f);
                //child.transform.Rotate(new Vector3(RandomHelper.Range(0, 360), RandomHelper.Range(0, 360), RandomHelper.Range(0, 360)));
            }
            go.IterateGameObject((child) =>
            {
                child.isStatic = true;
            });

            go.name = string.Format("auto_tree_{0}", System.DateTime.Now.ToString("ddhhmmss"));
            BoxCollider bc = go.AddMissingComponent<BoxCollider>();
            bc.isTrigger = true;
            bc.center = new Vector3(0, 1.31f, 0);
            bc.size = new Vector3(1, 4, 1);
            go.AddMissingComponent<Obstacle>();
            go.SetAllLayer(LayerMask.NameToLayer(Define.LAYER_OBSTACLE));
            go.SetAllTag(Define.TAG_OBSTACLE);
            CombineMesh(go);
            //SaveMesh(go);

            //製作預製體到對應路徑
            bool success = false;
            Object tempPre = PrefabUtility.SaveAsPrefabAsset(go, string.Format("Assets/Res/FolderMode/Prefabs/AutoTree/{0}.prefab", go.name), out success);
            if (success)
            {
                Log.i("Create {0} Prefab Successful!!", go.name);
            }
            else
            {
                Log.e("Create {0} Prefab Failed!!", go.name);
            }
        }

        static void CombineMesh(GameObject go)
        {
            Transform tSelect = go.transform;
            if (!tSelect)
            {
                Log.e("{0} is null! please check!", go.name);
                return;
            }

            if (tSelect.childCount < 1)
            {
                return;
            }

            if (!tSelect.GetComponent<MeshFilter>())
            {
                tSelect.gameObject.AddComponent<MeshFilter>();
            }
         
            if (!tSelect.GetComponent<MeshRenderer>())
            {
                tSelect.gameObject.AddComponent<MeshRenderer>();
            }
         
            MeshFilter[] tFilters = tSelect.GetComponentsInChildren<MeshFilter>();

            //根據所有MeshFilter組件的個數申請一個用於Mesh聯合的類存儲信息
            CombineInstance[] tCombiners = new CombineInstance[tFilters.Length];

            //遍歷所有子物體的網格信息進行存儲
            for (int i = 0; i < tFilters.Length; i++)
            {
                //記錄網格
                tCombiners[i].mesh = tFilters[i].sharedMesh;
                //記錄位置
                tCombiners[i].transform = tFilters[i].transform.localToWorldMatrix;
            }
            //新申請一個網格用於顯示組合後的遊戲物體
            Mesh tFinalMesh = new Mesh();
            //重命名Mesh
            tFinalMesh.name = "AutoCombineMesh";
            //調用Unity內置方法組合新Mesh網格
            tFinalMesh.CombineMeshes(tCombiners);
            //賦值組合後的Mesh網格給選中的物體
            tSelect.GetComponent<MeshFilter>().sharedMesh = tFinalMesh;
            //賦值新的材質
            tSelect.GetComponent<MeshRenderer>().material = AssetDatabase.LoadAssetAtPath("Assets/Res/FolderMode/Models/Tree/treeMesh.mat", typeof(Material))as Material;
            tSelect.GetChild(0).gameObject.SetActive(false);

            //保存mesh
            string fullPath = "Assets/Res/FolderMode/Models/Tree/AutoTreeMesh/";
            if (!Directory.Exists(fullPath))
            {
                Directory.CreateDirectory(fullPath);
            }

            Mesh mesh = go.GetComponent<MeshFilter>().sharedMesh;

            string path = Path.Combine(fullPath, go.name + ".asset");
            AssetDatabase.CreateAsset(mesh, path);
            AssetDatabase.Refresh();
            EditorSceneManager.MarkAllScenesDirty();
        }
    }
}

附上IterateGameObject方法:

        /// <summary>
        /// 遍歷go c
        /// </summary>
        /// <param name="go"></param>
        /// <param name="handle"></param>
        static public void IterateGameObject(this GameObject go, Action<GameObject> handle)
        {
            Queue q = new Queue();
            q.Enqueue(go);
            while (q.Count != 0)
            {
                GameObject tmpGo = (GameObject)q.Dequeue();
                foreach (Transform t in tmpGo.transform)
                {
                    q.Enqueue(t.gameObject);
                }
                if (handle != null)
                {
                    handle(tmpGo);
                }
            }
        }
        /// <summary>
        /// 設置go層級關係 c
        /// </summary>
        /// <param name="go"></param>
        /// <param name="layer"></param>
        static public void SetAllLayer(this GameObject go, int layer)
        {
            IterateGameObject(go, (g) =>
            {
                g.layer = layer;
            });
        }

        static public void SetAllTag(this GameObject go, string tag)
        {
            IterateGameObject(go, (g) =>
            {
                g.tag = tag;
            });
        }

接下來展示實際的效果:

樹的模板:

其實是一個樹幹加樹葉,然後我們要做的就是隨機生成樹葉在樹幹上,做成一個模型,這樣就有各種各樣的樹。

實現效果:

生成的模型以及mesh

實際效果:

 

Ps:實時陰影都是自己用shader實現的,沒有使用光源:可以參考我的上一篇文章~。

需要模型資源的 之後我會附上下載鏈接賺點積分~~,知道爲啥static不管用的可以告訴我,這樣可能就不需要自己去合併mesh了。

下載鏈接:https://download.csdn.net/download/qq_32225289/11336626

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章