基于Godot与GDScript的2D Roguelike随机地图生成实战
2026/8/7 16:14:48 网站建设 项目流程

1. 项目概述:为什么选择Godot和GDScript来制作2D Roguelike地图?

如果你对独立游戏开发感兴趣,尤其是想尝试Roguelike这种充满随机性和重复可玩性的类型,那么从零开始构建一个随机地图生成器,无疑是理解其核心魅力的最佳切入点。我选择Godot 3.2.4和GDScript来完成这个项目,原因很直接:高效、直观、且完全免费。Godot引擎的轻量级和节点化场景管理,让2D游戏的逻辑构建变得像搭积木一样清晰;而GDScript作为其亲儿子语言,语法与Python高度相似,学习曲线平缓,能让你把精力集中在游戏逻辑本身,而不是与复杂的引擎API搏斗。

这个项目的目标,是打造一个经典的、基于“房间-走廊”结构的2D Roguelike随机地图。它不仅仅是随机撒点,而是要生成一个连通、可玩、且每次开局都截然不同的地下城。想象一下《以撒的结合》或《挺进地牢》里那些层层递进、充满未知的房间布局,我们的目标就是实现其最基础的地图骨架。通过这个实战,你不仅能掌握Godot中2D节点操作、随机数应用、算法逻辑实现等核心技能,更能深刻理解程序化内容生成(PCG)的设计思想,这是现代游戏开发中一项极具价值的能力。

2. 核心设计思路:从“房间和走廊”到可玩地图

在动手写代码之前,我们必须把地图生成的过程拆解成清晰的、可执行的步骤。一个典型的、结构良好的随机地下城,其生成逻辑可以归纳为以下四个阶段,这比单纯的随机漫步算法(如醉汉走路)能产生更可控、更有趣的结果。

2.1 阶段一:随机房间的生成与初步布局

第一步,我们不是直接画地图,而是先规划“房间”这个基本单元。我们会在一个足够大的网格化“画布”(比如一个二维数组)上,随机生成若干个矩形房间。每个房间有随机的宽度和高度,以及一个随机的左上角起始坐标(x, y)。

这里的“随机”并非完全无序。我们需要设定约束条件,比如房间的最小/最大尺寸,以确保房间不会太小(无法放置内容)或太大(破坏游戏节奏)。同时,在放置新房间时,必须检查它是否与已存在的房间重叠。一个简单的碰撞检测方法是:比较两个矩形的边界。如果新房间的右边界大于旧房间的左边界,且新房间的左边界小于旧房间的右边界,并且在Y轴方向上也满足类似条件,则判定为重叠。如果重叠,我们就丢弃这个新房间,重新生成一个。

注意:直接检查所有已存在房间的碰撞在房间数量多时会比较耗时。一种优化思路是使用空间划分数据结构,如四叉树,但对于初学者项目,房间数量(例如50-100个)不多时,简单的遍历检查完全可以接受。关键在于设定一个尝试次数的上限(比如1000次),避免在空间过于拥挤时陷入无限循环。

通过这个过程,我们得到了一堆互不重叠、随机散布的矩形房间。但它们目前还是孤立的“岛屿”,玩家无法从一个房间到达另一个。这就是下一个阶段要解决的问题。

2.2 阶段二:构建最小生成树(MST)确保连通性

如何用最少的“走廊”把所有房间连接起来,形成一个连通图?这正是图论中“最小生成树”算法要解决的问题。我们可以把每个房间看作图中的一个“节点”,而两个房间之间潜在的走廊就是一条“边”,边的“权重”可以是两个房间中心点之间的欧几里得距离。

为什么选择最小生成树?因为它能保证所有房间最终都被连接起来(连通性),同时使用的走廊总长度最短(经济性)。这避免了生成过多冗余、绕远的走廊,让地图结构看起来更紧凑、合理。常用的算法是普里姆(Prim)算法或克鲁斯卡尔(Kruskal)算法。在这个项目中,我推荐使用普里姆算法,它的逻辑更直观:从一个随机房间(起始节点)开始,不断寻找距离当前已连接房间集合“最近”的未连接房间,并用走廊连接它们,直到所有房间都被纳入集合。

实现时,我们需要维护两个列表:connected_rooms(已连接的房间)和unconnected_rooms(未连接的房间)。每次循环,遍历connected_rooms中的每个房间,找到unconnected_rooms中离它最近的那个房间,记录下这对“房间A”和“房间B”。然后从所有候选的“最近房间对”中,再选出距离最短的那一对,正式建立连接(即创建一条走廊),并将新房间从unconnected_rooms移到connected_rooms中。重复此过程,直到unconnected_rooms为空。

2.3 阶段三:走廊的生成与“拉直”优化

确定了需要连接哪些房间对之后,就要实际“画出”走廊。最朴素的方法是使用“曼哈顿距离”式的直线走廊:从房间A的中心点,先水平移动到达与房间B中心点相同的X坐标,再垂直移动到达房间B的中心点,形成一个L形的走廊。

但是,纯L形走廊看起来比较呆板。我们可以引入一些变化来增加自然感。一个简单有效的技巧是:在画L形走廊时,随机选择是先水平后垂直,还是先垂直后水平。更进一步,可以在水平或垂直段的中途,随机增加一个小的拐点,让走廊呈现“之”字形,这能有效打破机械感。

更高级的优化:“拉直”走廊。观察由MST生成的地图,你可能会发现有些走廊几乎是平行的且距离很近。我们可以增加一个“拉直”步骤:在生成所有基础走廊后,遍历检查。如果两条走廊的某一段(比如水平段)在Y坐标上相同或非常接近,且它们的X坐标范围有重叠或间隙很小,我们就可以尝试将它们“合并”成一条更长的、笔直的走廊。这不仅能优化地图美观度,有时还能创造出更宽敞、更适合战斗或事件的长廊区域。实现上,这需要对走廊线段进行排序和区间合并判断,是提升地图质量的关键一步。

2.4 阶段四:地图数据的最终渲染与碰撞体生成

当所有房间和走廊的“蓝图”(即它们在网格坐标系中的坐标范围)都确定后,最后一步就是在Godot场景中将其可视化并赋予物理属性。

  1. 创建TileMap节点:这是Godot中处理2D网格地图最高效的方式。你需要准备一份Tileset(图块集),至少包含三种图块:地板(可通行)、墙壁(不可通行)、以及可能的装饰物。
  2. 转换坐标与绘制:我们的“蓝图”是基于自定义的网格坐标系(比如以左上角为原点(0,0),每个格子代表一个游戏单位)。我们需要将这些坐标转换为TileMap的单元格坐标。然后,遍历所有房间和走廊的内部区域,在TileMap的对应单元格设置“地板”图块。
  3. 生成墙壁:这是让地图从“平面蓝图”变成“立体空间”的关键。一个常用的算法是:遍历所有已设置为“地板”的单元格,检查其上下左右四个邻居单元格。如果某个邻居单元格不是“地板”,那么就在该邻居单元格的位置放置“墙壁”图块。这个过程会自动为所有地板区域生成一圈包围着的墙壁。
  4. 添加碰撞与导航:为了让玩家不能穿墙,我们需要为墙壁生成碰撞体。Godot的TileMap可以自动为指定的图块(如墙壁)生成碰撞形状,非常方便。同时,如果你需要敌人寻路,还需要使用Navigation2D节点,并基于地板区域生成导航网格(NavigationMesh)。

至此,一个功能完整、每次运行都不同的2D Roguelike随机地图就生成了。这个流程融合了算法设计、数据结构和引擎API应用,是理解程序化生成精髓的绝佳范例。

3. 核心代码实现与GDScript详解

理论清晰后,我们进入实战环节。我会分模块详细解释核心的GDScript代码,并提供完整的、可运行的代码片段。我们假设在Godot中有一个名为MapGenerator的节点脚本。

3.1 数据结构定义:Room(房间)与Corridor(走廊)

首先,我们需要定义两个核心的数据结构来存储房间和走廊的信息。

# MapGenerator.gd 部分代码 extends Node2D # 定义房间类,存储其矩形区域 class Room: var rect: Rect2 # Godot的Rect2类型,包含position(x,y)和size(width, height) var center: Vector2 # 房间中心点,用于计算连接距离 func _init(x: int, y: int, w: int, h: int): rect = Rect2(x, y, w, h) center = Vector2(x + w/2.0, y + h/2.0) # 判断两个房间是否相交(重叠) func intersects(other_room: Room) -> bool: return rect.intersects(other_room.rect, true) # true 表示包括边缘接触 # 走廊本质上是由两个端点定义的一系列线段,这里简化为记录连接的两个房间 # 实际生成时,我们会根据两个房间中心点计算走廊路径

我们使用Godot内置的Rect2来表示房间的矩形区域,它提供了方便的intersects方法用于碰撞检测。center属性在后续的连通性计算中至关重要。

3.2 随机房间生成算法的实现

接下来是实现房间生成函数。我们需要控制房间数量、大小范围,并确保它们不重叠。

# MapGenerator.gd 继续 export var map_width: int = 100 # 地图网格宽度 export var map_height: int = 100 # 地图网格高度 export var max_rooms: int = 30 # 最大房间数 export var room_min_size: int = 6 # 房间最小尺寸 export var room_max_size: int = 12 # 房间最大尺寸 var rooms: Array = [] # 存储所有生成的Room对象 var corridors: Array = [] # 存储连接关系,元素为数组[room_index_a, room_index_b] func generate_rooms(): rooms.clear() corridors.clear() randomize() # 初始化随机数种子,确保每次运行结果不同 for _i in range(max_rooms): # 1. 随机生成房间宽高和位置 var w: int = (randi() % (room_max_size - room_min_size + 1)) + room_min_size var h: int = (randi() % (room_max_size - room_min_size + 1)) + room_min_size var x: int = randi() % (map_width - w - 1) + 1 # +1/-1 留出边界 var y: int = randi() % (map_height - h - 1) + 1 var new_room: Room = Room.new(x, y, w, h) var failed: bool = false # 2. 检查是否与已有房间重叠 for other_room in rooms: if new_room.intersects(other_room): failed = true break # 3. 如果不重叠,则加入房间列表 if not failed: rooms.append(new_room) print("成功生成 %d 个房间。" % rooms.size())

这段代码会尝试生成最多max_rooms个房间。export关键字使得这些变量可以在Godot编辑器的属性面板中直接调整,方便进行参数调优和测试。

3.3 普里姆算法实现房间连通

房间生成后,我们使用普里姆算法来构建最小生成树,确定连接关系。

# MapGenerator.gd 继续 func connect_rooms_with_mst(): if rooms.size() <= 1: return # 房间少于2个,无需连接 corridors.clear() # 初始化集合:已连接房间 和 未连接房间 var connected: Array = [] var unconnected: Array = range(rooms.size()) # 存储房间的索引 # 1. 随机选择一个房间作为起始点,放入已连接集合 var start_index: int = randi() % unconnected.size() connected.append(unconnected[start_index]) unconnected.remove(start_index) # 2. 循环,直到所有房间都已被连接 while unconnected.size() > 0: var best_pair = null # 存储[已连房间索引, 未连房间索引, 距离] var best_distance = INF # 3. 遍历所有已连接房间和未连接房间的组合,找出距离最近的一对 for connected_idx in connected: for unconnected_idx in unconnected: var dist: float = rooms[connected_idx].center.distance_to(rooms[unconnected_idx].center) if dist < best_distance: best_distance = dist best_pair = [connected_idx, unconnected_idx, dist] # 4. 找到了最佳连接对,记录到走廊列表,并更新集合 if best_pair: corridors.append([best_pair[0], best_pair[1]]) connected.append(best_pair[1]) unconnected.erase(best_pair[1]) else: break # 理论上不会发生,安全起见 print("MST生成完毕,共有 %d 条连接。" % corridors.size())

这个函数运行后,corridors数组里就存储了需要建造走廊的房间对索引。算法保证了连通性和经济性。

3.4 走廊生成与“拉直”优化

现在,我们需要根据corridors中的连接关系,在网格地图上实际“画出”走廊。我们同时实现基础的L形走廊和简单的“拉直”优化。

首先,我们需要一个代表整个地图网格的二维数组,并初始化一个函数来设置网格值(0=空,1=地板,2=墙壁等)。

# MapGenerator.gd 继续 var map_grid: Array = [] # 二维数组,map_grid[y][x] func _ready(): # 初始化地图网格 map_grid = [] for y in range(map_height): map_grid.append([]) map_grid[y].resize(map_width) for x in range(map_width): map_grid[y][x] = 0 # 0 表示空 # 执行生成流程 generate_rooms() connect_rooms_with_mst() carve_rooms_and_corridors() # 雕刻房间和走廊到网格 generate_walls() # 生成墙壁 render_to_tilemap() # 渲染到TileMap func carve_rect(rect: Rect2): # 将一个矩形区域雕刻为地板(值为1) for y in range(rect.position.y, rect.position.y + rect.size.y): for x in range(rect.position.x, rect.position.x + rect.size.x): if x >= 0 and x < map_width and y >= 0 and y < map_height: map_grid[y][x] = 1 func carve_corridor(start: Vector2, end: Vector2): # 雕刻一条从start到end的L形走廊,并随机选择拐弯顺序 var from = start var to = end # 随机决定是先横后竖,还是先竖后横 if randi() % 2 == 0: # 先水平,后垂直 carve_h_tunnel(from.x, to.x, from.y) carve_v_tunnel(from.y, to.y, to.x) else: # 先垂直,后水平 carve_v_tunnel(from.y, to.y, from.x) carve_h_tunnel(from.x, to.x, to.y) func carve_h_tunnel(x1: int, x2: int, y: int): # 雕刻水平隧道,处理x1可能大于x2的情况 for x in range(min(x1, x2), max(x1, x2) + 1): if x >= 0 and x < map_width and y >= 0 and y < map_height: map_grid[y][x] = 1 func carve_v_tunnel(y1: int, y2: int, x: int): # 雕刻垂直隧道 for y in range(min(y1, y2), max(y1, y2) + 1): if x >= 0 and x < map_width and y >= 0 and y < map_height: map_grid[y][x] = 1 func carve_rooms_and_corridors(): # 1. 雕刻所有房间 for room in rooms: # 房间内部留出一格边界作为墙壁,所以雕刻区域向内缩进1格 var inner_rect = Rect2(room.rect.position + Vector2.ONE, room.rect.size - Vector2(2, 2)) carve_rect(inner_rect) # 2. 雕刻所有走廊 for corridor in corridors: var room_a = rooms[corridor[0]] var room_b = rooms[corridor[1]] carve_corridor(room_a.center, room_b.center) # 3. (可选)简单的走廊拉直优化 optimize_corridors() func optimize_corridors(): # 这是一个简化的优化:合并相邻的水平走廊段 # 更复杂的优化需要记录每条走廊的线段并做几何判断,这里作为思路提示 # 例如,可以遍历网格,将连续的水平地板标记为同一个“走廊段”,然后尝试合并相邻且Y坐标相同的段 pass # 具体实现略,可根据项目复杂度决定是否添加

carve_rooms_and_corridors函数是核心,它先将每个房间的内部区域(扣除墙壁厚度)标记为地板,然后为每一对需要连接的房间中心点雕刻一条走廊。carve_corridor函数中的随机拐弯顺序,为走廊带来了基础的变化。

3.5 墙壁生成与TileMap渲染

当地板区域全部标记好后,生成墙壁就变得简单了:遍历所有地板单元格,检查其四邻,如果是空地(值为0),则将其标记为墙壁(值为2)。

# MapGenerator.gd 继续 func generate_walls(): # 方向数组:上、下、左、右 var dirs = [Vector2(0, -1), Vector2(0, 1), Vector2(-1, 0), Vector2(1, 0)] for y in range(map_height): for x in range(map_width): if map_grid[y][x] == 1: # 如果是地板 for dir in dirs: var nx = x + dir.x var ny = y + dir.y # 检查邻居是否在地图范围内且是空地 if nx >= 0 and nx < map_width and ny >=0 and ny < map_height: if map_grid[ny][nx] == 0: map_grid[ny][nx] = 2 # 标记为墙壁

最后,我们将map_grid中的数据渲染到Godot的TileMap节点上。

# MapGenerator.gd 继续 onready var tilemap: TileMap = $TileMap # 假设场景中有一个名为TileMap的子节点 func render_to_tilemap(): # 假设你的Tileset中,地板图块的ID是0,墙壁图块的ID是1 var FLOOR_TILE_ID = 0 var WALL_TILE_ID = 1 tilemap.clear() # 清除旧地图 for y in range(map_height): for x in range(map_width): var cell_val = map_grid[y][x] var tile_pos = Vector2(x, y) if cell_val == 1: # 地板 tilemap.set_cellv(tile_pos, FLOOR_TILE_ID) elif cell_val == 2: # 墙壁 tilemap.set_cellv(tile_pos, WALL_TILE_ID) # 值为0的空地不放置任何图块 tilemap.update_bitmask_region() # 更新自动瓦片遮罩(如果你使用了Autotile) # 为墙壁生成碰撞形状(需要在TileMap资源中预先设置) tilemap.update_dirty_quadrants()

MapGenerator节点添加到场景中,并将一个配置好Tileset的TileMap节点作为其子节点,命名为TileMap。运行游戏,你就能看到随机生成的地图了。

4. 进阶优化与内容扩展

一个基础的地图生成器已经完成,但要让它在游戏中真正可用、有趣,还需要很多打磨和扩展。这里分享几个关键的进阶方向。

4.1 提升地图的多样性与可玩性

  1. 房间形状多样化:不仅仅是矩形,可以尝试生成圆形房间、L形房间,或者将几个小矩形组合成复杂形状。这能显著增加地图的视觉丰富度。
  2. 走廊宽度与样式:目前的走廊是单格宽。你可以引入“宽走廊”的概念,在雕刻走廊时,不是画一条线,而是画一个指定宽度的矩形。这能创造出大厅、十字路口等更有战略意义的地形。
  3. 区域划分与主题:将地图分成几个区域(例如,森林区、洞穴区、神殿区),每个区域使用不同的TileSet,房间生成规则(大小、密度)也略有不同。这能加强游戏的叙事感和探索层次。
  4. 预置房间(Hand-crafted Rooms):这是商业Roguelike游戏的常用技巧。在完全随机生成的基础上,引入一些精心设计的“特殊房间”,如宝箱房、BOSS房、商店、陷阱房等。当地图生成后,随机挑选几个房间,用预置的、更复杂的布局替换掉简单的矩形。这能在保证随机性的同时,提供精心设计的游戏体验。

4.2 性能优化与大型地图处理

当房间数量成百上千,地图尺寸巨大时,我们的朴素算法可能会遇到性能瓶颈。

  1. 空间划分优化碰撞检测:在生成房间时,使用空间数据结构如四叉树(Quadtree)网格分区(Grid Partitioning)来管理房间。当检测新房间是否与已有房间重叠时,只需检查它所在分区及相邻分区内的房间,而不是遍历所有房间,能极大提升效率。
  2. 分步生成与流式加载:对于超大地图,不要一次性生成整个地图。可以采用“区块(Chunk)”系统,只生成玩家当前所在区块及周围区块的地图。当玩家移动时,动态加载新的区块,卸载远离的区块。这需要将地图坐标与区块坐标进行映射。
  3. 算法优化:普里姆算法的时间复杂度是O(N^2),对于大量房间(如超过500个)会变慢。可以考虑使用更高效的克鲁斯卡尔算法,并配合并查集(Union-Find)数据结构,可以将时间复杂度降至接近O(N log N)。

4.3 集成到完整游戏项目

地图生成器本身不是游戏,你需要将其融入一个游戏循环中。

  1. 玩家与实体生成:地图生成后,你需要在一个随机的“地板”单元格上实例化玩家场景。同样,敌人、物品、楼梯等实体也需要根据规则放置。例如,确保楼梯不会生成在墙壁里,宝箱周围有一定空间等。
  2. 视野与迷雾(Fog of War):经典的Roguelike游戏通常有视野系统。你可以实现一个基于玩家位置的视野计算,只显示已探索和当前可见的区域,其他区域被黑暗或迷雾覆盖。这能极大地增强探索感和 suspense。
  3. 导航与AI:使用Godot的Navigation2DNavigationPolygonInstance,根据地板的区域自动生成导航网格。这样,你的敌人就可以使用NavigationAgent2D在地图上智能地寻路追击玩家。
  4. 种子与可复现性:使用seed()函数初始化随机数生成器,而不是randomize()。这样,只要传入相同的种子值,就能生成完全一样的地图。这对于调试、分享特定地图或实现“每日地牢”功能至关重要。

5. 常见问题与调试技巧实录

在实际开发中,你肯定会遇到各种奇怪的问题。以下是我在多次实现类似系统时踩过的坑和总结的技巧。

5.1 地图生成异常排查表

问题现象可能原因排查与解决方法
房间全部堆叠在角落或根本不生成随机数种子未初始化或范围计算错误。检查randi() % N中的N是否为正数。确保在生成循环前调用了randomize()。打印出生成的房间坐标和大小,看是否在预期范围内。
走廊穿透了房间墙壁雕刻走廊的函数直接使用了房间rect的边界,没有考虑墙壁厚度。确保carve_corridor函数连接的是房间的center(中心点),并且carve_rect雕刻房间内部时进行了内缩(如rect.grow(-1))。
墙壁生成不全,有“孤岛”地板generate_walls函数只检查了四方向邻接,但地板可能斜向连接。墙壁生成算法需要检查八方向(包括对角线),或者更稳健的方法是:任何非地板与地板相邻(八方向)的格子都设为墙壁。
TileMap渲染后出现缝隙或错位TileMap的单元格大小(Cell Size)与代码中的逻辑网格单位不匹配。在代码中,我们假设一个网格单位对应TileMap的一个单元格。确保你在设计Tileset时,TileMap的Cell Size属性与你的精灵图尺寸匹配,并且在代码中set_cellv的坐标计算正确。
性能卡顿,特别是房间数多时1. 房间碰撞检测是O(N^2)复杂度。
2.render_to_tilemap每帧设置大量单元格。
1. 实现空间划分优化。
2. 确保地图生成和渲染只在游戏初始化时进行一次,而不是每帧进行。使用Profiler查看性能瓶颈。
地图看起来总是很“稀疏”或很“拥挤”房间数量(max_rooms)、房间大小范围(room_min/max_size)与地图尺寸(map_width/height)的比例不协调。这是参数调优问题。建立几个预设参数组进行测试。例如,小地图配小房间和高数量,容易拥挤;大地图配大房间和低数量,容易稀疏。需要反复试验找到适合游戏感觉的“黄金比例”。

5.2 调试与可视化技巧

  1. 分步可视化:不要一次性生成完整地图。在_ready函数中,将生成步骤分开,并用yield(get_tree().create_timer(0.5), “timeout”)加入短暂延迟,这样你就能在游戏中看到房间先出现,然后走廊一条条连接起来,最后墙壁生成的过程,非常有助于理解算法和定位问题。
  2. 控制台输出(Print Debugging):大量使用print()语句输出关键变量的值,如房间坐标、走廊连接对、网格状态等。Godot编辑器的“输出”面板是你的好朋友。
  3. 绘制调试图形:Godot的CanvasItem提供了draw_rectdraw_line等方法。你可以创建一个继承自Node2D的调试节点,在其_draw()函数中,根据算法中间数据(如房间矩形、房间中心点、走廊连接线)绘制半透明的几何图形。这能让你直观地看到算法每一步的结果,比看数字高效得多。
  4. 使用Export变量实时调整:将关键参数如max_roomsroom_min_size等设置为export变量。在游戏运行期间,你可以在编辑器的“远程”选项卡中实时修改这些参数,并重新调用生成函数,立即看到效果。这是迭代和平衡游戏性的强大工具。

5.3 关于GDScript的一些心得

  • 数组性能:GDScript的普通数组(Array)在存储大量简单数据(如整数、向量)时,性能不如类型化数组(PoolVector2Array等)或PackedByteArray。对于巨大的网格地图(如1000x1000),考虑使用PackedByteArray的一维模拟,并通过y * width + x计算索引,可以节省大量内存。
  • 信号与解耦:你的MapGenerator在生成完成后,应该发出一个信号,例如map_generation_completed。玩家控制器、敌人生成器、物品管理器等都应该监听这个信号,等地图就绪后再开始放置实体。这比在_ready里硬编码执行顺序要稳健得多。
  • 资源管理:如果你使用了预置房间场景,使用ResourceLoader.load()进行预加载,而不是在生成时才即时加载,以避免游戏卡顿。

从头开始构建一个随机地图生成器,就像在亲手为你的游戏世界制定自然法则。最初的版本可能简单甚至粗糙,但每一次迭代——无论是增加新的房间形状、优化走廊算法,还是加入预置内容——都会让你对“如何用代码创造有趣空间”有更深的理解。这个由Godot和GDScript搭建的框架,足够轻便和灵活,你可以随时将其融入一个更大的项目,或者基于它探索更复杂的PCG技术,如柏林噪声生成地形、 dungeon 的“任务流”生成等。最重要的是动手去试,参数调一调,代码改一改,亲眼看看算法是如何将一堆随机数转化为一个个独一无二、等待探索的世界的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询