Categories
A category represents one or more categories that an entity can belong to.
Categories are primarily used with the physics engine to determine which entities can collide with each other.
While you can use categories for general purpose tagging, there is a limit of up to 32 categories and so it is best to save them for physics-related uses.
Declaration
A category is declared using the category keyword followed by an identifier.
The identifier must begin with an uppercase letter.
Categories can be declared as public using the pub keyword,
which allows them to be accessed from all files in the program.
Otherwise they are private and can only be accessed from within the same file.
If two private categories have the same name but are in different files,
they are not the same category and will not interfere with each other.
pub category Category:Hero
category Category:AffectedByLava
Prefix the category with the keyword tangible to make a category tangible,
meaning it will collide with other tangible categories by default.
By default, categories are not tangible.
pub tangible category Category:Hero
category Category:AffectedByLava
Assigning categories
The first PolygonCollider decides the Category of the entity.
Spawn hero {
use body=hero
use shape=Circle(radius=1)
Body(pos=@(0,0))
PolygonCollider(category=Category:Hero)
PolygonSprite(color=#00ff00)
}
See the Collider vs Entity categories section for some caveats
about how the entity's Category property is set.
Manipulating categories
Categories can be combined using the | operator.
PolygonCollider(category = Category:Hero | Category:AffectedByLava)
The exclusive-OR (XOR) operator ^ can be used to toggle categories,
meaning it will remove the category if it is already present,
or add the category if it is not already present.
PolygonCollider(category = Category:Hero, collideWith = Category:Tangible ^ Category:Shield)
Category constants
There are a number of Category constants available for you to use:
Category:All: All categoriesCategory:Tangible: AlltangiblecategoriesCategory:None: No categories
For example, let's say these are all your categories:
pub tangible category Category:Hero
pub tangible category Category:Projectile
pub category Category:AffectedByLava
Category:Allis equivalent toCategory:Hero | Category:Projectile | Category:AffectedByLavaCategory:Tangibleis equivalent toCategory:Hero | Category:ProjectileCategory:Nonehas no categories
You will most commonly want to start with Category:Tangible and add or remove one category.
For example:
PolygonCollider(..., collideWith = Category:Tangible ^ Category:Shield)means collide with all tangible things except for shields.PolygonCollider(..., collideWith = Category:Tangible | Category:Grabbable)means collide with tangible things and grabbable things.
Checking for categories
An entity can be in more than one category, and so normally you will want to use the Overlaps to determine if there is any overlap between one set of categories and another.
on BeforeCollide that {
if this.Category.Overlaps(Category:Hero | Category:AffectedByLava) {
Print { "This entity is either a hero or affected by lava!" }
}
}
Alternatively, you can call Contains to determine if one set of categories is a superset of another.
Querying by category
Functions like Query and QueryNearest
allow you to limit the search to entities that belong to certain categories by using the filter parameter.
let projectile = QueryNearest(filter = Category:Projectile)
Category limit
You can only have up to 32 categories in your entire game. This limit is necessary because categories are part of the physics engine, and this limit allows for the categories to be stored and processed efficiently as a 32-bit bitmask. For this reason, it is best to save your categories for physics-related purposes only, so you don't run out.
Alternatives to categories
Boolean fields
Instead of creating a category like pub category Category:Guitar, create a field pub field this.IsGuitar.
You can have an unlimited number of fields.
pub field this.IsGuitar
Spawn item {
IsGuitar = true
}
on BeforeCollide that {
if that.IsGuitar {
Print { "We hit a guitar!" }
}
}
Collectors
If you want to use categories because you want to be able to Query for all entities of a certain category, consider using a collector instead.
pub collect World.AllGuitars
Spawn item {
give AllGuitars(this)
}
with AllGuitars {
Transmission { "There are " + AllGuitars.Length + " guitars in the world!" }
}
Collectors are generally better than using categories for this purpose:
- Collectors are more performant than querying because they maintain an up-to-date list of entities that only gets incrementally updated as needed.
- Collectors signal when they change, so you can use
awaitoronto respond to when they change. - Collectors are generally more flexible. For example, you can wait for an entity to complete its initialization animation
before you
giveit to a collector.
Collider vs Entity categories
The category is actually stored separately in two different places:
- Each PolygonCollider has its own
categoryparmaeter - Each entity also has a Category property
Normally, you can treat these two as being one and the same,
because creating a PolygonCollider will automatically set the entity's Category property to the same value.
You should not normally need to set the Category property manually,
as it is set automatically from your first PolygonCollider.
PolygonCollider only sets Category if not already set
Adding a PolygonCollider to an entity will automatically set the entity's Category property,
but only if the entity's Category property has not already been set.
That means, adding a second (or subsequent) PolygonCollider to the same entity will not change the entity's Category property.
Removing a PolygonCollider will also not change the entity's Category property.
This is intentional and is normally the desired behavior.
Here are some reasons why this behavior is useful:
- Imagine you have a proximity mine entity with a collider in the
Category:Minecategory. You add a second sensor collider to detect when enemies are nearby so that it can start moving towards them. This sensor collider is in theCategory:ProximitySensorcategory. You would not want the entity'sCategoryto change toCategory:ProximitySensorbecause the entity is still a mine. - Imagine if your have a hero entity with a collider in the
Category:Herocategory. The hero temporarily uses a phase shifting ability that removes its collider. The entity is still a hero, even though it has no collider at the moment.
If this behavior does not match your needs,
you can manually set the entity's Category property if you need to.