主要类
MapManger(面板填写参数)
启动时初始化地图生成器
// 地图尺寸
public int mapSize; // 一行或者一列有多少个地图块
public int mapChunkSize; // 一个地图块有多少个格子
public float cellSize; // 一个格子多少米
// 地图的随机参数
public float noiseLacunarity; // 噪音间隙
public int mapSeed; // 地图种子
public int spawnSeed; // 随时地图对象的种子
public float marshLimit; // 沼泽的边界
// 地图的美术资源
public Material mapMaterial;
public Texture2D forestTexutre;
public Texture2D[] marshTextures;
public MapConfig mapConfig; //地图配置
private MapGenerator mapGenerator;
public int viewDinstance; // 玩家可视距离,单位是Chunk
public Transform viewer; // 观察者
private Vector3 lastViewerPos = Vector3.one * -1;
public Dictionary<Vector2Int, MapChunkController> mapChunkDic; // 全部已有的地图块
public float updateChunkTime = 1f;
private bool canUpdateChunk = true;
private float chunkSizeOnWord; // 在世界中实际的地图块尺寸 单位米
private List<MapChunkController> lastVisibleChunkList = new List<MapChunkController>();
// 某个类型可以生成那些配置的ID
private Dictionary<MapVertexType, List<int>> spawnConfigDic;
void Start()
{
// 确定配置
Dictionary<int, ConfigBase> tempDic = ConfigManager.Instance.GetConfigs(ConfigName.MapObject);
spawnConfigDic = new Dictionary<MapVertexType, List<int>>();
spawnConfigDic.Add(MapVertexType.Forest, new List<int>());
spawnConfigDic.Add(MapVertexType.Marsh, new List<int>());
foreach (var item in tempDic)
{
MapVertexType mapVertexType = (item.Value as MapObjectConfig).MapVertexType;
spawnConfigDic[mapVertexType].Add(item.Key);
}
// 初始化地图生成器
mapGenerator = new MapGenerator(mapSize, mapChunkSize, cellSize,noiseLacunarity,mapSeed,spawnSeed,marshLimit,mapMaterial,forestTexutre,marshTextures, spawnConfigDic) ;
mapGenerator.GenerateMapData();
mapChunkDic = new Dictionary<Vector2Int, MapChunkController>();
chunkSizeOnWord = mapChunkSize * cellSize;
}
MapGenerator
/// <summary>
/// 生成地图数据,主要是所有地图块都通用的数据
/// </summary>
public MapGenerator(int mapSize, int mapChunkSize, float cellSize, float noiseLacunarity, int mapSeed, int spawnSeed, float marshLimit, Material mapMaterial, Texture2D forestTexutre, Texture2D[] marshTextures, Dictionary<MapVertexType, List<int>> spawnConfigDic)
{
this.mapSize = mapSize;
this.mapChunkSize = mapChunkSize;
this.cellSize = cellSize;
this.noiseLacunarity = noiseLacunarity;
this.mapSeed = mapSeed;
this.spawnSeed = spawnSeed;
this.marshLimit = marshLimit;
this.mapMaterial = mapMaterial;
this.forestTexutre = forestTexutre;
this.marshTextures = marshTextures;
this.spawnConfigDic = spawnConfigDic;
}
public void GenerateMapData()
{
Random.InitState(mapSeed);
// 生成噪声图
float[,] noiseMap = GenerateNoiseMap(mapSize * mapChunkSize, mapSize * mapChunkSize, noiseLacunarity);
// 生成网格数据
mapGrid = new MapGrid(mapSize * mapChunkSize, mapSize * mapChunkSize, cellSize);
// 确定网格 格子的贴图索引
mapGrid.CalculateMapVertexType(noiseMap, marshLimit);
// 初始化默认材质的尺寸
mapMaterial.mainTexture = forestTexutre;
mapMaterial.SetTextureScale("_MainTex", new Vector2(cellSize * mapChunkSize, cellSize * mapChunkSize));
// 实例化一个沼泽材质
marshMaterial = new Material(mapMaterial);
marshMaterial.SetTextureScale("_MainTex", Vector2.one);
chunkMesh = GenerateMapMesh(mapChunkSize, mapChunkSize, cellSize);
Random.InitState(spawnSeed);
List<int> temps = spawnConfigDic[MapVertexType.Forest];
for (int i = 0; i < temps.Count; i++) forestSpawanWeightTotal += ConfigManager.Instance.GetConfig<MapObjectConfig>(ConfigName.MapObject, temps[i]).Probability;
temps = spawnConfigDic[MapVertexType.Marsh];
for (int i = 0; i < temps.Count; i++) marshSpawanWeightTotal += ConfigManager.Instance.GetConfig<MapObjectConfig>(ConfigName.MapObject, temps[i]).Probability;
}
生成噪声图(MapGenerator中)
float[,] noiseMap = GenerateNoiseMap(mapSize * mapChunkSize, mapSize * mapChunkSize, noiseLacunarity);
/// <summary>
/// 生成噪声图
/// </summary>
private float[,] GenerateNoiseMap(int width, int height, float lacunarity)
{
lacunarity += 0.1f;
// 这里的噪声图是为了顶点服务的
float[,] noiseMap = new float[width-1,height-1];
float offsetX = Random.Range(-10000f, 10000f);
float offsetY = Random.Range(-10000f, 10000f);
for (int x = 0; x < width - 1; x++)
{
for (int y = 0; y < height - 1; y++)
{
noiseMap[x,y] = Mathf.PerlinNoise(x * lacunarity + offsetX,y * lacunarity + offsetY);
}
}
return noiseMap;
}
生成网格(MapGrid中)
mapGrid = new MapGrid(mapSize * mapChunkSize, mapSize * mapChunkSize, cellSize);
// 顶点数据
public Dictionary<Vector2Int, MapVertex> vertexDic = new Dictionary<Vector2Int, MapVertex>();
// 格子数据
public Dictionary<Vector2Int, MapCell> cellDic = new Dictionary<Vector2Int, MapCell>();
public MapGrid(int mapHeight, int mapWdith, float cellSize)
{
MapHeight = mapHeight;
MapWidth = mapWdith;
CellSize = cellSize;
// 生成顶点数据
for (int x = 1; x < mapWdith; x++)
{
for (int z = 1; z < mapHeight; z++)
{
AddVertex(x, z);
AddCell(x, z);
}
}
// 增加一行一列
for (int x = 1; x <= mapWdith; x++)
{
AddCell(x, mapHeight);
}
for (int z = 1; z < mapWdith; z++)
{
AddCell(mapWdith, z);
}
private void AddVertex(int x, int y)
{
vertexDic.Add(new Vector2Int(x, y)
, new MapVertex()
{
Position = new Vector3(x * CellSize, 0, y * CellSize)
});
}
private void AddCell(int x, int y)
{
float offset = CellSize / 2;
cellDic.Add(new Vector2Int(x, y),
new MapCell()
{
Position = new Vector3(x * CellSize - offset, 0, y * CellSize - offset)
});
}
}
随后计算贴图索引(MapGrid中)
mapGrid.CalculateMapVertexType(noiseMap, marshLimit);
/// <summary>
/// 计算格子贴图的索引数字
/// </summary>
public void CalculateMapVertexType(float[,] noiseMap,float limit)
{
int width = noiseMap.GetLength(0);
int height = noiseMap.GetLength(1);
for (int x = 1; x < width; x++)
{
for (int z = 1; z < height; z++)
{
// 基于噪声中的值确定这个顶点的类型
// 大于边界是沼泽,否则是森林
if (noiseMap[x,z] >=limit)
{
SetVertexType(x, z, MapVertexType.Marsh);
}
else
{
SetVertexType(x, z, MapVertexType.Forest);
}
}
}
/// <summary>
/// 设置顶点类型,通过计算顶点接触的四个面
/// </summary>
private void SetVertexType(Vector2Int vertexIndex, MapVertexType mapVertexType)
{
MapVertex vertex = GetVertex(vertexIndex);
if (vertex.VertexType != mapVertexType)
{
vertex.VertexType = mapVertexType;
// 只有沼泽需要计算
if (vertex.VertexType == MapVertexType.Marsh)
{
// 计算附近的贴图权重
MapCell tempCell = GetLeftBottomMapCell(vertexIndex);
if (tempCell != null) tempCell.TextureIndex += 1;
tempCell = GetRightBottomMapCell(vertexIndex);
if (tempCell != null) tempCell.TextureIndex += 2;
tempCell = GetLeftTopMapCell(vertexIndex);
if (tempCell != null) tempCell.TextureIndex += 4;
tempCell = GetRightTopMapCell(vertexIndex);
if (tempCell != null) tempCell.TextureIndex += 8;
}
}
}
/// <summary>
/// 获取格子,如果没有找到会返回Null
/// </summary>
public MapCell GetCell(Vector2Int index)
{
MapCell cell = null;
cellDic.TryGetValue(index, out cell);
return cell;
}
public MapCell GetCell(int x,int y)
{
return GetCell(new Vector2Int(x,y));
}
/// <summary>
/// 获取左下角格子
/// </summary>
public MapCell GetLeftBottomMapCell(Vector2Int vertexIndex)
{
return GetCell(vertexIndex);
}
/// <summary>
/// 获取右下角格子
/// </summary>
public MapCell GetRightBottomMapCell(Vector2Int vertexIndex)
{
return GetCell(vertexIndex.x+1,vertexIndex.y);
}
/// <summary>
/// 获取左上角格子
/// </summary>
public MapCell GetLeftTopMapCell(Vector2Int vertexIndex)
{
return GetCell(vertexIndex.x, vertexIndex.y+1);
}
/// <summary>
/// 获取右上角格子
/// </summary>
public MapCell GetRightTopMapCell(Vector2Int vertexIndex)
{
return GetCell(vertexIndex.x+1, vertexIndex.y + 1);
}
/// <summary>
/// 设置顶点类型
/// </summary>
private void SetVertexType(int x,int y,MapVertexType mapVertexType)
{
SetVertexType(new Vector2Int(x, y), mapVertexType);
}
}
随后生成材质球
mapMaterial.mainTexture = forestTexutre;
mapMaterial.SetTextureScale("_MainTex", new Vector2(cellSize * mapChunkSize, cellSize * mapChunkSize));
// 实例化一个沼泽材质
marshMaterial = new Material(mapMaterial);
marshMaterial.SetTextureScale("_MainTex", Vector2.one);
chunkMesh = GenerateMapMesh(mapChunkSize, mapChunkSize, cellSize);
/// <summary>
/// 生成地形Mesh
/// </summary>
private Mesh GenerateMapMesh(int height,int wdith, float cellSize)
{
Mesh mesh = new Mesh();
// 确定顶点在哪里
mesh.vertices = new Vector3[]
{
new Vector3(0,0,0),
new Vector3(0,0,height*cellSize),
new Vector3(wdith*cellSize,0,height*cellSize),
new Vector3(wdith*cellSize,0,0),
};
// 确定哪些点形成三角形
mesh.triangles = new int[]
{
0,1,2,
0,2,3
};
mesh.uv = new Vector2[]
{
new Vector3(0,0),
new Vector3(0,1),
new Vector3(1,1),
new Vector3(1,0),
};
// 计算法线
mesh.RecalculateNormals();
return mesh;
}
MapObjectConfig (这个类是用来存储地图物品预制体的)
[CreateAssetMenu(fileName = "地图物体配置", menuName = "Config/地图物体配置")]
public class MapObjectConfig : ConfigBase
{
[LabelText("空的 不生成物品")]
public bool IsEmpty = false;
[LabelText("所在的地图顶点类型")]
public MapVertexType MapVertexType;
[LabelText("生成的预制体")]
public GameObject Prefab;
[LabelText("生成概率 权重类型")]
public int Probability;
}
玩家移动位置(MapManger)
void Update()
{
UpdateVisibleChunk();
}
// 根据观察者的位置来刷新那些地图块可以看到
private void UpdateVisibleChunk()
{
// 如果观察者没有移动过,不需要刷新
if (viewer.position == lastViewerPos) return;
// 如果时间没到 不允许更新
if (canUpdateChunk == false) return;
// 当前观察者所在的地图快,
Vector2Int currChunkIndex = GetMapChunkIndexByWorldPosition(viewer.position);
// 关闭全部不需要显示的地图块
for (int i = lastVisibleChunkList.Count-1; i >= 0; i--)
{
Vector2Int chunkIndex = lastVisibleChunkList[i].ChunkIndex;
if (Mathf.Abs(chunkIndex.x - currChunkIndex.x)>viewDinstance
|| Mathf.Abs(chunkIndex.y - currChunkIndex.y)>viewDinstance)
{
lastVisibleChunkList[i].SetActive(false);
lastVisibleChunkList.RemoveAt(i);
}
}
int startX = currChunkIndex.x - viewDinstance;
int startY = currChunkIndex.y - viewDinstance;
// 开启需要显示的地图块
for (int x = 0; x < 2*viewDinstance+1; x++)
{
for (int y = 0; y < 2 * viewDinstance + 1; y++)
{
canUpdateChunk = false;
Invoke("RestCanUpdateChunkFlag", updateChunkTime);
Vector2Int chunkIndex = new Vector2Int(startX + x, startY + y);
// 之前加载过
if (mapChunkDic.TryGetValue(chunkIndex,out MapChunkController chunk))
{
// 这个地图是不是已经在显示列表
if (lastVisibleChunkList.Contains(chunk) == false)
{
lastVisibleChunkList.Add(chunk);
chunk.SetActive(true);
}
}
// 之前没有加载
else
{
chunk = GenerateMapChunk(chunkIndex);
if (chunk!=null)
{
chunk.SetActive(true);
lastVisibleChunkList.Add(chunk);
}
}
}
}
}
// 之前没有加载
else{
chunk = GenerateMapChunk(chunkIndex);
if (chunk!=null)
{
chunk.SetActive(true);
lastVisibleChunkList.Add(chunk);
}
}
private MapChunkController GenerateMapChunk(Vector2Int index)
{
// 检查坐标的合法性
if (index.x > mapSize-1 || index.y > mapSize-1) return null;
if (index.x < 0 || index.y < 0) return null;
MapChunkController chunk = mapGenerator.GenerateMapChunk(index, transform);
mapChunkDic.Add(index, chunk);
return chunk;
}
/// <summary>
/// 生成地图块
/// </summary>
public MapChunkController GenerateMapChunk(Vector2Int chunkIndex,Transform parent)
{
// 生成地图块物体
GameObject mapChunkObj = new GameObject("Chunk_" + chunkIndex.ToString());
MapChunkController mapChunk=mapChunkObj.AddComponent<MapChunkController>();
// 生成Mesh
mapChunkObj.AddComponent<MeshFilter>().mesh = chunkMesh;
// 添加碰撞体
mapChunkObj.AddComponent<MeshCollider>();
// 生成地图块的贴图
Texture2D mapTexture;
this.StartCoroutine
(
GenerateMapTexture(chunkIndex, (tex, isAllForest) => {
// 如果完全是森林,没必要在实例化一个材质球
if (isAllForest)
{
mapChunkObj.AddComponent<MeshRenderer>().sharedMaterial = mapMaterial;
}
else
{
mapTexture = tex;
Material material = new Material(marshMaterial);
material.mainTexture = tex;
mapChunkObj.AddComponent<MeshRenderer>().material = material;
}
}));
// 确定坐标
Vector3 position = new Vector3(chunkIndex.x * mapChunkSize * cellSize, 0, chunkIndex.y * mapChunkSize * cellSize);
mapChunk.transform.position = position;
mapChunkObj.transform.SetParent(parent);
// 生成场景物体数据
List<MapChunkMapObjectModel> mapObjectModelList = SpawnMapObject(chunkIndex);
mapChunk.Init(chunkIndex,position + new Vector3((mapChunkSize * cellSize) / 2, 0, (mapChunkSize * cellSize) / 2), mapObjectModelList);
return mapChunk;
}
// 生成地图块的贴图
Texture2D mapTexture;
this.StartCoroutine
(
GenerateMapTexture(chunkIndex, (tex, isAllForest) => {
// 如果完全是森林,没必要在实例化一个材质球
if (isAllForest)
{
mapChunkObj.AddComponent().sharedMaterial = mapMaterial;
}
else
{
mapTexture = tex;
Material material = new Material(marshMaterial);
material.mainTexture = tex;
mapChunkObj.AddComponent().material = material;
}
}));
/// <summary>
/// 分帧 生成地图贴图
/// 如果这个地图块完全是森林,直接返回森林贴图
/// </summary>
private IEnumerator GenerateMapTexture(Vector2Int chunkIndex,System.Action<Texture2D,bool> callBack)
{
// 当前地块的偏移量 找到这个地图块具体的每一个格子
int cellOffsetX = chunkIndex.x * mapChunkSize + 1;
int cellOffsetY = chunkIndex.y * mapChunkSize + 1;
// 是不是一张完整的森林地图块
bool isAllForest = true;
// 检查是否只有森林类型的格子
for (int y = 0; y < mapChunkSize; y++)
{
if (isAllForest == false) break;
for (int x = 0; x < mapChunkSize; x++)
{
MapCell cell = mapGrid.GetCell(x + cellOffsetX, y + cellOffsetY);
if (cell != null && cell.TextureIndex !=0)
{
isAllForest = false;
break;
}
}
}
Texture2D mapTexture = null;
// 有沼泽的情况
if (!isAllForest)
{
// 贴图都是矩形
int textureCellSize = forestTexutre.width;
// 整个地图块的宽高,正方形
int textureSize = mapChunkSize * textureCellSize;
mapTexture = new Texture2D(textureSize, textureSize, TextureFormat.RGB24, false);
// 遍历每一个格子
for (int y = 0; y < mapChunkSize; y++)
{
// 一帧只执行一列 只绘制一列的像素
yield return null;
// 像素偏移量
int pixelOffsetY = y * textureCellSize;
for (int x = 0; x < mapChunkSize; x++)
{
int pixelOffsetX = x * textureCellSize;
int textureIndex = mapGrid.GetCell(x + cellOffsetX, y + cellOffsetY).TextureIndex - 1;
// 绘制每一个格子内的像素
// 访问每一个像素点
for (int y1 = 0; y1 < textureCellSize; y1++)
{
for (int x1 = 0; x1 < textureCellSize; x1++)
{
// 设置某个像素点的颜色
// 确定是森林还是沼泽
// 这个地方是森林 ||
// 这个地方是沼泽但是是透明的,这种情况需要绘制groundTexture同位置的像素颜色
if (textureIndex < 0)
{
Color color = forestTexutre.GetPixel(x1, y1);
mapTexture.SetPixel(x1 + pixelOffsetX, y1 + pixelOffsetY, color);
}
else
{
// 是沼泽贴图的颜色
Color color = marshTextures[textureIndex].GetPixel(x1, y1);
if (color.a < 1f)
{
mapTexture.SetPixel(x1 + pixelOffsetX, y1 + pixelOffsetY, forestTexutre.GetPixel(x1, y1));
}
else
{
mapTexture.SetPixel(x1 + pixelOffsetX, y1 + pixelOffsetY, color);
}
}
}
}
}
}
mapTexture.filterMode = FilterMode.Point;
mapTexture.wrapMode = TextureWrapMode.Clamp;
mapTexture.Apply();
}
callBack?.Invoke(mapTexture,isAllForest);
}
// 生成场景物体数据
List mapObjectModelList = SpawnMapObject(chunkIndex);
/// <summary>
/// 生成各种地图对象
/// </summary>
private List<MapChunkMapObjectModel> SpawnMapObject(Vector2Int chunkIndex)
{
List<MapChunkMapObjectModel> mapChunkObjectList = new List<MapChunkMapObjectModel>();
int offsetX = chunkIndex.x * mapChunkSize;
int offsetY = chunkIndex.y * mapChunkSize;
// 遍历地图顶点
for (int x = 1; x < mapChunkSize; x++)
{
for (int y = 1; y < mapChunkSize; y++)
{
MapVertex mapVertex = mapGrid.GetVertex(x + offsetX, y + offsetY);
// 根据概率配置随机
List<int> configIDs = spawnConfigDic[mapVertex.VertexType];
// 确定权重的总和
int weightTotal = mapVertex.VertexType == MapVertexType.Forest?forestSpawanWeightTotal:marshSpawanWeightTotal;
int randValue = Random.Range(1, weightTotal+1); // 实际命中数字是从1~weightTotal
float temp = 0;
int spawnConfigIndex = 0; // 最终要生成的物品
for (int i = 0; i < configIDs.Count; i++)
{
temp +=ConfigManager.Instance.GetConfig<MapObjectConfig>(ConfigName.MapObject, configIDs[i]).Probability;
if (randValue < temp)
{
// 命中
spawnConfigIndex = i;
break;
}
}
int configID = configIDs[spawnConfigIndex];
// 确定到底生成什么物品
MapObjectConfig spawnModel = ConfigManager.Instance.GetConfig<MapObjectConfig>(ConfigName.MapObject, configID);
if (spawnModel.IsEmpty == false)
{
Vector3 position = mapVertex.Position + new Vector3(Random.Range(-cellSize / 2, cellSize / 2), 0, Random.Range(-cellSize / 2, cellSize / 2));
mapChunkObjectList.Add(
new MapChunkMapObjectModel() { Prefab = spawnModel.Prefab, Position = position }
);
}
}
}
return mapChunkObjectList;
}
mapChunk.Init(chunkIndex,position + new Vector3((mapChunkSize * cellSize) / 2, 0, (mapChunkSize * cellSize) / 2), mapObjectModelList);
public class MapChunkData
{
public List<MapChunkMapObjectModel> MapObjectList = new List<MapChunkMapObjectModel>();
}
public class MapChunkMapObjectModel
{
public GameObject Prefab;
public Vector3 Position;
}
public class MapChunkController : MonoBehaviour
{
public Vector2Int ChunkIndex { get; private set; }
public Vector3 CentrePosition { get; private set; }
private MapChunkData mapChunkData;
private List<GameObject> mapObjectList;
private bool isActive = false;
public void Init(Vector2Int chunkIndex, Vector3 centrePosition, List<MapChunkMapObjectModel> MapObjectList)
{
ChunkIndex = chunkIndex;
CentrePosition = centrePosition;
mapChunkData = new MapChunkData();
mapChunkData.MapObjectList = MapObjectList;
mapObjectList = new List<GameObject>(MapObjectList.Count);
}
}
回到MapManger
if (chunk!=null)
{
chunk.SetActive(true);
lastVisibleChunkList.Add(chunk);
}
//其中lastVisibleChunkList
private List<MapChunkController> lastVisibleChunkList = new List<MapChunkController>();
public void SetActive(bool active)
{
if (isActive!=active)
{
isActive = active;
gameObject.SetActive(isActive);
List<MapChunkMapObjectModel> ObjectList = mapChunkData.MapObjectList;
// 从对象池中获取所有物体
if (isActive)
{
for (int i = 0; i < ObjectList.Count; i++)
{
GameObject go = PoolManager.Instance.GetGameObject(ObjectList[i].Prefab, transform);
go.transform.position = ObjectList[i].Position;
mapObjectList.Add(go);
}
}
// 把所有物体放回对象池
else
{
for (int i = 0; i < ObjectList.Count; i++)
{
PoolManager.Instance.PushGameObject(mapObjectList[i]);
}
mapObjectList.Clear();
}
}
}
MapChunkMapObjectModel 存放了 地图物品预制体,三维位置