最近结合之前的一个项目,做了一个简单的小Demo,主要功能是动态加载场景资源,
以Demo为例,要在地面随机生成一些树木,要求:
- 游戏运行以后,动态加载;
- 随机树木位置,要生成在地面,不能悬空或低于地面,也不能重叠,不然就尴尬了
(发现王者荣耀游戏开始的防御塔,也是类似效果,其原理就不得而知了)
主要说明还是写在注释里,大概思路这样: - 首先加载地形
- 在地形上面,依次加载指定数量的树木
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