godot-quest-system

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Quest System

任务系统

Resource-based data, signal-driven updates, and AutoLoad coordination define scalable quest architectures.
基于资源的数据、信号驱动的更新以及AutoLoad协同构成了可扩展的任务系统架构。

Available Scripts

可用脚本

quest_manager.gd

quest_manager.gd

Expert AutoLoad quest tracker with objective progression and reward distribution.
专业的AutoLoad任务追踪器,支持目标进度管理和奖励分发。

quest_graph_manager.gd

quest_graph_manager.gd

Runtime manager for graph-based quests. Tracks objectives and node progression.
基于图结构的任务运行时管理器。追踪目标和节点进度。

NEVER Do in Quest Systems

任务系统中的绝对禁忌

  • NEVER store quest data in nodes — Quest progress in
    player.gd
    variables? Loss on scene reload. Use Resource-based Quests OR AutoLoad singleton.
  • NEVER use strings for quest IDs without registry
    update_objective("kil_bandts", ...)
    typo = silent failure. Use constants OR validate IDs against registry.
  • NEVER forget to disconnect signals — Quest completed but signal still connected? Quest completes again on next update = duplicate rewards. Disconnect in
    _on_quest_completed()
    .
  • NEVER poll for objective updates — Checking
    if enemy_count == 10
    every frame = wasteful. Use signals:
    enemy.died.connect(quest_manager.on_enemy_killed)
    .
  • NEVER skip save/load for quests — Player completes quest 5, game restarts, quest resets? Frustration. MUST persist
    active_quests
    +
    completed_quests
    arrays.
  • NEVER use
    all()
    for objectives without null check
    objectives.all(func(obj): return obj.is_complete())
    with null objectives? Crash. Validate array contents first.

gdscript
undefined
  • 绝对不要在节点中存储任务数据 — 把任务进度存在
    player.gd
    的变量里?场景重载时会丢失数据。请使用基于资源的任务或AutoLoad单例。
  • 绝对不要在没有注册表的情况下使用字符串作为任务ID
    update_objective("kil_bandts", ...)
    拼写错误会导致静默失败。请使用常量或对照注册表验证ID。
  • 绝对不要忘记断开信号 — 任务已完成但信号仍处于连接状态?下次更新时任务会再次完成,导致奖励重复。请在
    _on_quest_completed()
    中断开信号。
  • 绝对不要轮询目标更新 — 每帧检查
    if enemy_count == 10
    非常浪费性能。请使用信号:
    enemy.died.connect(quest_manager.on_enemy_killed)
  • 绝对不要跳过任务的保存/加载逻辑 — 玩家完成了任务5,游戏重启后任务重置?这会让玩家感到挫败。必须持久化
    active_quests
    completed_quests
    数组。
  • 绝对不要在没有空值检查的情况下对目标使用
    all()
    — 当目标数组中存在空值时,
    objectives.all(func(obj): return obj.is_complete())
    会导致崩溃。请先验证数组内容。

gdscript
undefined

quest.gd

quest.gd

class_name Quest extends Resource
signal progress_updated(objective_id: String, progress: int)signal completed
@export var quest_id: String @export var quest_name: String @export_multiline var description: String @export var objectives: Array[QuestObjective] = [] @export var rewards: Array[QuestReward] = [] @export var required_level: int = 1
func is_complete() -> bool: return objectives.all(func(obj): return obj.is_complete())
func check_completion() -> void: if is_complete(): completed.emit()
undefined
class_name Quest extends Resource
signal progress_updated(objective_id: String, progress: int)signal completed
@export var quest_id: String @export var quest_name: String @export_multiline var description: String @export var objectives: Array[QuestObjective] = [] @export var rewards: Array[QuestReward] = [] @export var required_level: int = 1
func is_complete() -> bool: return objectives.all(func(obj): return obj.is_complete())
func check_completion() -> void: if is_complete(): completed.emit()
undefined

Quest Objectives

任务目标

gdscript
undefined
gdscript
undefined

quest_objective.gd

quest_objective.gd

class_name QuestObjective extends Resource
enum Type { KILL, COLLECT, TALK, REACH }
@export var objective_id: String @export var type: Type @export var target: String # Enemy name, item ID, NPC name, location @export var required_amount: int = 1 @export var current_amount: int = 0
func progress(amount: int = 1) -> void: current_amount += amount current_amount = mini(current_amount, required_amount)
func is_complete() -> bool: return current_amount >= required_amount
undefined
class_name QuestObjective extends Resource
enum Type { KILL, COLLECT, TALK, REACH }
@export var objective_id: String @export var type: Type @export var target: String # Enemy name, item ID, NPC name, location @export var required_amount: int = 1 @export var current_amount: int = 0
func progress(amount: int = 1) -> void: current_amount += amount current_amount = mini(current_amount, required_amount)
func is_complete() -> bool: return current_amount >= required_amount
undefined

Quest Manager

任务管理器

gdscript
undefined
gdscript
undefined

quest_manager.gd (AutoLoad)

quest_manager.gd (AutoLoad)

extends Node
signal quest_accepted(quest: Quest) signal quest_completed(quest: Quest) signal objective_updated(quest: Quest, objective: QuestObjective)
var active_quests: Array[Quest] = [] var completed_quests: Array[String] = []
func accept_quest(quest: Quest) -> void: if quest.quest_id in completed_quests: return
active_quests.append(quest)
quest.completed.connect(func(): _on_quest_completed(quest))
quest_accepted.emit(quest)
func _on_quest_completed(quest: Quest) -> void: active_quests.erase(quest) completed_quests.append(quest.quest_id)
# Give rewards
for reward in quest.rewards:
    reward.grant()

quest_completed.emit(quest)
func update_objective(quest_id: String, objective_id: String, amount: int = 1) -> void: for quest in active_quests: if quest.quest_id == quest_id: for obj in quest.objectives: if obj.objective_id == objective_id: obj.progress(amount) objective_updated.emit(quest, obj) quest.check_completion() return
func get_active_quest(quest_id: String) -> Quest: for quest in active_quests: if quest.quest_id == quest_id: return quest return null
undefined
extends Node
signal quest_accepted(quest: Quest) signal quest_completed(quest: Quest) signal objective_updated(quest: Quest, objective: QuestObjective)
var active_quests: Array[Quest] = [] var completed_quests: Array[String] = []
func accept_quest(quest: Quest) -> void: if quest.quest_id in completed_quests: return
active_quests.append(quest)
quest.completed.connect(func(): _on_quest_completed(quest))
quest_accepted.emit(quest)
func _on_quest_completed(quest: Quest) -> void: active_quests.erase(quest) completed_quests.append(quest.quest_id)
# Give rewards
for reward in quest.rewards:
    reward.grant()

quest_completed.emit(quest)
func update_objective(quest_id: String, objective_id: String, amount: int = 1) -> void: for quest in active_quests: if quest.quest_id == quest_id: for obj in quest.objectives: if obj.objective_id == objective_id: obj.progress(amount) objective_updated.emit(quest, obj) quest.check_completion() return
func get_active_quest(quest_id: String) -> Quest: for quest in active_quests: if quest.quest_id == quest_id: return quest return null
undefined

Quest Triggers

任务触发器

gdscript
undefined
gdscript
undefined

Example: Kill quest integration

Example: Kill quest integration

enemy.gd

enemy.gd

func _on_died() -> void: QuestManager.update_objective("kill_bandits", "kill_bandit", 1)
func _on_died() -> void: QuestManager.update_objective("kill_bandits", "kill_bandit", 1)

Example: Collection integration

Example: Collection integration

item_pickup.gd

item_pickup.gd

func _on_collected() -> void: QuestManager.update_objective("gather_herbs", "collect_herb", 1)
func _on_collected() -> void: QuestManager.update_objective("gather_herbs", "collect_herb", 1)

Example: Talk integration

Example: Talk integration

npc.gd

npc.gd

func interact() -> void: DialogueManager.start_dialogue(dialogue_id) QuestManager.update_objective("find_elder", "talk_to_elder", 1)
undefined
func interact() -> void: DialogueManager.start_dialogue(dialogue_id) QuestManager.update_objective("find_elder", "talk_to_elder", 1)
undefined

Quest UI

任务UI

gdscript
undefined
gdscript
undefined

quest_ui.gd

quest_ui.gd

extends Control
@onready var quest_list := $QuestList
func _ready() -> void: QuestManager.quest_accepted.connect(_on_quest_accepted) QuestManager.objective_updated.connect(_on_objective_updated) refresh_ui()
func refresh_ui() -> void: for child in quest_list.get_children(): child.queue_free()
for quest in QuestManager.active_quests:
    var quest_entry := create_quest_entry(quest)
    quest_list.add_child(quest_entry)
func create_quest_entry(quest: Quest) -> Control: var entry := VBoxContainer.new()
var title := Label.new()
title.text = quest.quest_name
entry.add_child(title)

for obj in quest.objectives:
    var obj_label := Label.new()
    obj_label.text = "%s: %d/%d" % [obj.target, obj.current_amount, obj.required_amount]
    entry.add_child(obj_label)

return entry
undefined
extends Control
@onready var quest_list := $QuestList
func _ready() -> void: QuestManager.quest_accepted.connect(_on_quest_accepted) QuestManager.objective_updated.connect(_on_objective_updated) refresh_ui()
func refresh_ui() -> void: for child in quest_list.get_children(): child.queue_free()
for quest in QuestManager.active_quests:
    var quest_entry := create_quest_entry(quest)
    quest_list.add_child(quest_entry)
func create_quest_entry(quest: Quest) -> Control: var entry := VBoxContainer.new()
var title := Label.new()
title.text = quest.quest_name
entry.add_child(title)

for obj in quest.objectives:
    var obj_label := Label.new()
    obj_label.text = "%s: %d/%d" % [obj.target, obj.current_amount, obj.required_amount]
    entry.add_child(obj_label)

return entry
undefined

Best Practices

最佳实践

  1. Signal-Driven - Emit events, systems listen
  2. Save Progress - Track completed quests
  3. Validation - Check prerequisites before accepting
  1. 信号驱动 - 触发事件,由系统监听
  2. 保存进度 - 追踪已完成的任务
  3. 验证 - 接受任务前检查前置条件

Reference

参考

  • Related:
    godot-dialogue-system
    ,
    godot-save-load-systems
  • 相关:
    godot-dialogue-system
    ,
    godot-save-load-systems

Related

相关

  • Master Skill: godot-master
  • 大师技能:godot-master