游戏开发分享

[Godot] C#简单实现2D节点图生成

2025-10-20
2 分钟305 字

我需要做一个随机的2D节点图,用来作为游戏的大地图,对于我这种简单的需求,完全可以在划定地图长宽和最小距离后,直接全随机生成,当然,这样就显得不够“优雅”,在某些情况下还会有空洞或不均匀等问题,这里我就给大家分享一下两个简单的实现方法

实现过程

泊松盘采样

简单来说,泊松盘采样就是通过现有点来生成点集,规范的还会有 网格加速活跃点列表 来进行生成优化,对于我要实现的效果,这些优化暂时是不需要的

特性
  • 从一个随机起点开始,逐步生成新点。
  • 新点基于已有点,随机选择一个方向和距离(在某个范围内),并检查是否满足最小距离约束。
  • 如果新点满足约束(不与其他点太近且在边界内),则加入点集;否则放弃并重新尝试。

代码实现

代码部分

简单泊松盘采样
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);
        }
    }
 
}
简单随机
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);
        }
    }

效果展示

总结

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

许可协议: CC BY-SA 4.0 。转载请注明出处,允许商用;改编/转载须以相同许可(CC BY-SA 4.0)发布。如有问题请联系我。