【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地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
188 3
|
4月前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
113 1
|
4月前
|
图形学
小功能⭐️Unity UnityEvent实现代码的选择
小功能⭐️Unity UnityEvent实现代码的选择
|
4月前
|
API 开发工具 vr&ar
PicoVR Unity SDK⭐️一、SDK下载、项目设置与程序初始配置
PicoVR Unity SDK⭐️一、SDK下载、项目设置与程序初始配置
|
4月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
154 0
|
4月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
125 0
|
4月前
|
图形学 开发者
【Unity光照艺术手册】掌握这些技巧,让你的游戏场景瞬间提升档次:从基础光源到全局光照,打造24小时不间断的视觉盛宴——如何运用代码与烘焙创造逼真光影效果全解析
【8月更文挑战第31天】在Unity中,合理的光照与阴影设置对于打造逼真环境至关重要。本文介绍Unity支持的多种光源类型,如定向光、点光源、聚光灯等,并通过具体示例展示如何使用着色器和脚本控制光照强度,模拟不同时间段的光照变化。此外,还介绍了动态和静态阴影、全局光照及光照探针等高级功能,帮助开发者创造丰富多样的光影效果,提升游戏沉浸感。
106 0
|
4月前
|
开发者 图形学 UED
深度解析Unity游戏开发中的性能瓶颈与优化方案:从资源管理到代码执行,全方位提升你的游戏流畅度,让玩家体验飞跃性的顺滑——不止是技巧,更是艺术的追求
【8月更文挑战第31天】《Unity性能优化实战:让你的游戏流畅如飞》详细介绍了Unity游戏性能优化的关键技巧,涵盖资源管理、代码优化、场景管理和内存管理等方面。通过具体示例,如纹理打包、异步加载、协程使用及LOD技术,帮助开发者打造高效流畅的游戏体验。文中提供了实用代码片段,助力减少内存消耗、提升渲染效率,确保游戏运行丝滑顺畅。性能优化是一个持续过程,需不断测试调整以达最佳效果。
106 0
|
4月前
|
图形学
小功能⭐️Unity中一些简便程序写法
小功能⭐️Unity中一些简便程序写法
|
4月前
|
图形学
小功能⭐️Unity改变代码执行顺序
小功能⭐️Unity改变代码执行顺序