今日速览
- 电脑生成不了「真随机」,Godot 底层用的是 PCG 系列伪随机数生成器(PRNG)——同一种子必然得到同一串随机数。
- Godot 有两条路:全局函数(
randi()/randf())图省事,RandomNumberGenerator类可实例化、各自带种子和状态,适合多人联机、回放、地牢生成。 - 想复现结果就
seed(12345);想要「每次都不一样」就randomize()。这是程序化关卡「可分享种子」的基础。 - 加权随机用
randf()分区间,或RandomNumberGenerator.rand_weighted()按相对权重取索引——让稀有真正稀有。 - 随机抽卡容易「欧非两极」,用洗牌袋(Shuffle Bag):抽完不放回,抽空再重填洗牌,分布更均匀、体验更稳。
核心学习内容
1. 种子(Seed)与状态(State):随机的「可复现」开关
Godot 默认每次运行都用随机种子,所以结果不可复现。只要在初始化时 seed(12345),同一串随机数就会被稳定重放;也可以把字符串哈希成种子,例如 seed("Hello world".hash()),这样「种子」就能当作玩家可输入、可分享的短码——roguelike 常见的「种子号」正是这么来的。
RandomNumberGenerator 类还多了一个 state 属性。种子的作用是重置整条序列,而状态可以在不改种子的前提下回到序列中的某一点。用法很简单:先设 seed,再存一份 state,之后把 state 写回去,就能重放同一段结果:
var rng = RandomNumberGenerator.new()
rng.seed = 12345 # 改种子会重置状态,所以一定要先设种子
var saved = rng.state # 存档点
print(rng.randi()) # 产生一个数
rng.state = saved # 回到存档点
print(rng.randi()) # 会打印同一个数
这条特性对多人联机、录像回放、撤销/回退都关键:只要双方种子与调用顺序一致,就能得到同一个世界。
2. 四个常用随机函数,各司其职
randi()→ 0 到 2³²−1 的整数,取模后才是你要的范围:randi() % 50(0–49)、randi() % 51 + 10(10–60)。randf()→ 0.0 到 1.0 的浮点,用来做概率判断(见下一节)。randf_range(from, to)→ 指定区间的浮点,例如randf_range(-4, 6.5),适合抖动、散射、随机偏移。randi_range(from, to)→ 指定区间的整数,例如randi_range(-10, 10),比手写取模更安全、更直观。randfn(mean, deviation)→ 正态分布,默认均值 0、标准差 1,适合「大多数普通、偶尔极端」的数值(如伤害浮动)。
取数组随机元素有 Array.pick_random();取字典则先 .values() 再索引。想避免「连续重复」时,不要只靠 while 重抽(会「乒乓」),应该上洗牌袋。
3. 加权随机:让稀有真正稀有
最简单的方式是拿 randf() 切区间:
func get_item_rarity() -> String:
var r = randf()
if r < 0.80:
return "Common" # 80%
elif r < 0.95:
return "Uncommon" # 15%
else:
return "Rare" # 5%
如果候选很多,用实例版 RandomNumberGenerator.rand_weighted() 更清爽:传入一组相对权重数组,它会返回对应索引。[0.5, 1, 1, 2] 表示索引 3 的出现概率是索引 1/2 的两倍,是索引 0 的四倍。
var fruits = ["apple", "orange", "pear", "banana"]
var weights = [0.5, 1, 1, 2]
var rng = RandomNumberGenerator.new()
print(fruits[rng.rand_weighted(weights)])
小坑:数组里的值一旦被引用,重抽时记得
.duplicate(),避免改动污染原数据。
4. 洗牌袋(Shuffle Bag):把「欧非两极」熨平
真正的随机容易被玩家感知成「不公平」——连出三张同卡,玩家会骂系统坏了。洗牌袋的做法是:选过一个就从池子里拿走;池子空了再重填并洗牌。于是一轮之内每种元素恰好出现一次,分布均匀,又不会像固定循环那样死板。
var _fruits = ["apple", "orange", "pear", "banana"]
var _full = []
func _ready():
_full = _fruits.duplicate()
_fruits.shuffle()
func get_fruit():
if _fruits.is_empty():
_fruits = _full.duplicate()
_fruits.shuffle()
return _fruits.pop_front()
掉落表、事件池、BGM 随机播放、敌人刷新,凡是「希望平均但又要随机」的地方,都优先用洗牌袋。
🎯 DeepSeek 提示词
提示词 1:让 DeepSeek 写一个可复现的随机数管理器(适合地牢/关卡生成)
【角色】你是一位资深 Godot 4.x GDScript 游戏开发工程师。
【任务】为我的 2D roguelike 游戏写一个"可控随机管理器",作为 Autoload 单例使用。
【需求】
1. 用一个 RandomNumberGenerator 实例统一管理游戏内所有"程序化生成"相关的随机;
2. 提供 set_seed(value: int) 与 set_seed_from_string(s: String) 两个方法;
3. 提供 next_int(min, max)、next_float(min, max)、chance(p: float) 三个便捷封装;
4. 提供 get_seed() 与 get_state()/set_state(),便于存档时保存/恢复随机进度;
5. 额外提供一个独立的 rng 实例给"纯表现层随机"(粒子、抖动)用,避免它污染生成序列。
【输出格式】只给完整 .gd 文件,带中文注释,说明每段用途,并附 3 行使用示例。
【约束】Godot 4.x 语法;不要用全局 seed()/randi() 全局函数;注释解释"为什么表现层随机要分开"。
提示词 2:让 DeepSeek 实现加权随机掉落表(适合战利品/事件系统)
【角色】你是一位擅长数值设计的 Godot 工程师。
【任务】实现一个数据驱动的"加权随机抽取"工具类 WeightedPicker。
【输入】一个 Array[Dictionary],形如 [{id="sword", weight=1.0}, {id="potion", weight=5.0}, ...]。
【输出】pick(rng: RandomNumberGenerator) -> Dictionary,按相对权重返回其中一项;
另外提供 pick_unique(n: int, rng) -> Array,一次抽 n 个不重复项。
【格式】完整 .gd 文件 + 1 个最小可运行示例(含示例数据)。
【约束】Godot 4.x;用 RandomNumberGenerator.rand_weighted() 实现;权重为 0 的项永不返回;
非法输入(空数组、负权重)要给出明确报错或降级处理,并写明处理策略。
提示词 3:让 DeepSeek 用洗牌袋改写「随机 BGM/事件池」(适合手感与公平性调优)
【角色】你是 Godot 4.x 高级工程师,注重玩家体验。
【任务】把一段"每次都用 pick_random() 抽背景音乐 / 随机事件"的代码,重构为"洗牌袋(Shuffle Bag)"实现。
【要求】
1. 封装成可复用的 ShuffleBag 类:支持 add(item)、next()、reset()、remaining();
2. 池空自动重填并 shuffle(),且避免"新一轮第一个"与"上一轮最后一个"重复;
3. 支持设置"冷却/权重":最近出现过的项在一定次数内降低被抽中概率(可选开关)。
【输出格式】重构后的完整代码 + 一段对比说明:为什么洗牌袋比 pick_random 体验更稳。
【约束】Godot 4.x 语法;解释什么场景该用洗牌袋、什么场景用加权随机、什么场景两者结合。
学习心得
今天这节直接补上了「程序化生成」最容易被忽略的底座:随机不是不可控的黑盒。对「用 DeepSeek 做一款 Steam 游戏」这个目标来说,价值有三:
- 可控 = 可调试 = 可复现 bug。 只要随机走统一的种子管理器,玩家报「第三关卡死」时,我能用同一个种子号把那一局完整重放出来,而不是靠碰运气复现。
- 体验比概率更重要。 加权随机的「感知公平」、洗牌袋的「均匀分布」,是让玩家愿意反复开局的隐形手感;这部分 DeepSeek 能按提示词稳定生成,我只需把关数值与开关。
- 提示词要点:把「随机架构」当需求说清楚。 明确指出「生成随机与表现随机要分离」「用实例 RNG 而非全局函数」「要能存 state」——这几句约束一加,DeepSeek 产出的代码就从玩具变成能进版本库的工程件。
明日关注
- 把「随机数管理器」真正接进项目:给现有的地牢/关卡生成器换上统一 RNG,并加一个「输入种子号重开」的调试按钮。
- 给掉落系统换成 WeightedPicker + 保底机制,验证稀有物「感知公平」。
- 检索 Godot 的 Noise(FastNoiseLite)与程序化地形生成,把「一维随机」扩展到「二维噪声地图」。
本文由自动化学习流水线每日生成,坚持即学即记。
评论