godot-genre-roguelike

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Genre: Roguelike

游戏类型:Roguelike

Expert blueprint for roguelikes balancing challenge, progression, and replayability.
兼顾挑战性、进度系统与重玩价值的Roguelike游戏专家级开发蓝图。

NEVER Do

绝对不要做的事

  • NEVER make runs pure RNG — Skill should mitigate bad luck. Provide guaranteed item shops, reroll mechanics, or starting loadout choices.
  • NEVER overpowered meta-upgrades — If meta-progression is too strong, game becomes "grind to win" not "learn to win". Keep modest (+10% damage max).
  • NEVER lack variety in content — Procedural generation shuffles content. Need 50+ rooms, 20+ enemies, 100+ items minimum for freshness.
  • NEVER use unseeded RNG — Always initialize RandomNumberGenerator with seed. Enables shareable/reproducible runs.
  • NEVER allow save scumming — Save state only on floor transition. Delete save on load (standard for strict roguelikes).

  • 绝对不要让游戏局完全依赖纯RNG — 玩家的操作技巧应能抵消坏运气。需提供有保障的道具商店、重roll机制,或初始装备选择。
  • 绝对不要设计过于强力的元升级 — 如果元进度升级过强,游戏会变成“刷到赢”而非“学到赢”。升级幅度需保持克制(最多+10%伤害)。
  • 绝对不要缺乏内容多样性 — 程序化生成只是对现有内容进行重组。至少需要50+种房间、20+种敌人、100+种道具才能保持新鲜感。
  • 绝对不要使用无种子的RNG — 始终用种子初始化RandomNumberGenerator。这样才能支持可分享/可复现的游戏局。
  • 绝对不要允许存档刷分 — 仅在楼层切换时保存状态。加载时删除存档(严格类Roguelike的标准做法)。

Available Scripts

可用脚本

MANDATORY: Read the appropriate script before implementing the corresponding pattern.
强制要求:在实现对应模式前,请先阅读相应脚本。

meta_progression_manager.gd

meta_progression_manager.gd

Cross-run persistence for currency and upgrades. JSON save/load with upgrade purchase/level tracking. Encrypt for production builds.

跨游戏局的货币与升级持久化系统。支持JSON保存/加载,跟踪升级购买与等级信息。生产构建时需加密。

Core Loop

核心循环

  1. Preparation: Select character, equip meta-upgrades.
  2. The Run: complete procedural levels, acquire temporary power-ups.
  3. The Challenge: Survive increasingly difficult encounters/bosses.
  4. Death/Victory: Run ends, resources calculated.
  5. Meta-Progression: Spend resources on permanent unlocks/upgrades.
  6. Repeat: Start a new run with new capabilities.
  1. 准备阶段:选择角色,装备元升级内容。
  2. 游戏局进行:完成程序化生成的关卡,获取临时增益。
  3. 挑战阶段:在难度逐渐提升的遭遇战/Boss战中存活。
  4. 死亡/胜利:游戏局结束,计算获得的资源。
  5. 元进度更新:消耗资源解锁永久内容/升级。
  6. 重复挑战:带着新能力开启新的游戏局。

Skill Chain

技能链

PhaseSkillsPurpose
1. Architecture
state-machines
,
autoloads
Managing Run State vs Meta State
2. World Gen
godot-procedural-generation
,
tilemap
,
noise
Creating unique levels every run
3. Combat
godot-combat-system
,
enemy-ai
Fast-paced, high-stakes encounters
4. Progression
loot-tables
,
godot-inventory-system
Managing run-specific items/relics
5. Persistence
save-system
,
resources
Saving meta-progress between runs
阶段技能用途
1.架构设计
state-machines
,
autoloads
管理运行状态与元状态
2.世界生成
godot-procedural-generation
,
tilemap
,
noise
每次游戏局生成独特关卡
3.战斗系统
godot-combat-system
,
enemy-ai
快节奏、高风险的战斗遭遇
4.进度系统
loot-tables
,
godot-inventory-system
管理游戏局专属道具/遗物
5.持久化
save-system
,
resources
在游戏局之间保存元进度

Architecture Overview

架构概述

Roguelikes require a strict separation between Run State (temporary) and Meta State (persistent).
Roguelike游戏要求严格区分运行状态(临时)与元状态(持久)。

1. Run Manager (AutoLoad)

1. 游戏局管理器(AutoLoad)

Handles the lifespan of a single run. Resets completely on death.
gdscript
undefined
处理单局游戏的生命周期。死亡时完全重置状态。
gdscript
undefined

run_manager.gd

run_manager.gd

extends Node
signal run_started signal run_ended(victory: bool) signal floor_changed(new_floor: int)
var current_seed: int var current_floor: int = 1 var player_stats: Dictionary = {} var inventory: Array[Resource] = [] var rng: RandomNumberGenerator
func start_run(seed_val: int = -1) -> void: rng = RandomNumberGenerator.new() if seed_val == -1: rng.randomize() current_seed = rng.seed else: current_seed = seed_val rng.seed = current_seed
current_floor = 1
_reset_run_state()
run_started.emit()
func _reset_run_state() -> void: player_stats = { "hp": 100, "gold": 0 } inventory.clear()
func next_floor() -> void: current_floor += 1 floor_changed.emit(current_floor)
func end_run(victory: bool) -> void: run_ended.emit(victory) # Trigger meta-progression save here
undefined
extends Node
signal run_started signal run_ended(victory: bool) signal floor_changed(new_floor: int)
var current_seed: int var current_floor: int = 1 var player_stats: Dictionary = {} var inventory: Array[Resource] = [] var rng: RandomNumberGenerator
func start_run(seed_val: int = -1) -> void: rng = RandomNumberGenerator.new() if seed_val == -1: rng.randomize() current_seed = rng.seed else: current_seed = seed_val rng.seed = current_seed
current_floor = 1
_reset_run_state()
run_started.emit()
func _reset_run_state() -> void: player_stats = { "hp": 100, "gold": 0 } inventory.clear()
func next_floor() -> void: current_floor += 1 floor_changed.emit(current_floor)
func end_run(victory: bool) -> void: run_ended.emit(victory) # Trigger meta-progression save here
undefined

2. Meta-Progression (Resource)

2. 元进度系统(Resource)

Stores permanent unlocks.
gdscript
undefined
存储永久解锁内容。
gdscript
undefined

meta_progression.gd

meta_progression.gd

class_name MetaProgression extends Resource
@export var total_runs: int = 0 @export var unlocked_weapons: Array[String] = ["sword_basic"] @export var currency: int = 0 @export var skill_tree_nodes: Dictionary = {} # node_id: level
func save() -> void: ResourceSaver.save(self, "user://meta_progression.tres")
static func load_or_create() -> MetaProgression: if ResourceLoader.exists("user://meta_progression.tres"): return ResourceLoader.load("user://meta_progression.tres") return MetaProgression.new()
undefined
class_name MetaProgression extends Resource
@export var total_runs: int = 0 @export var unlocked_weapons: Array[String] = ["sword_basic"] @export var currency: int = 0 @export var skill_tree_nodes: Dictionary = {} # node_id: level
func save() -> void: ResourceSaver.save(self, "user://meta_progression.tres")
static func load_or_create() -> MetaProgression: if ResourceLoader.exists("user://meta_progression.tres"): return ResourceLoader.load("user://meta_progression.tres") return MetaProgression.new()
undefined

Key Mechanics implementation

核心机制实现

Procedural Dungeon Generation (Walker Method)

程序化地牢生成(Walker算法)

A simple "drunkard's walk" algorithm for organic, cave-like or connected room layouts.
gdscript
undefined
一种简单的“醉汉漫步”算法,用于生成自然的、洞穴风格或连通的房间布局。
gdscript
undefined

dungeon_generator.gd

dungeon_generator.gd

extends Node
@export var map_width: int = 50 @export var map_height: int = 50 @export var max_walkers: int = 5 @export var max_steps: int = 500
func generate_dungeon(tilemap: TileMapLayer, rng: RandomNumberGenerator) -> void: tilemap.clear() var walkers: Array[Vector2i] = [Vector2i(map_width/2, map_height/2)] var floor_tiles: Array[Vector2i] = []
for step in max_steps:
    var new_walkers: Array[Vector2i] = []
    for walker in walkers:
        floor_tiles.append(walker)
        # 25% chance to destroy walker, 25% to spawn new one
        if rng.randf() < 0.25 and walkers.size() > 1:
            continue # Destroy
        if rng.randf() < 0.25 and walkers.size() < max_walkers:
            new_walkers.append(walker) # Spawn
        
        # Move walker
        var direction = [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT].pick_random()
        new_walkers.append(walker + direction)
    
    walkers = new_walkers

# Set tiles
for pos in floor_tiles:
    tilemap.set_cell(pos, 0, Vector2i(0,0)) # Assuming source_id 0 is floor

# Post-process: Add walls, spawn points, etc.
undefined
extends Node
@export var map_width: int = 50 @export var map_height: int = 50 @export var max_walkers: int = 5 @export var max_steps: int = 500
func generate_dungeon(tilemap: TileMapLayer, rng: RandomNumberGenerator) -> void: tilemap.clear() var walkers: Array[Vector2i] = [Vector2i(map_width/2, map_height/2)] var floor_tiles: Array[Vector2i] = []
for step in max_steps:
    var new_walkers: Array[Vector2i] = []
    for walker in walkers:
        floor_tiles.append(walker)
        # 25% chance to destroy walker, 25% to spawn new one
        if rng.randf() < 0.25 and walkers.size() > 1:
            continue # Destroy
        if rng.randf() < 0.25 and walkers.size() < max_walkers:
            new_walkers.append(walker) # Spawn
        
        # Move walker
        var direction = [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT].pick_random()
        new_walkers.append(walker + direction)
    
    walkers = new_walkers

# Set tiles
for pos in floor_tiles:
    tilemap.set_cell(pos, 0, Vector2i(0,0)) # Assuming source_id 0 is floor

# Post-process: Add walls, spawn points, etc.
undefined

Item/Relic System (Resource-based)

道具/遗物系统(基于Resource)

Relics modify stats or add behavior.
gdscript
undefined
遗物可修改属性或添加新行为。
gdscript
undefined

relic.gd

relic.gd

class_name Relic extends Resource
@export var id: String @export var name: String @export var icon: Texture2D @export_multiline var description: String
class_name Relic extends Resource
@export var id: String @export var name: String @export var icon: Texture2D @export_multiline var description: String

Hook system for complex interactions

Hook system for complex interactions

func on_pickup(player: Node) -> void: pass
func on_damage_dealt(player: Node, target: Node, damage: int) -> int: return damage # Return modified damage
func on_kill(player: Node, target: Node) -> void: pass

```gdscript
func on_pickup(player: Node) -> void: pass
func on_damage_dealt(player: Node, target: Node, damage: int) -> int: return damage # Return modified damage
func on_kill(player: Node, target: Node) -> void: pass

```gdscript

example_relic_vampirism.gd

example_relic_vampirism.gd

extends Relic
func on_kill(player: Node, target: Node) -> void: player.heal(5) print("Vampirism triggered!")
undefined
extends Relic
func on_kill(player: Node, target: Node) -> void: player.heal(5) print("Vampirism triggered!")
undefined

Common Pitfalls

常见陷阱

  1. RNG Dependency: Don't make runs entirely dependent on luck. Good roguelikes allow skill to mitigate bad RNG.
  2. Meta-progression Imbalance: If meta-upgrades are too strong, the game becomes a "grind to win" rather than "learn to win".
  3. Lack of Variety: Procedural generation is only as good as the content it arranges. You need a lot of content (rooms, enemies, items) to keep it fresh.
  4. Save Scumming: Players will try to quit to avoid death. Save the state only on floor transition or quit, and delete the save on load (optional, but standard for strict roguelikes).
  1. RNG过度依赖:不要让游戏局完全依赖运气。优秀的Roguelike游戏应允许玩家用技巧抵消糟糕的RNG结果。
  2. 元进度失衡:如果元升级过强,游戏会变成“刷到赢”而非“学到赢”。
  3. 内容缺乏多样性:程序化生成的质量取决于它所重组的内容。你需要大量的内容(房间、敌人、道具)才能保持新鲜感。
  4. 存档刷分:玩家会尝试通过退出游戏来避免死亡。仅在楼层切换或退出时保存状态,加载时删除存档(可选,但属于严格类Roguelike的标准做法)。

Godot-Specific Tips

Godot专属技巧

  • Seeded Runs: Always initialize
    RandomNumberGenerator
    with a seed. This allows players to share specific run layouts.
  • ResourceSaver: Use
    ResourceSaver
    for meta-progression, but be careful with cyclical references in deeply nested resources.
  • Scenes as Rooms: Build your "rooms" as separate scenes (
    Room1.tscn
    ,
    Room2.tscn
    ) and instance them into the generated layout for handcrafted quality within procedural layouts.
  • Navigation: Rebake
    NavigationRegion2D
    at runtime after generating the dungeon layout if using 2D navigation.
  • 带种子的游戏局:始终用种子初始化
    RandomNumberGenerator
    。这样玩家可以分享特定的游戏局布局。
  • ResourceSaver:使用
    ResourceSaver
    实现元进度保存,但要注意深度嵌套资源中的循环引用问题。
  • 场景作为房间:将“房间”制作成独立场景(如
    Room1.tscn
    Room2.tscn
    ),并将它们实例化到生成的布局中,在程序化布局中融入手工设计的质量。
  • 导航系统:如果使用2D导航,在地牢布局生成完成后,需重新烘焙
    NavigationRegion2D

Advanced Techniques

进阶技巧

  • Synergy System: Tag items (
    fire
    ,
    projectile
    ,
    companion
    ) and check for tag combinations to create emergent power-ups.
  • Director AI: An invisible "Director" system that tracks player health/stress and adjusts spawn rates dynamically (like Left 4 Dead).
  • 协同系统:给道具添加标签(如
    fire
    projectile
    companion
    ),通过检测标签组合来创造出 Emergent 的增益效果。
  • 导演AI:一个隐形的“导演”系统,跟踪玩家的生命值/压力值,动态调整敌人刷新速率(类似《求生之路》)。

Reference

参考资料

  • Master Skill: godot-master
  • 核心技能:godot-master