/* * @Author: 白哉 * @Description: 地图生成器 * @Date: 2025年04月01日 星期二 11:04:43 * @Modify: */ using UnityEngine; using System; using System.Collections.Generic; using System.Linq; using RuntimeData; using TH1_Logic.Core; using Unity.VisualScripting; namespace Logic { public class MapGenerator { private Main _main; private MapData _mapData; //海陆层参数,柏林噪音 private const float Scale = 20f; // 噪声的缩放因子 private float _landThreshold; // 陆地的阈值 private float _mountainThreshold = 0.7f; // 山地的阈值 private float[,] _heightMap; private const int SmoothIterations = 3; //平滑次数 private uint _width, _height; private List _tribes; //用来避免treasure连着生成 private List _treasure; private List _innerCityGrid; private List _outerCityGrid; public List PlayerCivOri; // 初始化地图 public MapGenerator(Main main,MapData mapData) { _main = main; _mapData = mapData; } public void GenerateMap(MapData mapData) { _width = mapData.MapConfig.Width; _height = mapData.MapConfig.Height; _heightMap = new float[_width, _height]; if (DebugCenter.Instance.DebugLandThreshold < 0) _landThreshold = new System.Random().Next(200, 400) / 1000f; else _landThreshold = DebugCenter.Instance.DebugLandThreshold; _tribes = new List(); _treasure = new List(); _innerCityGrid = new List(); _outerCityGrid = new List(); //用柏林噪音生成地图高度 GenerateHeightMap(); //平滑地图 SmoothHeightMap(); //归一化处理地图,均匀分布0~1范围 NormalizeHeightMap(); //在连续的大片海洋上生成岛屿 GenerateIsland(); //在连续的陆地上生成湖泊 GenerateLakes(); //生成所有tribe GenerateTribes(); //创建文明摇篮城市(每个玩家的原始首都) GenerateCradleCity(mapData); //初始化所有Grid信息 InitGridMapData(mapData); } //在MapRenderer初始化之后,再进行一些mapgenerator的其他操作 public void GenerateMapAfterMapRenderer(MapData mapData) { //初始化所有玩家的初始视野信息 InitAllPlayerSight(mapData); //初始化Debug的参数 InitDebugInfo(mapData); //初始化所有玩家的特殊领地技能(目前主要是kaguya) InitPlayerSpecialSkill(mapData); } //海陆层使用的地形高度数据 private void GenerateHeightMap() { for (var x = 0; x < _width; x++) { for (var y = 0; y < _height; y++) { float seda = new System.Random().Next(1, 30); float sedb = new System.Random().Next(2, 40); // 使用种子值来改变噪声的生成,保证每次生成的噪声不同 var xCoord = (float)x / _width * Scale / seda; var yCoord = (float)y / _height * Scale / sedb; _heightMap[x, y] = Mathf.PerlinNoise(xCoord, yCoord); //Debug.Log($"sed = {seda}\\{sedb} xC = {xCoord} yC = {yCoord} height = {heightMap[x, y]}"); } } } //海陆层使用的地形平滑工具 private void SmoothHeightMap() { for (var iteration = 0; iteration < SmoothIterations; iteration++) { var newHeightMap = new float[_width, _height]; for (int x = 0; x < _width; x++) { for (int y = 0; y < _height; y++) { // 计算该位置的邻域平均值 var average = GetAverageHeight(x, y); newHeightMap[x, y] = average; } } _heightMap = newHeightMap; } } // 获取该位置的邻域平均高度值 private float GetAverageHeight(int x, int y) { var total = 0f; var count = 0; // 获取周围的邻域高度值 for (var dx = -1; dx <= 1; dx++) { for (var dy = -1; dy <= 1; dy++) { var nx = x + dx; var ny = y + dy; if (nx < 0 || nx >= _width || ny < 0 || ny >= _height) continue; total += _heightMap[nx, ny]; count++; // 确保在有效范围内 } } return total / count; } //归一化处理高度 private void NormalizeHeightMap() { var minHeight = Mathf.Infinity; var maxHeight = -Mathf.Infinity; // 计算最小值和最大值 for (var x = 0; x < _width; x++) { for (int y = 0; y < _height; y++) { if (_heightMap[x, y] < minHeight) minHeight = _heightMap[x, y]; if (_heightMap[x, y] > maxHeight) maxHeight = _heightMap[x, y]; } } // 将高度图归一化到 [0, 1] 范围 for (var x = 0; x < _width; x++) { for (var y = 0; y < _height; y++) { _heightMap[x, y] = (_heightMap[x, y] - minHeight) / (maxHeight - minHeight); } } } //生成湖泊 private void GenerateLakes() { //Step #1 每次寻找一个最高的山,开始制造河流,每次流往附近最低的格子,直到格子周围没有比自己更低的格子,停下。 //每次找的山头,周围2格内不能有水流 List<(Vector2Int pos, float height)> sorth = GetSortedTilesByHeight(_heightMap); //目前先暂定river最多占掉0.05的陆地格子 int riverCount = (int)(sorth.Count * 0.05); for (int i = sorth.Count - 1; i >= 0; i--) if(sorth[i].height <= _landThreshold) { riverCount = (int)((sorth.Count - i) * 0.05); if(i * 2 < _width * _height) riverCount = (int)((sorth.Count - i) * 0.2); break; } for(int i = sorth.Count - 1;i >= 0 && riverCount > 0; i-- ) { //寻找下一个河流源头 if (!CheckGridRiverHead(sorth[i].pos.x, sorth[i].pos.y, 2)) continue; var x = sorth[i].pos.x; var y = sorth[i].pos.y; for (;riverCount > 0;)//p用来保底 { //当前水流x,y,找四个方向最低的 int tx = x, ty = y; if (x - 1 >= 0 && _heightMap[x - 1, y] < _heightMap[tx, ty] && _heightMap[x - 1, y] > _landThreshold) {tx = x - 1; ty = y; } if (x + 1 < _width && _heightMap[x + 1, y] < _heightMap[tx, ty] && _heightMap[x + 1, y] > _landThreshold) {tx = x + 1; ty = y; } if (y - 1 >= 0 && _heightMap[x, y - 1] < _heightMap[tx, ty]&& _heightMap[x, y - 1] > _landThreshold) {tx = x; ty = y - 1; } if (y + 1 < _height && _heightMap[x, y + 1] < _heightMap[tx, ty] && _heightMap[x, y + 1] > _landThreshold) {tx = x; ty = y + 1; } //让当前格子变为水流 _heightMap[x, y] = 0; riverCount--; //如果不能再流 if (tx == x && ty == y) break; x = tx; y = ty; } } } //给生成湖泊用的函数 给格子排序高度 public static List<(Vector2Int pos, float height)> GetSortedTilesByHeight(float[,] heights) { int width = heights.GetLength(0); int height = heights.GetLength(1); // 使用 LINQ 将二维数组展平、转换、排序并生成列表,一行搞定。 return Enumerable.Range(0, width) .SelectMany(x => Enumerable.Range(0, height) .Select(y => (pos: new Vector2Int(x, y), height: heights[x, y]))) .OrderBy(tile => tile.height) .ToList(); } //给生成湖泊用的函数 ,确认一个点周围x范围内有没有水 public bool CheckGridRiverHead(int X,int Y,int r) { for (var x = X - r; x < X + r; x++) for (var y = Y - r; y < Y + r; y++) { if(x < 0 || x >= _width || y < 0 || y >= _height) return false; if (_heightMap[x, y] <= _landThreshold) return false; } return true; } //在连续大片海洋中生成岛屿 private void GenerateIsland() { for (var x = 1; x < _width - 1; x++) for (var y = 1; y < _height - 1; y++) { var t = GenerateTerrain(x, y); var canBeIsland = false; if (t == TerrainType.DeepSea) { canBeIsland = true; var landNearby = 0; for (var i = -2; i <= 2; i++) for (var j = -2; j <= 2; j++) if (x + i >= 0 && x + i < _width && y + j >= 0 && y + j < _height && GenerateTerrain(x + i, y + j) != TerrainType.DeepSea) landNearby++; if (landNearby > 9) canBeIsland = false; } if (canBeIsland && UnityEngine.Random.Range(0, 100) < 50) _heightMap[x, y] = _landThreshold + 1; } } //生成所有tirbes 以及对应的innergrids private void GenerateTribes() { var cant = new bool[_width, _height]; var count = _width * _height; //剩余多少格子能用 var cityNum = (int)(count / 12); Array.Clear(cant, 0, cant.Length); for (var x = 0; x < _width; x++) for (var y = 0; y < _height; y++) if (_heightMap[x, y] <= _landThreshold || x == 0 || x == _width - 1 || y == 0 || y == _height - 1) { cant[x, y] = true; count--; } for (int c = 0; count > 0 && c <= cityNum; c++) { var choose = (int)UnityEngine.Random.Range(1, count); //每次随机一个可用的格子(序号) //Debug.Log("this time " + count + " choose : " + choose); var tmp = 0; for (int x = 0; x < _width; x++) { for (int y = 0; y < _height; y++) { if (cant[x, y]) continue; tmp++; if (tmp != choose) continue; _tribes.Add(new MapPosition(x,y)); for (var xx = -2; xx <= 2; xx++) for (var yy = -2; yy <= 2; yy++) { var nx = x + xx; var ny = y + yy; if (nx < 0 || nx >= _width || ny < 0 || ny >= _height || cant[nx, ny]) continue; cant[nx, ny] = true; // 把新城市方圆2距离的格子都ban了 count--; } break; } if (tmp == choose) break; } if (count <= 0) break; } foreach (var cent in _tribes) { for(int i = cent.X - 1; i<= cent.X + 1 ; i++) for(int j = cent.Y - 1; j<= cent.Y + 1 ; j++) if(i >= 0 && i < _width && j >= 0 && j < _height) _innerCityGrid.Add(new MapPosition(i,j)); } foreach (var cent in _tribes) { for(int i = cent.X - 2; i<= cent.X + 2 ; i++) for(int j = cent.Y - 2; j<= cent.Y + 2 ; j++) if(i >= 0 && i < _width && j >= 0 && j < _height) _outerCityGrid.Add(new MapPosition(i,j)); } //List LandCenters = new List(); //_map.cityCount = map.cityCenters.Count; } private void InitGridMapData(MapData mapData) { //标记innerCity和OnterCity for (int x = 0; x < _width; x++) { for (int y = 0; y < _height; y++) { // 生成文明层 var civId = GenerateCivilization(mapData,x, y); CivEnum civEnum = (CivEnum)(civId + 1); InnerOuterCity innerOuterCity = InnerOuterCity.Edge; if(_innerCityGrid.Contains(new MapPosition(x, y))) innerOuterCity = InnerOuterCity.InnerCity; else if(_outerCityGrid.Contains(new MapPosition(x, y))) innerOuterCity = InnerOuterCity.OuterCity; // 生成海陆层(陆地、浅海、深海) var terrain = GenerateTerrain(x, y,civEnum); // 生成地形层(是否有山) var feature = (terrain == TerrainType.Land) ? GenerateFeature(x, y,civEnum) : TerrainFeature.None; //如果是摇篮城市,terrain设置为road if (mapData.GridMap.GetGridDataByPos(x, y, out var gg) && mapData.GetCityDataByGid(gg.Id,out var cc)) feature = TerrainFeature.Road; // 生成植被层(是否有树木) var vegetation = (feature == TerrainFeature.None && terrain == TerrainType.Land) ? GenerateVegetation(x, y,civEnum) : Vegetation.None; // 生成资源层 var resource = GenerateResource(x, y, terrain, feature, vegetation,civEnum,innerOuterCity); //生成四个角的塔 if (HasTower(x, y)) { feature = TerrainFeature.None; resource = ResourceType.Tower; vegetation = Vegetation.None; } // 设置这个地块的所有层信息 //Debug.Log(map.grid[x,y]); if (mapData.GridMap.GetGridDataByPos(x, y, out GridData g)) g.SetGridData(terrain,feature,vegetation,resource,civId); } } } private bool HasTower(int x, int y) { if (x == 0 && y == 0) return true; if (x == 0 && y == _height - 1) return true; if (x == _width - 1 && y == 0) return true; if (x == _width - 1 && y == _height - 1) return true; return false; } //生成文明摇篮城市(每个玩家的原始首都) private void GenerateCradleCity(MapData mapData) { //制作文明摇篮地点候选列表 var cityIndices = new System.Collections.Generic.List(); foreach (var c in _tribes) { var nearbyLand = 0; var cx = (int)c.PosId / 1000; var cy = (int)c.PosId % 1000; for (int x = cx - 1; x <= cx + 1; x++) for (int y = cy - 1; y <= cy + 1; y++) if (x >= 0 && x < _width && y >= 0 && y < _height && _heightMap[x, y] > _landThreshold) nearbyLand++; if (nearbyLand > 0) cityIndices.Add(new MapPosition(cx,cy)); } if (cityIndices.Count < mapData.MapConfig.PlayerCount) mapData.MapConfig.PlayerCount = (uint)cityIndices.Count; // 从制作列表随机排序并取前m个 PlayerCivOri = cityIndices .OrderBy(x => Guid.NewGuid()) // 使用 Guid 确保随机性 .Take((int)mapData.MapConfig.PlayerCount) .ToList(); //建立玩家档案,建立具体城市,建立初始单位 int rk = 0; foreach (var p in mapData.PlayerMap.PlayerDataList) { GridData g; mapData.GridMap.GetGridDataByPos(PlayerCivOri[rk].X, PlayerCivOri[rk].Y, out g); //建设一个新城市 CityData c = mapData.AddCityData(g.Id, p.Id); //设置该城市为首都 Main.CityLogic.SetCapital(mapData, c.Id); //设置该玩家的文明摇篮城市为该城市 p.CradleCityId = c.Id; //Main.CityLogic.SetCradle(mapData, c.Id); //TODO grid还没初始化好,这里不应该先建立unit的,应该在grid初始化结束后建单位 //建立文明的开国单位,并额外赋予初始行动点 if(mapData.AddUnitData(g.Id,c.Id,new UnitFullType(UnitType.Warrior,GiantType.None,0),out var newUnit)) newUnit.SetFullAPCPMP(); rk++; } } //返回x,y位置的海陆层信息 private TerrainType GenerateTerrain(int x, int y,CivEnum civ = CivEnum.Common,InnerOuterCity innerOuterCity = InnerOuterCity.All) { // 走柏林噪音判断 var heightValue = _heightMap[x, y]; if (heightValue > _landThreshold) return TerrainType.Land; if (x + 1 < _width && _heightMap[x + 1, y] > _landThreshold) return TerrainType.ShallowSea; if (x - 1 > 0 && _heightMap[x - 1, y] > _landThreshold) return TerrainType.ShallowSea; if (y + 1 < _height && _heightMap[x, y + 1] > _landThreshold) return TerrainType.ShallowSea; if (y - 1 > 0 && _heightMap[x, y - 1] > _landThreshold) return TerrainType.ShallowSea; return TerrainType.DeepSea; } //返回x,y位置的地形层信息 private TerrainFeature GenerateFeature(int x, int y,CivEnum civ = CivEnum.Common,InnerOuterCity innerOuterCity = InnerOuterCity.All) { if (_tribes.Contains(new MapPosition(x, y))) return TerrainFeature.None;//如果是城市中心那必没有山 var table = Table.Instance.PlayerDataAssets; return UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(TerrainFeature.Mountain, civ) ? TerrainFeature.Mountain : TerrainFeature.None; } //返回x,y位置的植被层信息 private Vegetation GenerateVegetation(int x, int y,CivEnum civ = CivEnum.Common,InnerOuterCity innerOuterCity = InnerOuterCity.All) { if (_tribes.Contains(new MapPosition(x,y))) return Vegetation.None;//如果是城市中心那必没有树 var table = Table.Instance.PlayerDataAssets; var a = UnityEngine.Random.Range(0f, 1f); var b = table.GetLandformRate(Vegetation.Trees, civ); //Debug.Log($"{x},{y},{civ},{innerOuterCity} : {a}/{b}, Tree"); return a < b ? Vegetation.Trees : Vegetation.None; } //返回x,y位置的资源层信息 private ResourceType GenerateResource(int x, int y, TerrainType terrain, TerrainFeature feature, Vegetation vegetation,CivEnum civ = CivEnum.Common,InnerOuterCity innerOuterCity = InnerOuterCity.All) { if (_tribes.Contains(new MapPosition(x,y))) return ResourceType.CityCenter; var table = Table.Instance.PlayerDataAssets; //Step #1 不再二环以内的,只能考虑遗迹和海星,且不能有其他资源 if (innerOuterCity == InnerOuterCity.Edge) { //先考虑遗迹 if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Treasure, civ, innerOuterCity)) { //如果遗迹附近有别的遗迹,不连着生成 if (_treasure.Any(P => Math.Abs(P.X - x) + Math.Abs(P.Y - y) <= 2)) return ResourceType.None; _treasure.Add(new MapPosition(x,y)); return ResourceType.Treasure; } //再考虑鲸鱼 if (terrain != TerrainType.Land) { if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Starfish, civ, innerOuterCity)) return ResourceType.Starfish; } return ResourceType.None; } //Step #2 如果是海洋 if (terrain != TerrainType.Land) { //先考虑渔业,再考虑鲸鱼,最后考虑Treasure if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Fish, civ, innerOuterCity)) return ResourceType.Fish; if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Starfish, civ, innerOuterCity)) return ResourceType.Starfish; if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Treasure, civ, innerOuterCity)) { //如果遗迹附近有别的遗迹,不连着生成 if (_treasure.Any(P => Math.Abs(P.X - x) + Math.Abs(P.Y - y) <= 2)) return ResourceType.None; _treasure.Add(new MapPosition(x,y)); return ResourceType.Treasure; } return ResourceType.None; } //Step #3 如果是陆地先处理山,再处理树,最后处理Fruit和Crop if (feature == TerrainFeature.Mountain) { var a = UnityEngine.Random.Range(0f, 1f); var b = table.GetLandformRate(ResourceType.Metal, civ, innerOuterCity); //Debug.Log($"{x},{y},{a}/{b},Metal"); //return UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Metal, civ, innerOuterCity) return a < b ? ResourceType.Metal : ResourceType.None; } if (vegetation == Vegetation.Trees) { var a = UnityEngine.Random.Range(0f, 1f); var b = table.GetLandformRate(ResourceType.Animal, civ, innerOuterCity); //Debug.Log($"{x},{y},{a}/{b},Animal"); return a < b ? ResourceType.Animal : ResourceType.None; } if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Fruit, civ, innerOuterCity)) return ResourceType.Fruit; if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Crop, civ, innerOuterCity)) return ResourceType.Crop; if (UnityEngine.Random.Range(0f, 1f) < table.GetLandformRate(ResourceType.Treasure, civ, innerOuterCity)) { //如果遗迹附近有别的遗迹,不连着生成 if (_treasure.Any(P => Math.Abs(P.X - x) + Math.Abs(P.Y - y) <= 2)) return ResourceType.None; _treasure.Add(new MapPosition(x,y)); return ResourceType.Treasure; } return ResourceType.None; } //返回x,y位置的文化层信息 private uint GenerateCivilization(MapData mapData,int x, int y) { // 简单的随机分配一个文明,或者使用更复杂的算法进行分布 var minDis = 2139062143; var civSeed = -1; var tt = 0; //Debug.Log(PlayerCivOri.Count); foreach (var p in PlayerCivOri) { if (Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y) < minDis) { minDis = (int)(Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y)); civSeed = (int)_mapData.PlayerMap.PlayerDataList[tt].PlayerCivId; } else if ((Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y) == minDis) && UnityEngine.Random.Range(0, 2) == 1) { minDis = (int)(Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y)); civSeed = (int)_mapData.PlayerMap.PlayerDataList[tt].PlayerCivId; } else if ((Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y) == minDis + 1) && UnityEngine.Random.Range(0, 3) == 1) { minDis = (int)(Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y)); civSeed = (int)_mapData.PlayerMap.PlayerDataList[tt].PlayerCivId; } tt++; } if (civSeed == -1) return (uint)UnityEngine.Random.Range(1, mapData.MapConfig.PlayerCount + 1); return (uint)civSeed; } //初始化所有玩家的视野信息 private void InitAllPlayerSight(MapData mapData) { foreach (var cityData in mapData.CityMap.CityList) { mapData.GetPlayerDataByCityId(cityData.Id, out var playerData); mapData.GetGridDataByCityId(cityData.Id, out var gridData); Main.PlayerLogic.UpdateSight(mapData, playerData, mapData.GridMap.GetAroundGridIdList(2,gridData,true),0.01f); } } private void InitDebugInfo(MapData mapData) { if (DebugCenter.Instance.DebugSelfPlayerAllSight) { System.Collections.Generic.List tt = new System.Collections.Generic.List(); foreach (var gridData in mapData.GridMap.GridList) tt.Add(gridData.Id); Main.PlayerLogic.UpdateSight(mapData,mapData.PlayerMap.SelfPlayerData,tt); } if (DebugCenter.Instance.DebugAIAllTech) { foreach (var playerData in mapData.PlayerMap.PlayerDataList) { if (playerData == mapData.PlayerMap.SelfPlayerData) continue; playerData.TechTree.LearnTech(TechType.Fishing); playerData.TechTree.LearnTech(TechType.Organization); playerData.TechTree.LearnTech(TechType.Farming); playerData.TechTree.LearnTech(TechType.Mining); playerData.TechTree.LearnTech(TechType.Forestry); playerData.TechTree.LearnTech(TechType.Hunting); playerData.TechTree.LearnTech(TechType.Smithery); playerData.TechTree.LearnTech(TechType.Mathematics); playerData.TechTree.LearnTech(TechType.FreeSpirit); playerData.TechTree.LearnTech(TechType.Chivalry); playerData.TechTree.LearnTech(TechType.Sailing); playerData.TechTree.LearnTech(TechType.Navigation); playerData.TechTree.LearnTech(TechType.Ramming); playerData.TechTree.LearnTech(TechType.Riding); playerData.TechTree.LearnTech(TechType.Archery); playerData.TechTree.LearnTech(TechType.Spiritualism); playerData.TechTree.LearnTech(TechType.Construction); playerData.TechTree.LearnTech(TechType.Meditation); playerData.TechTree.LearnTech(TechType.Aquatism); } } if (DebugCenter.Instance.DebugAIMoreMoney) { foreach (var playerData in mapData.PlayerMap.PlayerDataList) { if (playerData == mapData.PlayerMap.SelfPlayerData) continue; playerData.PlayerWealth += 10; } } } //初始化所有玩家的特殊领地技能(目前主要是kaguya) private void InitPlayerSpecialSkill(MapData mapData) { //遍历每一个玩家,处理每一种可能得specialSkill foreach (var player in mapData.PlayerMap.PlayerDataList) { //首先通过Tech 赋予玩家技能 //处理kaguya专属skill if (player.GetSkill(SkillType.KaguyaFrenchNapoleonicCode, out var _)) { var cityList = new List(); mapData.GetCityDataListByPlayerId(player.Id,cityList); foreach(var city in cityList) Main.PlayerLogic.SetCityTerritoryGridSp(mapData,city,GridSpType.KaguyaGrid); } } } } }