最近结合之前的一个项目,做了一个简单的小Demo,主要功能是动态加载场景资源,
以Demo为例,要在地面随机生成一些树木,要求:

  1. 游戏运行以后,动态加载;
  2. 随机树木位置,要生成在地面,不能悬空或低于地面,也不能重叠,不然就尴尬了
    (发现王者荣耀游戏开始的防御塔,也是类似效果,其原理就不得而知了)
    主要说明还是写在注释里,大概思路这样:
  3. 首先加载地形
  4. 在地形上面,依次加载指定数量的树木
    2.1 将需要加载的树木,以队列的形式,排队加载
    2.2 随机位置,随机数获取X,Z,然后判断与当前场景已存在的树木的距离,保证随机结果分散
    2.3 在随机结果X,Z值得基础上,往上偏移Y值,以该点向下射线检测,检测到地面后,获取Y值,作为随机位置的Y值,此时的随机位置,刚好在地形表面,在此点实例化树木。
    2.4 利用协程,树木对象实例化完成后,再实例化下一个,避免在一帧里实例化大量资源

主要代码如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapManager : MonoBehaviour
{
    //Resources资源名字
    [SerializeField] string mapPlaneName;
    [SerializeField] string treePrefabName;
    //地图长宽
    [SerializeField] Vector2 mapSize;
    //要生成的树木数量
    [SerializeField] int treeCount;
    //树之间的最小距离
    [SerializeField] float treesMinDistance;
    //场景物体最大高度,射线检测时,射线发射点需要高于该高度
    float mapMaxHight = 10;
    //随机位置时,最多随机次数
    int randomMaxCount = 20;
    //地面Tag
    string planeTag = "Plane";
    Transform planeObj;
    Transform allTrees;
    Transform treePrefab;
    //记录加载到场景中的树
    List<Transform> treeList = new List<Transform> ();
    void Start () 
    {
        LoadPlaneAndTree();
	}
    void LoadPlaneAndTree()
    {
        if (planeObj == null)
            LoadPlane();
        if (allTrees == null)
            LoadTree();
    }
    //加载地形,仅限于简单地形
    //地形较简单时,可采用此方法直接加载,稍大地形,可以采用协程,加载完地形之后,在加载其他资源
    //若地形较为复杂,可将地形切割,依次加载,暂不考虑此情况
    void LoadPlane()
    {
        if(!string.IsNullOrEmpty(mapPlaneName))
        {
            GameObject newObj = Resources.Load<GameObject>(mapPlaneName);
            if (newObj != null)
            {
                planeObj = Instantiate(newObj, transform).transform;
                Debug.Log("<color=green> Map Load Success </color>");
            }
            else
                Debug.Log("<color=red> Map Resources Fail </color>");
        }
        else
            Debug.Log("<color=red> MapPlaneName is Empty </color>");
    }
    //加载场景资源
    void LoadTree()
    {
        //先要有地形
        if (planeObj == null)
        {
            Debug.Log("<color=red> Plane is Null </color>");
            return;
        }
        //设置父节点
        if(allTrees == null)
        {
            allTrees = new GameObject("allTrees").transform;
            allTrees.parent = transform;
        }
        //预加载prefab
        if (treePrefab == null)
        {
            if(!string.IsNullOrEmpty(treePrefabName))
            {
                GameObject newObj = Resources.Load<GameObject>(treePrefabName);
                if (newObj != null)
                {
                    treePrefab = newObj.transform;
                    Debug.Log("<color=green> TreePrefab Load Success </color>");
                }
                else
                {
                    Debug.Log("<color=red> TreePrefab Resources Fail </color>");
                    return;
                }
            }
            else
                Debug.Log("<color=red> TreePrefabName is Empty </color>");
        }
        //
        StartCoroutine(LoadAllTree());
    }
    IEnumerator LoadAllTree()
    {
        yield return new WaitForEndOfFrame();
        //利用协程依次生成
        while(treeList.Count < treeCount)
        {
            yield return StartCoroutine(LoadOneTree());
            //可在此设置间隔时间,调整加载间隔时间,调节整体效果
            //yield return new WaitForSeconds(0.5f);
        }
    }
    IEnumerator LoadOneTree()
    {
        //实例化到场景
        Transform newTree = Instantiate(treePrefab, allTrees);
        //随机位置
        newTree.localPosition = GetRandomPosition();
        //添加到list方便后管理
        treeList.Add(newTree);
        yield return new WaitForEndOfFrame();
    }
    Vector3 GetRandomPosition()
    {
        Vector3 randomPos = Vector3.zero;
        bool isOk = false;
        int count = 0;
        while(!isOk)
        {
            //随机X,Z
            randomPos.x = Random.Range(0, mapSize.x);
            randomPos.z = Random.Range(0, mapSize.y);
            count++;
            isOk = true;
            //计算随机位置与已有位置的距离
            for (int i = 0; i < treeList.Count; i++)
            {
                if (Vector3.Distance(randomPos, new Vector3(treeList[i].position.x, 0, treeList[i].position.z)) < treesMinDistance)
                {
                    isOk = false;
                    break;
                }
            }
            //若随机次数超过指定次数,随机到的位置都小于指定距离,采用此次位置,避免死循环
            if (!isOk && count > randomMaxCount)
            {
                isOk = true;
                Debug.Log("<color=blue> While Random Count > RandomMaxCount </color>");
            }
            //射线检测,确定生成位置的高度
            if (isOk)
            {
                //射线发射点高于地形最高点,向下发射射线
                randomPos.y = mapMaxHight + 10;
                Ray ray = new Ray(randomPos, Vector3.down);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit))
                {
                    //射线检测点是否是地面,若是地面,获取该点Y值
                    if(hit.transform.CompareTag(planeTag))
                        randomPos.y = hit.point.y;
                    else
                    {
                        isOk = false;
                        Debug.Log("<color=blue> Ray Position is not Plane </color>");
                    }
                }
                else
                {
                    isOk = false;
                    Debug.Log("<color=blue> Ray is Error </color>");
                }
            }
        }
        return randomPos;
    }
}

Demo下载:链接:https://pan.baidu.com/s/1Ve5tRiB7HpmlXEwwJI34EQ 密码:0z4c