TH1/My project/Assets/Scripts/Logic/Map/MapGenerator.cs
2025-06-20 03:21:01 +08:00

513 lines
20 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* @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;
}
}
}
}
}