Questify for Godot 4

repository·main·Indexed 42 days ago

https://github.com/thewalruzz/godot-questify

A graph-based quest editor and manager for Godot 4. It allows developers to design complex quest lines using nodes and manage their lifecycle via a singleton, featuring a query-based condition system, parametrized quests, and support for both GDScript and C#.

Tokens
2.2K
Snippets
7
Records
13
Agent score
4%

What's inside Questify

  1. Handle condition queries

    main

    Questify uses an architecture-agnostic query system. Instead of checking variables directly, Questify emits a condition_query_requested signal. You must connect to this signal and implement the logic to check your game data. If the condition is met, call requester.set_completed(true).

    Questify.condition_query_requested.connect(
      func(type: String, key: String, value: Variant, requester: QuestCondition):
        if type == "variable":
          if get_value(key) == value:
            requester.set_completed(true)
    )
  2. Use metadata in Objective Nodes

    main

    Objective nodes can store dynamic data using Godot's built-in metadata system. This is useful for storing information like map coordinates for quest markers. You can retrieve this data in GDScript using get_meta().

    # Example: retrieving a custom 'marker' metadata value
    objective.get_meta("marker")
  3. Understand Quest Node states

    main

    Nodes in a quest graph operate using two primary states:

    • Active: The node is currently being processed. For most nodes, this is persistent once activated. However, Objective nodes become inactive once they are completed. Start Nodes activate the quest flow.
    • Completed: The node has satisfied its requirements and will no longer be processed. For Objective nodes, completion only occurs when all attached Condition nodes return true.
  4. Understand Questify node types

    main

    Questify quests are built using a graph of specialized nodes. Each node type serves a specific role in the quest lifecycle:

    • Start Node: The mandatory single entry point. A quest starts when this node's active state is set to true.
    • End Node: The mandatory single node marking quest completion. A quest is complete when all connected previous nodes are finished.
    • Objective Node: The primary building block representing player tasks. They store descriptions, metadata, and an optional flag. If optional is true, any subsequent node treats this objective as completed regardless of its actual state.
    • Condition Nodes: Used to gate objectives. By default, all attached conditions must pass. Use Any Condition to require only one to pass, or Not Condition to negate a condition's result.
    • Branching Nodes: Used to manage multiple quest paths.
      • Any Previous: Completes when at least one parent objective is finished.
      • Exclusive Branch Connector: Used for long branches to ensure only one path remains active. You attach the outputs of the first objectives of each branch to this node's inputs; completing one branch activates the connector, which in turn disables the other branches.
    • Conditional Branch Node: Used to hide or show quest branches based on specific conditions. Objectives attached to the output are only available if the conditions are met.
  5. Implement Questify in C#

    main

    Questify provides a C# wrapper for the main singleton. While most methods follow the GDScript API using PascalCase instead of snake_case, there are specific patterns for instantiation, condition management, and signal connection.

    // Instantiating a quest from a Resource
    Questify.Instantiate(Resource questResource);
    
    // Setting a condition's state
    Questify.SetConditionCompleted(Resource questCondition, bool complete);
    
    // Starting a quest
    Questify.StartQuest(questResource);
    
    // Getting the resource path
    string path = Questify.GetResourcePath(Resource quest);
  6. Use parametrized quests

    main

    You can inject dynamic values into a quest by passing a Dictionary of parameters when starting it. This allows you to reuse the same quest graph for different targets (e.g., different NPCs or items).

    In the Editor: In a Condition Node, set the value type to String and use the syntax {param_name} in the text field. All occurrences will be replaced with the value from the dictionary.

    In Code:

    Questify.start_quest(quest, { "param_name": "Rat" })

    Formatting Descriptions: Quest descriptions are not automatically formatted. Use the built-in format() method to inject parameters into objective descriptions:

    quest_objective.description.format(quest.params)
  7. Install Questify

    main

    To install Questify in your Godot 4 project:

    1. Copy the addons/questify directory into your project's addons directory.
    2. Enable the Questify plugin in Godot's Project Settings.
    3. Restart the editor.

    You can access the graph editor by clicking the Questify button at the top of the editor.

  8. Start a quest in code

    main

    To start a quest, you must first load the QuestResource and call .instantiate() on it. This creates a unique instance so the original resource remains unmodified. Then, pass that instance to the Questify singleton using start_quest().

    @export var quest: QuestResource
    
    func _ready() -> void:
      var instance := quest.instantiate()
      Questify.start_quest(instance)
  9. Configure condition polling and updates

    main

    Questify handles condition checks in two ways:

    1. Polling (Default): Periodically sends query requests. The interval is configurable in Project Settings -> Questify -> General -> Update Interval (default is 0.5s).
    2. On-Demand: You can disable polling in Project Settings -> Questify -> General -> Update Polling. If disabled, you must manually call Questify.update_quests() whenever your game data changes to trigger immediate condition checks.

    You can also pause/resume polling via code: Questify.toggle_update_polling(false)

  10. Serialize and deserialize quest state

    main

    Holistic Serialization

    To save or load the entire state of all active quests at once, use the Questify singleton:

    var serialized_state := Questify.serialize() # Returns an Array
    Questify.deserialize(serialized_state)

    Manual Serialization

    If you need to manage quests individually, use the methods on QuestResource:

    1. Call quest_instance.serialize() to get a Dictionary.
    2. To restore, instantiate the resource, call instance.deserialize(data), and then add it to the singleton using Questify.set_quests([instance]).
    # Holistic approach
    var serialized_state := Questify.serialize()
    Questify.deserialize(serialized_state)
  11. Observe quest and objective signals

    main

    The Questify singleton provides signals to track the lifecycle of quests and objectives. Use these to update your UI or trigger game events.

    • quest_started(quest: QuestResource): Emitted when a new quest begins.
    • quest_objective_added(quest: QuestResource, objective: QuestObjective): Emitted when a new objective becomes active.
    • quest_objective_completed(quest: QuestResource, objective: QuestObjective): Emitted when an objective is finished and becomes inactive.
    • quest_completed(quest: QuestResource): Emitted when the quest reaches its end node.
  12. Get active and completed quests

    main

    You can retrieve lists of quests currently in progress or already finished using the following methods on the Questify singleton:

    • Questify.get_active_quests(): Returns an Array of active quests.
    • Questify.get_completed_quests(): Returns an Array of completed quests.