【unity小技巧】手戳代码程序化绘制地形Terrain树和预制体物品、动物

简介: 【unity小技巧】手戳代码程序化绘制地形Terrain树和预制体物品、动物

基础

【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);
                }
            }
        }
    }
}

目录
相关文章
|
4天前
|
图形学 开发者
【Unity小技巧】unity移动物体的探究——使用8个不同方法
【Unity小技巧】unity移动物体的探究——使用8个不同方法
11 1
|
4天前
|
图形学
【unity小技巧】实现投掷物品,比如长矛,刀具,手榴弹和其他物体
【unity小技巧】实现投掷物品,比如长矛,刀具,手榴弹和其他物体
6 1
|
4天前
|
图形学
【unity小技巧】Unity中实现一个战斗连击连招系统,可以动态添加减少连击连招段数功能
【unity小技巧】Unity中实现一个战斗连击连招系统,可以动态添加减少连击连招段数功能
5 0
|
4天前
|
存储 图形学
【unity小技巧】unity事件系统创建通用的对象交互的功能
【unity小技巧】unity事件系统创建通用的对象交互的功能
7 0
|
4天前
|
图形学
【unity小技巧】unity通过代码进行更改后处理效果
【unity小技巧】unity通过代码进行更改后处理效果
6 0
|
4天前
|
图形学
【unity小技巧】unity3D寻路指示轨迹预测
【unity小技巧】unity3D寻路指示轨迹预测
10 0
|
4天前
|
图形学
【unity小技巧】unity读excel配置表操作,excel转txt文本,并读取txt文本内容,实例说明
【unity小技巧】unity读excel配置表操作,excel转txt文本,并读取txt文本内容,实例说明
9 0
|
4天前
|
编解码 算法 图形学
【unity小技巧】减少Unity中的构建打包大小
【unity小技巧】减少Unity中的构建打包大小
5 0
|
4天前
|
图形学
【unity小技巧】Unity人物衣服布料系统的探究 —— Cloth组件
【unity小技巧】Unity人物衣服布料系统的探究 —— Cloth组件
6 0
|
4天前
|
存储 JSON 关系型数据库
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
14 2