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

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

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

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

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

效果截图如下:

Unity画三维线 unity 三维地图_unity

Unity画三维线 unity 三维地图_Unity画三维线_02

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

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 三维地图_Unity画三维线_03