三维地形下载资源使用了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)最终性能测试