Skip to content

A plugin for Godot game engine that functions as a framework for Rule-Based Systems. This was developed as the capstone project for my Computer Science bachelor degree at USP.

License

Notifications You must be signed in to change notification settings

rvbatt/rule-based-godot

Repository files navigation

Rule-Based Godot

A plugin for Godot Engine 4.1+ that functions as a Rule-Based System framework. This was developed as the capstone project for my Computer Science bachelor degree at USP.

Table of contents

  1. Installation
    1. Using gd-plug
    2. Directly from the Repository
  2. Adding rules to a scene
    1. Creating rules in the Inspector
    2. Declaring rules in the RulesEditor
  3. Resources
    1. Available
    2. Creating new ones
  4. API
    1. RuleBasedSystem
    2. AbstractArbiter
    3. AbstractBooleanMatch
    4. AbstractAtomicMatch
    5. AbstractMatch
    6. AbstractAction
    7. Rule
    8. RuleList
    9. RuleBasedResource
    10. RuleDB
  5. JSON syntax
    1. How to read the syntax documentation
    2. Syntax documentation

Installation

Using gd-plug

Supposing the plugin manager is already installed (check its documentation for details), you need only to:

  1. Add the plug.gd file below in the project root. If the file already exists, add the new line in the _plugging() function:
# plug.gd
extends "res://addons/gd-plug/plug.gd"

func _plugging():
	plug("rvbatt/rule-based-godot", {"include": ["addons/rule_based_godot/", "test_scenes/", "script_templates/"]})
  1. Still on the project root, run the following command in the terminal:
godot --no−window -s plug.gd install
  1. On Godot's Editor, enter the Project menu, click in ProjectSettings, got to the Plugins tab and enable the Rule-Based Godot plugin.
Plugins tab with Rule-Based Godot enabled
Project > ProjectSettings > Plugins > Rule-Based Godot (Enable)

Directly from the Repository

  1. Download or clone this repository. Use the last version on the main branch. If you decide to download, the content will come in a .zip file, so you will need to unpack it

  2. Copy the addons/rule-based-godot/ folder to your Godot's project root. If the addons folder already exists, copy only the rule_based_godot subfolder and place it there

  3. (Recommend) Copy the test_scenes and script_templates folders as well. If the templates folder already exists, copy only its subfolders

  4. Follow the same instructions given on the Step 3 of Installation Using gd-plug

Adding rules to a scene

  1. Add a RuleBasedSystem node to the scene

    Recomendation: children of the RuleBasedSystem will have a smaller relative path, so make the system node be the parent of the nodes you want to control

  2. Set the type of Iteration Update that the system will use:
    • Every frame: iterates every physics frame. Use this with caution, a large set of rules can take a while to run through
    • On Timer: iterates every wait_time seconds, which can be defined in the Timer category on the Inspector
    • On Call: only iterates when the method iterate() is called explicity. You can connect external signals to this method if you want to have a better control over it
  3. Add an Arbiter. Don't choose the AbstractArbiter, because that's the abstract class and it doesn't implement the necessary function

    Recomendation: save and Quick Load a resource file instead of creating a new arbiter

Inspector with basic RuleBasedSystem configuration
Inspector after following steps 1 to 3

Now you can choose to use either the Inspector, with a graphical interface, or the Rules Editor bottom panel, with a code driven approach, to declare the rules

Creating rules in the Inspector

Since the RuleList and all of its components are resources, you can save and load any part of the data structure, sharing common rules, conditions or actions between several systems. The following steps talk about creating new ones from scratch, but you can skip any creation step if you load an existing resource instead.

  1. Create a new RuleList and start adding rules on the array, as many as you need
  2. For each Rule, create its components (never choose the ones that start with Abstract):
    1. Create a Condition. If you choose a boolean match (NOT, OR, AND), you can then create its subconditions, repeating this step. If you choose a datum match, edit its properties, and don't forget to expand the groups to check them out
    2. Add an Action to the array, then edit its properties, including the ones on the groups. You can repeat this step as many times as you want
  3. (optional) Once you have defined the behavior you want, you can save a part or all of the list of rules as a resource file. You can save a Condition, an individual Action, a Rule or the whole RuleList

Obs.: You can give Rules, Matches and Actions a name, by editing the Name property on the Resource group (it's actually resource_name)

Inspector with rule declaration
Inspector after following steps 4 and 5

Declaring rules in the Rules Editor

You can define rules in the Rules Editor bottom panel using a JSON syntax. The panel connects itself to the last RuleBasedSystem that you've clicked, which will be the one showing on the Inspector.

  1. Open the Rules Editor and use the Reset button if there is some leftover text in the editor
RulesEditor default text
RulesEditor after step 4
  1. Add new rules using the New Rule button, as many as you need. Be careful with the position of the cursor, because the template will be inserted right at that position
  2. For each rule, replace the "condition" and "actions" placeholders, using the corresponding buttons. Always be mindful of the cursor position
    1. Delete the "condition", keep the cursor at that position and click the New Match button. Choose one of the options that popped up and click it. The format of the selected match will be inserted in the editor, where the cursor was
    2. Follow the syntax explained in the documentation and replace the match's placeholders with the configuration you want. If you chose a boolean match, there will be another "condition" or "conditions", so repeat the previous step
    3. Delete "actions" and click the New action button. Click one option. The JSON format of that type of action will be inserted where the cursor was
    4. Replace the action's placeholders with the action properties, following the syntax
    5. Repeat steps iii. (without erasing the placeholder) and iv. to add more actions
RulesEditor with match and action templates
RulesEditor in the middle of step 6
  1. When your rules are done, click the Apply button to set the current RuleBasedSystem's rule list. If the syntax is wrong, a JSON parsing error will appear and the "apply" will abort
  2. (optional) Save the created text in a .json file in case you want to reuse some part later
RulesEditor with rule declaration
RulesEditor after steps 6 and 7

Resources

Available

Type Identifier Description
Arbiters FirstApplicable selects the first satisfied rule (assumes they are ordered by priority)
LeastRecentlyUsed selects the satisfied rule that was triggered the longest time ago
Boolean Matches NOT logic gate
AND multiple-input logic gate
OR multiple-input logic gate
Atomic Matches Numeric tests if a numeric value, obtained through a property or method call, is in an interval
String tests if a string, obtained through a property or method call, is equal to a constant
Hierarchy tests if two nodes have a certain relation: the first is "Parent of" the second, the first is "Sibling of" the second or the first is "Child of" the second
Distance tests if the distance between the origin of two nodes is in an interval
AreaDetection tests if there are objects (specific ones, or any) in an area
DistinctVariables applies a substitution that makes sure every listed variable has a distinct value
Actions SetProperty sets the property of a node
CallMethod calls the method of a node passing the arguments in a vector
EmitSignal adds a signal to a Node, if it doesn't have it, and emits it, passing the arguments in a vector

Creating new ones

To add new types of: Arbiters, Boolean Matches, Atomic Matches and/or Actions

  1. Copy the script_templates folder to your Godot's project root. If this folder already exists, copy all of its subfolders
  2. Create a new script and select the appropriate class to inherit from. Then, check the template box and select the "New ___"
    • Arbiter strategy: inherits from: AbstractArbiter
    • Multiple-entry Boolean Match: inherits from AbstractBooleanMatch
    • Atomic Match: inherits from AbstractAtomicMatch
    • Action command: inherits from AbstractAction
Creating a New Action using the template
Template option when creating a script
  1. Follow the instructions given on the template. For actions and matches, there are some flags that define which functions need to be implemented:

    • Actions:
    Action Flag Methods that MUST be implemented Methods that could be overriden
    trigger(bindings)
    Agent Nodes trigger_node(agent_node, bindings) get_agent_nodes(bindings)
    • Atomic Matches:
    Atomic Match Flags Methods that MUST be implemented Methods that could be overriden
    is_satisfied(bindings)
    Tester Node node_satisfies_match(node, bindings) get_candidates()
    Tester Node, Data Based Node get_data(node), data_satisfies_match(data) get_candidates()
    Tester Node, Data Based Node, Get Node Data Preset data_satisfies_match(data) get_candidates()

API

RuleBasedSystem

Extends Timer

Properties

  • iteration_update: IterationUpdate

Should the system iterate ON_CALL, ON_TIMER or EVERY_FRAME

The type of rule arbitration strategy used

The list of rules

Methods

  • iterate() -> Array

Iterates through the rule_list, gathers the satisfied ones, asks the arbiter to select one of them, triggers the selected rule and returns the results


AbstractArbiter

Extends Resource

  • select_rule_to_trigger(satisfied_rules: Array[Rule]) -> Rule

Receives an array of satisfied rules and returns one of them


AbstractBooleanMatch

Extends AbstractMatch

The matches children of this boolean operator


AbstractAtomicMatch

Extends AbstractMatch

Properties

  • Tester_Node: bool

Configuration flag: is this match node-based?

  • Data_Based_Node: bool

Configuration flag: is this match based on the data from a node? Can only be active if Tester_Node = true

  • Get_Node_Data_Preset: bool

Configuration flag: should this match use preset ways of getting data from a node? Can only be active if Data_Based_Node = true

Methods

  • _preset_node_path(path_variable: StringName, node_variable: StringName) -> void

Sets the node_variable with the node found on the path_variable at the moment the system is ready and begins setup

  • _pre_connect(node_variable: StringName, signal_name: StringName, function: Callable) -> void

Connects the signal signal_name to the node's method defined by node_variable.function. Does this soon after _preset_node_path

  • _get_candidates() -> Array[Node]

Returns the first batch of candidates for this match. If Tester_Node = true and the target is a wildcard, use the node search groups. If there are no groups, defaults to children of the RuleBasedSystem

  • _node_satisfies_match(tester_node: Node, bindings: Dictionary) -> bool

If Data_Based_Node = true, uses _get_data and _data_satisfies_match to return true if the node satisfies the match, and false otherwise. Also saves the data variable, if Should Retrieve Data option was on

  • _data_satisfies_match(data: Variant) -> bool:

If Data_Based_Node = true, return true if the data satisfies the math and false otherwise

  • _get_data(tester_node: Node) -> Variant:

If Get_Node_Data_Preset = true, extracts the data from the tester_node, whether by getting a property value or the return of a mehod call


AbstractMatch

Extends RuleBasedResource

  • is_satisfied(bindings) -> bool

Receives the Dictionary of bindings between variable names and possible candidates. Returns true if the match is satisfied with current variable substitutions, and false otherwise


AbstractAction

Extends RuleBasedResource

Properties

  • Agent_Nodes: bool

Configuration flag: is this action node-based?

Methods

  • _pre_add_signal(name_var: StringName, param_to_type_var: StringName) -> void

Adds a user signal with the name defined in the variable named name_var and parameters defined by the variable named param_to_type_var. See Object.add_user_signal for more information

  • trigger(bindings: Dictionary) -> Array

Receives the Dictionary of bindings between variable names and possible candidates. Returns the results from triggering this action. If Agent_Nodes= true, uses _get_agent_nodes and _trigger_node to trigger all necesary nodes

  • _trigger_node(agent_node: Node, bindings: Dictionary) -> Variant:

If Agent_Nodes= true, triggers the agent_node using the substitutions defined by bindings. Returns a value that represents the action's result

  • _get_agent_nodes(bindings: Dictionary) -> Array

If Agent_Nodes = true, receives the Dictionary of bindings between variable names and possible candidates. Returns all the nodes that must perform the action


Rule

Extends RuleBasedResource

Properties

Points to the root of the tree of matches that defines the condition

The actions triggered by this rule. Follows execution order

  • _bindings: Dictionary

Associations between variable names and possible substitutions

Methods

  • condition_satisfied() -> bool

Clears the _bindings and returns whether the condition is satisfied

  • trigger_actions() -> Array

Triggers all the actions in the order they appear in the array. Returns the list of all actions results


RuleList

Extends RuleBasedResource

Properties

  • rules: Array[Rule]

The rules, ordered by priority (descending)

Methods

  • satisfied_rules() -> Array[Rule]

Returns all the rules that are satisfied in the current system iteration, ordered by priority (descending)


RuleBasedResource

Extends Resource

Properties

A reference to the node of the system this resource belongs to

A reference to the Rules DataBase used in the current scope (belongs to the RuleList)

Methods

  • json_format() -> String

Returns a String with a template for this resource's JSON representation format

  • to_json_repr() -> Variant

Returns the JSON representation of this resource, which can be a Dictionary (RuleList and Rule) or an Array (AbstractBooleanMatch, AbstractAtomicMatch and AbstractAction)

  • build_from_repr(json_repr) -> void

Receives a JSON representation equal to the one defined in to_json_repr() and builds itself with it, setting the corresponding properties


RuleDB

Extends Object

Properties

  • actions: Dictionary

Associations between the action identifier (without suffix "Action") and the script path

  • matches: Dictionary

Associations between the match identifier (without suffix "Match") and the script path

Methods

Receives the JSON representation of a match and returns the corresponding object. Uses recursion for boolean matches

Receives the JSON representation of a action and returns the corresponding object

  • rule_from_json(json_repr: Dictionary) -> Rule:

Receives the JSON representation of a rule and returns the corresponding object. Uses match_from_json and action_from_json


JSON syntax

How to read the syntax documentation

  • The term condition can be replaced by a NOTMatch, subtypes of MultiBoolMatch or subtypes of DatumMatch.

  • The ID of a component is its class_name without the "Action" or "Match" suffix.

Here are the patterns adopted to represent the syntax:

  • "constant": if something is between " ", then it is a constant and it should be written the exact same way, including the quotation marks

    Ex.: "Fixed" should be copied as "Fixed" and not be replaced

  • ?variable: any name prefixed by an ? represents a variable, or wild card. It can renamed to anything you want, but the string must contain the question mark

    Ex.: ?data represents a variable named data, but it can be replaced by ?example

  • [items]: represents an array of arbitrary length, including empty, containing elements of type item separated by commas

    Ex.: [fruits] can be replaced by [], [apple] or [apple, banana, mango]

  • items...: represents an arbitrary number (which can be zero) of elements of type item separated by commas, but not inside an array

    Ex.: colors... can be replaced by red or red, green, blue or by nothing

  • {keys: values}: represents a dictionary where the keys have the type key and the values are of the type value, with arbitrary size, including empty

    Ex.: {characters: classes} can be replaced by {} or by {Alice: mage, Bob: fighter}

    Note: If the terms inside { } are on the singular form (don't end with s), then the dictionary must contain only one entry

  • <optional>: if something is between < >, you can choose to include it or not

    Ex.: <adjective>, noun can be replaced by red, ball or just ball

    Note: When used on templates, it indicates that the subtypes may or may not have this element

  • (choiceA|choiceB|choiceC.1, choiceC.2): you need to pick one of the choices separated by | and between ( ). One choice can have several items separated by commas

    Ex.: (melee_weapon|ranged_weapon, ammunition) can be replaced by sword or by bow, arrow

Syntax documentation

  • Rule List: {"Rules": [rules]}
  • Rule: {"if": condition, "then": [actions]}
  • Matches:
    • NOT: ["NOT", condition]
    • (Multiple-entry) Boolean template: [ID, [conditions]]
      • AND: ["AND", [conditions]]
      • OR: ["OR", [conditions]]
    • Atomic template: [ID, <?data>, vars..., (tester_path|?wild, [groups]) <, (prop|method, [args])>]
      • DistinctVariables: ["DistinctVariables", [distinct_variables]]
      • Area Detection: ["AreaDetection", area_path, (tester_path|?wild, [groups])]

        Obs: area_path must be the path to either an Area2D or Area3D

      • Distance: ["Distance", <?dist,> source_path, min_distance, max_distance, (tester_path|?wild, [groups])]

        Obs: min_distance e max_distance are float number, "inf" or "-inf"

      • Hierarchy: ["Hierarchy", source_path, ("Parent of"|"Sibling of"|"Child of"), (tester_path|?wild, [groups])]
      • Numeric: ["Numeric", <?number,> min_value, max_value, (tester_path|?wild, [groups]), (prop|method, [args])

        Obs: min_value e max_value are float number, "inf" or "-inf"

      • String: ["String", <?string,> string_value, (tester_path|?wild, [groups]), (prop|method, [args])
  • Action template: [ID, (agent_path|?wild|[groups]|), vars...]
    • Set Property: ["SetProperty", (agent_path|?wild|[groups]|), {property: value}]

      Obs: {property: value} only has only one entry

    • Call Method: ["CallMethod", (agent_path|?wild|[groups]|), method, [args]]
    • Emit Signal: ["EmitSignal", (agent_path|?wild|[groups]|), signal <, {params: types}, [args]>]

      Obs: {params: types} is a dictionary with the name of the signal parameters as keys and an arbitrary element with the same type as the corresponding argument as value. For example: {number_param: 1, string_param: ""}

About

A plugin for Godot game engine that functions as a framework for Rule-Based Systems. This was developed as the capstone project for my Computer Science bachelor degree at USP.

Resources

License

Stars

Watchers

Forks

Packages

No packages published