Skip to content

Asset Tool

雨世界网格地图生成器

之前做来放进网页里当待机动画的素材的,和原作的其实不是很相似,主打一个神似。

有两个版本,一个跑很快,但没有噪点,可以用运气对冲随机性,之后再用csp处理;另一个用分形噪音做了噪点,跑很慢,而且效果没有好到哪里去。

快速版

点击这里查看 Python 源码
import random
import math
from PIL import Image, ImageDraw, ImageColor
from opensimplex import OpenSimplex
import numpy as np

## --- 1. 可控参数 (您可以随意调整这些参数) ---

## --- 画布参数 ---
WIDTH = 1560
HEIGHT = 1192
COLOR_BG = (0, 0, 0, 0)  # 透明背景 (R,G,B,A)

## --- (新) 随机网格集群参数 (第 2 层) ---
## <--- 已重构: 这是"随机放置"的集群, 它们可以重叠 ---
NUM_GRID_CLUSTERS = 15  # <--- 总共生成 10 个"组" (值越大, 越密集且重叠越多)
CLUSTER_BLOCK_WIDTH_RANGE = (150, 1200)  # "组"的宽度范围
CLUSTER_BLOCK_HEIGHT_RANGE = (400, 900)  # "组"的高度范围
CLUSTER_TILT_CHANCE = 0.8  # <--- 70% 的"组"会发生倾斜
CLUSTER_TILT_ANGLE_RANGE = (-30, 45)  # "组"的倾斜角度范围

## --- (新) "组"内部的网格线参数 (第 2.5 层) ---
INTERNAL_GRID_MIN_RECT_SIZE = 40  # <--- "组"内部的最小网格大小
INTERNAL_GRID_SPLIT_CHANCE = 0.85
INTERNAL_GRID_SPLIT_RANGE = (0.05, 0.9)

## --- 网格线外观参数 (已恢复) ---
COLOR_GRID = (29,255, 162, 200)  # 网格颜色
GRID_LINE_WIDTH_RANGE = (1, 4)
GRID_BROKEN_DASH_RANGE = (30, 60)  # "破损线"中实线部分的长度
GRID_BROKEN_GAP_RANGE = (3, 10)  # "破损线"中缺口部分的长度

## --- 环境噪点参数 (第3层) ---
NOISE_DETAIL_SCALE = 50.0
NOISE_MASK_SCALE = 250.0
NOISE_MASK_CONTRAST = 2
NOISE_SPARSITY = 3
NOISE_THRESHOLD_1 = 0.55
NOISE_THRESHOLD_2 = 0.45
COLOR_NOISE_1 = (120, 0, 120)
COLOR_NOISE_2 = (200, 0, 50)

## --- POI 兴趣点参数 (第4层) ---
POI_NUM_CLUSTERS = 3
POI_PER_CLUSTER = (3, 7)
POI_CLUSTER_RADIUS = 45.0
POI_SIZE = 24
COLOR_POI_FILL = (255, 0, 0)
COLOR_POI_BORDER = (255, 100, 100)


## --- 2. 辅助函数:点旋转 (与之前相同) ---
def rotate_point(px, py, cx, cy, angle_deg):
    angle_rad = math.radians(angle_deg)
    s = math.sin(angle_rad)
    c = math.cos(angle_rad)
    px_c = px - cx;
    py_c = py - cy
    x_new = px_c * c - py_c * s;
    y_new = px_c * s + py_c * c
    return (x_new + cx, y_new + cy)


## --- 3. 辅助函数:绘制破损线段 (与之前相同) ---
def draw_broken_line(draw, p1, p2, fill, width, dash_range, gap_range):
    dx = p2[0] - p1[0];
    dy = p2[1] - p1[1]
    length = math.hypot(dx, dy)
    if length == 0: return
    dx /= length;
    dy /= length
    current_pos = 0.0
    while current_pos < length:
        dash_len = random.uniform(*dash_range)
        gap_len = random.uniform(*gap_range)
        start_draw = current_pos
        end_draw = min(current_pos + dash_len, length)
        if end_draw > start_draw:
            start_point = (p1[0] + dx * start_draw, p1[1] + dy * start_draw)
            end_point = (p1[0] + dx * end_draw, p1[1] + dy * end_draw)
            draw.line([start_point, end_point], fill=fill, width=width)
        current_pos += dash_len + gap_len


## --- 4. 核心网格生成器 (与之前相同) ---
def _generate_recursive_lines_list(x, y, w, h, min_size, split_chance, split_range):
    lines_list = []
    if (w < min_size or h < min_size) and (w < min_size and h < min_size): return []
    if random.random() > split_chance: return []
    split_vertical = (w > h)
    if w == h: split_vertical = random.choice([True, False])
    if split_vertical:
        split_x = x + int(random.uniform(*split_range) * w)
        lines_list.append(((split_x, y), (split_x, y + h)))
        lines_list.extend(_generate_recursive_lines_list(x, y, split_x - x, h, min_size, split_chance, split_range))
        lines_list.extend(
            _generate_recursive_lines_list(split_x + 1, y, w - (split_x - x) - 1, h, min_size, split_chance,
                                           split_range))
    else:
        split_y = y + int(random.uniform(*split_range) * h)
        lines_list.append(((x, split_y), (x + w, split_y)))
        lines_list.extend(_generate_recursive_lines_list(x, y, w, split_y - y, min_size, split_chance, split_range))
        lines_list.extend(
            _generate_recursive_lines_list(x, split_y + 1, w, h - (split_y - y) - 1, min_size, split_chance,
                                           split_range))
    return lines_list


## --- 5. (新) 绘制随机网格集群 (可重叠) ---
def draw_grid_clusters(draw):
    """
    在画布上随机放置 N 个网格集群, 它们可以重叠
    """
    print(f"Drawing {NUM_GRID_CLUSTERS} grid clusters (overlapping is allowed)...")

    for _ in range(NUM_GRID_CLUSTERS):

        # 1. 随机选择集群中心、大小和角度
        # (中心点可以在画布的任何地方)
        center_x = random.randint(0, WIDTH)
        center_y = random.randint(0, HEIGHT)

        angle = 0.0
        if random.random() < CLUSTER_TILT_CHANCE:
            angle = random.uniform(*CLUSTER_TILT_ANGLE_RANGE)

        block_w = random.uniform(*CLUSTER_BLOCK_WIDTH_RANGE)
        block_h = random.uniform(*CLUSTER_BLOCK_HEIGHT_RANGE)

        # 2. 在 "本地" 坐标系 (以 0,0 为中心) 生成内部网格线
        local_lines = _generate_recursive_lines_list(
            -block_w / 2.0, -block_h / 2.0, block_w, block_h,
            INTERNAL_GRID_MIN_RECT_SIZE,
            INTERNAL_GRID_SPLIT_CHANCE,
            INTERNAL_GRID_SPLIT_RANGE
        )

        # 3. 遍历所有本地线段, 将它们旋转并平移到"组"的正确位置
        for (lx1, ly1), (lx2, ly2) in local_lines:
            # 转换为 "预旋转" 的全局坐标
            pre_rot_p1 = (lx1 + center_x, ly1 + center_y)
            pre_rot_p2 = (lx2 + center_x, ly2 + center_y)

            # 旋转
            p1_final = rotate_point(pre_rot_p1[0], pre_rot_p1[1], center_x, center_y, angle)
            p2_final = rotate_point(pre_rot_p2[0], pre_rot_p2[1], center_x, center_y, angle)

            # 绘制旋转后的破损线
            draw_broken_line(
                draw, p1_final, p2_final,
                COLOR_GRID,
                random.randint(*GRID_LINE_WIDTH_RANGE),
                GRID_BROKEN_DASH_RANGE,
                GRID_BROKEN_GAP_RANGE
            )


## --- 6. 噪声生成函数 (与之前相同) ---
def draw_environmental_noise(draw):
    print("Generating noise map (this may take a second)...")
    simplex_gen_detail = OpenSimplex(seed=random.randint(0, 100000));
    simplex_gen_mask = OpenSimplex(seed=random.randint(0, 100000))
    x_indices = np.linspace(0, 1, WIDTH);
    y_indices = np.linspace(0, 1, HEIGHT);
    world_x, world_y = np.meshgrid(x_indices, y_indices)
    v_noise_detail = np.vectorize(simplex_gen_detail.noise2);
    detail_noise_map = v_noise_detail(world_x * NOISE_DETAIL_SCALE, world_y * NOISE_DETAIL_SCALE);
    detail_noise_map = (detail_noise_map + 1) / 2
    v_noise_mask = np.vectorize(simplex_gen_mask.noise2);
    mask_noise_map = v_noise_mask(world_x * NOISE_MASK_SCALE, world_y * NOISE_MASK_SCALE);
    mask_noise_map = (mask_noise_map + 1) / 2
    if NOISE_MASK_CONTRAST != 1.0: mask_noise_map = np.power(mask_noise_map, NOISE_MASK_CONTRAST)
    final_noise_map = detail_noise_map * mask_noise_map
    print("Drawing noise pixels...");
    draw_size = max(1, NOISE_SPARSITY)
    for y in range(0, HEIGHT, draw_size):
        for x in range(0, WIDTH, draw_size):
            val = final_noise_map[y, x]
            if val > NOISE_THRESHOLD_1:
                color = COLOR_NOISE_1
            elif val > NOISE_THRESHOLD_2:
                color = COLOR_NOISE_2
            else:
                continue
            if draw_size == 1:
                draw.point((x, y), fill=color)
            else:
                draw.rectangle([x, y, x + draw_size - 1, y + draw_size - 1], fill=color)


## --- 7. POI 兴趣点生成函数 (与之前相同) ---
def draw_poi(draw, x, y):
    draw.rectangle([x, y, x + POI_SIZE - 1, y + POI_SIZE - 1], fill=COLOR_POI_FILL, outline=COLOR_POI_BORDER, width=1)


def draw_poi_clusters(draw):
    print("Drawing POI clusters...")
    for _ in range(POI_NUM_CLUSTERS):
        center_x = random.randint(int(WIDTH * 0.1), int(WIDTH * 0.9));
        center_y = random.randint(int(HEIGHT * 0.1), int(HEIGHT * 0.9))
        num_pois = random.randint(*POI_PER_CLUSTER)
        for _ in range(num_pois):
            poi_x = int(random.normalvariate(center_x, POI_CLUSTER_RADIUS));
            poi_y = int(random.normalvariate(center_y, POI_CLUSTER_RADIUS))
            if not (0 < poi_x < WIDTH - POI_SIZE and 0 < poi_y < HEIGHT - POI_SIZE): continue
            draw_poi(draw, poi_x, poi_y)


## --- 8. 主执行函数 (已修改) ---
def generate_map():
    print(f"Creating new image ({WIDTH}x{HEIGHT})...")
    image = Image.new('RGBA', (WIDTH, HEIGHT), COLOR_BG)
    draw = ImageDraw.Draw(image)

    # <--- (已修改) ---
    # 移除了"拼布"函数 (v9)
    # 调用新的"集群"函数 (v10, 允许重叠)
    print("Generating grid clusters...")
    draw_grid_clusters(draw)

    # 绘制噪点和 POI (不变)
    draw_environmental_noise(draw)
    draw_poi_clusters(draw)

    output_filename = "generated_procedural_map_v10.png"
    image.save(output_filename)  # 直接保存RGBA图像
    print(f"Done! Map saved as '{output_filename}'")
    # image.show()


if __name__ == "__main__":
    generate_map()

分型噪声版

点击这里查看 Python 源码
import random
import math
from PIL import Image, ImageDraw
## 画布
WIDTH = 1560
HEIGHT = 1192
COLOR_BG = (0, 0, 0, 0)

NUM_GRID_CLUSTERS = 15
CLUSTER_BLOCK_WIDTH_RANGE = (150, 1200)
CLUSTER_BLOCK_HEIGHT_RANGE = (400, 900)
CLUSTER_TILT_CHANCE = 0.8
CLUSTER_TILT_ANGLE_RANGE = (-30, 45)
INTERNAL_GRID_MIN_RECT_SIZE = 30
INTERNAL_GRID_SPLIT_CHANCE = 0.85
INTERNAL_GRID_SPLIT_RANGE = (0.05, 0.9)

#网格线
COLOR_GRID = (29, 255, 162, 200)
GRID_LINE_WIDTH_RANGE = (1, 4)
GRID_BROKEN_DASH_RANGE = (30, 60)
GRID_BROKEN_GAP_RANGE = (3, 10)


## POI 参数
POI_NUM_CLUSTERS = 3
POI_PER_CLUSTER = (3, 8)
POI_CLUSTER_RADIUS = 45
POI_SIZE = 24
COLOR_POI_FILL = (255, 0, 0)
COLOR_POI_BORDER = (255, 100, 100)


## 点旋转
def rotate_point(px, py, cx, cy, angle_deg):
    angle_rad = math.radians(angle_deg)
    s = math.sin(angle_rad)
    c = math.cos(angle_rad)
    px_c = px - cx
    py_c = py - cy
    x_new = px_c * c - py_c * s
    y_new = px_c * s + py_c * c
    return (x_new + cx, y_new + cy)



def draw_broken_line(draw, p1, p2, fill, width, dash_range, gap_range):
    dx = p2[0] - p1[0]
    dy = p2[1] - p1[1]
    length = math.hypot(dx, dy)
    if length == 0: return
    dx /= length
    dy /= length
    current_pos = 0.0
    while current_pos < length:
        dash_len = random.uniform(*dash_range)
        gap_len = random.uniform(*gap_range)
        start_draw = current_pos
        end_draw = min(current_pos + dash_len, length)
        if end_draw > start_draw:
            start_point = (p1[0] + dx * start_draw, p1[1] + dy * start_draw)
            end_point = (p1[0] + dx * end_draw, p1[1] + dy * end_draw)
            draw.line([start_point, end_point], fill=fill, width=width)
        current_pos += dash_len + gap_len


## 核心网格
def _generate_recursive_lines_list(x, y, w, h, min_size, split_chance, split_range):
    lines_list = []
    if (w < min_size or h < min_size) and (w < min_size and h < min_size): return []
    if random.random() > split_chance: return []
    split_vertical = (w > h)
    if w == h: split_vertical = random.choice([True, False])
    if split_vertical:
        split_x = x + int(random.uniform(*split_range) * w)
        lines_list.append(((split_x, y), (split_x, y + h)))
        lines_list.extend(_generate_recursive_lines_list(x, y, split_x - x, h, min_size, split_chance, split_range))
        lines_list.extend(
            _generate_recursive_lines_list(split_x + 1, y, w - (split_x - x) - 1, h, min_size, split_chance,
                                           split_range))
    else:
        split_y = y + int(random.uniform(*split_range) * h)
        lines_list.append(((x, split_y), (x + w, split_y)))
        lines_list.extend(_generate_recursive_lines_list(x, y, w, split_y - y, min_size, split_chance, split_range))
        lines_list.extend(
            _generate_recursive_lines_list(x, split_y + 1, w, h - (split_y - y) - 1, min_size, split_chance,
                                           split_range))
    return lines_list


## 随机网格集群
def draw_grid_clusters(draw):
    print(f"Drawing {NUM_GRID_CLUSTERS} grid clusters (overlapping is allowed)...")
    for _ in range(NUM_GRID_CLUSTERS):
        center_x = random.randint(0, WIDTH)
        center_y = random.randint(0, HEIGHT)
        angle = 0.0
        if random.random() < CLUSTER_TILT_CHANCE:
            angle = random.uniform(*CLUSTER_TILT_ANGLE_RANGE)
        block_w = random.uniform(*CLUSTER_BLOCK_WIDTH_RANGE)
        block_h = random.uniform(*CLUSTER_BLOCK_HEIGHT_RANGE)
        local_lines = _generate_recursive_lines_list(-block_w / 2.0, -block_h / 2.0, block_w, block_h,
                                                     INTERNAL_GRID_MIN_RECT_SIZE, INTERNAL_GRID_SPLIT_CHANCE,
                                                     INTERNAL_GRID_SPLIT_RANGE)
        for (lx1, ly1), (lx2, ly2) in local_lines:
            pre_rot_p1 = (lx1 + center_x, ly1 + center_y)
            pre_rot_p2 = (lx2 + center_x, ly2 + center_y)
            p1_final = rotate_point(pre_rot_p1[0], pre_rot_p1[1], center_x, center_y, angle)
            p2_final = rotate_point(pre_rot_p2[0], pre_rot_p2[1], center_x, center_y, angle)
            draw_broken_line(draw, p1_final, p2_final, COLOR_GRID, random.randint(*GRID_LINE_WIDTH_RANGE),
                             GRID_BROKEN_DASH_RANGE, GRID_BROKEN_GAP_RANGE)


#POI 兴趣点
def draw_poi(draw, x, y):
    draw.rectangle([x, y, x + POI_SIZE - 1, y + POI_SIZE - 1], fill=COLOR_POI_FILL, outline=COLOR_POI_BORDER, width=1)


def draw_poi_clusters(draw):
    print("Drawing POI clusters...")
    for _ in range(POI_NUM_CLUSTERS):
        center_x = random.randint(int(WIDTH * 0.1), int(WIDTH * 0.9))
        center_y = random.randint(int(HEIGHT * 0.1), int(HEIGHT * 0.9))
        num_pois = random.randint(*POI_PER_CLUSTER)
        for _ in range(num_pois):
            poi_x = int(random.normalvariate(center_x, POI_CLUSTER_RADIUS))
            poi_y = int(random.normalvariate(center_y, POI_CLUSTER_RADIUS))
            if not (0 < poi_x < WIDTH - POI_SIZE and 0 < poi_y < HEIGHT - POI_SIZE): continue
            draw_poi(draw, poi_x, poi_y)


#$1
def generate_map(file_index):
    print(f"Creating new image ({WIDTH}x{HEIGHT})...")
    image = Image.new('RGBA', (WIDTH, HEIGHT), COLOR_BG)
    draw = ImageDraw.Draw(image)

    print("Generating grid clusters...")
    draw_grid_clusters(draw)


    print("Drawing POI clusters...")
    draw_poi_clusters(draw)

    output_filename = f"generated_procedural_map_{file_index}.png"
    image.save(output_filename)
    print(f"Done! Map saved as '{output_filename}'")
    # image.show()


if __name__ == "__main__":
    num_images_to_generate = 20
    print(f"=== Starting Batch Generation for {num_images_to_generate} Maps ===")

    for i in range(1, num_images_to_generate + 1):
        print(f"\n--- Generating Map {i}/{num_images_to_generate} ---")
        generate_map(i)

    print(f"\n=== Batch Generation Complete. {num_images_to_generate} maps saved. ===")