513 lines
20 KiB
C#
513 lines
20 KiB
C#
/*
|
||
* @Author: 白哉
|
||
* @Description: 地图生成器
|
||
* @Date: 2025年04月01日 星期二 11:04:43
|
||
* @Modify:
|
||
*/
|
||
|
||
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using System;
|
||
using System.Linq;
|
||
using RuntimeData;
|
||
using TMPro.Examples;
|
||
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<MapPosition> _tribes;
|
||
public List<MapPosition> 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<MapPosition>();
|
||
|
||
//用柏林噪音生成地图高度
|
||
GenerateHeightMap();
|
||
//平滑地图
|
||
SmoothHeightMap();
|
||
//归一化处理地图,均匀分布0~1范围
|
||
NormalizeHeightMap();
|
||
//在连续的大片海洋上生成岛屿
|
||
GenerateIsland();
|
||
//生成所有tribe
|
||
GenerateTribes();
|
||
//创建文明摇篮城市(每个玩家的原始首都)
|
||
GenerateCradleCity(mapData);
|
||
//初始化所有Grid信息
|
||
InitGridMapData(mapData);
|
||
//初始化所有玩家的初始视野信息
|
||
InitAllPlayerSight(mapData);
|
||
//初始化Debug的参数
|
||
InitDebugInfo(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 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
|
||
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;
|
||
}
|
||
|
||
//List<Vector2Int> LandCenters = new List<Vector2Int>();
|
||
//_map.cityCount = map.cityCenters.Count;
|
||
}
|
||
|
||
private void InitGridMapData(MapData mapData)
|
||
{
|
||
for (int x = 0; x < _width; x++)
|
||
{
|
||
for (int y = 0; y < _height; y++)
|
||
{
|
||
// 生成海陆层(陆地、浅海、深海)
|
||
var terrain = GenerateTerrain(x, y);
|
||
|
||
// 生成地形层(是否有山)
|
||
var feature =
|
||
(terrain == TerrainType.Land) ? GenerateFeature(x, y) : 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)
|
||
: Vegetation.None;
|
||
|
||
// 生成资源层
|
||
var resource = GenerateResource(x, y, terrain, feature, vegetation);
|
||
|
||
//生成四个角的塔
|
||
if (HasTower(x, y))
|
||
{
|
||
feature = TerrainFeature.None;
|
||
resource = ResourceType.Tower;
|
||
vegetation = Vegetation.None;
|
||
}
|
||
|
||
|
||
// 生成文明层
|
||
var civId = GenerateCivilization(mapData,x, y);
|
||
|
||
// 设置这个地块的所有层信息
|
||
//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 List<MapPosition>();
|
||
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);
|
||
//建立文明的开国单位,并额外赋予初始行动点
|
||
Main.UnitLogic.AddMPAPCP(mapData, mapData.AddUnitData(g.Id,c.Id,UnitType.Warrior));
|
||
rk++;
|
||
}
|
||
}
|
||
|
||
//返回x,y位置的海陆层信息
|
||
private TerrainType GenerateTerrain(int x, int y)
|
||
{
|
||
// 走柏林噪音判断
|
||
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)
|
||
{
|
||
if (_tribes.Any(p => p.X == x && p.Y == y))
|
||
return TerrainFeature.None;//如果是城市中心那必没有山
|
||
|
||
var heightValue = _heightMap[x, y];
|
||
if (heightValue > _mountainThreshold)
|
||
return UnityEngine.Random.Range(0, 100) < 30 ? TerrainFeature.Mountain : TerrainFeature.None;
|
||
return UnityEngine.Random.Range(0, 100) < 10 ? TerrainFeature.Mountain : TerrainFeature.None;
|
||
|
||
}
|
||
|
||
//返回x,y位置的植被层信息
|
||
private Vegetation GenerateVegetation(int x, int y)
|
||
{
|
||
if (_tribes.Any(p => p.X == x && p.Y == y))
|
||
return Vegetation.None;//如果是城市中心那必没有树
|
||
return UnityEngine.Random.Range(0, 100) < 30 ? Vegetation.Trees : Vegetation.None;
|
||
}
|
||
|
||
//返回x,y位置的资源层信息
|
||
private ResourceType GenerateResource(int x, int y, TerrainType terrain, TerrainFeature feature, Vegetation vegetation)
|
||
{
|
||
var treasurePossible = true;
|
||
if (_tribes.Any(p => p.X == x && p.Y == y))
|
||
return ResourceType.CityCenter;
|
||
if (_tribes.Any(p => Mathf.Abs(p.X - x) + Mathf.Abs(p.Y - y) <= 2))
|
||
treasurePossible = false;
|
||
if (treasurePossible && UnityEngine.Random.Range(0, 100) < 5)
|
||
return ResourceType.Treasure;
|
||
if (terrain == TerrainType.ShallowSea)
|
||
return UnityEngine.Random.Range(0, 100) < 40 ? ResourceType.Fish : ResourceType.None;
|
||
if (terrain == TerrainType.ShallowSea)
|
||
return UnityEngine.Random.Range(0, 100) < 10 ? ResourceType.Starfish : ResourceType.None;
|
||
if (terrain == TerrainType.DeepSea)
|
||
return UnityEngine.Random.Range(0, 100) < 15 ? ResourceType.Starfish : ResourceType.None;
|
||
if (feature == TerrainFeature.Mountain)
|
||
return UnityEngine.Random.Range(0, 100) < 60 ? ResourceType.Metal : ResourceType.None;
|
||
if (vegetation == Vegetation.Trees)
|
||
return UnityEngine.Random.Range(0, 100) < 30 ? ResourceType.Animal : ResourceType.None;
|
||
if (UnityEngine.Random.Range(0, 100) < 50)
|
||
return UnityEngine.Random.Range(0, 100) < 50 ? ResourceType.Fruit : ResourceType.Crop;
|
||
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.UpdateSightByRadius(mapData, playerData, gridData, 2);
|
||
}
|
||
}
|
||
|
||
private void InitDebugInfo(MapData mapData)
|
||
{
|
||
if (DebugCenter.Instance.DebugSelfPlayerAllSight)
|
||
{
|
||
List<uint> tt = new List<uint>();
|
||
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;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
}
|
||
} |