基础
【2023Unity游戏开发教程】零基础带你从小白到超神04——地形的绘制和基础使用介绍
每次运行,随机批量绘制地形树
public class TreeSpawner : MonoBehaviour { public int numberOfTrees = 100; // 需要生成的树木数量 public float spawnRadius = 50f; // 生成范围半径 private Terrain terrainInstance; private TreePrototype[] treePrototypes; // 树木原型数组 void Start() { terrainInstance = GetComponent<Terrain>(); treePrototypes = terrainInstance.terrainData.treePrototypes; // 获取树木原型数组 ClearTrees(); // 清空树木数据 SpawnTrees(); } void ClearTrees() { terrainInstance.terrainData.treeInstances = new TreeInstance[0]; } void SpawnTrees() { for (int i = 0; i < numberOfTrees; i++) { // 在指定范围内随机生成树木位置 Vector3 randomPosition = new Vector3(Random.Range(0, terrainInstance.terrainData.size.x), 0, Random.Range(0, terrainInstance.terrainData.size.z)); randomPosition.y = terrainInstance.SampleHeight(randomPosition) + terrainInstance.transform.position.y; if (Vector3.Distance(randomPosition, transform.position) < spawnRadius) { TreeInstance newTree = new TreeInstance(); newTree.position = new Vector3(randomPosition.x / terrainInstance.terrainData.size.x, randomPosition.y / terrainInstance.terrainData.size.y, randomPosition.z / terrainInstance.terrainData.size.z); newTree.widthScale = 1f; newTree.heightScale = 1f; // newTree.prototypeIndex = 0; // 使用第一个树木原型 newTree.prototypeIndex = Random.Range(0, treePrototypes.Length); terrainInstance.AddTreeInstance(newTree); } } terrainInstance.Flush(); // 刷新地形数据 } }
指定某些地形纹理不生成树木
using UnityEngine; public class TreeSpawner : MonoBehaviour { public int numberOfTrees = 100; // 需要生成的树木数量 public float spawnRadius = 50f; // 生成范围半径 private Terrain terrainInstance; private TreePrototype[] treePrototypes; // 树木原型数组 void Start() { terrainInstance = GetComponent<Terrain>(); treePrototypes = terrainInstance.terrainData.treePrototypes; // 获取树木原型数组 ClearTrees(); // 清空树木数据 SpawnTrees(); } void ClearTrees() { terrainInstance.terrainData.treeInstances = new TreeInstance[0]; } void SpawnTrees() { // 获取地形纹理的混合权重数组 float[,,] alphamap = terrainInstance.terrainData.GetAlphamaps(0, 0, terrainInstance.terrainData.alphamapWidth, terrainInstance.terrainData.alphamapHeight); for (int i = 0; i < numberOfTrees; i++) { // 在指定范围内随机生成树木位置 Vector3 randomPosition = new Vector3(Random.Range(0, terrainInstance.terrainData.size.x), 0, Random.Range(0, terrainInstance.terrainData.size.z)); randomPosition.y = terrainInstance.SampleHeight(randomPosition) + terrainInstance.transform.position.y; if (Vector3.Distance(randomPosition, transform.position) < spawnRadius) { // 获取树木位置所在的地形纹理索引 int textureIndexX = (int)(randomPosition.x / terrainInstance.terrainData.size.x * terrainInstance.terrainData.alphamapWidth); int textureIndexY = (int)(randomPosition.z / terrainInstance.terrainData.size.z * terrainInstance.terrainData.alphamapHeight); // 获取树木位置的地形纹理混合权重 float[] textureWeights = new float[terrainInstance.terrainData.alphamapLayers]; for (int layer = 0; layer < terrainInstance.terrainData.alphamapLayers; layer++) { //textureWeights保存的其实就是每个地形纹理的混合比例 textureWeights[layer] = alphamap[textureIndexY, textureIndexX, layer]; // Debug.Log(textureWeights[layer]); } // 判断是否满足条件不生成树木, // 如果第一个草地层的混合为小于0.5则不生成树,你可以简单的理解为只在第一个草地层绘制树 if (textureWeights[0] < 0.5f) { continue; // 跳过不生成树木 } TreeInstance newTree = new TreeInstance(); newTree.position = new Vector3(randomPosition.x / terrainInstance.terrainData.size.x, randomPosition.y / terrainInstance.terrainData.size.y, randomPosition.z / terrainInstance.terrainData.size.z); newTree.widthScale = 1f; newTree.heightScale = 1f; // newTree.prototypeIndex = 0; // 使用第一个树木原型 newTree.prototypeIndex = Random.Range(0, treePrototypes.Length); terrainInstance.AddTreeInstance(newTree); } } terrainInstance.Flush(); // 刷新地形数据 } }
可能你还想加入坡度限制
自己定义的坡度限制值。当地形坡度超过这个限制时,将跳过树木生成
using UnityEngine; public class TreeSpawner : MonoBehaviour { public int numberOfTrees = 100; // 需要生成的树木数量 public float spawnRadius = 50f; // 生成范围半径 public float slopeLimit = 30f;//坡度限制 private Terrain terrainInstance; private TreePrototype[] treePrototypes; // 树木原型数组 void Start() { terrainInstance = GetComponent<Terrain>(); treePrototypes = terrainInstance.terrainData.treePrototypes; // 获取树木原型数组 ClearTrees(); // 清空树木数据 SpawnTrees(); } void ClearTrees() { terrainInstance.terrainData.treeInstances = new TreeInstance[0]; } void SpawnTrees() { // 获取地形纹理的混合权重数组 float[,,] alphamap = terrainInstance.terrainData.GetAlphamaps(0, 0, terrainInstance.terrainData.alphamapWidth, terrainInstance.terrainData.alphamapHeight); for (int i = 0; i < numberOfTrees; i++) { // 在指定范围内随机生成树木位置 Vector3 randomPosition = new Vector3(Random.Range(0, terrainInstance.terrainData.size.x), 0, Random.Range(0, terrainInstance.terrainData.size.z)); randomPosition.y = terrainInstance.SampleHeight(randomPosition) + terrainInstance.transform.position.y; // 检查地形坡度 float slopeAngle = terrainInstance.terrainData.GetSteepness(randomPosition.x / terrainInstance.terrainData.size.x, randomPosition.z / terrainInstance.terrainData.size.z); if (slopeAngle > slopeLimit) { continue; // 跳过不生成树木 } if (Vector3.Distance(randomPosition, transform.position) < spawnRadius) { // 获取树木位置所在的地形纹理索引 int textureIndexX = (int)(randomPosition.x / terrainInstance.terrainData.size.x * terrainInstance.terrainData.alphamapWidth); int textureIndexY = (int)(randomPosition.z / terrainInstance.terrainData.size.z * terrainInstance.terrainData.alphamapHeight); // 获取树木位置的地形纹理混合权重 terrainInstance.terrainData.alphamapLayers获取当前地形中使用的地形纹理数量 float[] textureWeights = new float[terrainInstance.terrainData.alphamapLayers]; for (int layer = 0; layer < terrainInstance.terrainData.alphamapLayers; layer++) { //textureWeights保存的其实就是每个地形纹理的混合比例 textureWeights[layer] = alphamap[textureIndexY, textureIndexX, layer]; // Debug.Log(textureWeights[layer]); } // 判断是否满足条件不生成树木, // 如果第一个草地层的混合为小于0.5则不生成树,你可以简单的理解为只在第一个草地层绘制树 if (textureWeights[0] < 0.5f) { continue; // 跳过不生成树木 } TreeInstance newTree = new TreeInstance(); newTree.position = new Vector3(randomPosition.x / terrainInstance.terrainData.size.x, randomPosition.y / terrainInstance.terrainData.size.y, randomPosition.z / terrainInstance.terrainData.size.z); newTree.widthScale = 1f; newTree.heightScale = 1f; // newTree.prototypeIndex = 0; // 使用第一个树木原型 newTree.prototypeIndex = Random.Range(0, treePrototypes.Length); terrainInstance.AddTreeInstance(newTree); } } terrainInstance.Flush(); // 刷新地形数据 } }
效果
将地形树替换成预制体树
为了性能考虑,实现只替换角色附近指定范围的树,这样后续还可以使用对象池优化代码
public class TreeReplace : MonoBehaviour { public Transform player;// 设置替换范围中心点 public float radius = 10;// 设置替换范围半径 Terrain terrainInstance; private GameObject tree; void Start() { terrainInstance = GetComponent<Terrain>(); TreeInstance[] trees = terrainInstance.terrainData.treeInstances; // 获取地形上的树木实例数组 for (int i = 0; i < trees.Length; i++) { Vector3 worldPosition = terrainInstance.transform.position + Vector3.Scale(trees[i].position, terrainInstance.terrainData.size);//转换为世界坐标 if (Vector3.Distance(player.position, worldPosition) <= radius) { TreePrototype treePrototype = terrainInstance.terrainData.treePrototypes[trees[i].prototypeIndex]; // 获取树木实例对应的原型 tree = treePrototype.prefab;//树木预制体 Instantiate(tree, worldPosition, Quaternion.identity); trees[i] = new TreeInstance(); // 将该位置的树木实例置为空 } } terrainInstance.terrainData.treeInstances = trees; terrainInstance.terrainData.RefreshPrototypes(); } }
其实也可以直接在地形上绘制生成树预制体
public class TreeSpawner : MonoBehaviour { public GameObject[] treePrefabs; // 多种树木预制体 public int numberOfTrees = 100; // 需要生成的树木数量 public float spawnRadius = 50f; // 生成范围半径 private Terrain terrainInstance; void Start() { terrainInstance = GetComponent<Terrain>(); SpawnTrees(); } void SpawnTrees() { for (int i = 0; i < numberOfTrees; i++) { // 在指定范围内随机生成树木位置 Vector3 randomPosition = new Vector3(Random.Range(0, terrainInstance.terrainData.size.x), 0, Random.Range(0, terrainInstance.terrainData.size.z)); randomPosition.y = terrainInstance.SampleHeight(randomPosition) + terrainInstance.transform.position.y; if (Vector3.Distance(randomPosition, transform.position) < spawnRadius) { // 生成随机索引,用于选择一个随机的树木预制体 int randomIndex = Random.Range(0, treePrefabs.Length); GameObject selectedTreePrefab = treePrefabs[randomIndex]; // 实例化所选的树木预制体,并进行位置和缩放调整 GameObject newTree = Instantiate(selectedTreePrefab, randomPosition, Quaternion.identity); newTree.transform.localScale = Vector3.one; // 根据需要进行缩放调整 // 可以在此处对新生成的树木进行其他设置或操作 terrainInstance.Flush(); // 刷新地形数据 } } } }
要在地形上程序化放置物品和动物
可以使用类似生成树木预制体的方法,不过要注意的是需要避免与具有碰撞体的物体冲突,通常我们还需要排除某些层的检测,比如地形层,石头层
using UnityEngine; public class ObjectSpawner : MonoBehaviour { public GameObject[] objectsToSpawn; // 需要生成的物品和动物Prefab数组 public int numberOfObjects = 50; // 需要生成的数量 public float spawnRadius = 50f; // 生成范围半径 private Terrain terrainInstance; private Collider terrainCollider; void Start() { terrainInstance = GetComponent<Terrain>(); terrainCollider = terrainInstance.GetComponent<Collider>(); SpawnObjects(); } void SpawnObjects() { for (int i = 0; i < numberOfObjects; i++) { Vector3 randomPosition = new Vector3(Random.Range(0, terrainInstance.terrainData.size.x), 0, Random.Range(0, terrainInstance.terrainData.size.z)); randomPosition.y = terrainInstance.SampleHeight(randomPosition) + terrainInstance.transform.position.y; if (Vector3.Distance(randomPosition, transform.position) < spawnRadius) { // 排除两个层 // int layerMask = ~(LayerMask.GetMask("Layer1") | LayerMask.GetMask("Layer2")); // 射线检测,检查生成位置是否与其他物体的碰撞体相交 bool isColliding = Physics.CheckSphere(randomPosition, 1f, ~LayerMask.GetMask("Terrain")); if (!isColliding) { // 实例化需要生成的物品和动物Prefab GameObject objectToSpawn = objectsToSpawn[Random.Range(0, objectsToSpawn.Length)]; Instantiate(objectToSpawn, randomPosition, Quaternion.identity); } } } } }