我需要做一个随机的2D节点图,用来作为游戏的大地图,对于我这种简单的需求,完全可以在划定地图长宽和最小距离后,直接全随机生成,当然,这样就显得不够"优雅",在某些情况下还会有空洞或不均匀等问题,这里我就给大家分享一下两个简单的实现方法
实现过程
泊松盘采样
简单来说,泊松盘采样就是通过现有点来生成点集,规范的还会有网格加速 、活跃点列表来进行生成优化,对于我要实现的效果,这些优化暂时是不需要的
特性
-
从一个随机起点开始,逐步生成新点。
-
新点基于已有点,随机选择一个方向和距离(在某个范围内),并检查是否满足最小距离约束。
-
如果新点满足约束(不与其他点太近且在边界内),则加入点集;否则放弃并重新尝试。
代码实现
代码部分
简单泊松盘采样
cs
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
public partial class PointGenerate : Node2D
{
//大地图点生成
[Export] public int mapX; //长
[Export] public int mapY; //宽
[Export] public int pointCount; //数量
[Export] public int tryCount; //尝试次数
[Export] public float mixLength; //最小距离
private List<PointScript> points = new(); //地图节点集
[ExportCategory("Node")]
[Export] public PackedScene testPoint; //测试节点
[Export] public Node2D mapPoints; //挂载节点
public override void _Ready()
{
PointMap();
}
public override void _Process(double delta)
{
if (Input.IsKeyPressed(Key.Space)) //为了方便调试用空格生成
PointMap();
}
private void PointMap() //点生成
{
mapPoints.GetChildren().ToList().ForEach(child => child.QueueFree());
points.Clear();
var firstPoint = testPoint.Instantiate() as PointScript;
float pointX = GD.RandRange(0, mapX);
float pointY = GD.RandRange(0, mapY);
firstPoint.Position = new Vector2(pointX, pointY);
points.Add(firstPoint);
mapPoints.AddChild(firstPoint);
int attempts = 0;
while (points.Count < pointCount && attempts < tryCount * points.Count){
attempts++;
var basePoint = points[GD.RandRange(0, points.Count - 1)];
float angle = GD.Randf() * MathF.Tau;
float dist = (float)GD.RandRange(mixLength, mixLength * 2f);
Vector2 newPoint = basePoint.Position + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * dist;
//边界检查
if (newPoint.X < 0 || newPoint.Y < 0 || newPoint.X > mapX || newPoint.Y > mapY)
continue;
//点距离检查
bool isDist = false;
foreach (var p in points)
{
if (p.Position.DistanceTo(newPoint) < mixLength)
{
isDist = true;
break;
}
}
if (isDist)
continue;
var point = testPoint.Instantiate() as PointScript;
point.Position = newPoint;
points.Add(point);
mapPoints.AddChild(point);
}
}
}
简单随机
cs
private void PointMap()
{
mapPoints.GetChildren().ToList().ForEach(child => child.QueueFree());
points.Clear();
int attempts = 0;
while (points.Count < pointCount && attempts < tryCount)
{
attempts++;
float pointX = GD.RandRange(0, mapX);
float pointY = GD.RandRange(0, mapY);
Vector2 newPoint = new Vector2(pointX, pointY);
//点距离检查
bool isDist = false;
foreach (var p in points)
{
if (p.Position.DistanceTo(newPoint) < mixLength)
{
isDist = true;
break;
}
}
if (isDist)
continue;
var point = testPoint.Instantiate() as PointScript;
point.Position = newPoint;
points.Add(point);
mapPoints.AddChild(point);
}
}
效果展示

总结
还有很多问题,比如形状和分布问题,还可能会有点生成缺失问题(可以将边界检查去掉),大家可以根据自己的需要进行修改