Unity三维真实地形离线地形实时刷新,模拟飞行

三维地形下载资源使用了WorldComposer工具,推荐使用正版,具体使用流程不再多说,网上资料一大堆。

实现方式主要有几个步骤:

        1)下载地图,并渲染地形,这个对内存有一定的要求,下载300公里-15级地形预计要32G内存,下载完成做成预制体,后面作为实时加载的资源(不过我是将Image Import Setting ->Apply的自动取消了的,通过代码自己贴纹理,这样就不需要多大内存了);

        2)给飞机添加脚本,脚本中增加Vector3D.y方向的射线,检测地形与射线的碰撞,获取当前地形,将当前地形作为中心地形向外扩展指定个数的地形;

        3)给飞机添加飞行控制脚本,控制飞机飞行;

效果截图如下:

核心代码分享给大家做参考:

1)飞机控制脚本

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class Move : MonoBehaviour
{
    public float flySpeed = 100;
    public Dropdown speedDropdown;
    /// <summary>
    /// 起飞标志
    /// </summary>
    bool isStartFly = false;
    /// <summary>
    /// 地形背景
    /// </summary>
    public Transform terrainBackGround = null;
    /// <summary>
    /// 横滚速度
    /// </summary>
    public float rollSpeed = 5;
    /// <summary>
    /// 偏航速度
    /// </summary>
    public float yawSpeed = 10;
    /// <summary>
    /// 俯仰速度
    /// </summary>
    public float pitchSpeed = 5;    
    /// <summary>
    /// 允许修正
    /// </summary>
    bool isAllowCorrection = true;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {

    }
    private void FixedUpdate()
    {
        // 开始飞行
        if (isStartFly)
        {
            transform.Translate(Vector3.forward * flySpeed * Time.deltaTime);
        }
        // 爬升
        if (Input.GetKey(KeyCode.S))
        {
            transform.RotateAround(transform.position, transform.right, -pitchSpeed * Time.deltaTime);
        }
        // 降落
        if (Input.GetKey(KeyCode.W))
        {
            transform.RotateAround(transform.position, transform.right, pitchSpeed * Time.deltaTime);
        }
        // 左转弯
        if (Input.GetKey(KeyCode.Q))
        {
            transform.RotateAround(transform.position, transform.forward, rollSpeed * Time.deltaTime);
            transform.RotateAround(transform.position, Vector3.up, -yawSpeed * Time.deltaTime);
            isAllowCorrection = false;
        }
        // 右转弯
        if (Input.GetKey(KeyCode.E))
        {
            transform.RotateAround(transform.position, transform.forward, -rollSpeed * Time.deltaTime);
            transform.RotateAround(transform.position, Vector3.up, yawSpeed * Time.deltaTime);
            isAllowCorrection = false;
        }
        if (!Input.GetKey(KeyCode.Q) && !Input.GetKey(KeyCode.E))
        {
            isAllowCorrection = true;
        }
        // 修正
        if (isAllowCorrection && (Math.Abs(transform.localEulerAngles.z) >= 0.1))
        {
            float roteValue = (transform.localEulerAngles.z > 0 && transform.localEulerAngles.z < 180) ? -rollSpeed : rollSpeed;
            transform.RotateAround(transform.position, transform.forward, roteValue * 2 * Time.deltaTime);
            if (Math.Abs(transform.localEulerAngles.z) <= 0.2)
            {
                Vector3 vector3 = transform.localEulerAngles;
                vector3.z = 0;
                transform.localEulerAngles = vector3;
            }            
        }

        if (terrainBackGround != null)
        {
            Vector3 vector3 = new Vector3
            {
                x = transform.position.x,
                z = transform.position.z,
                y = -1,
            };
            terrainBackGround.position = vector3;
        }
    }
    public void StartFly()
    {
        isStartFly = !isStartFly;
    }
    public void SpeedChanged(int speedLevel)
    {
        flySpeed = (speedDropdown.value + 1) * speedLevel;
    }
}


2)地形刷新核心功能

重要变量:

/// <summary>
    /// 纹理地形名称
    /// </summary>
    public string terrainName = "";
    /// <summary>
    /// 中心地形(每次通过飞机所在地形改变)
    /// </summary>
    Terrain curCenterTerrain = null;
    /// <summary>
    /// 地形宽度(单位块),目前默认地形的宽和长都为25块地形(可根据实际情况设置)
    /// </summary>
    public byte terrainCellNum = 17;
    /// <summary>
    /// 当前地形池(每次通过curCenterTerrain中心地形的改变而改变)
    /// <地形名称, 地形对象>
    /// </summary>
    Dictionary<string, Terrain> curTerrainsPool = new Dictionary<string, Terrain>();
    /// <summary>
    /// 当前场景地形(后期可以通过外部随时修改)
    /// </summary>
    GameObject curSceneTerrainObj = null;
    /// <summary>
    /// 地形场景对象
    /// </summary>
    TerrainScene terrainSceneObj = null;

加载纹理协程:

/// <summary>
    /// 纹理路径
    /// </summary>
    string terrainPath;
    /// <summary>
    /// 贴材质的标志,true表示结束,false表示未结束
    /// </summary>
    bool pasteTextureFlag = false;
    public static int Value = 0;
    /// <summary>
    /// 纹理贴图协程
    /// </summary>
    /// <returns></returns>
    IEnumerator PasteTexture()
    {
        List<string> terrainsName = new List<string>(curTerrainsPool.Keys);
        // 给所有地形贴图
        foreach (var name in terrainsName)
        {
            yield return null;
            Value++;            
            if (!curTerrainsPool.ContainsKey(name))
            {
                continue;
            }
            Terrain terrain = curTerrainsPool[name];
            // 如果地形为空,则从指定地形的预制体中获取地形
            if (terrain == null && curSceneTerrainObj != null)
            {
                GameObject tmpObj = null;
                for (int i = 0; i < curSceneTerrainObj.transform.childCount; ++i)
                {
                    GameObject obj = curSceneTerrainObj.transform.GetChild(i).gameObject;
                    if (name == obj.name && obj.GetComponent<Terrain>() != null)
                    {
                        tmpObj = Instantiate(obj);
                        tmpObj.name = tmpObj.name.Replace("(Clone)", "");
                        curTerrainsPool[name] = tmpObj.GetComponent<Terrain>();
                        tmpObj.transform.SetParent(transform);
                        Debug.Log("加载数量:" + Value + "总数:" + curTerrainsPool.Count);
                        //Debug.Log("加载数量:" + Value);
                        break;
                    }
                }
                terrain = curTerrainsPool[name];
            }            
            //Material material = terrain.materialTemplate;
            // 判断是否不是当前的纹理或者纹理不存在,则需要重新贴
            if (terrain != null && terrain.materialTemplate == null/* || terrain.name != material.name*/)
            {
                string path = terrainPath + terrain.name + ".png";
                if (File.Exists(path) && terrain.gameObject.activeSelf)
                {
                    // 为地形贴新材质
                    terrain.materialTemplate = LoadTextureFromPath(path);
                    //material = terrain.materialTemplate;
                    //Debug.Log("原纹理:" + terrain.name + "新纹理:" + material.name + "材质:" + material);
                    continue;
                }
            }
        }
        Value = 0;
        pasteTextureFlag = false;
    }

地形实时刷新:

/// <summary>
    /// 根据指定中心的地形刷新地形池(实际下载地形的左下角为(0,0)),而文件名称中的x为列,y表示行,即类似于直角坐标系
    /// </summary>
    /// <param name="terrain">指定中心地形</param>
    void UpdateTerrainsPool(Terrain terrain)
    {
        // 正在贴材质,则不做判断
        if (pasteTextureFlag)
        {
            return;
        }
        curCenterTerrain = terrain;
        // 中心地形所在行列
        string[] arr = curCenterTerrain.name.Replace("terrain_x", "").Replace("_y", ",").Split(',');        
        int columnIndex = int.Parse(arr[0]);
        int rowIndex = int.Parse(arr[1]);
        // 扩展地形的名称列表
        List<string> extensionNames = new List<string>();

        #region 找到地图文件的左右上下开始的纹理索引
        // 左边界开始列索引
        int columnIndex_s = columnIndex - (terrainCellNum - 1) / 2;
        // 右边界结束列索引
        int columnIndex_e = columnIndex + (terrainCellNum - 1) / 2;

        // 下边界开始行索引
        int rowIndex_s = rowIndex - (terrainCellNum - 1) / 2;
        // 上边界开始行索引
        int rowIndex_e = rowIndex + (terrainCellNum - 1) / 2;
        #endregion

        #region 添加地形和删除地形
        // 按列查找到该区域的地形名称(目前只能判断不小于0的边界,但是实际地形的最大确定,所以可以不用处理)
        for (int column = columnIndex_s < 0 ? 0 : columnIndex_s; column <= columnIndex_e; ++column)
        {
            for (int row = rowIndex_s < 0 ? 0 : rowIndex_s; row <= rowIndex_e; ++row)
            {
                extensionNames.Add(string.Format("terrain_x{0}_y{1}", column, row));
            }
        }
        List<string> addNames = new List<string>();
        // 1)添加新地形
        foreach (var name in extensionNames)
        {
            // 如果该地形不存在则添加
            if (!curTerrainsPool.ContainsKey(name))
            {
                curTerrainsPool.Add(name, null);
                addNames.Add(name);
            }
        }
        // 2)删除被删除的地形
        List<string> deletedNames = new List<string>();
        foreach (var name in curTerrainsPool.Keys)
        {
            // 如果该地形不存在则添加到将要删除的地形列表中
            if (!extensionNames.Contains(name))
            {
                deletedNames.Add(name);
            }
        }
        foreach (var name in deletedNames)
        {
            if (!curTerrainsPool.ContainsKey(name) || curTerrainsPool[name] == null)
            {
                continue;
            }
            GameObject obj = curTerrainsPool[name].transform.gameObject;
            // 销毁地形
            Destroy(obj);
            curTerrainsPool.Remove(name);
        }
        #endregion

        // 启动材质刷新协程
        pasteTextureFlag = true;
        StartCoroutine(PasteTexture());
    }

3)最终性能测试

 

几个星期前,我在想,能否使用Unity来进行地形实时变化?比如说,如果发生爆炸,它能否在地形中显示一个气泡?我认为学习更多高级的Unity中的地形特征将会是一个好的项目。 这要比我预想的更具挑战性,因为很难在执行这些操作时获得帧率来保持稳定。使用一些技巧,最终实现了我预想的效果。 示例是这样工作的。脚本随机产生“shell”并很快掉落到地面。每一个shell都依附着一个onTrigger对撞机。如果它和拥有 TerrainDeformer组件的地形相冲突的话,shell将告诉组件它的位置和爆炸的作用力。shell然后实例化一个爆炸(explosion)(由Ben Throop great Detonator framework提供),然后移除它自身。 TerrainDeformer脚本然后将那个位置翻译成相对地形的正确位置,并修改heightmap(地形高度)和alphamap(地形纹理)。一点儿数学知识用来在影响圆周内部查找所有的heightmap和alphamap位置。 被用于在影响范围重绘质地的纹理,是从基于传递到脚本(Terrain Deformation Texture Num)中的数字顺序索引值的地形纹理列表中选择的。在这个例子中,它被设置为1,因此它将使用列表中的第二个纹理来重绘质地。 在创建你自己的地形时,有必要将地形的高度设置为大于0米,从而形成弹坑。我建议深度至少为3米。这可以通过将地形高度设为大于3米的任何值,然后点击Terrain->Flatten Heightmap并输入3米。 出于执行的原因,保持你的Terrain Size为small是非常重要的,以及更重要的是保持Heightmap Resolution为low。在这个例子中,设置为33。 目前版本没有想缺乏地形边缘检测的局限性,并提供多个地形。这个例子表明它不仅能够工作,而且不会有大的性能损失。 感谢Calin创建一个泥土纹理。 为检测示例,<http://blog.almostlogical.com/workingExamples/RealTimeTerrainDeformation/index.html> 为获得源码(unitypackage), 源码要求:Unity 2.6 以及 Detonator Framework。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值