Releases: csprance/gecs
3.4.1
Full Changelog: 3.3.0...3.4.0
Full Changelog: 3.4.0...3.4.1
3.3.0
Full Changelog: 3.2.0...3.3.0
3.2.0
Full Changelog: 3.1.0...3.2.0
3.1.2
Fix a small bug with the combine query
Full Changelog: 3.1.0...3.1.2
3.1.1
Component Queries
Component Queries in GECS
Component Queries provide a powerful way to filter entities not just based on the presence of components but also on the data within those components. This allows for more fine-grained control when querying entities in your game or application.
Introduction
In the Entity-Component-System (ECS) architecture, entities are passive data holders defined by their components. While querying entities based on their components is straightforward, there are cases where you need to filter entities based on the properties of those components.
Component Queries enable you to specify conditions on component properties using comparison operators.
Using Component Queries with QueryBuilder
The QueryBuilder
class allows you to construct queries to retrieve entities that match certain criteria. With component queries, you can specify conditions on component properties within with_all
and with_any
methods.
Syntax
A component query is a Dictionary
that maps a component class to a query Dictionary
specifying property conditions.
{ ComponentClass: { property_name: { operator: value } } }
Supported Operators
_eq
: Equal to_ne
: Not equal to_gt
: Greater than_lt
: Less than_gte
: Greater than or equal to_lte
: Less than or equal to_in
: Value is in a list_nin
: Value is not in a list
Examples
1. Basic Component Query
Retrieve entities where C_TestC.value
is equal to 25
.
var result = QueryBuilder.new(world).with_all([
{ C_TestC: { "value": { "_eq": 25 } } }
]).execute()
2. Multiple Conditions on a Single Component
Retrieve entities where C_TestC.value
is between 20
and 25
.
var result = QueryBuilder.new(world).with_all([
{ C_TestC: { "value": { "_gte": 20, "_lte": 25 } } }
]).execute()
3. Combining Component Queries and Regular Components
Retrieve entities that have C_TestD
component and C_TestC.value
greater than 20
.
var result = QueryBuilder.new(world).with_all([
C_TestD,
{ C_TestC: { "value": { "_gt": 20 } } }
]).execute()
4. Using with_any
with Component Queries
Retrieve entities where C_TestC.value
is less than 15
or C_TestD.points
is greater than or equal to 100
.
var result = QueryBuilder.new(world).with_any([
{ C_TestC: { "value": { "_lt": 15 } } },
{ C_TestD: { "points": { "_gte": 100 } } }
]).execute()
5. Using _in
and _nin
Operators
Retrieve entities where C_TestC.value
is either 10
or 25
.
var result = QueryBuilder.new(world).with_all([
{ C_TestC: { "value": { "_in": [10, 25] } } }
]).execute()
6. Complex Queries
Retrieve entities where:
C_TestC.value
is greater than or equal to25
, andC_TestD.points
is greater than75
or less than30
, and- Excludes entities with
C_TestE
component.
var result = QueryBuilder.new(world).with_all([
{ C_TestC: { "value": { "_gte": 25 } } }
]).with_any([
{ C_TestD: { "points": { "_gt": 75 } } },
{ C_TestD: { "points": { "_lt": 30 } } }
]).with_none([C_TestE]).execute()
Important Notes
-
Component Queries with
with_none
: Component queries are not supported with thewith_none
method. This is because querying properties of components that should not exist on the entity doesn't make logical sense. Usewith_none
to exclude entities that have certain components.# Correct usage of with_none var result = QueryBuilder.new(world).with_none([C_Inactive]).execute()
-
Empty Queries Match All Instances of the Component
If you provide an empty query dictionary for a component, it will match all entities that have that component, regardless of its properties.
# This will match all entities that have C_TestC component var result = QueryBuilder.new(world).with_all([ { C_TestC: {} } ]).execute()
-
Non-existent Properties
If you query a property that doesn't exist on the component, it will not match any entities.
# Assuming 'non_existent' is not a property of C_TestC var result = QueryBuilder.new(world).with_all([ { C_TestC: { "non_existent": { "_eq": 10 } } } ]).execute() # result will be empty
Comprehensive Example
Here's a full example demonstrating several component queries:
# Setting up entities with components
var entity1 = Entity.new()
entity1.add_component(C_TestC.new(25))
entity1.add_component(C_TestD.new(100))
var entity2 = Entity.new()
entity2.add_component(C_TestC.new(10))
entity2.add_component(C_TestD.new(50))
var entity3 = Entity.new()
entity3.add_component(C_TestC.new(25))
entity3.add_component(C_TestD.new(25))
var entity4 = Entity.new()
entity4.add_component(C_TestC.new(30))
world.add_entity(entity1)
world.add_entity(entity2)
world.add_entity(entity3)
world.add_entity(entity4)
# Query: Entities with C_TestC.value == 25 and C_TestD.points > 50
var result = QueryBuilder.new(world).with_all([
{ C_TestC: { "value": { "_eq": 25 } } },
{ C_TestD: { "points": { "_gt": 50 } } }
]).execute()
# result will include entity1
Conclusion
Component Queries extend the querying capabilities of the GECS framework by allowing you to filter entities based on component data. By utilizing the supported operators and combining component queries with traditional component filters, you can precisely target the entities you need for your game's logic.
For more information on how to use the QueryBuilder
, refer to the query_builder.gd
documentation and the test cases in test_query_builder.gd
.
Full Changelog: 3.0.0...3.1.0
3.0.0 - Relationships!
This release contains the first version of relationships.
With some first draft of a reverse relationship.
Relationships
Link entities together
What are relationships in GECS?
In GECS relationships consist of two parts, a component and an entity. The component represents the relationship, the entity specifies the entity it has a relationship with.
Relationships allow you to easily associate things together and simplify querying for data by being able to use relationships as a way to search in addition to normal query methods.
Definitions
Name | Description |
---|---|
Relationship | A relationship that can be added and removed |
Pair | Relationship with two elements |
Relation | The first element of a pair |
Target | The second element of a pair |
Source | Entity which a relationship is added |
# Create a new relationship
Relationship.new(C_Relation, E_Target)
# c_likes.gd
class_name C_Likes
extends Component
# c_loves.gd
class_name C_Loves
extends Component
# c_eats.gd
class_name C_Eats
extends Component
@export var quantity :int = 1
func _init(qty: int = quantity):
quantity = qty
# e_food.gd
class_name Food
extends Entity
# example.gd
# Create our entities
var e_bob = Person.new()
var e_alice = Person.new()
var e_heather = Person.new()
var e_apple = Food.new()
world.add_entity(e_bob)
world.add_entity(e_alice)
world.add_entity(e_heather)
world.add_entity(e_apple)
# Create our relationships
# bob likes alice
e_bob.add_relationship(Relationship.new(C_Likes.new(), e_alice))
# alice loves heather
e_alice.add_relationship(Relationship.new(C_Loves.new(), e_heather))
# heather likes food
e_heather.add_relationship(Relationship.new(C_Likes.new(), Food))
# heather eats 5 apples
e_heather.add_relationship(Relationship.new(C_Eats.new(5), e_apple))
# Alice attacks all food
e_alice.add_relationship(Relationship.new(C_IsAttacking.new(), Food))
# bob cries in front of everyone
e_bob.add_relationship(Relationship.new(C_IsCryingInFrontOf.new(), Person))
Relationship Queries
We can then query for these relationships in the following ways
- Relation: A component type, or an instanced component with data
- Target: Either a reference to an entity, or the entity archetype.
# Any entity that likes alice
ECS.world.query.with_relationship([Relationship.new(C_Likes.new(), e_alice)]).execute()
# Any entity with any relations toward heather
ECS.world.query.with_relationship([Relationship.new(null, e_heather)]).execute()
# Any entity with any relations toward heather that don't have any relationships with bob
ECS.world.query.with_relationship([Rel.new(ECS.WildCard.Relation, e_heather)]).without_relationship([Rel.new(C_Likes, e_bob)])
# Any entity that eats 5 apples
ECS.world.query.with_relationship([Relationship.new(C_Eats.new(5), e_apple)]).execute()
# any entity that likes the food entity archetype
ECS.world.query.with_relationship([Relationship.new(C_Eats.new(5), e_apple)]).execute()
# Any entity that likes anything
ECS.world.query.with_relationship([Relationship.new(C_Likes.new(), null)]).execute()
ECS.world.query.with_relationship([Relationship.new(C_Likes.new())]).execute()
# Any entity with any relation to Food archetype
ECS.world.query.with_relationship([Relationship.new(null, Food)]).execute()
# Food being attacked
ECS.world.query.with_reverse_relationship([Relationship.new(C_IsAttacking.new())]).execute()
Relationship Wildcards
When querying for relationship pairs, it can be helpful to find all entities for a given relation or target. To accomplish this, we can use a wildcard expression: ECS.wildcard
There are two places it can be used in a Relationship and not in both places at once:
- The Relation
- The Target
Omitting the target in a a pair implicitly indicates ECS.wildcard
You can also just use null in place of ECS.wildcard (Since that's all it is anyways)
Check out the docs for more info.
https://github.com/csprance/gecs/blob/zombies-ate-my-neighbors/addons/gecs/RELATIONSHIPS.md
Full Changelog: 2.1.0...3.0.0